Risk Management
gordon-risk is the portfolio-level safety layer. It watches trading.* aggregates and intervenes when portfolio-level thresholds are breached. It never touches individual orders — it commands, it does not execute. Per-order invariants (SL, notional cap, idempotency) live in gordon-executor.
Five circuit breakers
The scheduler runs at 30 Hz. Each tick evaluates all five breakers independently:
| Breaker | Signal | Threshold |
|---|---|---|
| DrawdownBreaker | Peak-to-trough portfolio equity drawdown | Configurable percentage from equity high |
| ConnectivityBreaker | gordon-data freshness check (latest kline age) | Stale if no kline within threshold window |
| VpinBreaker | VPIN (Volume-synchronized Probability of Informed Trading) computed live from 1m klines | Toxic-flow threshold |
| MacroBreaker | FRED macro regime indicators | Macro deterioration threshold |
| CorrelationBreaker | Cross-asset correlation density | High-correlation (risk-on/risk-off) regime |
When a breaker fires, commit_risk_action writes an atomic five-row transaction: risk_events, bot_commands, risk_state, risk_state_history, and bus.outbox (for the NATS risk.events.{breaker} payload). Either all five rows commit or none do.
Halt-latch state machine
The halt is a hard latch, not a soft signal. Once set, the system stays halted until an operator explicitly calls POST /risk/resume. A resolved breaker condition does not automatically clear the halt — this is intentional. The operator must verify conditions before resuming.
The latch state is stored in trading.risk_state.halted (singleton row). The halt-latch contract is documented in full at docs/contracts/halt-latch.md.
Emergency flatten
POST /risk/emergency-flatten (operator-only, proxied through gordon-manager BFF):
- Writes a
flatten_allcommand totrading.bot_commandsandbus.outbox(same transaction). - gordon-executor's
executor-riskconsumer (risk.commandsdurable) receives the command. - gordon-executor closes all open positions via Binance REST.
- All bots receive a
pausecommand viarisk.commands. - The halt latch is set.
Drill target: less than 30 seconds end-to-end from operator POST to positions closed.
The POST /risk/emergency-flatten endpoint also writes to trading.risk_audit_log and trading.risk_events. gordon-risk is the sole writer of these audit tables.
Pause commands
POST /bots/:id/pause suspends a specific bot without affecting others:
- gordon-risk writes a column-level UPDATE on
bot_configs.desired_state(column-level grant from migration 0011 — no other columns can be written by the risk role). - gordon-bot's reconciler loop detects the state change and stops emitting intents.
- The pause is reversible via
POST /risk/resume(fleet-wide) or manager bot lifecycle controls (per-bot).
Escalation state machine
The escalation manager tracks whether a flatten has been attempted and whether it succeeded. States: Idle, FlattenRequested, FlattenInFlight, FlattenConfirmed, FlattenFailed. A failed flatten after the full drain timeout triggers a second escalation attempt and an operator alert.
Metrics
All circuit-breaker evaluations, fires, and command writes are tracked via Prometheus counters. Key metrics:
| Metric | Labels |
|---|---|
gordon_risk_breaker_evaluations_total | — |
gordon_risk_breaker_fires_total | breaker |
gordon_risk_bot_commands_issued_total | command |
gordon_risk_command_write_errors_total | breaker, outcome_type, error_class |
gordon_risk_operator_flatten_requests_total | scope, outcome |
gordon_risk_operator_resume_requests_total | outcome |
Alert GordonRiskCommandWriteFailed fires on any command_write_errors_total rate above zero over a 5-minute window — a breaker fired but the command never landed.
Invariants
- Portfolio-level only. Per-order invariants live in gordon-executor.
- Halt is a hard latch. Only explicit
POST /risk/resumeclears it. commit_risk_actionis an atomic five-row transaction. No partial writes.- gordon-risk is the sole writer of
trading.risk_events,trading.risk_audit_log, andtrading.risk_state. - gordon-risk holds a column-level UPDATE grant on
bot_configs.desired_stateonly — widening this grant requires an explicit migration and a test update. - SL is mandatory on all positions — enforced in gordon-executor, not here.
- Emergency flatten drill target: less than 30 seconds.
Related
- Execution — how gordon-executor enforces per-order invariants and reacts to risk commands.
- BFF Boundary — how operator commands reach gordon-risk through gordon-manager.
- Event Flow —
risk.commandsandrisk.events.>subject topology.