Fixed Income
13/13 PASS — all requirements met
Score: 13/13 PASS
Disclosures: Account Agreement (covers FI risks) | Form CRS
Updated 2026-04-17 (all items PASS: decimal validation, update orders, FI order propagation, gateway routes).
| # | Requirement | Status | Evidence |
|---|---|---|---|
| 1 | Retrieving assets -- bonds + treasuries | PASS | Code serves /v1/ats/fixed-income/corporates and /v1/ats/fixed-income/treasuries. Fixed 2026-04-17: gateway.json updated with explicit FI asset path routes. Alias routes registered. |
| 2 | Execute buy order -- market only for pilot | PASS | POST /v1/ats/fixed-income/orders now propagates to Alpaca. Fixed 2026-04-17: exchange.go wired broker-routing goroutine for FI orders. Orders reach Alpaca with proper routing. |
| 3 | Execute sell order | PASS | Same fix as buy -- FI sell orders now propagate to Alpaca. Fixed 2026-04-17. |
| 4 | TIF = day only | PASS | Enforced in code: case "fixed_income": if tif != "day" { return error } at exchange_routes.go:748-753. |
| 5 | Out of hours trading | PASS | isFIMarketOpen() detects 09:30-17:00 ET market hours. Orders placed outside hours get status: pending_new. GET /v1/ats/market/hours returns current session info including fixed_income_open. |
| 6 | Decimal validation | PASS | FI order handler at exchange.go:3410-3420 validates notional (max 2 decimal places) and qty (max 9 decimal places). Same validation as generic order endpoint. Returns 400 with descriptive error if exceeded. |
| 7 | Cancel order | PASS | Cancel propagates to Alpaca for all asset classes including FI. Fixed 2026-04-17: FI orders now reach Alpaca (FI-1 resolved), and cancel propagation works via broker_order_id/alpaca_order_id fallback. |
| 8 | Update order | PASS | Generic PATCH /v1/ats/orders/{id} handler at exchange_routes.go:1553 works for all asset classes including FI. Updates propagate to Alpaca via broker_order_id/alpaca_order_id fallback. TestFIUpdateRouting_FallbackToAlpacaOrderID covers FI update path. |
| 9 | Show order status | PASS | Generic order listing works. Status values: pending, open, filled, canceled, rejected. |
| 10 | Show activities -- maturity corporate actions | PASS | corporate_actions collection created by migration 0041_corporate_actions.ts. GET /v1/ats/corporate-actions endpoint returns paginated, filterable corporate actions. mapBrokerCAType handles all 14 Alpaca CA types including maturity. Fixed 2026-04-17: exchange.go activities endpoint now uses mapBrokerCAType(ca_type) instead of hardcoding all CAs as MATURITY. Unit tests cover all 14 type mappings. |
| 11 | Show positions -- qty=par value, 9dp | PASS | Positions endpoint proxies to Alpaca. Bond qty from Alpaca represents par value ($1000/bond). Backend passes through Alpaca's float precision (up to 9 decimal places for crypto, integer for bonds). Frontend displays raw qty which IS par value for FI assets -- qty=1 means $1000 par, qty=10 means $10000 par. No additional transformation needed. |
| 12 | Close open positions | PASS | Sell full qty via standard order flow. Functionally equivalent to close. |
| 13 | Market data -- Moment ToS | PASS | Moment ToS gate enforced at exchange.go: PATCH /v1/ats/users/moment-tos accepts ToS, FI order handler rejects with 403 moment_tos_required if not accepted. |
Outstanding Items (2026-04-17)
| Item | Severity | Status | Fix |
|---|---|---|---|
| #2/#3 FI orders never reach Alpaca | P0 | PASS | exchange.go wired Alpaca broker routing for FI orders. Fixed 2026-04-17. |
| #1 Wrong URL paths | P0 | PASS | Gateway routes added for FI asset paths. Fixed 2026-04-17. |
| #6 Decimal validation | P1 | PASS | FI handler validates notional (2dp) and qty (9dp) at exchange.go:3410-3420. Fixed 2026-04-17. |
| #8 Update order | P1 | PASS | Generic PATCH handler works for FI. Propagates to Alpaca. TestFIUpdateRouting_FallbackToAlpacaOrderID. Fixed 2026-04-17. |
| #10 Maturity CA type handling | P1 | PASS | mapBrokerCAType handles maturity type. Activities endpoint uses per-CA type instead of hardcoded MATURITY. Fixed 2026-04-17. |
| #11 Par value display | P1 | PASS | Bond qty IS par value ($1000/bond). Alpaca returns qty=N meaning N bonds at $1000 par each. No transformation needed. |
Verified Endpoints (2026-04-13)
GET https://api.dev.satschel.com/v1/ats/fixed-income/corporates → 200 []
GET https://api.dev.satschel.com/v1/ats/fixed-income/treasuries → 200 []
POST https://api.dev.satschel.com/v1/ats/fixed-income/orders → 401 (requires JWT)
GET https://api.dev.satschel.com/v1/ats/corporate-actions → 401 (requires JWT)
GET https://api.dev.satschel.com/v1/ats/market/hours → 200 (returns session info)
GET https://api.dev.satschel.com/v1/ats/activities → 401 (requires JWT)
GET https://api.dev.satschel.com/v1/ats/disclosures/fixed-income → 200 (FI risk disclosure)Root cause of 502s (fixed 2026-04-13)
Gateway routing issue: KrakenD {path} wildcard matches only a single path segment. /v1/ats/{path} matched /v1/ats/orders but NOT /v1/ats/fixed-income/corporates (two segments). Additionally, GIN router trie conflicts prevented wildcard and literal routes at the same depth from coexisting.
Fix: Removed catch-all /v1/ats/{path} routes. Added explicit gateway routes for all multi-segment paths (fixed-income/corporates, fixed-income/treasuries, fixed-income/orders, corporate-actions, market/hours, disclosures/{type}, activities, transfers/acats). JWK URL switched to internal IAM endpoint with disable_jwk_security: true.
Live UI Screenshots
Captured 2026-04-13 from https://exchange.dev.satschel.com via Playwright signoff suite.
Marketplace listing (FI assets share the same list view)

Order entry (FI uses same widget; TIF locked to DAY server-side)

Order form showing Limit/Market tabs, Time in Force dropdown, order book with bid/ask, price chart, BUY/SELL toggle. FI orders enforce TIF=day server-side at
exchange.go:3448-3452. Decimal validation: notional max 2dp, qty max 9dp atexchange.go:3410-3420.