Read the economic calendar for one country, keep the releases whose topic you trade and sort them by the at timestamp. Each entry gives the release time in UTC, the publisher's time zone, the period the figures cover and the previous value of each measure, so a trader sees what is due and what it will be compared with.
Look up one release for one country
Ask the calendar for a country and a day with GET /v1/economics/calendar. This request asks for Japan on 20 November.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/economics/calendar?countries=JPN&start_date=2026-11-20&end_date=2026-11-20&limit=100"{
"releases": [
{
"release_id": "6c00d274-da35-4ed4-bcc1-574101544a03",
"revision": 3,
"country": "JPN",
"series_id": "jpn-consumer-prices",
"name": "Consumer prices",
"topic": "consumer_prices",
"family": "prices",
"period": "2026-10",
"status": "scheduled",
"at": "2026-11-19T23:30:00Z",
"timezone": "Asia/Tokyo",
"values": [
{
"measure": "core_core_cpi_yoy",
"concept": "cpi_ex_fresh_food_energy",
"unit": "percent",
"transformation": "year_over_year",
"previous": "2.4",
"not_seasonally_adjusted": true
},
{
"measure": "core_cpi_yoy",
"concept": "cpi_ex_fresh_food",
"unit": "percent",
"transformation": "year_over_year",
"previous": "2.1",
"not_seasonally_adjusted": true
},
{
"measure": "cpi_mom",
"concept": "cpi",
"unit": "percent",
"transformation": "month_over_month",
"previous": "0.4"
},
{
"measure": "cpi_yoy",
"concept": "cpi",
"unit": "percent",
"transformation": "year_over_year",
"previous": "2.1",
"not_seasonally_adjusted": true
}
]
}
],
"next_cursor": null
}The release is Japan's consumer prices for period 2026-10, so the figures cover October. at is 2026-11-19T23:30:00Z and timezone is Asia/Tokyo. Each item in values has a measure and a previous value; the previous cpi_yoy is 2.1 and the previous cpi_mom is 0.4. The figures are strings, so convert them to numbers before comparing.
transformation says how a figure is derived: year_over_year for cpi_yoy and month_over_month for cpi_mom. Three of the four measures carry not_seasonally_adjusted: true, so check the flag before you compare a forecast with the previous figure.
The request asked for 20 November, yet at falls on 19 November in UTC. The date window follows the publisher's local calendar, and 23:30 UTC is 08:30 the next morning in Tokyo.
Pick the next high-interest releases and convert the time
The calendar has no importance rating, so you decide what counts as high interest. The script below uses a watch list of topic values, keeps releases that are still scheduled and have an at time, sorts them and takes the first three.
- Set the watch list and your own zone Put the topics you trade in a set. The script watches consumer prices, GDP, labour force, employment and wages, and converts to
America/New_Yorkas the trader's zone. - Request the window Ask for one country and a range of dates. The script uses Japan from 16 to 29 November with
limit100. - Filter, sort and convert
datetime.fromisoformatreads the UTC timestamp.astimezoneconverts it to the publisher's zone from thetimezonefield and to the trader's zone. The previous values come fromvalues[].previous.import os from datetime import datetime from zoneinfo import ZoneInfo import requests BASE = "https://api.narwhalapi.com" WATCH = {"consumer_prices", "gdp", "labour_force", "employment", "wages"} MY_TZ = ZoneInfo("America/New_York") r = requests.get( f"{BASE}/v1/economics/calendar", params={"countries": "JPN", "start_date": "2026-11-16", "end_date": "2026-11-29", "limit": 100}, headers={"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}, timeout=10, ) r.raise_for_status() picked = [ x for x in r.json()["releases"] if x.get("topic") in WATCH and x["status"] == "scheduled" and "at" in x ] for x in sorted(picked, key=lambda x: x["at"])[:3]: utc = datetime.fromisoformat(x["at"].replace("Z", "+00:00")) local = utc.astimezone(ZoneInfo(x["timezone"])) mine = utc.astimezone(MY_TZ) prev = ", ".join(f"{v['measure']} {v['previous']}" for v in x["values"] if "previous" in v) print(x["name"], x["period"]) print(" UTC ", utc.strftime("%a %d %b %H:%M")) print(" Tokyo", local.strftime("%a %d %b %H:%M")) print(" NYC ", mine.strftime("%a %d %b %H:%M")) print(" previous:", prev or "none listed")
GDP 2026-Q3
UTC Sun 15 Nov 23:50
Tokyo Mon 16 Nov 08:50
NYC Sun 15 Nov 18:50
previous: none listed
Consumer prices 2026-10
UTC Thu 19 Nov 23:30
Tokyo Fri 20 Nov 08:30
NYC Thu 19 Nov 18:30
previous: core_core_cpi_yoy 2.4, core_cpi_yoy 2.1, cpi_mom 0.4, cpi_yoy 2.1
Wages and earnings 2026-09
UTC Tue 24 Nov 23:30
Tokyo Wed 25 Nov 08:30
NYC Tue 24 Nov 18:30
previous: average_cash_earnings_yoy 3.9, real_wages_yoy 1.6The GDP row shows Sun 15 Nov 23:50 in UTC although the window began on 16 November, because it is 08:50 on Monday 16 November in Tokyo. Its previous line reads none listed, since this GDP release has an empty values list. The script prints a placeholder whenever no previous exists, so one missing figure does not stop the loop.
One moment in three clocks
| Clock | Consumer prices release | How it is derived |
|---|---|---|
| UTC | Thu 19 Nov 23:30 | The at value |
| Tokyo | Fri 20 Nov 08:30 | at converted to the timezone field, Asia/Tokyo |
| New York | Thu 19 Nov 18:30 | at converted to the trader's zone |
Tokyo is nine hours ahead of UTC, and 23:30 plus nine hours is 08:30 the next day. New York is five hours behind UTC on that date, so 23:30 minus five hours is 18:30. The script uses zoneinfo, which applies daylight saving rules by itself, so you do not store offsets.
Read the same release again later
Every release has a release_id. GET /v1/economics/calendar/{release_id} returns that one release with two extra fields, version and observed_at, which says when Narwhal last saw the release in the publisher's calendar. Use it to refresh the row for a release you are watching without downloading the whole window.
status moves from scheduled to published when the figures are out, and a value can then carry actual beside previous. Some releases also have a stage, such as flash, advance, preliminary or final. Show it next to the name so a first estimate is not mistaken for the final one.
Does the calendar rank releases by importance?
No. Releases carry a family such as prices, labour or gdp, and a topic such as consumer_prices, but no rating. Build the watch list from those two fields, and use .get("topic") because some releases have no topic.
Why do some releases have no previous value?
values is always present and can be an empty list, and a measure can lack previous. The Japan GDP release above has an empty list. When a release revises the earlier number, previous_revised_from holds the figure it replaced.
What if a release has no exact time?
It has no at. It carries date, between or by instead, which the script skips because it needs a timestamp to convert. If you also want those rows, print the date and mark the time as unknown rather than guessing one.
Published .