Research guide
Backtest macro events without look-ahead bias
The acceptance rule for a point-in-time backtest is strict: a forecast is usable only when it was knowable before the release — that is, when generated_at < announcement_datetime. This page explains how FXMacroData enforces that rule structurally, which fields to join, what pre-release forecast history actually exists, and how to pull it.
Structural guarantee
No look-ahead rows exist
The prediction store rejects any forecast whose generated_at is not strictly before the linked announcement timestamp — at write time, not just at query time.
Join key
announcement_id
Stable id shared by /v1/predictions/{currency}/{indicator} and /v1/announcements/{currency}/{indicator}, in the form {currency}_{indicator}_{date}.
Honest coverage
Depth varies by series
Some pairs carry years of monthly pre-release forecasts; others have none. Check the coverage matrix before designing a study.
The pre-release rule, enforced for you
Query the predictions endpoint with pre_release_only=true (the default). Every returned forecast carries its own generated_at timestamp so you can re-verify the rule independently — reject any row where generated_at ≥ announcement_datetime and your acceptance test will simply never fire, because the storage layer refuses to persist such rows in the first place.
GET https://api.fxmacrodata.com/v1/predictions/usd/inflation
?pre_release_only=true
&start_date=2013-08-01
&end_date=2026-08-31
&limit=100
&api_key=YOUR_API_KEY
start_date is omitted the endpoint defaults to roughly the last six months. An empty result for an old window means no eligible pre-release forecast was recorded for those releases — not that the data is hidden behind another parameter or plan.
Every field a backtest join needs
/v1/predictions/{currency}/{indicator}
| Field | Meaning |
|---|---|
| announcement_id | Join key to the realised announcement |
| announcement_datetime | Release timestamp, Unix epoch seconds (UTC); announcement_datetime_local adds the source-agency local time |
| predictions[].predicted_value | The forecast, in the units of the selected series |
| predictions[].prediction_source | Stable source slug (e.g. cleveland_fed_nowcast) |
| predictions[].prediction_type | What the forecast represents — see the type reference |
| predictions[].generated_at | When the forecast became knowable, epoch seconds (UTC) |
/v1/announcements/{currency}/{indicator}
| Field | Meaning |
|---|---|
| val | Realised value of the selected series; variant selectors (e.g. frequency=mom) choose the series, and headline convenience fields such as val_mom appear where published |
| previous_value / previous_date | Prior observation, with change_from_previous computed |
| revisions[] | Revision history as {epoch, val} entries where source history is available; request revisions=all (or first/final) |
| announcement_datetime | Release timestamp, epoch seconds (UTC) |
| data_quality.point_in_time_safe | Whether every returned row carries a real release timestamp |
What pre-release history actually exists (USD)
Coverage depth is a property of the upstream publisher, not of your plan. The verified USD picture:
| Event family | Pre-release source | History |
|---|---|---|
| CPI and Core CPI (y/y and m/m) | Cleveland Fed Inflation Nowcasting | Every monthly release from the August 2013 reference period onward |
| Unemployment rate | FOMC SEP medians + NY Fed Survey of Market Expectations | Every year-end (December) release from 2015, each with its full SEP projection vintage path (~4 vintages per year); SME adds vintages from 2023. Monthly releases have no official pre-release consensus source |
| FOMC rate decision | Atlanta Fed Market Probability Tracker | Sparse — roughly three meetings per year from September 2023 |
| Non-farm payrolls, average hourly earnings, PPI, retail sales | — | No historical event-specific market consensus is available, on any plan |
The FXMacroData blended forecast additionally covers every confirmed release-calendar announcement going forward, and each blended row obeys the same pre-release rule. For non-USD coverage, see the full source-labelled coverage matrix.
Pulling the full archive
There is no separate bulk-export route for forecasts — the API is the canonical surface, and at forecast row counts a full pull is a handful of requests. Page with limit=100 and the before_date cursor, join to actuals on announcement_id, and keep the realised values from the announcements endpoint:
import requests
BASE = "https://api.fxmacrodata.com/v1"
KEY = {"api_key": "YOUR_API_KEY"}
preds = requests.get(
f"{BASE}/predictions/usd/inflation",
params={"pre_release_only": "true", "frequency": "mom",
"start_date": "2013-08-01", "end_date": "2026-08-31",
"limit": 100, **KEY},
).json()["data"]
actuals = requests.get(
f"{BASE}/announcements/usd/inflation",
params={"frequency": "mom", "revisions": "all",
"start_date": "2013-08-01", "end_date": "2026-08-31",
"limit": 100, **KEY},
).json()["data"]
by_id = {row["announcement_id"]: row for row in actuals}
for group in preds:
actual = by_id.get(group["announcement_id"])
if actual is None:
continue
for p in group["predictions"]:
assert p["generated_at"] < group["announcement_datetime"]
surprise = actual["val"] - p["predicted_value"]
Both endpoints accept the same series selectors (seasonality, frequency, annualization), so a m/m study queries frequency=mom on both sides and the forecasts land on the matching series.
Access for historical research
Without a key
USD announcements and predictions are open for evaluation over the most recent 90 days, up to 100 requests per day.
Individual plan
Full stored history for every supported currency and indicator, including the complete pre-release forecast archive. Every paid plan receives the same data — there is no higher tier holding back deeper history.