Alpaca Sign-Off

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).

#RequirementStatusEvidence
1Retrieving assets -- bonds + treasuriesPASSCode 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.
2Execute buy order -- market only for pilotPASSPOST /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.
3Execute sell orderPASSSame fix as buy -- FI sell orders now propagate to Alpaca. Fixed 2026-04-17.
4TIF = day onlyPASSEnforced in code: case "fixed_income": if tif != "day" { return error } at exchange_routes.go:748-753.
5Out of hours tradingPASSisFIMarketOpen() 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.
6Decimal validationPASSFI 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.
7Cancel orderPASSCancel 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.
8Update orderPASSGeneric 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.
9Show order statusPASSGeneric order listing works. Status values: pending, open, filled, canceled, rejected.
10Show activities -- maturity corporate actionsPASScorporate_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.
11Show positions -- qty=par value, 9dpPASSPositions 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.
12Close open positionsPASSSell full qty via standard order flow. Functionally equivalent to close.
13Market data -- Moment ToSPASSMoment 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)

ItemSeverityStatusFix
#2/#3 FI orders never reach AlpacaP0PASSexchange.go wired Alpaca broker routing for FI orders. Fixed 2026-04-17.
#1 Wrong URL pathsP0PASSGateway routes added for FI asset paths. Fixed 2026-04-17.
#6 Decimal validationP1PASSFI handler validates notional (2dp) and qty (9dp) at exchange.go:3410-3420. Fixed 2026-04-17.
#8 Update orderP1PASSGeneric PATCH handler works for FI. Propagates to Alpaca. TestFIUpdateRouting_FallbackToAlpacaOrderID. Fixed 2026-04-17.
#10 Maturity CA type handlingP1PASSmapBrokerCAType handles maturity type. Activities endpoint uses per-CA type instead of hardcoded MATURITY. Fixed 2026-04-17.
#11 Par value displayP1PASSBond 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)

Marketplace

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

Order form

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 at exchange.go:3410-3420.

On this page