Knowledge baseResearch

Our backtest read MACD seven bars into the future

One entry rule, written twice — once for the live engine, once for the backtester. Reading the code showed nothing. Comparing them bar by bar showed twenty broken pairs out of fifty-eight.

11 min read

Anton Shchur· Founder· Updated September 19, 2026

"Why does my backtest not match live trading?" usually gets answered with slippage, fees and latency. Those are real. In our own platform they were not the reason.

The reason was that the rule deciding whether to enter a trade existed twice — once in the live engine, once in the backtester — and the two copies disagreed. On 28 August 2026 we built an instrument to compare them bar by bar. It found twenty disagreeing pairs out of fifty-eight, and the worst of them let the backtester see the future.

Why the rule is written twice at all

This is not carelessness, and if you use any bot platform it is almost certainly true there too.

A live engine receives candles from an exchange one at a time and decides at the moment the last one closes. A backtester holds the whole history in arrays and walks it vectorially, because doing it any other way makes a year of minutes take hours.

Same intent, two shapes. The indicator math can be shared — ours is — and still the two can disagree about *which bar you are standing on*. That single question turns out to be where the money is.

What we measured

One system configuration is sent down both paths: straight into the live engine's filter, and into the backtester through the same owner the product uses. Both are asked on every bar, and the verdicts are compared. 29 entry filters × 2 sides × 2 candle series (smooth and choppy).

The instrument also measures itself, and that turned out to matter more than the comparison: for each pair it prints how many bars the filter actually distinguished. A filter that never blocked — or always blocked — has not compared anything, and "matched" from it means nothing. Six pairs were blind on the first run.

Finding 1: the array alignment was guessed

To ask "what was the MACD at bar 400?", you need to know where in the indicator array bar 400 sits. Indicator arrays are shorter than the candle array, because the first N bars are consumed warming the indicator up. Our backtester inferred that offset from the indicator's period.

That inference was correct for exactly one indicator out of nine.

indicatorbar actually read
MACDidx +7
ADXidx +13
Stochasticidx +1
EMA, SMA, CCI, %R, Bollingeridx −1

A positive offset means the backtester consulted a bar that, at decision time, had not happened yet. For the two most commonly used filters on the platform, entries in the backtest were gated by future data.

This is the class of defect that makes a strategy look good on history and behave differently in production — and no amount of reading the two implementations side by side reveals it, because both look correct in isolation.

The fix is not a better guess: the offset is now derived from the array lengths, and it resolves to the last *closed* bar — the same one the live engine sees.

Finding 2: three different ways to read half a candle

The current candle is still forming. Whether you include it is a decision, and it has to be the same decision on both paths.

whatdisagreements
volume filter consumed the forming bar twice, live side17 out of 400
candle3 and divergence read up to the forming bar, backtest side60 and 28 out of 400
ATR % divided by different prices (closed bar vs current)7 out of 400

Note the second row is 60 out of 400 — fifteen percent of bars. That is not an edge case; it is a different strategy.

Finding 3: warm-up is part of the rule, not a detail

A recursive indicator (RSI, ATR, ADX — the Wilder family) never fully forgets its seed value; it only decays. We had a rule for this: feed enough bars that the seed's weight is under 1%, then start.

That rule came from reasoning, not from measuring outcomes. The instrument measured the outcome: at 1% residual weight the seed still decides — RSI 54.7 against a threshold of 55, ADX −DI 24.7 against +DI 25.4. Three pairs out of fifty-eight flipped on it.

The threshold is now 0.1%, and it was extended to the whole Wilder family and to the EMA family, where the live engine had been taking period + 21 candles — leaving the seed about 45% of the weight.

Finding 4: the exit rule was worse

Then we pointed the same method at the exit — where price closes a position. Ten disagreeing pairs out of twenty, with zero blind ones.

