Skip to content
strategies8 min read

The backtest that lied: same-bar re-entry artifact (part 3)

A backtest artifact made one coin look like +5,032% in a year. Fixing the same-bar re-entry bug dropped returns 30-50x and turned 5-7 winning strategies into zero.

Dark analytics dashboard with multiple charts and metrics, the kind of output a backtester produces before you find the bug that makes all the numbers wrong

Affiliate disclosure: This post may contain affiliate links. If you sign up through a link on this page I may earn a commission at no extra cost to you. This does not affect my ratings. Learn more.

Most algo traders have at least one backtest they were genuinely excited about before they realized the code was lying to them. What varies is how bad the lie was.

Mine was pretty bad. One coin showed +5,032% in a single year. I almost believed it. This is Part 3 of the build-in-public series documenting my Binance futures bot. Part 2 was about survivorship bias and a holdout test that killed the 1-hour version. This one is about the dumber mistake that came before it, a mistake that wasn't about strategy selection at all. It was about code that let the backtester see the future.

What "+5,032%" should have told me

Here's the thing about an obviously absurd backtest number: your brain wants to explain it away rather than take it seriously as a red flag. I caught myself thinking things like "the market was wild that year," or "maybe this coin really did have that kind of trend." Both true in some sense. Both completely wrong as explanations for what I was seeing.

The real explanation was that my backtester was re-entering positions at a price it had no right to know about.

The specific sequence goes like this. A position is open. The price moves against it intra-bar, meaning at some point during a 4-hour candle the stop-loss level gets touched. The stop fires. Position closed. So far so good. Now here's where the backtester cheated: instead of waiting for the next bar to open a new position, it immediately re-entered on the same bar, at that bar's open price.

The bar's open. Not the current price after the stop. The open, which happened hours earlier in the candle's timeline.

That's time travel. The live bot physically cannot do this. On a live exchange you get stopped out, the order settles, and the earliest you can re-enter is when the next bar opens. You don't get to jump back to the beginning of the bar you just got stopped on. But the backtester didn't know that, and I hadn't told it.

When a backtester can re-enter at a price that existed before the stop triggered, it's trading information it shouldn't have access to. Every re-entry in that scenario is front-running itself. In a trending market, this compounds hideously: get stopped, immediately re-enter lower, catch the rest of the move, stop again, re-enter, catch more move. It looks like precision. It's fiction.

The fix was a single rule: after any stop fill, the backtester must skip at least one full bar before opening a new position. One line of code. It changed everything.

What happened when I fixed it

I added a 1-bar gap after any stop fill. You get stopped, next 4-hour bar is off-limits, and only from the bar after that can the strategy re-enter.

Returns dropped 30 to 50 times.

Not 30 to 50 percent. Thirty to fifty times. A strategy that looked like it returned hundreds of percent annualized became something that returns single to low-double digits. Which, honestly, is what a real trend-following system on crypto futures should return at moderate risk.

The more humbling number: I had somewhere between 5 and 7 strategy configurations that "passed" my original testing criteria across multiple coins and time periods. After the fix, the number that passed all periods cleanly dropped to zero. Not "fewer," zero. Every single one of them owed a significant portion of their apparent edge to same-bar re-entry, and once that was gone, the parameters that survived were much more conservative and the headline numbers much more boring.

I want to be precise about what "passed all periods" means here because it matters. I wasn't just looking at overall returns. I was requiring a strategy to show positive expectancy across multiple rolling windows, not just in aggregate. A strategy that returns +200% over 3 years might still lose badly in one of those years. "Passing all periods" means it didn't have a year where the edge completely disappeared. After the fix, nothing passed that bar cleanly. Some came close. None cleared it.

Crypto candlestick chart on a dark screen, the kind of price action a trend-following backtester works on before you audit its re-entry logic

Why this is so common and so invisible

This class of bug is specifically nasty because it doesn't feel like a bug. The code isn't crashing. The backtester isn't doing something obviously wrong. It's re-entering after a stop, which is a completely valid trading behavior. The only problem is when it re-enters, and that's a one-bar question that's trivially easy to get wrong.

The QuantStart guide on backtesting biases calls this category "look-ahead bias": any situation where your backtest uses information that wouldn't have been available at the moment of the simulated decision. Same-bar re-entry is a textbook example. You're using the information that the bar opened at price X to make a re-entry decision, but that information "arrived" hours before your stop triggered inside the same bar.

The reason it's so common is that bar-level backtesting is inherently a simplification. You're collapsing a 4-hour candle into four numbers (open, high, low, close) and a volume figure. The sequence of events within that bar is gone. Did the high come before the low? Did the stop get triggered at minute 15 or minute 230? You can't know from OHLCV data alone. Most backtesting frameworks make a simplifying assumption about intra-bar sequencing, and if yours assumes the open is always "available" for re-entry after a stop, you get this bug.

The fix is also a simplification, but a conservative one: don't re-enter on the same bar, period. Assume worst-case intra-bar timing. You lose some theoretical entries at good prices, but you stop making entries at prices you had no right to make. Better to miss a few real trades than to simulate thousands of impossible ones.

What this does to expectations

Honest answer: the corrected backtest is a lot less exciting than the original. That's the point. A strategy that shows 30-50x inflated returns is not a strategy that has a 30-50x inflated edge. It has a code error. Once the error is gone, what you're left with is the real edge, which in my case is genuinely more modest.

Even the corrected backtest number needs a further discount. My live experience, and I've compared notes with enough other algo traders to believe this generalizes, is that live performance tends to run at roughly 30 to 60% of the corrected backtest headline. Not because the strategy doesn't work, but because backtests still don't fully model slippage, funding rate drag, the odd rejection or partial fill, or the fact that market regimes shift. A corrected backtest showing +35%/year at a given risk level translates to something more like +10 to +20% in a good live year, with wide variance, and drawdowns that look normal on the backtest but feel brutal when it's real money.

I've said before that my central expectation for this bot is low-double-digit annual returns. This is where that number comes from. Not from being pessimistic, from auditing the code and then applying the live-vs-backtest discount on top.

The thing I'd tell every bot builder

Audit your re-entry logic before you audit anything else. Before you look at Sharpe ratios, win rates, or drawdown stats, answer this question: can my backtester re-enter a position on the same bar that just stopped it out?

If the answer is yes, your best numbers are fiction. Not all of them, but enough to matter.

The test is simple. Take any trade in your backtest where a stop fired. Look at the timestamp of the stop fill and the timestamp of the re-entry. If they share the same bar, you have the bug. Run a sample of 10 or 20 stopped trades and check. It takes an hour and will either give you confidence or save you from running a live account on a liar.

My backtester caught this on coin after coin once I knew to look. The absurd "+5,032%" number was actually useful in one sense: it was so obviously wrong that it forced me to look. A more "plausible" number like "+400%" might have slipped through. If your returns are suspiciously too good, that's not a reason to feel good. That's the red flag.

Part 4 will cover the bugs that a corrected backtest still can't catch, the ones that only show up live. Spoiler: I found three of them, and they cost real money.

The full series lives on the journey page. I'll keep posting as long as there's something honest to say.

Share:X / TwitterReddit
Hung Phu
Hung Phu
DCA BotsGrid BotsPythonCrypto FuturesBacktesting

Python algo trader since 2019. I build and test trading bots with real capital on Bybit and Binance. AlgoGrade is my lab notebook.

Related posts