Reconstructing Historical Spread by Session When Tick Data Doesn't Go Back Far Enough
Your OHLC history usually goes back years further than your broker's actual spread history does, and a flat average spread over that gap is a worse assumption than most backtests admit to.
Why this gap exists in the first place
OHLC price history and bid/ask spread history are not the same asset, even though they come from the same broker and the same symbol. Aggregated open/high/low/close bars can often be sourced or backfilled from long-running third-party feeds going back many years. The actual bid/ask spread at any given historical moment is a different kind of record — it requires the broker (or you) to have captured the real tick stream at the time, and most brokers simply don’t retain or expose that at the same depth as their price history. It’s common to have several years of clean OHLC bars and only six months to two years of genuine spread data behind them, even from the same MT5 terminal, on the same symbol. If your walk-forward validation needs a couple of years of history to get a defensible trade count, a meaningful chunk of that window can be sitting on OHLC data with no real cost record attached to it at all.
The naive fix, and why it costs you exactly where it matters
The obvious workaround is to take whatever average spread you can measure from the period you do have data for and apply it flat across the rest of the history. This is better than pretending costs are zero, but it reintroduces the same problem covered in the MT5 tick-modeling piece: a flat average spread systematically understates cost during the specific conditions where real spread actually widens — thin Asian-session hours, news releases, weekend open and close — and those are disproportionately the moments where your stop and target are both close to being hit, meaning the flat assumption distorts the trades that matter most to your win rate, not just the average cost figure.
Building a session-based lookup instead of one number
A meaningfully better approach doesn’t require real historical tick data for the missing years — it requires building a spread profile from the period you do have real data for, broken out by session rather than averaged across the whole day, and then applying that profile backward onto the bars where real spread doesn’t exist. Bucket your available real-spread data by the standard session windows — Asian (00:00–08:00 UTC), London (08:00–16:00 UTC), New York (13:00–21:00 UTC), and the London/New York overlap specifically (13:00–16:00 UTC) — and compute a typical spread for each bucket rather than one number for the whole dataset.
| Session (UTC) | Typical spread pattern |
|---|---|
| Asian, 00:00–08:00 | Widest on average, thinnest liquidity |
| London, 08:00–16:00 | Tightens as European liquidity comes online |
| NY/London overlap, 13:00–16:00 | Tightest of the day, peak liquidity |
| New York, 13:00–21:00 | Widens again toward the US afternoon lull |
This alone is a real improvement, because a trade taken during the overlap window now gets backfilled with a realistically tight spread assumption, while a trade taken during the Asian session gets a realistically wider one, instead of both getting the same flattened average.
Making the reconstruction react to volatility, not just the clock
Session buckets capture the structural pattern but miss the idiosyncratic spikes — an unscheduled volatility event during an otherwise quiet session still widens real spread even though the clock says it should be calm. You can get a meaningfully closer approximation by conditioning the lookup on both session and a volatility proxy computed from the bar itself, most simply the bar’s own high-low range relative to its recent average range. Splitting each session bucket into range quartiles — building, in effect, a small grid of “session × relative volatility” — lets a historically wide-ranging bar during an otherwise quiet Asian session get assigned a wider reconstructed spread than a typical Asian-session bar would receive, even without any real quote data for that specific moment. It’s still an approximation built from correlation rather than a measurement, but it captures a real relationship: spread and realized range both respond to the same underlying liquidity conditions, so one is a usable, if imperfect, stand-in for the other when the direct record doesn’t exist.
Being honest about what this reconstruction actually is
This method produces a plausible cost estimate, not a real one, and the discipline that matters most here is not treating it as equivalent to genuine measured spread once it’s built. The practical habit worth adopting is tagging every trade in your backtest output with whether its cost figure came from real recorded spread or from the session/volatility reconstruction — a simple boolean or confidence flag alongside the rest of your trade log. This turns an invisible assumption into a visible one, and it means any win rate or Sharpe figure that leans heavily on the reconstructed portion of the dataset can be flagged and discounted accordingly, rather than blending seamlessly into a number that looks equally solid across the whole test window.
Feeding this into the rest of the validation process
Once trades are tagged this way, the natural next step is to fold that confidence flag into how you read a walk-forward chain. A window that sits entirely within your real-spread history deserves more trust than one that’s mostly reconstructed, even if both report a similar win rate in the 52–62% range, because the confidence interval around the reconstructed window’s numbers is genuinely wider than a naive trade-count calculation would suggest — you’re not just uncertain about sample size, you’re also uncertain about whether the cost side of each trade in that segment was accurately modeled at all. Treating “how much of this segment relied on reconstructed spread” as its own piece of metadata, tracked alongside the JSON config for that window, keeps that uncertainty from quietly disappearing by the time the backtest report gets summarized into a single headline number.
Validating the reconstruction the same way you’d validate anything else
There’s a natural check worth running before trusting this method at all: split your real-spread reference period the same way you’d split any other dataset. Build the session/volatility lookup table from an earlier portion of your real spread history, then test how closely it predicts the actual spread observed in the held-out remainder. If the reconstructed estimate consistently lands within a reasonable band of the real figure on data it wasn’t built from, that’s a genuine reason to trust the backward extrapolation into years where no real data exists at all. If it doesn’t — if the lookup table’s predictions and the real held-out spread diverge substantially — that’s useful information too, and it usually means your session/volatility buckets are too coarse, or that spread behavior on this particular symbol has structural quirks the simple session model isn’t capturing. Either way, this is the same train/test discipline the rest of the validation process already depends on, just applied to a cost-estimation model instead of a trading strategy.
Where this matters most in practice
This gap matters disproportionately for exactly the kind of pattern-based strategies this site is about — anything scoped to a specific session window, where the strategy’s entire edge might depend on the tighter spread conditions of the London/New York overlap actually holding historically, not just on average. If your only real spread data happens to come from a stretch where liquidity conditions were unusually good or unusually thin compared to the years you’re backfilling, the reconstruction inherits that bias too. It’s worth periodically checking whether your “real spread” reference window itself looks like a fair representative sample of session behavior, rather than assuming any few months of real data are automatically a safe baseline to extrapolate from.