whatlive enginebacktesterwhat it meant for the user
break-even stopavg ± 0.2 % (two taker fees)exactly avgthe backtest drew break-even as free; live it costs a round trip
trailing before break-eventrails only after break-even is passedtrailed from the first barthreshold 99.50 vs the live 95.14 — the backtest closed cycles that never existed live
ATR stopreturned the ATR level immediately, skipping break-even and trailingapplied protection on top of the ATR baseprofit protection the user had switched on never worked
stepped break-evenwalks the stop to filled partial take-profitsdid not know about it at allup to 5 % of price — the backtest drew a drawdown that does not occur live

Three of the four were defects of the backtester. One was a defect of the live engine: the ATR branch silently disabled protection the user had explicitly enabled. That is not a modelling choice — the setting was saved, shown in the form, and not executed.

This is the part worth sitting with. An exit rule decides how much money stays in the cycle. A user proving a system on history was being shown one rule and trading another.

Finding 5: there is a third path, and it is the sneakiest

Backtest and live are the two everybody compares. Most platforms have a third: paper trading — the mode people use to build confidence before funding.

Ours is a separate implementation of the tick with no shared code with the live engine. When we finally compared decisions — same configuration, byte for byte, one price path — we found entry filters were not running on the paper path at all. Measured on a pair of systems configured identically (short, RSI ≥ 70): live placed zero orders in 33 hours; paper ran four cycles, entering at RSI 67.1, 45.6, 43.4 and 34.5.

The paper track was proving a system that had no entry filter — that is, somebody else's system.

Reading the code did not find this either. The gate sat below an early return in the paper branch: the rule's name was present in the file, and its execution was not.

The most useful thing we learned about instruments

An intermediate version of the parity instrument aligned both paths "by bar close", and the disagreements disappeared. It looked like success.

But the project's declared decision model is different — the indicator level from the last *closed* bar, compared against the price *now* — and an existing guard caught the discrepancy. Had we kept that version, we would have proven agreement with the bug.

Tuning the instrument until the code passes is the most expensive mistake available here, because what you get is not a red instrument you ignore. You get a green one you trust.

How to check this on any platform, including ours

You do not need our code. You need one discipline: compare decisions, not results.

  1. Take one configuration and run it down both paths — backtest and live (or paper).
  2. Compare on every bar, not at the end. Equity curves hide compensating errors; per-bar verdicts do not.
  3. Count how many bars the rule actually distinguished. If a filter never blocks in your sample, it has proven nothing, and its "match" is an artefact.
  4. Check the boundary explicitly: at the moment of decision, which was the last closed bar, and did both sides use it?
  5. Check warm-up: how many bars before your first signal, and what weight does the seed still carry?

If a platform tells you its backtest and its live engine share code, the follow-up question is which parts — ours shared the indicator math and still disagreed on twenty pairs out of fifty-eight.

Limits of this measurement

  • Our implementation, not a claim about others. We can say what we found in our own code. Any statement about other platforms would need the same instrument pointed at them.
  • Synthetic candle series, two shapes. Smooth and choppy, chosen so filters would actually fire. This measures rule agreement, not profitability.
  • Agreement is not correctness. Two paths can agree and both be wrong. Parity is a necessary condition, not a sufficient one — which is exactly why the "align it until it passes" version had to be rejected.
  • Fees, slippage and latency are separate questions. They are real and they are not what this measured.
  • This is not investment advice, and none of the above is a recommendation to trade.

Where it stands now

Entry parity: 58 pairs out of 58 agree, zero blind. Exit parity: 34 out of 34. Both instruments run in our pre-push gate, so a re-divergence fails the build rather than waiting for someone to remember to check.

The paper path is now judged by a third instrument that compares decisions rather than text — placed levels, how many are actually resting, entries filled, position, cycle, take-profit — against a frozen ledger of known debt, so that a *new* disagreement fails while known ones stay visible.

We are publishing the numbers because the alternative framing is dishonest. Nothing here was exotic. It was one ordinary question — *which bar are we standing on* — answered differently in two places by people who were each right about their own half.

Reading is free. So is testing.

Paper trading costs nothing and needs no card. Build a system, backtest it on real candles, and decide from the result.