Files
broker/ARCHITECTURE.md
zeekayandHanzo Dev 53f764d698 chore(aml): drop optional external-Jube provider — luxfi/aml is native
Remove the optional github.com/luxfi/compliance/pkg/jube provider from the
broker compliance layer. AML screening keeps working via the OFAC SDN list
and the ScamSniffer database; the native github.com/luxfi/aml engine (amld)
is the deeper risk-scoring backend. Removed the jube import, the jubeClient
field/option, and every jube-guarded block; docs updated to reflect the
native engine.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-17 12:16:00 -07:00

426 lines
17 KiB
Markdown

# Broker Architecture
Smart order router and multi-venue execution engine. Single Go binary (`brokerd`), 16 venue adapters, institutional-grade order routing with net-price optimization.
## Design Principles
1. Single process. No microservices. One binary handles routing, risk, execution, settlement, compliance, market data, and streaming.
2. Venue adapters are the only abstraction. Everything else is concrete.
3. All order flow passes through risk engine before reaching any venue.
4. Market data is aggregated in-process. No external message bus required (NATS available but optional).
5. Secrets from KMS. Never in manifests, never in env files committed to git.
## System Topology
```
Clients
|
TLS (Cloudflare)
|
hanzoai/gateway
(api.lux.network)
|
+---------+---------+
| brokerd |
| :8090 |
+---+---+---+---+---+
| | | |
+--------------+ | | +--------------+
| | | |
+----+----+ +------+------+ +------+------+
| Router | | Risk Engine | | Settlement |
| (SOR) | | (pre-trade) | | (pool) |
+----+----+ +-------------+ +------+------+
| |
+---------+---------+ +-----+-----+
| Market Data | | Funding |
| Feed (BBO agg) | | (deposit/ |
+---+---+---+---+--+ | withdraw) |
| | | | +------------+
+---+---+---+---+---+---+---+---+
| Provider Adapters (16 venues) |
+---+---+---+---+---+---+---+---+
| | | | | | |
CB KRK BIN GEM SFOX FLX LMAX ...
```
## Component Responsibilities
### Router (`pkg/router/`)
The smart order router. Stateless decision engine.
**Input:** Symbol, side (buy/sell), quantity.
**Decision algorithm:**
1. Fan out `GetAsset` + `GetSnapshot` to all registered providers in parallel (goroutines, channel collection).
2. Filter: drop providers where asset is not tradable or snapshot fails.
3. Score each venue: `net_price = raw_price * (1 + taker_fee_bps / 10000)` for buys; `raw_price * (1 - taker_fee_bps / 10000)` for sells (negated so higher net = better).
4. Sort ascending by score. Index 0 is best.
5. Return ranked list.
**Split execution (VWAP):**
1. Get all routes (same as above).
2. Filter to routes with live quotes.
3. Weight inversely proportional to score: better score = more volume.
4. Allocate quantity proportionally. Last leg absorbs rounding remainder.
5. Calculate estimated VWAP, fees, net price, and savings vs. single-venue worst case.
6. Execute all legs in parallel via `CreateOrder` with `type=market`, `time_in_force=ioc`.
7. Aggregate fills: compute actual VWAP from weighted average of fill prices.
**Fee schedule:** Hard-coded defaults per provider (see `defaultFees()`). Override at runtime via `SetFees()`.
```
Provider Maker/Taker (bps) Notes
----------- ------------------ -----
alpaca 0/0 Commission-free equities
binance 10/10 Standard tier
kraken 16/26 Standard tier
sfox 15/25 Prime dealer
coinbase 40/60 Advanced Trade
gemini 20/40 ActiveTrader
falcon 5/10 Institutional RFQ
lmax 2/3 Institutional CLOB
ibkr 0.5/0.5 Per-share approximation
```
### Market Data Feed (`pkg/marketdata/`)
In-process consolidated order book and ticker aggregation.
**Data model:**
- `Ticker`: per-symbol, aggregates quotes from all providers. Tracks best bid, best ask, spread (absolute + bps), provider attribution for each side.
- `ConsolidatedBook`: per-symbol, sorted bid/ask levels with provider tags.
- `Quote`: per-provider raw quote (bid/ask price+size, last price).
**Update flow:**
1. Provider snapshot polled or WebSocket update arrives.
2. `UpdateQuote(symbol, provider, bid, ask, bidSize, askSize, last)` called.
3. Feed recalculates BBO across all providers: highest bid wins, lowest ask wins.
4. Computes cross-venue spread: `best_ask - best_bid` (can be negative = arbitrage).
5. Notifies all subscribers via non-blocking channel send.
**Arbitrage detection:** When `best_bid_provider != best_ask_provider` and `best_bid > best_ask`, there is a cross-venue arbitrage. The router exposes this through the `/v1/bbo/{symbol}` endpoint. Execution is the client's responsibility.
**Polling vs. streaming:**
- SSE streaming via `pkg/ws/`: clients subscribe to symbols, receive consolidated ticker updates.
- Snapshot polling via `Feed.PollSnapshots()`: background goroutine polls all providers at configurable interval (default: provider-dependent, typically 1-5s for crypto).
- WebSocket feeds from venues (Binance, Kraken, Coinbase) update the feed directly when available.
### Risk Engine (`pkg/risk/`)
Pre-trade validation. Synchronous. Blocks order submission if any check fails.
**Checks (in order):**
1. Provider allowlist (global).
2. Symbol blocklist (global + per-account).
3. Symbol allowlist (per-account, if configured).
4. Order value vs. max single order limit.
5. Daily volume accumulator vs. daily limit.
6. Open order count vs. concurrent order limit.
7. Rate limit: orders per minute (sliding window from timestamps).
**Limits hierarchy:** Per-account overrides global. Global defaults: $1M max order, $10M daily, 100 open orders, 60 orders/min.
**Usage tracking:** In-memory. Daily counters reset after 24h. Rate limit uses a sliding window of order timestamps.
### Settlement Engine (`pkg/settlement/`)
Prefunding pool for instant crypto buys while ACH settles.
**Flow:**
```
User "Buy 1 BTC" -> Validate KYC tier limit
-> Check outstanding exposure
-> Reserve pool capital (amount = qty * price)
-> Execute trade on best venue (caller)
-> Initiate ACH (caller)
-> Track lifecycle:
ACH clears -> Release pool credit
ACH fails -> Mark for liquidation
Price drops -> Margin call / auto-liquidate
```
**KYC tiers:**
```
basic $250 Email-verified
standard $5,000 ID-verified
enhanced $25,000 Full KYC + accredited
institutional $250,000 Institutional accounts
```
**Margin policy:** Configurable thresholds. Default: 20% warning, 30% margin call, 50% auto-liquidation. `CheckMarginHealth()` scans all pending reservations against current prices.
**Pool:** In-memory reservation store. Each reservation tracks: account, asset, amount, entry price, status, timestamps, events. Statuses: `pending_settlement`, `settled`, `failed`, `margin_called`, `liquidated`.
### Provider Interface (`pkg/provider/`)
```go
type Provider interface {
Name() string
CreateAccount(ctx, req) // Onboarding
GetAccount(ctx, id) // Account detail
ListAccounts(ctx) // All accounts
GetPortfolio(ctx, id) // Holdings snapshot
CreateOrder(ctx, id, req) // Place order
ListOrders(ctx, id) // Order history
GetOrder(ctx, id, oid) // Order detail
CancelOrder(ctx, id, oid) // Cancel
CreateTransfer(ctx, id, r) // Fund movement
ListTransfers(ctx, id) // Transfer history
CreateBankRelationship() // Link bank
ListBankRelationships() // List banks
ListAssets(ctx, class) // Tradable instruments
GetAsset(ctx, symbol) // Asset detail
GetSnapshot(ctx, symbol) // Real-time price
GetSnapshots(ctx, syms) // Batch snapshots
GetBars(ctx, ...) // Historical OHLCV
GetLatestTrades(ctx, syms) // Latest trades
GetLatestQuotes(ctx, syms) // Latest quotes
GetClock(ctx) // Market hours
GetCalendar(ctx, s, e) // Trading calendar
}
```
**Optional capability interfaces** (type-asserted at runtime):
- `TradingExtended` -- Replace, cancel-all, positions.
- `AccountManager` -- Update, close, activities.
- `DocumentManager` -- Upload, list, download.
- `JournalManager` -- Inter-account transfers (JNLC/JNLS).
- `TransferExtended` -- Cancel transfer, ACH deletion, wire banks.
- `CryptoDataProvider` -- Crypto-specific bars/quotes/trades/snapshots.
- `EventStreamer` -- SSE streams for trade/account/transfer/journal events.
- `PortfolioAnalyzer` -- Portfolio equity history.
- `WatchlistManager` -- CRUD watchlists.
**Provider registration:** `pkg/provider/envconfig/` reads 16 sets of env vars and registers configured providers. Breaks import cycle: envconfig imports provider sub-packages, provider sub-packages import the provider interface package.
### Venues
| Provider | Pkg | Asset Classes | Execution | Market Data |
|----------|-----|--------------|-----------|-------------|
| Alpaca | `provider/alpaca/` | Equities, Crypto | Full (100% API) | Stocks + Crypto |
| Interactive Brokers | `provider/ibkr/` | Equities, Options, Futures, FX, Crypto | Full | Full |
| Tradier | `provider/tradier/` | Equities, Options | Full | Full |
| Coinbase | `provider/coinbase/` | Crypto | Advanced Trade | Full |
| Binance | `provider/binance/` | Crypto | Spot/Margin | Full |
| Kraken | `provider/kraken/` | Crypto | Full | Full |
| Gemini | `provider/gemini/` | Crypto | Full | Full |
| SFOX | `provider/sfox/` | Crypto | Market/Limit + Algo | Best-price + OHLCV |
| FalconX | `provider/falcon/` | Crypto | RFQ | Quotes |
| Fireblocks | `provider/fireblocks/` | Crypto | Transfer/Custody | Balances |
| BitGo | `provider/bitgo/` | Crypto | Custody + Prime | Balances |
| Circle | `provider/circle/` | Stablecoins | Mint/Burn/Transfer | Balances |
| CurrencyCloud | `provider/currencycloud/` | FX (35+ pairs) | Spot/Forward | Rates |
| LMAX | `provider/lmax/` | FX, Crypto, Metals | CLOB | Full |
| Polygon.io | `provider/polygon/` | Equities, FX, Crypto, Options | Data only | Full |
| Finix | `provider/finix/` | Payments | ACH/Wire/Card | N/A |
### Compliance (`pkg/compliance/`)
KYC/KYB onboarding, fund management, eSign, RBAC, reporting. Mounted at `/compliance`. PostgreSQL or in-memory store. AML/fraud screening runs against the OFAC SDN list and the ScamSniffer scam-address database, with the native `github.com/luxfi/aml` engine (`amld`) as the deeper risk-scoring backend, plus a webhook dispatcher (`compliance/webhooks/`).
### Audit (`pkg/audit/`)
Append-only log of every order, fill, cancellation, transfer. Queryable by action, provider, account, symbol, time range. Exportable as JSON.
### Auth (`pkg/auth/` + `pkg/admin/`)
- **API auth:** API key or Bearer token validation. Keys scoped to org via IAM OIDC `owner` claim.
- **Admin auth:** JWT (HMAC-SHA256). Password hashed (SHA-256 + salt). Admin users from env vars.
## Data Flow: Smart Order Execution
```
Client POST /v1/smart-order
|
v
API Server (auth middleware validates JWT/API key)
|
v
Risk Engine: PreTradeCheck(provider, account, symbol, side, qty, price)
|-- REJECT if any check fails (returns errors)
|-- WARN if approaching limits (returns warnings)
|
v
Router: FindBestProvider(ctx, symbol, side)
|-- Fan out GetAsset + GetSnapshot to all providers (parallel)
|-- Score: net_price = raw_price * (1 +/- fee_bps)
|-- Return best provider
|
v
Provider: CreateOrder(ctx, accountID, request)
|-- HTTP to venue API
|-- Return unified Order struct
|
v
Audit: Record(action=order_created, provider, account, symbol, ...)
|
v
Risk Engine: RecordOrder(provider, account, orderValue)
|
v
Client receives Order response
```
## Data Flow: Split Order (VWAP)
```
Client POST /v1/smart-order/split
|
v
Router: BuildSplitPlan(ctx, symbol, side, qty)
|-- GetAllRoutes (parallel venue polling)
|-- Score-weighted allocation
|-- Return SplitPlan with legs
|
v
Router: ExecuteSplitPlan(ctx, plan, accounts)
|-- For each leg (parallel goroutines):
| Provider.CreateOrder(ctx, accountID, {type:market, tif:ioc})
|-- Collect results via channel
|-- Compute actual VWAP = sum(price_i * filled_i) / sum(filled_i)
|
v
Return ExecutionResult {vwap, legs[], status, latency}
```
## Data Flow: Market Data Aggregation
```
Background poller (configurable interval)
|
v
For each registered provider (parallel):
Provider.GetSnapshot(ctx, symbol)
|
v
Feed.UpdateQuote(symbol, provider, bid, ask, sizes, last)
|-- Update per-provider quote in ticker
|-- Recalculate BBO: max(bids), min(asks) across providers
|-- Compute spread + spread_bps
|-- Notify subscribers (non-blocking channel send)
|
v
SSE Server: HandleSSE streams to connected clients
|-- event: ticker
|-- data: {symbol, best_bid, best_ask, spread, sources{}}
```
## API Surface
```
/healthz Health check (no auth)
/v1/route/{symbol} Best venues for symbol
/v1/route/{symbol}/split VWAP split plan
/v1/smart-order Execute via best venue
/v1/smart-order/split Execute split across venues
/v1/bbo/{symbol} Best bid/offer (cross-venue)
/v1/stream SSE consolidated tickers
/v1/assets Aggregated tradable assets
/v1/accounts CRUD accounts
/v1/accounts/{p}/{id}/orders CRUD orders
/v1/accounts/{p}/{id}/positions Positions
/v1/accounts/{p}/{id}/transfers Transfers
/v1/accounts/{p}/{id}/portfolio Portfolio snapshot
/v1/market/{provider}/snapshot/{sym} Per-venue snapshot
/v1/market/{provider}/bars/{sym} Historical OHLCV
/v1/risk/check Pre-trade risk check
/v1/audit Query audit log
/v1/providers Registered providers
/v1/providers/capabilities Provider feature matrix
/compliance/* KYC/KYB, onboarding, RBAC
```
## Deployment
### Single Binary
```
brokerd
|- HTTP server (:8090)
|- Provider registry (16 adapters, env-configured)
|- Router (SOR)
|- Risk engine
|- Market data feed + SSE
|- Settlement service + pool
|- Compliance router (PostgreSQL or in-memory)
|- Audit log
|- Admin JWT auth
```
### K8s (lux-k8s cluster)
```yaml
Namespace: broker
Deployment: broker (1 replica, scales horizontally)
Image: ghcr.io/luxfi/broker:{tag}
Port: 8090
Resources: 100m-500m CPU, 64Mi-256Mi RAM
Probes: /healthz (readiness 5s, liveness 30s)
Secrets: broker-secrets (KMSSecret CRD -> kms.hanzo.ai)
Service: broker:8090
Ingress: via hanzoai/gateway at api.lux.network
```
**Secrets flow:**
```
kms.hanzo.ai (Hanzo KMS)
-> KMSSecret CRD in broker namespace
-> K8s Secret: broker-secrets
-> Pod env: DATABASE_URL, ALPACA_API_KEY, etc.
```
### compose.yml (local dev)
```
services:
sql: postgres:17-alpine on :5434
```
Broker runs natively (`make run`) against the local Postgres and configured provider sandboxes.
### CI/CD
Image built by PaaS (platform.hanzo.ai) via Tekton pipeline. Cloud Build config exists for GCP fallback (`cloudbuild.yaml`). Images pushed to `ghcr.io/luxfi/broker:{tag}`.
## Technology Choices
| Choice | Rationale |
|--------|-----------|
| Go | Low-latency order routing requires predictable GC, goroutines for parallel venue polling, stdlib HTTP. |
| Single binary | No inter-service latency for routing decisions. One process = one failure domain. |
| chi router | stdlib-compatible, middleware chains, zero allocation routing. |
| zerolog | Structured JSON logging, zero allocation. |
| pgx/v5 | PostgreSQL driver for compliance store. Direct, no ORM. |
| NATS (go.mod) | Available for inter-process pub/sub if broker scales to multiple instances. Not required for single-instance. |
| SSE over WebSocket | Works through all proxies/load balancers. Simpler. Sufficient for ticker updates. |
| In-memory risk/settlement | Latency-critical paths. PostgreSQL for durable compliance data. |
## Scaling Path
The broker is designed to scale horizontally when needed:
1. **Current:** Single instance handles all routing, risk, execution. Sufficient for <10k orders/day.
2. **Next:** NATS for cross-instance market data fan-out. Sticky sessions for settlement state. PostgreSQL for risk state.
3. **Future:** Dedicated market data service (separate binary from same codebase). Sharded by asset class.
The single-binary architecture means "scaling" is primarily about running more replicas behind the gateway, not decomposing into microservices.
## Related Systems
| System | Relationship |
|--------|-------------|
| `luxfi/compliance` | Extracted compliance module (KYC/AML) |
| `luxfi/bank` | Customer-facing apps consuming broker API |
| `hanzoai/commerce` | Payment processors (Plaid, Braintree, Stripe) used by funding service |
| `hanzoai/iam` | Identity provider. Broker validates OIDC tokens, scopes to org. |
| `kms.hanzo.ai` | All provider credentials. KMSSecret CRDs sync to K8s. |