Feature Specification: CDM Instruments
Feature Branch: YU16-cdm-instruments
Created: 2026-08-10
Status: In implementation
Input: Fold of source packs 016-cdm-generic-instruments and 017-us-treasury-trading, parented on YU15-eod-risk-extract
User Storiesβ
- As a trader, I want ETFs and U.S. Treasuries in the same selectors and blotters as equities; with honest labels for what the numbers mean (face amount, clean price as % of par, coupon, maturity, YTM); so I trade the wider universe without learning a second workflow.
- As a risk-engine consumer, I want the instrument's type and its bond static (coupon, maturity) on every extract row, so I can price a Treasury position without a second lookup.
- As the platform owner, I want the CDM instrument model added with the deterministic core's stored state, snapshot format and contracts untouched, because a matching engine that is price-time priority over integers has no business knowing what a coupon is; and because rolling core state cannot be done gradually.
- As the operator of the YU04 durable control feed, I want
/stocks/control-snapshotto keep serving exactly what it serves today, because a replica bootstrap that fails on a renamed route is an outage bought for a tidier name. - As a maintainer, I want the divergence from the folded source packs declared requirement by
requirement, so keeping
/stocksreads as a decision with a reason, not an oversight.
Functional Requirementsβ
Instrument model (reference-data)β
- FR-CDM01: reference-data SHALL expose
GET /instrumentsandGET /instruments/{instrumentKey}, serving CDM-shaped instrument records; an unknown key SHALL return 404. - FR-CDM02: An instrument record SHALL carry
instrumentKey,displayName,currency,securityType(a CDMSecurityTypeEnumliteral), its matching CDM sub-type discriminator, andidentifiers(a list of CDMAssetIdentifiervalues); Treasuries additionally carryshortDisplayName,assetClass,maturedanddebtEconomics. - FR-CDM03:
securityTypeSHALL use CDMSecurityTypeEnumliterals; this state servesEquity,FundandDebt. - FR-CDM04: Exactly one sub-type discriminator SHALL be present and SHALL agree with
securityType;equityTypeforEquity,fundTypeforFund,debtEconomicsforDebt; enforced at seed load; a disagreement throws rather than loads. - FR-CDM05: Identifiers SHALL use CDM
AssetIdTypeEnumliterals. Equities and funds carryBBGTICKER(equal toinstrumentKey) plusFIGIwhere one is baked into the seed; a seed row with no resolvable FIGI keepsBBGTICKERonly and logs a warning, and SHALL NOT fail startup. Treasuries carryFIGIplus anOtheridentifier equal to the instrument key, and SHALL NOT claimBBGTICKER. - FR-CDM06: Identifiers SHALL be baked into seed data offline; the runtime SHALL NOT call an external symbology provider.
- FR-CDM07: The seed universe SHALL include the five ETFs
SPY, QQQ, IWM, VTI, GLDasFund/ExchangeTradedFund, and the five TreasuriesUST-20280630,UST-20310630,UST-20360515,UST-20460515,UST-20560515asDebtwith their real FIGIs, coupon, issue and maturity dates, original term, and TreasuryDirect auction price provenance. - FR-CDM08: An ETF SHALL be tradable through the inherited order, trade and position paths exactly as an equity; same flow, same position row shape.
/stocks retention and the control feedβ
- FR-CDM09:
/stocksand/stocks/{ticker}SHALL remain served, unchanged in shape. This supersedes source pack 016's FR-01602 ("/stocksand/stocks/{ticker}SHALL be removed and SHALL NOT be aliased or redirected"), and its SC-01607 ("GET /stocksreturns 404") is NOT adopted:/stocks/control-snapshotis the YU04 durable control feed's bootstrap source and is load-bearing for theyu04-live-deltaandyu04-offline-catchupproofs. - FR-CDM10:
/stocks/control-snapshotSHALL keep its exact YU04 contract; watermark fields plus{ticker, companyName}rows; over the same store and outbox watermark as before. - FR-CDM11: reference-data SHALL additionally expose
/instruments/control-snapshot, serving the same control-snapshot contract over the same store and the same watermark. New fields are additive only; a reader of the/stockssnapshot can be repointed to it with no code change. - FR-CDM12: The order-matcher risk bootstrap SHALL default to
/instruments/control-snapshotat this state's layer; a configuration repoint ofrisk.bootstrap.securities-snapshot-url, not a code change; and the two YU04 proofs SHALL probe the general route. The proof-suite readiness gate SHALL keep probing/stocks/control-snapshot, as the standing check that retention holds. - FR-CDM13: ETFs and Treasuries SHALL flow through the existing
SECURITY_CONTROLfeed as ordinary securities (ticker+companyName), so the engine registers them with no new command type and no feed-contract change.
Bond price and quantity conventionsβ
- FR-CDM14: Bond prices SHALL be stored as a fraction of par everywhere inside the system; the
price publisher's emission, the engine's ticks, the read model's rows, and the extract; never
as a percentage. A bond quoted 99.886% is stored
0.998860, which is998,860ticks at the 1e6 scale. The contract multiplier for a Treasury SHALL be 1, and notional SHALL bequantity(face) Γ price(fraction ticks) Γ 1through the unchanged engine risk gate. - FR-CDM15: Bond marks SHALL keep six-decimal precision end to end: the pricing feed's binary
tick for a Treasury SHALL equal
round(fraction Γ 1e6)(the inherited 3-decimal equity rounding SHALL NOT apply to Treasury payloads), and every SQL column that carries a bond price SHALL hold six decimals. - FR-CDM16: Bond order quantity SHALL be a positive integer USD face amount, at least 100 and
a multiple of 100, validated at the order-entry boundary (the gateway REST validation and the
UI tickets) with the exact messages "Bond quantity must be at least 100." and "Bond
quantity must be a multiple of 100.". Trade and position quantity columns carry face.
This applies to every debt instrument, Treasury and corporate alike, because quantity is
USD face for all of them. It was originally scoped to Treasuries by a
startsWith("UST-")test, which meant a corporate order for 50 face was ACCEPTED; a rule this document stated and the system did not enforce. The routing predicate is now a declared set of bond key prefixes, and the AUTHORITATIVE check is the reference-data join (securityType == "Debt") in trade-service and trade-processor: a bond-shaped key whose metadata disagrees is refused, never forwarded on the strength of its name. The gateway keeps a prefix test deliberately; it is the pre-consensus hot path and an HTTP join there would put a network dependency on every order. The messages were widened from "Treasury quantity ..." at the same time: the old wording named the wrong instrument class on a corporate rejection. NOTE the deliberate asymmetry with ADR-060's book grid, which stays Treasury-only. The two are the same-shaped predicate and different in kind: a coarse grid is a capability limit and honest, whereas this was a validation gap. - FR-CDM17: Percent-of-par SHALL be display only: the UI multiplies the stored fraction by 100 and appends the sign; nothing downstream of a display ever converts back.
Treasury pricing (price-publisher)β
- FR-CDM18: price-publisher SHALL seed the five Treasuries from their auction-derived clean
prices and walk them with the term-profiled correlated model: per-batch shared roll weighted
0.8 against a 0.2 local roll, mean reversion of
0.02 Γ (seed β current), and a hard clamp toseed Β± maxDistance, where a longer original term has a largermaxStepand a widermaxDistance; so long-maturity prices move more, exactly as duration says they should. - FR-CDM19: Treasury payloads on
pricing.<instrumentKey>SHALL extend the inherited tick shape additively:assetClass,cleanPrice(fraction of par, equal toprice),priceSemantics: "CLEAN_FRACTION_OF_PAR",ytmPercent,yieldConvention,dayCount,quoteTimestamp(equal toasOf),maturityDate,matured,simulated,officialSeedCleanPrice. Subject names and every inherited field are unchanged. - FR-CDM20: Yield SHALL be computed only by the publisher,
nullat or after maturity, carrying the same quote timestamp as the price. The UI parses it and SHALL NOT compute it. It SHALL be a real priceβyield solve; safeguarded Newton with a bisection fallback, so it converges quadratically where Newton behaves and cannot diverge where it does not; over a coupon schedule generated from the issue date forward, so a short or long first coupon is modelled rather than assumed away. The day count SHALL be named on the wire, never assumed: ACT/ACT (ICMA) for Treasuries, 30/360 for corporates. Every instrument SHALL be quoted on one basis (SEMIANNUAL_BOND), coupon-bearing or zero alike, so the points are comparable and a consumer can bootstrap a curve across them. A zero-coupon instrument SHALL be priced on the zero-coupon path; it has no coupon schedule to walk, and accrues nothing, ever. (This supersedes the one-line textbook approximation((coupon + (par β clean)/yearsRemaining) / ((par + clean)/2)) Γ 100this state shipped first. That form has no schedule, no day count and no solve; it is wrong by tens of basis points on a long bond, cannot express a zero at all; a bill has no coupon to put in its numerator; and its error is smooth and plausible, so a curve bootstrapped off it would be wrong everywhere and obviously wrong nowhere.) - FR-CDM21: A matured Treasury SHALL stop quoting; its payloads are suppressed, its stored quote
no longer advances; and SHALL be rejected for new order entry at the validation boundary. An
unknown
UST--prefixed key SHALL return 404 with no fallback quote; unknown equities keep the inherited lazy fallback.
Post-tradeβ
- FR-CDM22: trade-processor SHALL book Treasury trades with face-weighted average cost; a buy
re-weights
(oldAvg Γ oldFace + price Γ buyFace) Γ· newFace, a sell preserves the average, a flat position resets it; over the same asynchronous trade path equities use. - FR-CDM23: Trades SHALL support a
Rejectedstate withrejectionReasonandsourceOrderId, persisted and published on the account trade subject with no position update; the fail-closed landing for booking-time validation failures, including Treasury reference metadata being unavailable (UST-routing is the discriminator; metadata must still confirmUS_TREASURY+Debtbefore a Treasury booking). - FR-CDM24: Booking-time Treasury metadata SHALL be resolved before the database transaction with configurable timeouts, and non-Treasury bookings SHALL do no metadata lookup.
Risk extractβ
- FR-CDM25: The extract's
instrumentTypeSHALL gainTREASURY, derived by join against the state's instrument static exactly ascounterpartyIdalready is, and Treasury rows SHALL carrycouponandmaturityDatefrom the same join. The.cutformat SHALL NOT change. - FR-CDM26: The extract CSV schema SHALL bump to 2,
risk.extract.readySHALL announceschema: 2, anddocs/engineering/risk-extract-consumer-guide.mdSHALL document the new columns and the bond-price convention. - FR-CDM27: Treasury rows SHALL additionally carry
lastCouponDateandaccruedInterestFraction, derived from the joined static plus the session date rather than joined from new reference data, bumping the CSV schema to 3 andrisk.extract.readytoschema: 3. The coupon schedule SHALL be generated backwards frommaturityDatein six-month steps measured from the maturity anchor; day count SHALL be ACT/ACT (ICMA); accrual SHALL run tosessionDate, not to a settlement date; and the value SHALL be a fraction of par in the same unit asclosingMark, soclosingMark + accruedInterestFractionis the dirty price.marketValueandunrealizedPnlSHALL remain clean. The.cutformat SHALL NOT change. - FR-CDM28:
accruedInterestFractionSHALL round HALF_EVEN at six decimals; the single exception to the extract's exact-or-abort rule, becauseelapsed/perioddoes not terminate in decimal; and rounding SHALL be deterministic so byte-identical rendering across members and rebuild-from-stored-cut both continue to hold. Every convention above SHALL be stated in the fixture's own#header, not only in the consumer guide.
Frontendβ
- FR-CDM27: The UI SHALL offer an asset-class filter (All / Stocks / ETFs / U.S. Treasuries) on
the instrument selectors and blotters, group the selector typeahead by asset class, label
Treasury inputs honestly (Face Amount; Limit Clean Price (% of par)), estimate clean value as
face Γ fraction, show coupon, maturity and YTM for Treasuries, format bond prices as percentages with no currency prefix, and surface a rejected trade's reason in the blotter.
Non-Functional Requirementsβ
- NFR-CDM01: The deterministic core SHALL NOT change its stored state or contracts: no snapshot
field or format change, no new command type, no risk-gate change, no matching-policy change.
The one core change this state makes is a derived per-security book grid for
UST-tickers (ADR-060); a pure function of the committed ticker, stored nowhere, consulted only at cold book creation; because the inherited 0.001 book grid rejects six-decimal bond limits as off-grid. Instrument semantics otherwise live in reference-data, pricing, post-trade and display layers. - NFR-CDM02:
SNAPSHOT_FORMATSHALL remain 4 andMIN_READABLE_SNAPSHOT_FORMATSHALL remain 3. - NFR-CDM03: The state SHALL NOT require a fresh epoch or a PVC wipe; the running cluster's disks
and epoch stay valid. The image rolls member by member because old and new code behave
identically for every input that references no
UST-symbol, and Treasury securities SHALL be registered only after every member runs this state's image (ADR-060); the bring-up seeds fixtures after the roll, which makes the mixed window benign by construction. - NFR-CDM04: Every inherited proof SHALL remain green; the full
scripts/yu15/run-proofs.shsuite, the order-matcher suite, and all allocation and no-GC gates. - NFR-CDM05: The adopted CDM subset SHALL be documented in
data-model.mdwith enum literals quoted from CDM source. The runtime record stays flat; the CDMAsset β Instrument β Securitychoice tree is taxonomy documentation, not a runtime discriminated union. - NFR-CDM06: No messaging subject SHALL be added, removed or renamed. The durable control feed
keeps stream
TRADERX_CONTROL_SECURITYand subjecttraderx.control.security.deltas. - NFR-CDM07: No external symbology dependency, no CUSIP or ISIN values, no live Treasury API, no credential. FIGIs and auction prices are baked offline.
- NFR-CDM08: Bond arithmetic SHALL be exact; integer ticks and
BigDecimal, never floating point; in the engine (unchanged), the read model and the extract. - NFR-CDM09: The fixed-clock contract
TRADERX_FIXED_UTC_INSTANTSHALL be honored by reference-data'smaturedflag and price-publisher's Treasury clock, so maturity behavior is testable at a chosen instant.
Simulated curve pointsβ
The five Treasuries this state started with are auction-sourced: real FIGIs, TreasuryDirect provenance, prices quoted from the auction PDF. They are also a sparse long end with nothing under two years, which is why the risk engine has no zero curve to bootstrap; a curve needs short-dated discount factors and there were none.
Ten instruments were added to close that gap. They are not real securities and carry
priceProvenance.sourceType: SIMULATED_CURVE_POINT and no FIGI, because a FIGI-shaped string
we invented is worse than an absent one: it would look up-able. assertCdmConditions enforces
both halves; an auction-sourced Debt instrument requires a FIGI, a simulated one is refused
if it ever grows one, so the two can never be confused by a downstream consumer.
| Added | Why |
|---|---|
| 4 bills; 4/13/26/52-week, all issued 2026-08-13 | The short end, which did not exist |
| 4 principal STRIPS; 2028, 2031, 2036, 2056 | Zero-coupon Treasuries are discount factors |
| 2 coupon points; 3Y (2029-07-15), 7Y (2033-07-31) | Density between the existing 2Y/5Y/10Y |
All ten are keyed UST-β¦ so ADR-060's ticker-derived book grid covers them with no engine change.
How the prices were derivedβ
One settle date, 2026-08-13, and one curve. The five auction prices back out at that settle to 4.190749 / 4.200770 / 4.469089 / 5.122502 / 5.045654 percent (2Y/5Y/10Y/20Y/30Y), and every added point was chosen to sit on that curve rather than beside it:
- Bills, bank-discount basis:
price% = 100 x (1 - d x days/360), at d = 4.10 / 4.08 / 4.05 / 4.00%. Those imply bond-equivalent yields of 4.170 / 4.180 / 4.192 / 4.227%; a flat-to-slightly- upward short end running into the 2Y at 4.191%. - STRIPS, semiannual compounding:
price% = 100 / (1 + y/2)^(2t)withtin ACT/365 years to maturity, at y = 4.20 / 4.25 / 4.55 / 5.15%. - 3Y and 7Y notes: the standard ACT/ACT (ICMA) semiannual PV at 4.19% and 4.32%, coupons on the usual 1/8 grid (4.125% and 4.250%).
A zero-coupon instrument carries couponRatePercent: 0 in instruments.csv and
debtEconomics.zeroCoupon (never fixedInterest) in the CDM record. That is the discriminator the
extract's zero-coupon branch keys off: a bill has no coupon schedule, which is a different
statement from a schedule that pays zero, and emitting a fabricated lastCouponDate for one is the
bug ADR-061's branch exists to prevent.
Technical Debt Registerβ
- TD-CDM01: One display-name attribute has two wire names:
companyNameon/stocksand both control snapshots (the YU04 feed reads it by that name),displayNameon the CDM/instrumentsview. The Angular model and service keep their historicStock/getStocksnames with the new fields. RetiringcompanyNameis a feed-consumer flag day, not a rename. - TD-CDM02: The control-feed routes now have a general name (
/instruments/control-snapshot) but the durable stream and subject keep theirSECURITYnames. Renaming a durable stream subject moves the consumer's position with it; a flag day this state deliberately does not buy. - TD-CDM03: Maturity enforcement lives at the validation boundary and the publisher, not in the engine; an order injected by a path that skips validation would still match. The earliest seed maturity is 2028-06-30, so no seeded instrument can mature in a running session today.
Success Criteriaβ
- SC-CDM01:
GET /instrumentsserves the full universe; the SPY row carriessecurityType: Fund,fundType: ExchangeTradedFund, and identifiersBBGTICKER SPY+FIGI BBG000BDTBL9; an equity row carriessecurityType: EquitywithequityType.equityType: Ordinaryand nofundType. - SC-CDM02:
GET /instruments/UST-20360515returnssecurityType: Debt, coupon 4.375, maturity2036-05-15,FIGI BBG0221YLR31,matured: false, and noBBGTICKERidentifier. - SC-CDM03:
GET /stocksreturns 200 with its inherited shape, directly and through the suite readiness gate. - SC-CDM04:
/stocks/control-snapshotand/instruments/control-snapshotboth return 200 with the same watermark, rows include the ten new keys, andyu04-live-delta+yu04-offline-catchuppass probing the general route. - SC-CDM05: A Treasury order at face 100,000 and limit 0.998860 books through the cluster; the position row shows quantity 100,000 and cost basis 0.998860; the blotter displays 99.886%.
- SC-CDM06: An ETF order books and produces a position row identically to an equity.
- SC-CDM07:
pricing.UST-*payloads carrycleanPriceas a fraction withpriceSemantics: CLEAN_FRACTION_OF_PARand a YTM; the binary tick equalsround(fraction Γ 1e6)with all six decimals intact. - SC-CDM08: A Treasury order at face 50 is rejected with the minimum message; at face 150 with the multiple message; at the gateway REST boundary, before the engine sees either.
- SC-CDM09: An extract cut from a session holding a Treasury position carries
schema=3, aninstrumentTypeofTREASURYon the bond row with its coupon, maturity, last coupon date and accrued interest, and rebuilding the fixture from the stored cut reproduces identical bytes. - SC-CDM09a: For a 4.125% bond maturing 2028-06-30 in a session dated 2026-07-21, the row reads
lastCouponDate=2026-06-30andaccruedInterestFraction=0.002367(21/183 of the 2.0625% semiannual coupon); on 2026-06-30 itself the accrual is0.000000, and at or past maturity the last coupon date is the maturity with a0.000000accrual. - SC-CDM10: The full proof suite passes on a rig rolled to this state's image with its PVCs and
epoch intact;
SNAPSHOT_FORMATstill reads 4. - SC-CDM11: The order-matcher, trade-processor and position-service suites pass, including this state's new Treasury pricing, booking and validation tests.