Why multi-timeframe backtesting breaks on real data
The most common reason is that the higher timeframe is read as already closed. The strategy makes a decision based on the H4 bar, but at that moment the bar is still forming, and part of the movement on which the signal “triggered” appeared after entry.
On history, such logic produces a smooth upward curve; in the production environment, it produces entries at a price that did not exist at that moment.
The second source is timeframe mismatch. The bar boundaries of the higher and lower series do not coincide: different trading sessions, weekends, gaps in quotes, daylight saving time transitions.
Aligning by the open label or substituting the last known value pulls future data into the calculation, and one shifted bar changes not only the entry but also all dependent indicators.
The third is curve fitting to history. The more parameter combinations are run on a single segment, the higher the chance that the best result is an accident: Lopez de Prado wrote about this in detail.
On the higher timeframe there are few trades, and a “profitable” configuration often rests on a dozen entries that do not survive transfer to another data segment.
The fourth group is errors in running strategies in execution and data: the trigger moment inside the bar, commissions and slippage, quote gaps, and divergence between the historical and live price source by arrival time.
We analyze how this is closed: from strict series alignment and banning unclosed bars to checking the configuration for out-of-sample robustness.
Effect of coordinated calculation across multiple timeframes
While a strategy makes decisions on one timeframe, the calculation is transparent: one stream of candles, one time scale.
But as soon as confirmation from a higher period is added, a source of discrepancies appears: the lower bar manages to close several times inside one higher bar, and the entry may rely on data that did not yet exist at the moment of calculation.
We assemble multi-timeframe backtesting in a single event core (based on Backtrader and our own extensions), so signal consistency is checked by a reproducible run on history, not by eye.
We close indicator repainting with a confirmed-bar mode: higher timeframe values are recalculated only at its close, lower candles are aligned to the higher timeframe boundary, and the unclosed higher bar is excluded from the calculation window.
This removes a class of failures that looks like a smooth equity curve in reports and diverges from actual trades.
Strategy run time changes nonlinearly. Higher series are aggregated once and cached; the strategy accesses ready-made slices instead of recalculating them at every step: a run on three timeframes is more expensive than a single one by roughly 20–40%, not threefold.
The share of false entries falls due to a filter: a lower timeframe signal is accepted only when the higher timeframe direction matches.
According to our runs, such a rule cuts 30–50% of entries against the higher trend — but it also cuts some profitable trades, so the confirmation threshold is selected by metrics rather than enabled “just in case.”
Modes of multi-timeframe backtesting: history, stream, hybrid
The run mode determines how the strategy receives candles from the lower and higher timeframes and at what moment they become visible. In batch calculation, the quote archive is loaded in full, higher series are aggregated in advance, and the loop runs over the common array.
This is the fastest option on long history, but it is also where future information most often leaks.
Streaming calculation feeds events sequentially, candle by candle: the higher timeframe closes only when the last lower candle inside it has closed.
Looking into the future disappears, but there is a price of its own — indicator state must be stored and correctly replayed when transitioning between series.
| Option | When it fits | Limitations |
|---|---|---|
| Batch calculation | Debugging entry rules, testing hypotheses on long history, running across a set of instruments | Memory usage grows with the number of timeframes; close synchronization has to be checked manually |
| Streaming calculation | Final strategy validation where the honesty of event order is critical | Noticeably slower; harder to reproduce a failing run and debug transition logic |
| Hybrid scheme | Production mode of multi-timeframe backtesting: the lower series runs as a stream, higher ones are calculated incrementally | Requires an explicit rule for when the higher timeframe is considered available |
The hybrid scheme usually becomes the main mode: the lower series is processed as a stream, higher ones are recalculated at close, so the run does not hit memory limits and does not lose order reliability.
Batch remains for quick checks, streaming — for verifying disputed segments on a short interval where result divergence is immediately visible.
Implementation stages: from data audit to environment handover
Multi-timeframe backtesting breaks not on strategy logic but on data: higher candles are assembled from lower ones, and an error in this assembly produces a beautiful but false equity curve.
Therefore, we begin implementation with a source audit, not with code, and only after that do we fix the calculation rules.
- Historical data audit — we check the completeness of series by instrument and timeframe, look for gaps, duplicates and breaks, and compare closing prices with an independent source.
- Candle reassembly scheme — we describe aggregation rules: higher interval boundaries, bar close moment, behavior on incomplete and anomalous segments.
- Environment setup — we deploy the calculation environment, version datasets and configurations, and separate historical storage from the compute part.
- Robustness check — we run the rules on different periods and subsamples, separately controlling look-ahead bias and divergence between history and the online stream.
- Calculation acceptance — we compare control examples and a manually reviewed trade sample, and fix acceptable deviations and the threshold beyond which the result is considered unreliable.
- Environment handover — we provide documentation, data update regulations and access, and train the team to reproduce the calculation without our involvement.
The key principle: aggregation and validation rules live in one place for both history and real time. Otherwise, historical and live results will diverge, and trust in the system will not emerge.
What we hand over: configurations, schemes and regulations
Handover is not an archive with code, but a set of artifacts by which your team can run the test itself, change parameters, and get the same result. Below is what remains in your repository and internal knowledge base; we check each item at acceptance.
- Run configurations — a separate profile for each scenario: timeframe composition, rule for resampling higher intervals from lower ones, synchronization window, commissions and slippage. They are changed by editing one file, without touching the code.
- Data schema — candle source, time zones, bar close moment, handling of gaps and corporate actions, plus a validation checklist for duplicates and history breaks.
- Reference history slice — a fixed dataset on which the run is reproduced down to the last trade: it is used to compare the result after any edits.
- Run procedure — order of steps: data preparation, indicator warm-up, run, metric collection, discrepancy analysis; with notes on where calculation hits resource limits.
- Access policies — roles and rights to change configurations, read-only keys to market data, and a parameter edit log with author and reason.
- Report templates — run metrics, including the share of trades where a lower timeframe entry is confirmed by the higher timeframe, and an analysis of divergences between intervals.
- Documentation — description of the data model and bar close time conventions, a “how to add a new timeframe” guide, and a methodology change log.
- Handover plan — a walkthrough with your engineers and a list of open questions that we close after handover.
Case: eliminated signal divergence across timeframes
The client came with an environment where the strategy produced a smooth curve on history, but on live data it produced trades that were not in the report.
The analysis showed signal divergence: confirmation came from the higher timeframe, while entry was executed on the lower one, and some bars were not yet closed at the moment of the decision.
In multi-timeframe backtesting this is a typical trap — series are joined in a single pass, the bar close moment is not taken into account, and the result looks more convincing than it is.
Initial state
Running one parameter grid took about three hours, so hypotheses were tested in batches at night. The share of trades where the higher timeframe signal diverged from the actual entry reached 6.4% — over a span of hundreds of trades, this ate away the strategy’s edge. Coverage was limited to two timeframes: adding a third doubled run time and broke reproducibility.
What we changed
The calculation was split into independent timeframe calendars, and decisions were made strictly on bar close — the signal enters execution with an explicit deterministic lag.
Higher series aggregates are calculated once and cached, so adding a timeframe no longer recalculates the entire history. The core was kept on Backtrader: the library provides an event-driven model, but series synchronization and execution order had to be built manually.
Outcome
The divergence share fell to 0.2% — these are trades at the boundary of data gaps; they are marked and analyzed separately. A full run takes 22 minutes, and the grid now has four timeframes instead of two.
Re-running on the same data reproduces the result down to the last trade, and the calculation configuration version is saved together with the report — rolling back to the previous one takes minutes.
The production environment is now unified: test and live trading read the same bar close rules, so divergence between them is no longer the norm.
How is candle reassembly different from streaming calculation?
The difference is at what moment the bar value is formed. Candle reassembly unfolds the primary trade stream into aggregates anew on each run: change the timeframe, bar close rule or outlier filter — and the entire history is recalculated, giving a reproducible result.
Streaming calculation maintains the aggregate incrementally: state is updated as data arrives, without re-passing through history.
Candle reassembly pays for calculation accuracy with data latency — until the series is unfolded and aggregated, multi-timeframe backtesting does not start, and on deep history this is a noticeable pause.
Streaming calculation responds almost instantly, but its state lives in memory: if the channel breaks or the process restarts, the aggregator must be restored, otherwise the bar will “drift.”
| Approach | When it fits | Limitations |
|---|---|---|
| Candle reassembly | Research, logic changes, verification and reproducibility of runs | Data latency at start, grows with history depth |
| Streaming calculation | Real time, long runs, incremental updates | Calculation accuracy depends on state recovery after failure |
The working scheme is a hybrid: history goes to candle reassembly, the “live” tail is calculated as a stream, and after session close we compare the tail with a full recalculation.
A discrepancy of even one candle at the junction is the first sign that the aggregator has lost state, and calculation accuracy on that segment is no longer guaranteed.
Common technical doubts before starting work
Doubts before starting are usually engineering ones: whether there is enough history, whether the result will turn out to be overfit to the past, whether we will hit the limits of the environment when new timeframes are added. We address four questions that most often decide whether to move forward.
How do you check the quality of historical data?
We compare the source with an independent one, look for gaps, duplicates and breaks in trading sessions, and bring bars to a single time zone.
For multi-timeframe backtesting, alignment is critical: the higher timeframe is assembled only from closed lower timeframe bars, otherwise the future leaks into the calculation.
Data with holes and incorrect aggregation produces a beautiful curve on history and zero result in the live market.
How can we be sure the result is not overfit to the past?
We split history into periods: tuning on one, validation on data the strategy has not seen. We look at parameter stability — if the result holds only in a narrow corridor of values, we say so before going live, not after the first trades.
What happens if we add a new timeframe or instrument?
The strategy describes rules, not a specific set of bars: timeframes, instruments and sources are connected via configuration. A new series is added without rewriting logic and tests — the environment is designed for growth in the number of timeframes and instruments.
Can the strategy be tested on a demo account?
Yes. The same strategy code runs on a live stream in demo mode, and signals are compared with the calculation on history. This reveals discrepancy due to delays, commissions and slippage — the model stops being divorced from the market.
Let's discuss the task and come back with a work plan
To make the discussion of the task substantive, it is worth collecting the inputs that determine the architecture: how data from different timeframes is stored and synchronized, where the speed bottlenecks are, and what is considered a correct result. For a backtesting system with multi-timeframe support, this is the core of the future solution.
- List of instruments and timeframes to cover — from minute to daily, indicating the higher series that influence decisions.
- Source and depth of quotes: tick data or candles, whether there are gaps and breaks, and how to restore them.
- Strategy logic: on which timeframe the decision is made and on which the signal is confirmed, where time discrepancy is acceptable.
- How costs are modeled — commission, slippage, order execution delay.
- What is considered a result: set of metrics, report forms, exports for subsequent analysis.
- Mode of operation: single runs or parameter sweep, how much time it is acceptable to wait for one result.
- Infrastructure and access constraints: where we host the system and who owns the historical data.
On our side, the first response is a working document, not a letter of intent.
It will contain a stage-by-stage work plan, timeline estimate for each stage, team composition with roles (architect, data engineer, calculation core developer, QA engineer) and a list of risks with how we mitigate them.
If during the review it turns out that part of the task can be solved more simply, we will say so directly. Describe the task — we will come back with an estimate and a plan.







