Back to Insights
Strategy Deep Dive·2026-04-18·5 min read

We gave carry trade real history. It had opinions.

Our carry strategy MVP used a single 2026 rate snapshot — JPY 0.73, USD 3.64 — as if that table had always been true. Connecting FRED's historical series changed the story: our signal count nearly 6x'd, and some long-term positions we thought were correct turned out to have been short the same currency a year earlier.

By Li Tan

When we first shipped the FX carry strategy, we took a shortcut. Central bank policy rates change slowly — USD goes 0.25 → 5.50 over two years, but within any given month the number barely moves. So we baked today's rate table (a Python dict) into the strategy and called it done. The rankings came out sensible: USD > NZD > AUD > CAD > EUR > CHF > JPY. Long USD_JPY. Short EUR_USD. Ship it.

The obvious problem: if you run this on 2020-2024 data, the strategy STILL thinks USD has a 3.64% rate. In 2020. When the actual Fed Funds was 0.25%. That's not a small error — it's a 3.4 percentage point mis-representation of the funding cost, applied to every historical rebalance date. A backtest from that period would tell you 'carry works great' when the backtest is secretly using 2026 rates on 2020 prices.

The fix

FRED publishes most of what we need for free. A thin fetcher, a disk cache with 7-day TTL, and a daily cron to refresh the snapshot. Eight currencies, 9,604 daily observations back to 2000-01-01, 603 KB of CSV sitting in the repo. Total engineering: about three hours.

The strategy gains a rates_df parameter. When provided, every rebalance date does a point-in-time lookup — base currency rate and quote currency rate BOTH taken as of the signal date, not some global snapshot. No look-ahead, no hindsight. When rates_df is None, it falls back to the static dict (still useful for unit tests and offline dev).

What actually changed

Ran a controlled experiment on the same 5 years of synthetic FX data. Static rates: 3 total signals over the backtest. FRED rates: 17 total signals. The static version is a one-time ranking that never re-ranks — if USD is top at init, USD is top forever. The FRED version re-ranks every month on point-in-time rates, so when USD crossed from low-yielding to high-yielding mid-2022, the book flipped:

  • 2020-2021: USD low, JPY low, EUR/AUD/NZD moderate. Book: long AUD_JPY, long NZD_JPY, short USD_CHF (USD was BELOW CHF back then).
  • Mid-2022: USD crosses AUD on the way up. AUD_USD carry flips sign. Position closes, then reopens inverted 3 months later.
  • 2023-2024: USD is the cleanest top-yielder. Book consolidates around long USD_JPY, long USD_CHF. The static snapshot 'always long USD_JPY' rule turns out to be correct for this era — but only after we actually saw the data.
  • 2025-2026: USD declining as Fed eases. Rankings start to compress, signal frequency drops.

The honest takeaway

There's a temptation in quant work to treat 'the current state of the world' as the universal truth for backtesting. It's almost always wrong. A strategy's true performance depends on what was knowable at each historical decision point, not what you know today. Static snapshots let you lie to yourself without realizing it.

For carry specifically, the upgrade was cheap and the honesty improvement was large. For strategies where 'the past' IS the decision — regime detection, ML models, etc. — we already run walk-forward. Carry had quietly been the exception. It's not anymore.

The FRED snapshot refreshes daily at 02:00 UTC via systemd. If you're running a local backtest and want the freshest rates, run scripts/refresh_fred_rates.py — or just let the 7-day disk cache handle it for normal development.

One last thing we found interesting: for JPY, AUD, NZD, CHF, CAD, FRED still only has monthly policy-rate proxies (BIS series). True daily rates for those five currencies would need direct central-bank API integration. We probed 15 FRED candidate series — none are daily. So there's still a small layer of 'discrete monthly steps' in the rate path. In practice this matters less than you'd think: policy rates don't change within a month anyway, so forward-filling a monthly series gives you the right number most of the time.

Like this kind of analysis? Upgrade to Learner for weekly articles, real-time signals, and full educational content.

See Learner plan