Skip to content

gordon-executor

Purpose

gordon-executor is the sole exchange integration point in the v7 stack. It holds the only trading-capable Binance API keys, consumes OrderIntent rows from the bus, enforces per-order safety invariants, submits orders to Binance (live or testnet), tracks fills via dual user-data WebSockets, and reconciles state on every startup to detect orphan orders and exchange divergence. It is the only service that can place or cancel orders. Portfolio-level risk commands (flatten, pause) arrive from gordon-risk via NATS risk.commands and trigger the FlattenExecutor pipeline.

Version + port + env var

FieldValue
Version5.3.0
Port8085
Env overrideGORDON_EXECUTOR_BIND_ADDR
DB rolegordon_executor
Imageghcr.io/dlepaux/gordon-executor

HTTP endpoints

Health / ops

MethodPathPurpose
GET/healthzLiveness + degraded-state probe (returns {"status":"degraded","reason":"..."} on soft failures)
GET/readyzReadiness — only releases after reconcile completes for ALL networks
GET/metricsPrometheus metrics
GET/configRedacted config dump

Business endpoints

MethodPathPurpose
GET/ordersList orders (paginated)
GET/orders/{intent_id}Single order by intent ID
GET/positionsCurrent open positions
GET/statusExecutor status summary
POST/flattenTrigger flatten (risk-command path — not operator-direct)
POST/clear-quarantineClear quarantine state after operator review
POST/executor/break-glass/flattenBreak-glass flatten (emergency, bypasses standard pipeline)

NATS subjects

SubjectDirectionDurable consumer
intents.executorConsumes OrderIntent payloads from gordon-botexecutor-default (blue) / executor-shadow-<deploy_id> (shadow during green/blue)
risk.commandsConsumes flatten + pause commands from gordon-riskexecutor-risk
trading.fills.{bot_id}Publishes fill events after every confirmed fill

Fill events are also written via pg-NOTIFY on order_intents / bot_events channels. Both paths fire on every fill — NATS is additive (story 08a, 2026-05-14).

Database access

ActionDetail
Writertrading.orders, trading.trades, trading.order_events, trading.fill_events
Outboxbus.outbox INSERT + UPDATE (drain path for NATS dual-write)
FenceEXECUTE on increment_fence function
DB rolegordon_executor — least-privilege, migration 0044

Does not read market_data.* directly. Last/mark price comes from the Binance exchange client.

Prometheus metrics

  • executor_intents_total
  • executor_orders_placed_total
  • executor_orders_filled_total
  • executor_orders_rejected_total
  • executor_reconcile_duration_seconds
  • executor_reconcile_anomalies_total
  • executor_flatten_total
  • executor_flatten_runs_total
  • gordon_executor_daily_notional_used_usd
  • gordon_executor_daily_notional_used_usd_global
  • gordon_executor_daily_notional_reset_ts_seconds
  • gordon_executor_daily_notional_would_reject_total
  • gordon_executor_daily_notional_rejected_total
  • gordon_errors_total

Key env vars

VariablePurpose
GORDON_EXECUTOR_BIND_ADDRHTTP bind address (default :8085)
GORDON_DATABASE_URLPostgres connection string
GORDON_BUS_NATS_URLNATS JetStream URL
BINANCE_API_KEY / BINANCE_API_SECRETProd trading-capable keys
BINANCE_TESTNET_API_KEY / BINANCE_TESTNET_API_SECRETTestnet keys
GORDON_EXECUTOR_MAX_DAILY_NOTIONAL_USD_PER_BOTDP-01 per-bot daily cap (omit = warn-only)
GORDON_EXECUTOR_MAX_DAILY_NOTIONAL_USD_GLOBALDP-01 global daily cap (omit = warn-only)

Invariants

  • Sole holder of trading keys. Any other service that places orders is a bug. Vault slot: gordon-executor.
  • SL is mandatory. No order leaves without an attached stop-loss. Intents without SL are rejected at the invariant layer.
  • Risk halt is a hard barrier. Every fresh intent is gated on the halt latch before submission. Contract: halt-latch.
  • Startup probes (all networks, fatal on failure):
    • canWithdraw=false on prod keys (testnet excluded — endpoint behaviour verified 2026-05-12).
    • canTrade=true on both networks.
    • dualSidePosition=false (hedge mode corrupts reconcile).
  • Reconcile-before-readyz ordering. /readyz is not released until quarantine.seed_from_outcomes() completes for ALL networks, not just the first finisher.
  • Idempotency on retry. Duplicate intent_id is a no-op — no duplicate order.
  • Fill dedup by content-hash. trade_fingerprint is the dedup key, not exchange_trade_id. Full contract: fill-tracker-contract.
  • FlattenExecutor::run() always emits a structured completion log. A silent flatten dispatch is a defect.
  • Dual-network isolation. Prod and testnet keys are distinct credentials; intent network field routes correctly.
  • DP-01 daily notional caps. GORDON_EXECUTOR_MAX_DAILY_NOTIONAL_USD_PER_BOT and GORDON_EXECUTOR_MAX_DAILY_NOTIONAL_USD_GLOBAL gate orders by UTC-day rolling notional. Unset = warn-only mode (logs would_reject counter but does not block).

Status

Phase 3 Executor: 10/10 stories done (2026-04-16). Backend close-out audit passed (2026-04-24). Coverage ≥85% on safety-critical modules (invariants, submission, reconcile, flatten). Stable.

Gordon — keep compounding without blowing up