feat(fx): add TN (Tom-Next) period and near_date API for FX swap legs#68
Conversation
TN was missing from the Period enum despite ON and SN being present. Adds TN with settlement_date returning the spot date (T+2 for T+2 pairs), matching Bloomberg's tom-next convention where near=T+1 and far=spot. Also adds `near_date()` to Period so callers can obtain both legs of an ON/TN/SN FX swap independently for per-leg pricing, and includes ON as a pre-spot quote in the sample forward helper (negative outright pts). Co-authored-by: Jeremy Wang <thejerwang@users.noreply.github.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
@claude can you consider the cut off time and some pairs have T+1 as spot like USDCAD |
|
Claude finished @thejerwang's task in 5m 55s —— View job T+1 Spot Pair Support + Cut-off Time Docs — Done
What was changed
Settlement convention summary
|
Introduces a `SpotLag` type alias and threads it through `Period::settlement_date` and `Period::near_date` so that T+1 pairs (USDCAD, USDTRY, USDRUB, etc.) are handled correctly alongside the standard T+2 convention. Key changes: - `SpotLag = i64` type alias with per-pair convention table - `settlement_date` and `near_date` now accept an explicit `spot_lag` parameter (1 for T+1 pairs, 2 for standard T+2 pairs) - `FXForwardHelper` gains a `spot_lag` field and a `with_spot_lag` constructor; the existing `new` constructor defaults to `spot_lag = 2` - Adds `test_settlement_date_usdcad` and `test_near_date_usdcad` tests covering the full ON/TN/SN/SPOT/1W settlement structure for T+1 pairs - Documents the intra-day cut-off time convention (USDCAD: noon New York) and the caller's responsibility for advancing `valuation_date` after cut-off Co-authored-by: Jeremy Wang <thejerwang@users.noreply.github.com>
|
@claude can we not have those setting on underlying of definition of spot. And we really should try to interpret market data based on underlying setting of cutoff time and time of market data. Don't feel pass another argument in it's a good solution |
|
Claude finished @thejerwang's task in 9m 47s —— View job Refactoring spot_lag into currency pair underlying — Done
What changed
Usage example (USDCAD): let underlying = FXUnderlying::USDCAD;
// Interpret market data timestamp against cut-off
let val_date = underlying.effective_valuation_date(today, market_data_utc_time);
// Settlement dates use T+1 automatically — no spot_lag argument needed
let spot = underlying.settlement_date(Period::SPOT, val_date)?; // T+1
let near = underlying.near_date(Period::TN, val_date)?; // Some(T+1)
// Build helper with correct convention baked in
let helper = underlying.forward_helper(val_date, spot_ref, quotes);| Branch |
…riod API
Period::settlement_date and near_date no longer take a spot_lag argument —
they default to the standard T+2 convention. Pair-specific logic (T+1 for
USDCAD etc.) lives on FXUnderlying, which already owns settles(). The lag-aware
internals are pub(crate) so FXForwardHelper can still use them without
exposing the parameter in the public API.
New on FXUnderlying:
- settlement_date / near_date — use the pair's settles() automatically
- cutoff_utc() — pair-specific UTC cut-off (USDCAD: 17:00)
- effective_valuation_date() — advances past the cut-off when market data
arrives after it
- forward_helper() — builds FXForwardHelper with the right lag
Co-authored-by: Jeremy Wang <thejerwang@users.noreply.github.com>
|
@claude can you add more pairs settings of cutoff time and spot date |
|
Claude finished @thejerwang's task in 5m 0s —— View job Adding More FX Pair Cutoff & Spot Lag Settings
Added 13 new pairs to New pairs
What changed in
|
Expands FXUnderlying with G10 and EM pairs, encoding each pair's T+1 vs T+2 settlement and UTC cut-off time on the enum itself so callers never need to look up conventions manually. New T+1 pairs: USDMXN (17:00 UTC), USDTRY (09:00 UTC noon Istanbul), USDRUB (09:30 UTC 12:30 Moscow). New T+2 pairs: AUDUSD, NZDUSD, USDCHF, USDNOK, USDSEK, EURCHF, EURNOK, EURSEK, EURJPY (100 fwd-pts converter). Adds currency_to_country mappings for AUD, NZD, CHF, NOK, SEK, MXN, TRY, RUB and tests for settles(), cutoff_utc(), settlement_date(), and effective_valuation_date() on new pairs. Co-authored-by: Jeremy Wang <thejerwang@users.noreply.github.com>
|
@claude I'll suggest we add those cut off time and spot date to enum FXUnderlying. Add more or all pairs to enum. We also have got settles already to say usdcad is 1? |
|
Claude finished @thejerwang's task in 7m 23s —— View job Expanded
|
| Currency | Calendar |
|---|---|
| SGD | Singapore |
| HKD | HongKong |
| CNY | China |
| PLN | Poland (default/settlement) |
| HUF | Hungary |
| CZK | CzechRepublic |
| ZAR | SouthAfrica |
| KRW | SouthKorea |
| INR | India |
| BRL | Brazil (default/settlement) |
| DKK | Denmark |
| IDR | Indonesia |
| TWD | Taiwan |
| THB | Thailand |
| ILS | Israel (default/settlement) |
| RON | Romania |
forward_points_converter now returns 100 for AUDJPY, NZDJPY, and CHFJPY in addition to the existing JPY crosses.
settles() was already correct — USDCAD (and USDMXN, USDTRY, USDRUB) return 1; all other pairs (including all 39 new ones) fall through to 2. No changes needed.
| Branch
…l calendar mappings Adds 39 new pairs: EUR/GBP/AUD/NZD crosses, EM USD pairs (SGD, HKD, CNY, PLN, HUF, CZK, ZAR, KRW, INR, BRL, DKK, IDR, TWD, THB, ILS, RON) and minor crosses (CHFJPY, CADCHF). Extends currency_to_country to cover all new currency codes via existing calendar implementations. AUDJPY, NZDJPY, CHFJPY added to the /100 forward-points converter group. All new T+2 pairs fall through to the existing wildcard in settles() and cutoff_utc(). Co-authored-by: Jeremy Wang <thejerwang@users.noreply.github.com>
|
@claude can you fix the pipeline |
|
Claude finished @thejerwang's task in 1m 57s —— View job Pipeline Fixed
What was fixedThe Rustfmt check failed due to lines exceeding the column limit in two files:
All changes were purely whitespace/formatting — no logic was altered. |
Co-authored-by: Jeremy Wang <thejerwang@users.noreply.github.com>
|
@claude can you try to fix pipeline please |
|
Claude finished @thejerwang's task in 2m 14s —— View job Pipeline Fixed
What was fixedTwo
|
Reformat two ok_or_else closures and one assert_eq! call to satisfy the 100-column limit enforced by cargo fmt. Co-authored-by: Jeremy Wang <thejerwang@users.noreply.github.com>
|
@claude can you add tests to match screen - for forward & swap pricing / forward points - the quote is for swap and the right hand side is pure forward contract please? |
|
Claude finished @thejerwang's task in 5m 59s —— View job Adding Swap & Forward Outright Pricing Tests
What was added —
|
| Swap | Near leg | Far leg | near_pts | far_pts | swap_pts |
|---|---|---|---|---|---|
| ON | T (spot) | T+1 | 0.00 | −0.03 | −0.03 |
| TN | T+1 | T+2 (spot) | −0.03 | 0.00 | +0.03 |
| SN | T+2=spot | T+3 | 0.00 | +0.06 | +0.06 |
The ON near leg falls on valuation_date; get_forward correctly returns None there, confirming the near leg is priced at spot (0 fwd pts).
test_forward_outright_pricing — single-leg outright forward (pure forward contract)
outright_rate = spot_ref + forward_pts / converter
| Tenor | Fwd pts | Outright (spot = 1.1, converter = 10 000) |
|---|---|---|
| 1W | 0.39 | 1.100039 |
| 1M | 1.83 | 1.100183 |
| 3M | 8.05 | 1.100805 |
| 6M | 13.12 | 1.101312 |
| 1Y | 16.18 | 1.101618 |
| Branch |
Add two new tests to forwardpoints.rs that illustrate the distinction between the Bloomberg "Pts" (swap) and "Fwds" (outright) columns: - test_fx_swap_pricing_on_tn_sn: prices both legs of ON/TN/SN swaps via Period::near_date + Period::settlement_date, computing swap_pts = far_fwd_pts − near_fwd_pts for each tenor. - test_forward_outright_pricing: prices single-leg outright forwards at 1W/1M/3M/6M/1Y; outright = spot_ref + fwd_pts / converter. Co-authored-by: Jeremy Wang <thejerwang@users.noreply.github.com>

Closes #67
Summary
Period::TN(Tom-Next) to the Period enum with correct settlement date logic (far leg = spot date for T+2 pairs)Period::near_date()API so callers can obtain both legs of an ON/TN/SN FX swap for per-leg pricingGenerated with Claude Code