You have a terminal open all day. Give it a useful view of what is about to happen outside your application.
In this project, we will build a command-line release radar powered by FXMacroData. It will fetch the economic release schedule for a currency, show the next 24 hours in your chosen timezone, and mark the next timed event.
The finished tool is small enough to keep beside a development server or run before starting a research session. Its output could also become the data layer for a desktop widget or an internal status panel.
The interesting implementation detail is time: a published release date is not always a published release time. We will handle that distinction explicitly.
What we are building
FXMacroData calendar → select the next 24 hours → convert timed events → render a terminal radar
| Input | What the tool does with it |
|---|---|
| Currency code | Selects the calendar to request |
| IANA timezone | Controls the displayed local times |
| Exact scheduled timestamp | Places an event in the 24-hour timeline |
| Date-only entry | Shows it separately without inventing a time |
Prerequisites
Use Python 3.11 or later, HTTPX and IANA timezone data. On Windows, Python's zoneinfo may need the tzdata package available in your environment.
The USD calendar supports no-key evaluation within the published request allowance. For non-USD calendars, use an API key with the required access. Store it in FXMD_API_KEY; the program sends it in the X-API-Key header.
Save the following five Python snippets, in order, into release_radar.py.
1. Request the actual calendar
Start with the imports and request function:
import os
import sys
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
import httpx
def fetch_calendar(currency, now, horizon):
key = os.environ.get("FXMD_API_KEY")
headers = {"X-API-Key": key} if key else {}
params = {
"start_date": (now - timedelta(days=1)).date().isoformat(),
"end_date": (horizon + timedelta(days=1)).date().isoformat(),
}
url = f"https://api.fxmacrodata.com/v1/calendar/{currency.lower()}"
with httpx.Client(timeout=15, headers=headers) as client:
response = client.get(url, params=params)
response.raise_for_status()
return response.json()["data"]
The endpoint is GET /v1/calendar/{currency}. The response's data array contains calendar rows, including the release identifier and scheduled timestamp.
We request a date range with a one-day margin on each side. The precise 24-hour filter happens locally, using UTC instants. The wider request avoids relying on a publisher's date boundary being the same as your display timezone.
Each run makes one request. It does not keep polling in the background.
2. Separate timed events from date-only entries
Use time_announced before interpreting a timestamp as an exact scheduled time:
def select_events(rows, now, horizon):
timed, date_only = [], []
for row in rows:
if row.get("time_announced") is not True:
date_only.append(row)
continue
when = datetime.fromtimestamp(
row["announcement_datetime"], tz=timezone.utc
)
if now <= when <= horizon:
timed.append((when, row))
return sorted(timed, key=lambda item: item[0]), date_only
For a timed row, announcement_datetime is a Unix timestamp in seconds. We turn it into a timezone-aware UTC datetime, apply the window, and sort the result.
A date-only row can contain a midnight day marker. Treating that marker as an actual scheduled time would create a convincing but incorrect countdown. Rows without an explicitly confirmed time stay outside the timeline.
Those entries remain visible in a separate section. Because the request includes a date margin, that section contains nearby notices rather than claiming every notice falls inside the precise 24-hour interval.
3. Render local times and highlight the next event
Add the presentation function:
def render(currency, timed, date_only, zone):
print(f"{currency.upper()} | next 24 hours | {zone.key}")
if not timed:
print("No exact-time releases returned in this window.")
for index, (when, row) in enumerate(timed):
stamp = when.astimezone(zone).strftime("%Y-%m-%d %H:%M %z")
marker = "NEXT" if index == 0 else " "
print(f"{marker} {stamp} {row['release']}")
if date_only:
print("\nDate/time not confirmed: nearby calendar entries")
for row in date_only:
day = row.get("announcement_date") or "Date unavailable"
print(f" {day} (publisher date) {row['release']}")
The first sorted event receives the NEXT marker. The numeric UTC offset is displayed beside every local time, which is useful around daylight-saving changes or when sharing a terminal screenshot with someone elsewhere.
Date-only notices retain their publisher date. We do not convert an unknown release time into your timezone.
An empty timed list gets an explicit message. That means no exact-time rows were returned inside the selected interval; it does not claim the entire calendar is empty.
4. Add the command-line entry point
Finish the file with:
def main():
currency = sys.argv[1] if len(sys.argv) > 1 else "USD"
zone = ZoneInfo(sys.argv[2] if len(sys.argv) > 2 else "UTC")
now = datetime.now(timezone.utc)
horizon = now + timedelta(hours=24)
try:
rows = fetch_calendar(currency, now, horizon)
except httpx.HTTPError as error:
raise SystemExit(f"Calendar request failed: {type(error).__name__}")
timed, date_only = select_events(rows, now, horizon)
render(currency, timed, date_only, zone)
if __name__ == "__main__":
main()
Now run:
python release_radar.py USD Australia/Sydney
Or choose another supported calendar and timezone when your API access permits it:
python release_radar.py EUR Europe/Berlin
No arguments defaults to USD and UTC. An HTTP failure exits with an error category instead of displaying a failed request as an empty schedule.
The display has this structure; the rows come from your calendar response:
<CURRENCY> | next 24 hours | <TIMEZONE>
NEXT <local date and time> <UTC offset> <release identifier>
<local date and time> <UTC offset> <release identifier>
The identifiers are retained from the API, so the output stays useful when you later add indicator filters or link the selected row to another part of your application.
5. Turn the script into a useful workspace tool
Start by running it manually when you begin a session. Then choose an extension based on how you work:
- Add an optional indicator argument and pass it as the calendar's
indicatorquery parameter. - Add a configurable hour window, using the same UTC selection logic.
- Write a JSON output mode so another local program can consume the selected events without parsing terminal text.
- Add a refresh command that fetches a new schedule, rather than assuming a saved timestamp can never change.
Keep the distinction between a scheduled event and a delivered release. This tool shows the schedule; reaching that time does not establish that a new value has been published. If your next feature needs to react to new data, use the appropriate announcement or event-delivery interface rather than inferring publication from the clock.
The result is a working FXMacroData application: one calendar request, a local event window, timezone-aware rendering and explicit treatment of uncertain times. You can use it as-is or build a richer interface on the same functions.
References
The FXMacroData calendar reference covers the endpoint and access model. Python's zoneinfo documentation explains timezone-data availability and conversion behavior.
Robert Tidball is the founder of FXMacroData.
AI was used for research, drafting and editing.
Top comments (0)