Settle cross-border trades when two countries' holidays differ

A broker back office settling a trade across two countries must land on a day when both are open, so each currency's holidays move the date. Narwhal returns each country's public holidays with their scope and subdivisions, and a short script can run a whole blotter against both calendars and mark every trade whose date differs from a weekday count.

Why a cross-border trade needs two holiday calendars

A broker back office settling a USD/JPY trade has to find a day on which both Japan and the United States are open. A weekday-only count is wrong whenever either country has a holiday inside the window, and a settlement instruction a day off fails or arrives late. The holiday endpoints answer one country at a time, so the back office asks for each leg and combines the results. This request asks whether Japan has a holiday on 23 November 2026.

curl, Japanese holidays on one date
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/calendars/holidays/JPN/on/2026-11-23"
Response
{
  "country": "JPN",
  "coverage": [
    {
      "scope": "national_public",
      "subdivision_codes": []
    }
  ],
  "holidays": [
    {
      "date": "2026-11-23",
      "name": "Labor Thanksgiving Day",
      "holiday_key": "labor_thanksgiving_day",
      "local_name": "勤労感謝の日",
      "scope": "national_public",
      "subdivision_codes": []
    }
  ]
}

The entry is Labor Thanksgiving Day with scope national_public, and an empty subdivision_codes list means it applies nationwide. The same request for the United States on 26 November returns Thanksgiving Day with scope federal_government. Each country's list carries its own scope, so the back office records which list it relies on. A weekday count alone would still call Monday 23 November a settlement day.

Run the blotter through both calendars

The script settles each trade of a blotter against the countries of its two currencies, and reports the date that a weekday-only count would have given beside the real one. A trade where the two differ is marked CHECK, which is the list an operations lead reviews.

  1. Name the countries of each leg JPY lists Japan and the United States, and GBP lists the United Kingdom with the desk's subdivision GB-ENG and the United States.
  2. Load each year once holidays fetches a country's list for a year and keeps it, so a blotter of hundreds of trades makes a handful of requests. A trade near 31 December loads the next year when the walk reaches it.
  3. Skip weekends and closures of either country closures returns the holidays that fall on a day in any listed country, and a day counts toward N only when that list is empty. A holiday with a subdivision list closes the desk only when its subdivision is in the list.
    Python, a settlement blotter for two currency pairs
    import os
    from datetime import date, timedelta
    from functools import cache
    import requests
    
    HEADERS = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}
    
    
    @cache
    def holidays(country, year):
        r = requests.get(
            f"https://api.narwhalapi.com/v1/calendars/holidays/{country}/{year}",
            headers=HEADERS,
            timeout=10,
        )
        r.raise_for_status()
        return r.json()["holidays"]
    
    
    def closures(day, places):
        out = []
        for country, region in places.items():
            for h in holidays(country, day.year):
                # an empty subdivision list means the holiday applies nationwide
                if h["date"] == day.isoformat() and (not h["subdivision_codes"] or region in h["subdivision_codes"]):
                    out.append(f"{country} {h['name']}")
        return out
    
    
    def settle(trade, n, places):
        day, held = trade, []
        while n > 0:
            day += timedelta(1)
            if day.weekday() >= 5:
                continue
            shut = closures(day, places)
            if shut:
                held.append(f"{day:%d %b} {'; '.join(shut)}")
            else:
                n -= 1
        return day, held
    
    
    JPY = {"USA": None, "JPN": None}
    GBP = {"GBR": "GB-ENG", "USA": None}  # country -> subdivision of the desk
    blotter = [
        ("USD/JPY", date(2026, 11, 20), 2, JPY),
        ("USD/JPY", date(2026, 11, 24), 2, JPY),
        ("GBP/USD", date(2026, 12, 23), 2, GBP),
    ]
    for pair, trade, n, places in blotter:
        day, held = settle(trade, n, places)
        plain, _ = settle(trade, n, {})
        flag = "CHECK" if day != plain else "same"
        print(f"{pair} {trade} T+{n} settles {day}, weekdays only {plain} [{flag}]", "; ".join(held))
Output of the script
USD/JPY 2026-11-20 T+2 settles 2026-11-25, weekdays only 2026-11-24 [CHECK] 23 Nov JPN Labor Thanksgiving Day
USD/JPY 2026-11-24 T+2 settles 2026-11-27, weekdays only 2026-11-26 [CHECK] 26 Nov USA Thanksgiving Day
GBP/USD 2026-12-23 T+2 settles 2026-12-29, weekdays only 2026-12-25 [CHECK] 25 Dec GBR Christmas Day; USA Christmas Day; 28 Dec GBR Boxing Day

The first trade, on Friday 20 November, would settle on Tuesday 24 November by weekdays alone. Japan is closed on Monday 23 November, so the second day moves to Wednesday 25 November. The second trade is held by the US Thanksgiving Day, and the third by Christmas Day in both countries and by Boxing Day in the United Kingdom, on Monday 28 December. Every line in this blotter differs from a weekday count.

What the back office still checks by hand

CheckWhere it comes from
WeekendsThe loop, which skips Saturday and Sunday
Public holidays of each currency's countryThe holiday endpoints, one country per leg
A regional holiday at the booking desksubdivision_codes, compared with the desk's subdivision
Closures of an exchange or a payment systemThe venue's own list, added to the set the loop checks
The number of days, T+1 or T+2The product and venue rules, which the API does not set

The last two rows stay with the back office, because the holiday list belongs to a country and not to a venue.

Warn the trader when a closure sits near the trade

GET /v1/calendars/holidays/{country}/next?from_date=2026-11-20 returns the first holiday on or after that date in a holiday object. For Japan it returned 23 November, and for the United States it returned 26 November, both inside a T+2 window. Run it for each leg when a trade is booked and tell the trader that a closure is coming. Use the year list for the date itself.

Does the list match an exchange or payment calendar?

It lists public holidays for a country, with scope and subdivisions. A closure that exists only at an exchange or a payment system is absent. The US list for 2026 has 11 entries and none on Good Friday, 3 April, a day some exchanges close. Add the venue's closures to the set the loop checks.

What happens when a country has no calendar for the year?

The API returns a 404 problem response with the code calendar_not_published. The United Kingdom's 2028 request did that. raise_for_status() stops the script, which is the safe behaviour for an instruction that must not go out wrong. Decide whether to fall back to weekdays and flag the date, or wait.

Which days should a currency pair skip?

A day is skipped when either country is closed. A USD/JPY trade waits for a day open in both Tokyo and the United States. Check the convention of the product, because T+1 or T+2 and any exception for the intermediate day come from the market rules, which the API does not state.

Published .

Next steps