YU17-otc-rates architecture
OTC interest-rate swaps and swaptions on the cluster tier: the first instrument class that neither matches nor nets. POST /swaps and POST /swaptions validate every term the fixed 64-byte record cannot represent and offer one sequenced command; the clustered service applies it into a replicated contract store and never hands it to the matching engine, because an OTC contract has no book to rest in and no position to accumulate. The risk gate gets a swap path - the notional is the notional, and quantity x rate would understate a 10mm swap by 24x. One EOD cut at one consensus sequence carries two sections and renders two artifacts: the netted position extract unchanged at CSV schema 3, and a per-contract OTC artifact carrying terms and no valuation, with swaps and swaptions sharing every column that describes the underlying.
- Inherits architectural baseline from:
YU16-cdm-instruments - Generated from:
system/architecture.model.json - Canonical flows:
architecture.md
Architecture Diagramβ
Node Catalogβ
| Node | Kind | Label | Notes |
|---|---|---|---|
swap_client | external | rates desk (RFQ, bilateral) | Swaps trade OTC, bilaterally, by request for quote. There is no order flow and no book to post into; a booking is the confirmation of a trade already agreed away from the venue, which is why nothing in this state matches. |
gateway | service | cluster gateway (POST /swaps, /swaptions) | One shared validator behind two routes, so they cannot drift into disagreeing about what a notional or a date is. Refuses, before anything is sequenced, every term the record cannot represent: a notional past int range (asLong, never asInt, because asInt saturates and would admit 5bn as 2.147bn), a date past 2149-06-06 (the packed fields mask and would wrap into a plausible date), a maturity at or before the effective date, a zero rate, an unknown conventions or exercise-style name, and a swaption expiry after the underlying's effective date. 400 means never sequenced; 422 means sequenced and refused by the gate. |
conventions | store | SwapConventions (compiled-in tables) | Two index-addressed tables stored nowhere in replicated state: five market conventions (float index, payment frequency, day count, currency) and three exercise styles (European, Bermudan, American). The pattern the option multiplier and the bond book grid already use. Append only: an index that has been journaled keeps its meaning forever, and an unknown index aborts the render rather than resolving to another - a Bermudan published as European is a different instrument. |
consensus_log | queue | Aeron consensus log (types 12 and 13 on template 1) | Both bookings are ordinary committed log entries. The codec copies commandType through without interpreting it, so two new command types cost no schema change. The product is the COMMAND, never a field's value: deriving 'this is a swaption' from a non-zero expiry would make 1970-01-01 a load-bearing sentinel. Sequencing is what keeps deterministic replay, byte-identical rendering on all three members, the quiescence witness and reproducibility from the stored cut. |
risk_gate | service | BlpRiskState.decideSwapBooking | The same ordered pipeline and stable precedence, measuring the NOTIONAL rather than quantity x price x multiplier, which would value a 10mm swap at 420,000. Drops the four checks that read state a swap does not have: security enabled/restricted/priced, and the position and concentration limits; the latter two because they project the very netting grain this state exists to show the loss of. |
contract_store | store | contract store (replicated, T_CONTRACT, format 6) | Terms plus booking account, in booking order, capped at 4096 and refusing at capacity deterministically. contractId is the booking's own consensus sequence: unique within the epoch by construction, needing no generator, no snapshot header field and no restore invariant. A swaption's record is a swap's plus three columns, because its underlying IS a swap - and restore reads a record at the width its FORMAT declares, since onSnapshotRecord is handed no length and a format-5 record read at the new width would take the next record's bytes as an expiry. |
engine | service | matching engine (untouched) | Byte-identical to the YU16 layer. A swap never reaches it; no order, no book entry, no crossing, no trade, no position; which is what makes 'a swap changes nothing for the instruments that already work' structural rather than asserted, and leaves the order hot path exactly what the allocation gates already measure. |
cut | store | the cut at N (schema 3, two sections) | One rendered artifact at one consensus sequence carrying the netted positions and then, after a #contracts marker, the OTC contracts. A section rather than a second message: two messages could be delivered apart, hashed apart and stored apart, which is the consistent-at-two-instants failure a sequenced cut exists to rule out. The section is emitted even when empty, because an absent section means an older producer. |
risk_extract | service | risk-extract (two artifacts, one stamp) | Renders both files from the same cut under the same stamp, so they share consensusSequence, sessionDate and cutSha256 by construction. Writes both write-once beside the single stored cut and announces both hashes on risk.extract.ready. --rebuild reproduces both from that cut alone. |
netted_csv | store | seq-N.csv (CSV schema 3, unchanged) | Equities, ETFs, Treasuries and listed options at the (accountId, security) grain, where netting is exact. Every column keeps its name, position and meaning; the reader stops at the section marker, so no swap row can appear here. |
contracts_csv | store | seq-N-contracts.csv (schema 2, terms only) | One row per booked contract, swaps and swaptions together: direction, notional, rate or strike, both underlying dates, float index, frequency, day count, currency, counterparty, netting set, then productType, expiry and exercise style. A swap leaves the last two empty. No NPV, no mark, no curve, no sensitivity - the consumer's engine is authoritative for what a contract is worth, and a second differently-derived number here would be a reconciliation break rather than data. |
consumer | external | pricing / risk engine | Reads both artifacts as one portfolio taken at one instant. Receives the terms it needs to value each contract and does the valuation itself; the netted file it already consumes is unchanged, so it reads without a code change. |