I keep running into the same quiet lie in backtests, and it hides in the most boring place imaginable. A single candle. You have a long open, a stop below entry, a take-profit above it, and then a bar prints where the low pierces your stop and the high pierces your target. Both got touched inside the same bar. So which one filled? The candle will not tell you. Open, high, low, close, in that order or any other, gives you four prices and zero information about the path between them. The bar could have tagged your target first and then rolled over into your stop, or knifed down to your stop and then recovered to your target. Same OHLC, opposite outcomes for your trade.
Most backtest engines resolve this ambiguity, and most of them resolve it optimistically without ever telling you. If the engine checks the target condition before the stop condition in its loop, you win that bar. Flip the order of two lines of code and you lose it. That is a genuinely absurd amount of your equity curve riding on the arbitrary order of two comparisons, and it is invisible unless you go looking.
Why this quietly inflates your results
The trades that hit both levels in one bar are not random. They cluster in exactly the conditions you care about most, which is high-volatility, fast-moving regimes where a bar can travel several percent tip to tip. Those are the bars that decide whether your strategy survives a crash or gets shredded by it. If your engine hands you the target every time both are touched, you are systematically booking the good outcome on the trades where the real outcome was a coin flip at best, and often worse than a coin flip because stops on fast moves tend to fill with slippage past the level.
The effect compounds with how tight your levels are. If your stop and target both sit close to entry relative to the average bar range, a large fraction of your exits will be same-bar ambiguous, and optimistic resolution can move a strategy from red to green on paper. Scalping and tight mean-reversion systems are the worst offenders. Wide swing systems on daily bars feel it less, though they never escape it entirely.
The conservative convention, and why to use it
The fix is not clever. When a bar touches both your stop and your target, assume the stop filled. Always. This is the worst-case, or pessimistic, fill convention, and it exists because the cost of being wrong is asymmetric. If you assume the stop and you were actually right about the target, you understated a winner. Annoying but survivable. If you assume the target and you were actually wrong, you are trading live money on a curve that never existed. One of those errors bankrupts you slowly and the other just makes you grumpy.
A defensible intrabar ordering for a long position, applied bar by bar while a position is open, looks like this:
- If the bar's open already sits beyond your stop, fill at the open. Gaps are real and they do not wait for your level.
- If both stop and target fall inside the bar's range, resolve as the stop. Same for shorts with the sign flipped.
- If only one level is inside the range, fill that one at the level, then add slippage on the stop side.
- If neither is touched, carry the position to the next bar and repeat.
That gap rule matters more than the same-bar rule for anything holding overnight. A stop is a trigger, not a guaranteed price. If a stock or a coin opens well below your stop, you do not get filled at your neat round number, you get filled somewhere in the mess of the open. Engines that fill every stop exactly at the stop price are telling you a second lie on top of the first one.
When you actually need finer data
The honest answer to \"which hit first\" is that you cannot know it from OHLC, and no fill convention recovers the truth, it only bounds it. If the ambiguity is costing you a meaningful slice of your results, the only real remedy is to drop to a finer timeframe for the resolution step. Re-run the same trades against one-minute or tick data purely to settle the same-bar cases, while keeping your signal logic on the original timeframe. You are not changing the strategy, you are just asking a higher-resolution witness what happened between the four prices you already had.
You do not always need this. The workflow I would actually follow, roughly in order of effort:
- Run the backtest once with optimistic same-bar resolution and once with conservative resolution. Look at the gap between the two equity curves.
- If the gap is small, you are done. Publish the conservative number and move on, because the pessimistic curve is the one you can defend.
- If the gap is large, your strategy's edge is concentrated in ambiguous bars, which is a warning in itself. Now it is worth pulling finer data to resolve those specific trades and see where the truth actually lands.
That first step is the whole trick and almost nobody does it. The spread between optimistic and pessimistic fills is a free diagnostic. A robust strategy barely moves between the two. A fragile one swings hard, and the size of the swing tells you how much of your backtested edge is really just the engine being generous. I treat a large spread as a reason to distrust the strategy, not a reason to go hunt for the timeframe that makes it look best.
A few things that bite in practice
Watch out for stop and target on the same bar as the entry. If you enter mid-bar on a signal and the same bar's range already covers both exits, you have no basis at all to claim you got in before you got stopped. Treat those as stops too, or forbid same-bar exit on the entry bar entirely. Also watch your data's timezone and session boundaries, because a bar that looks continuous might actually straddle a session gap where price was never tradable at the levels in between.
None of this makes a backtest true. It makes it less flattering, which is the direction you want to be wrong in. When we built the backtesting layer inside Blockcircle, the conservative same-bar rule was the default rather than an option you had to find, precisely because the optimistic version quietly makes almost everything look tradable. The strategies worth running are the ones that still clear the bar when you assume the stop won every argument. If yours only works when the engine takes your side of the coin flip, it does not work.