Guide/Backfill guide
Guide

How to backfill FX rates without look-ahead bias

When you backtest a strategy that traded EUR/USD in March, you need the rate that was knowable in March — not a revised ECB series published months later. Here's the pattern.

MMexchangerate.dev·Jun 14, 2026·6 min read

Point-in-time data returns the value that was actually published as of a given timestamp, with no later revisions folded in. It is the difference between a backtest you can trust and one quietly contaminated by information the market did not yet have.

Key points
Always query /v1/{date} rather than /v1/latest for historical strategy work.
Check is_forward_filled: true — this means no rate was published that day (weekend/holiday).
Use the data_updated_at timestamp to understand when the rate you're seeing was written.

What "point-in-time" means

Most data providers serve you the current best estimate of a historical value. When a figure is revised — a GDP print restated, a settlement corrected — the old number silently disappears. Query last March today and you get March as it is understood now, not as it was understood in March.

A point-in-time store keeps every vintage. Ask for the value as_of=2026-03-14 and you receive exactly what a subscriber would have seen on that date, revisions excluded.

Why look-ahead bias ruins backtests

If your historical series contains values that were only knowable later, your strategy is implicitly trading on the future. Returns look spectacular in simulation and collapse in production. The bias is subtle precisely because the numbers are real — just not real yet.

The tell
A backtest that beats live trading by a wide, consistent margin almost always has a look-ahead leak. Point-in-time data closes the most common one.

How Console stamps every value

Every observation we store carries the timestamp it became public. Add an as_of parameter and the API replays the series as it stood on that date — no special endpoint, same envelope.

replay · point-in-timecopy
$ curl https://api.console.dev/v1/fx/eurusd?as_of=2026-03-14
# → the rate as published on 2026-03-14, not today's restatement

Naive vs point-in-time

QueryNaive storePoint-in-time
as_of = 2024-03-14Restated current valueValue as known on 03-14
Holiday dateSilently uses nearestis_forward_filled: true set
Weekend dateECB: frozen at FridayLive weekend value if available

“If you cannot reproduce the number you traded on, you do not have a backtest — you have a story.”

Putting it together

Point-in-time is the default on every Console series — fx, metals, and power alike. You do not opt in; you opt out by omitting as_of to get the latest. Either way, every value names the feed and timestamp it came from, so the number is auditable end to end.

MM
exchangerate.dev
Data guides for quantitative developers.

Keep reading

BlogWhat the ECB-based APIs missed last weekendRead MethodologyHow we reconcile live spot and daily reference ratesRead GuideReading derived crosses and triangulation flagsRead