The most likely issue is that- because your collision detection is discrete rather than continuous- there are situations where an object moves from one side of a hitbox to another in a single step. You can always throw more steps at the problem so that fast-moving objects move less during each physics step. This doesn't completely prevent misses, it just means that objects would have to move faster in order to miss.
Another potential issue is that the reflection angle seems to be based entirely on the incident angle and not the normal of the hit surface. This will create unrealistic results and could cause objects to be sent further into each other upon colliding.
If you want the actual solution, it's a swept volume test. There are plenty of resources out there for implementing this yourself, so I'll skip right to the Python libraries. Google gives me pybox2d, or for 3D, pybullet, which in turn uses OpenGJK.