By the end of this guide you will have a Python script (and a JavaScript equivalent) that queries
the FXMacroData release calendar once to find the exact announcement_datetime of your
chosen indicator, then schedules a single targeted API call for that moment — eliminating continuous
polling and reducing unnecessary requests to zero between releases.
Prerequisites
- • A FXMacroData API key — USD is free without a key; all other currencies require one. Obtain yours at fxmacrodata.com
- • Python 3.9+ (or Node.js 18+ for the JavaScript variant)
- • The
requestslibrary installed (pip install requests) - • Basic familiarity with ISO 8601 datetimes and Unix timestamps
Why schedule instead of poll?
The naive approach to keeping macro data fresh is a simple polling loop: check the indicator endpoint every five or ten minutes and update your model whenever a new value appears. This works, but it wastes requests — and more importantly, it introduces latency. A CPI release at 08:30 UTC could be missed entirely if your poller last ran at 08:28 and next runs at 08:33.
The release calendar endpoint returns the precise UTC timestamp of every upcoming announcement. With that timestamp in hand you can sleep until one second before the release, fire a single targeted indicator call, and then immediately sleep until the following release. You make exactly one call per release and you receive the data within seconds of publication — with no wasted requests in between.
Core workflow
- Call
/api/release_calendar/{currency}— returns all upcoming releases (up to 30 days ahead). - Find the next entry for your target indicator and read its
announcement_datetime. - Sleep until release time − a small offset (e.g. 2 seconds).
- Fetch
/api/{currency}/{indicator}— you will receive the freshly published value. - Loop back to step 1 to schedule the release after that.
Step 1 — Understand the release calendar response
The release calendar endpoint returns a JSON object with a data array.
Each element represents one upcoming announcement, identified by the indicator's
release name and a Unix announcement_datetime timestamp.
curl "https://fxmacrodata.com/api/release_calendar/usd"
Response shape:
{
"currency": "usd",
"data": [
{
"release": "cpi",
"announcement_datetime": 1741860600
},
{
"release": "gdp",
"announcement_datetime": 1742119800
},
{
"release": "unemployment_rate",
"announcement_datetime": 1742378400
}
]
}
announcement_datetime is a Unix timestamp in seconds (UTC).
The releases are sorted chronologically, nearest first.
For non-USD currencies, append your API key:
https://fxmacrodata.com/api/release_calendar/aud?api_key=YOUR_API_KEY
You can browse all supported release names in the
API documentation
— each indicator page lists the exact release string you will filter on.
Step 2 — Find the next release time for a specific indicator
Once you understand the shape of the response you can write a helper that fetches the calendar and picks out the soonest upcoming entry for a chosen indicator.
import time
import requests
BASE = "https://fxmacrodata.com/api"
def next_release(currency: str, indicator: str, api_key: str | None = None) -> float | None:
"""Return the Unix timestamp (seconds, UTC) of the next scheduled release,
or None if no upcoming entry is found within the calendar window."""
url = f"{BASE}/release_calendar/{currency}"
params = {}
if api_key:
params["api_key"] = api_key
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
payload = resp.json()
events = payload.get("data", [])
now = time.time()
for event in events:
if event.get("release") == indicator:
ts = event.get("announcement_datetime")
if ts and float(ts) > now:
return float(ts)
return None
The function iterates the calendar array — which is already sorted by date — and returns the
announcement_datetime of the first entry that matches your indicator and lies in
the future. It returns None when no upcoming release is found in the current
calendar window (typically 30 days).
Step 3 — Sleep until the release, then fetch the indicator
With the target timestamp available you can pause execution until just before the release fires, then call the indicator endpoint to retrieve the freshly published value.
def wait_and_fetch(currency: str, indicator: str, api_key: str | None = None) -> dict | None:
"""Block until the next release of `indicator` for `currency`, then return
the latest indicator data immediately after the announcement."""
release_ts = next_release(currency, indicator, api_key)
if release_ts is None:
print(f"No upcoming release found for {currency}/{indicator} in the next 30 days.")
return None
# Wake up 2 seconds before the scheduled announcement
wake_at = release_ts - 2
sleep_secs = max(0.0, wake_at - time.time())
print(f"Sleeping {sleep_secs:.0f}s until {currency.upper()} {indicator} release…")
time.sleep(sleep_secs)
# Fetch the indicator — released data will be available within a few seconds
url = f"{BASE}/{currency}/{indicator}"
params = {"api_key": api_key} if api_key else {}
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
return resp.json()
Step 4 — Build a continuous scheduling loop
Combine the helper functions into a loop that refreshes the calendar after each release and immediately schedules the next call. This pattern handles consecutive releases cleanly and never leaves gaps in your data feed.
import time
import requests
BASE = "https://fxmacrodata.com/api"
API_KEY = "YOUR_API_KEY" # omit or set to None for free USD endpoints
CURRENCY = "usd"
INDICATOR = "cpi"
def next_release(currency, indicator, api_key=None):
url = f"{BASE}/release_calendar/{currency}"
params = {"api_key": api_key} if api_key else {}
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
now = time.time()
for event in resp.json().get("data", []):
if event.get("release") == indicator:
ts = event.get("announcement_datetime")
if ts and float(ts) > now:
return float(ts)
return None
def on_release(data):
"""Called immediately after each release — replace with your own logic."""
latest = data[-1] if data else {}
print(f" New value: {latest.get('value')} announced at {latest.get('announcement_datetime')}")
def run():
print(f"Starting scheduler for {CURRENCY.upper()} {INDICATOR}")
while True:
release_ts = next_release(CURRENCY, INDICATOR, API_KEY)
if release_ts is None:
print("No release found within 30-day window — retrying in 24 hours.")
time.sleep(86_400)
continue
sleep_secs = max(0.0, release_ts - 2 - time.time())
print(f"Next release in {sleep_secs / 3600:.1f}h ({time.strftime('%Y-%m-%d %H:%M UTC', time.gmtime(release_ts))})")
time.sleep(sleep_secs)
url = f"{BASE}/{CURRENCY}/{INDICATOR}"
params = {"api_key": API_KEY} if API_KEY else {}
resp = requests.get(url, params=params, timeout=10)
if resp.ok:
on_release(resp.json())
else:
print(f" Fetch failed: {resp.status_code}")
# Brief pause before re-querying the calendar so the API registers the new release
time.sleep(5)
if __name__ == "__main__":
run()
The 5-second pause after each fetch gives the API time to register the new release before you re-query the calendar. Without it you might accidentally schedule against the release you just fetched if it still appears in the “future” slice of the calendar.
Step 5 — Track multiple indicators across multiple currencies
If you need to monitor several indicator/currency pairs simultaneously, run one scheduler
per pair in a separate thread or async task. The pattern below uses Python's
threading module — no external scheduler or queue is required.
import threading
WATCHES = [
{"currency": "usd", "indicator": "cpi"},
{"currency": "usd", "indicator": "gdp"},
{"currency": "aud", "indicator": "policy_rate"},
{"currency": "gbp", "indicator": "unemployment_rate"},
]
threads = []
for watch in WATCHES:
t = threading.Thread(
target=run,
kwargs=watch,
name=f"{watch['currency']}-{watch['indicator']}",
daemon=True,
)
t.start()
threads.append(t)
for t in threads:
t.join()
Each thread independently queries the release calendar for its own currency and sleeps until its next event, so the threads do not interfere with each other. AUD and GBP endpoints require a paid API key — see the AUD policy rate docs and GBP unemployment docs for field details.
Step 6 — JavaScript / Node.js variant
The same pattern translates directly to Node.js for server-side applications or edge functions.
Use the native fetch API (Node.js 18+) and setTimeout in place of
time.sleep.
const BASE = "https://fxmacrodata.com/api";
const API_KEY = "YOUR_API_KEY"; // set to "" for free USD endpoints
async function nextRelease(currency, indicator) {
const url = API_KEY
? `${BASE}/release_calendar/${currency}?api_key=${API_KEY}`
: `${BASE}/release_calendar/${currency}`;
const resp = await fetch(url);
if (!resp.ok) throw new Error(`Calendar fetch failed: ${resp.status}`);
const { data = [] } = await resp.json();
const nowSec = Date.now() / 1000;
const match = data.find(ev => ev.release === indicator && ev.announcement_datetime > nowSec);
return match ? match.announcement_datetime : null;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function runScheduler(currency, indicator) {
console.log(`Scheduler started for ${currency.toUpperCase()} ${indicator}`);
while (true) {
const releaseSec = await nextRelease(currency, indicator);
if (!releaseSec) {
console.log("No release found — retrying in 24h");
await sleep(86_400_000);
continue;
}
const sleepMs = Math.max(0, (releaseSec - 2) * 1000 - Date.now());
const releaseDate = new Date(releaseSec * 1000).toISOString();
console.log(`Next ${indicator} release at ${releaseDate} — sleeping ${(sleepMs / 3600000).toFixed(1)}h`);
await sleep(sleepMs);
const indicatorUrl = API_KEY
? `${BASE}/${currency}/${indicator}?api_key=${API_KEY}`
: `${BASE}/${currency}/${indicator}`;
const data = await (await fetch(indicatorUrl)).json();
const latest = data[data.length - 1];
console.log(` New value: ${latest?.value} announced at ${latest?.announcement_datetime}`);
await sleep(5000); // brief pause before re-querying calendar
}
}
runScheduler("usd", "cpi");
announcement_datetime field in the
release calendar is always in seconds (Unix epoch). The same field in the
indicator response may also appear as seconds — multiply by 1 000 to get milliseconds
for JavaScript Date objects.
Step 7 — Handle edge cases robustly
A production scheduler should handle three edge cases that can appear in practice:
- Release delayed or rescheduled. Central banks and statistical offices occasionally postpone announcements. Re-query the calendar if the scheduled time passes without a new value appearing in the indicator response (add a short retry loop after waking).
-
No upcoming release in the 30-day window. The calendar only covers
roughly 30 days ahead. When
next_releasereturnsNone, sleep for 24 hours and try again — the next release date is usually published a few weeks in advance. - Network errors. Wrap your HTTP calls in a retry loop with exponential back-off. A transient 5xx or timeout should not crash the scheduler — log the error and retry after a short delay rather than re-sleeping until the next calendar entry.
import time, requests
from requests.adapters import HTTPAdapter, Retry
def make_session():
session = requests.Session()
retries = Retry(
total=5,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
)
session.mount("https://", HTTPAdapter(max_retries=retries))
return session
SESSION = make_session()
Replace the bare requests.get() calls in earlier steps with
SESSION.get() to gain automatic retries at no cost.
What you built
- ✓ Queried the release calendar to find the precise UTC announcement time for any indicator
- ✓ Built a
wait_and_fetchhelper that sleeps until just before the release and fires a targeted API call - ✓ Assembled a continuous scheduling loop that automatically advances to the next release after each one fires
- ✓ Scaled to multiple indicator/currency pairs using threads (Python) or concurrent async tasks (JavaScript)
- ✓ Added robust retry handling for network errors and delayed releases
Next steps
With a lean, event-driven data feed in place there are several natural extensions:
- Persist the fetched values to a time-series database (InfluxDB, TimescaleDB, or even a simple SQLite file) so your models can back-reference historical readings without an additional API call.
-
Compute surprises on release. Store the
previousfield from the indicator response alongside your consensus forecast to calculate the deviation the moment a release fires. - Trigger downstream actions — push the fresh reading to a Slack/Discord webhook, update a live dashboard, or submit a trade signal to your broker's API — all within seconds of the official announcement.
— The FXMacroData Team