From collection to a repeatable workflow
Select macro data known at a backtest decision
Select eligible value vintages at an explicit decision cutoff and preserve their evidence, instead of manually reconstructing which revised number was known at the time.
Check This Task's CoverageAccess and evaluation
The coverage check and this example are public. Run these subscriber examples on your own computer using Python, the requests library and an API key held in the FXMD_API_KEY environment variable. Keep keys out of browser code and shared files.
Individual is USD 50/month for one person; Business is USD 250/month for an organisation of up to 10 people. Both include the same available data. Eligible new customers can take a 14-day trial with a payment method required. Review current pricing, eligibility and cancellation terms.
No-key evaluation covers recent USD data under the free-tier limits; it does not validate full history or other currencies. Customer-facing display requires the appropriate redistribution licence.
1. Separate history from replay evidence
Check the series and observation window first. History depth, retained vintages and verified publication clocks are separate requirements. The task check identifies the catalogue entry; it does not certify a backtest.
2. Choose the decision clock
The example uses as_of with availability=public and revisions=latest to select the latest eligible vintage before pagination. Use availability=captured only when your study explicitly requires the captured-value clock. Neither clock proves when an API customer could read the value.
3. Reject unverified inputs
The example stops if a nonempty page is not marked data_quality.point_in_time_safe. It preserves source-unit and dataset-version evidence. Repeat selection at every decision cutoff in your study; do not reuse a single latest snapshot across historical decisions.
Run the demonstration
Dates below are example observation windows, not assertions of available data. Choose your required dates after checking coverage.
Download economic_workflows.py
python economic_workflows.py point-in-time --currencies usd --indicator inflation --start 2025-01-01 --end 2025-12-31 --as-of 2026-01-15T12:00:00Z
Read the complete Python example
"""Customer-side FXMacroData workflow demonstrations.
Run on your own machine with requests available and FXMD_API_KEY in your
environment. Prints JSON; does not place orders or create alert subscriptions.
"""
import argparse
import json
import os
from datetime import date, datetime, timezone
import re
import requests
BASE = "https://api.fxmacrodata.com"
def get_json(path, params):
key = os.environ.get("FXMD_API_KEY", "")
if not key:
raise RuntimeError("Set FXMD_API_KEY for these subscriber workflows.")
response = requests.get(BASE + path, params=params, headers={"X-API-Key": key}, timeout=30)
# Avoid exceptions containing an authenticated URL or response body.
if response.status_code != 200:
raise RuntimeError(f"Request failed with HTTP {response.status_code}; review access and rate limits.")
return response.json()
def history(currency, indicator, start, end, *, as_of=None):
"""Retain complete response pages and stop if a dataset changes mid-read."""
pages, offset, version = [], 0, None
while True:
params = {"start_date": start, "end_date": end, "limit": 100, "offset": offset,
"value_mode": "source", "series_mode": "raw", "revisions": "all"}
if version:
params["dataset_version"] = version
if as_of:
params.update(as_of=as_of, availability="public", revisions="latest")
page = get_json(f"/v1/announcements/{currency}/{indicator}", params)
current = page.get("dataset_version")
if not isinstance(current, str) or not current:
raise RuntimeError("Dataset-version evidence is missing; do not combine pages.")
if version is not None and current != version:
raise RuntimeError("Dataset changed during pagination; restart the download.")
version = current
if as_of and page.get("data") and page.get("data_quality", {}).get("point_in_time_safe") is not True:
raise RuntimeError("Selected history is not verified for point-in-time replay.")
if page.get("data") and (page.get("value_mode") != "source" or page.get("value_metadata", {}).get("normalization_applied") is not False):
raise RuntimeError("Source-unit values were not confirmed.")
pages.append(page)
pagination = page.get("pagination")
if not isinstance(pagination, dict) or not isinstance(pagination.get("has_more"), bool):
raise RuntimeError("Pagination evidence is missing; do not treat this download as complete.")
if not pagination["has_more"]:
break
next_offset = pagination.get("next_offset")
if not isinstance(next_offset, int) or isinstance(next_offset, bool) or next_offset <= offset:
raise RuntimeError("Pagination did not advance.")
offset = next_offset
return {"currency": currency, "indicator": indicator, "as_of": as_of,
"downloaded_at": datetime.now(timezone.utc).isoformat(),
"row_count": sum(len(p.get("data", [])) for p in pages), "pages": pages}
def inflation_history(currencies, start, end):
# Keep national measures separate: matching slugs do not establish comparability.
return [history(currency, "inflation", start, end) for currency in currencies]
def point_in_time(currency, indicator, start, end, as_of):
if not as_of:
raise ValueError("Supply an explicit timezone-aware decision cutoff with --as-of.")
cutoff = datetime.fromisoformat(as_of.replace("Z", "+00:00"))
if cutoff.tzinfo is None:
raise ValueError("The decision cutoff must include a timezone.")
return history(currency, indicator, start, end, as_of=cutoff.isoformat())
def monitor_release(currency, indicator, start, end):
# One bounded read, intended to be run by your scheduler. No inferred next dates.
result = history(currency, indicator, start, end)
return {"checked_at": datetime.now(timezone.utc).isoformat(),
"records": result,
"interpretation": "Inspect announcement_id, retained vintages and publication evidence. An empty range is not proof that no release occurred; this read is not a latency measurement."}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("workflow", choices=["inflation-history", "point-in-time", "release-monitoring"])
parser.add_argument("--currencies", default="usd,eur,gbp")
parser.add_argument("--indicator", default="inflation")
parser.add_argument("--start", required=True)
parser.add_argument("--end", required=True)
parser.add_argument("--as-of")
args = parser.parse_args()
currencies = [code.strip().lower() for code in args.currencies.split(",")]
if any(len(code) != 3 or not code.isascii() or not code.isalpha() for code in currencies):
parser.error("Use three-letter currency codes separated by commas.")
try:
if date.fromisoformat(args.start) > date.fromisoformat(args.end):
parser.error("The observation start must be on or before the end.")
except ValueError:
parser.error("Use valid YYYY-MM-DD observation dates.")
if not re.fullmatch(r"[a-z][a-z0-9_]{0,79}", args.indicator):
parser.error("Choose a valid indicator slug.")
if args.workflow != "inflation-history" and len(currencies) != 1:
parser.error("Choose one currency for a decision cutoff or monitoring read.")
if args.workflow == "inflation-history":
result = inflation_history(currencies, args.start, args.end)
elif args.workflow == "point-in-time":
result = point_in_time(currencies[0], args.indicator, args.start, args.end, args.as_of)
else:
result = monitor_release(currencies[0], args.indicator, args.start, args.end)
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
What the output contains
A JSON object containing the normalized decision cutoff, selected row count and response pages. A zero-row result remains empty. An unverified quality flag stops the example rather than silently admitting the input.
Interpret the result
This demonstrates input selection, not strategy performance. Date-only, assumed or unknown publication evidence cannot establish strict intraday eligibility. Even eligible returned rows do not certify that every expected observation or publisher vintage exists.