gordon-bot
Purpose
gordon-bot is the pure strategy engine. One container runs one strategy; manager spawns N containers via docker-socket-proxy. The bot subscribes to NATS JetStream kline events for its configured symbol and timeframe, runs the strategy via Strategy::evaluate, applies the overlay pipeline, and emits OrderIntent rows to trading.order_intents via pg-NOTIFY for gordon-executor to consume. The bot holds no exchange keys — any attempt to start with BINANCE_* environment variables set is a startup-fatal error. A lease guard on bot_leases (advisory lock + fence token) ensures that no two bots trade the same (symbol, strategy) pair simultaneously.
Version + port + env var
| Field | Value |
|---|---|
| Version | 4.6.0 |
| Port | 8084 (container-internal) |
| Env override | GORDON_BOT_BIND_ADDR |
| DB role | gordon_bot |
| Image | ghcr.io/dlepaux/gordon-bot |
The host-side port is dynamically assigned by gordon-manager at container spawn time. Do not hard-code it. The container-internal port 8084 is fixed.
HTTP endpoints
Health / ops
| Method | Path | Purpose |
|---|---|---|
| GET | /healthz | Liveness probe |
| GET | /readyz | Readiness — only releases after warmup completes |
| GET | /metrics | Prometheus metrics |
| GET | /config | Redacted config dump |
| GET | /strategies | Registered strategy names |
| GET | /strategies/{name}/schema | JSON schema for a strategy's parameters |
Test mode only
| Method | Path | Condition |
|---|---|---|
| POST | /test/emit-intent | Only registered when GORDON_BOT_STRATEGY=manual |
curl -X POST http://localhost:8084/test/emit-intent \
-H 'content-type: application/json' \
-d '{"intent_id":"01890a5b-12e0-7abc-8000-000000000001",
"side":"long","qty":"0.01","sl":"42000.0","tp":"43000.0"}'Idempotent on intent_id. The intent rides the same lease-guard + fence-token path as live strategy emissions.
NATS subjects
| Subject | Direction | Durable consumer |
|---|---|---|
market.klines.binance.spot.{symbol_lc}.{tf} | Subscribes — live tail after warmup | bot-{bot_id}-{symbol_lc}-{tf}-klines |
intents.executor | Publishes OrderIntent payloads | — |
Warmup is dual-sourced: in-retention 1m bars come from JetStream (up to ~7 days); older history (or higher TFs) comes from gordon-data POST /warmup + GET /klines. The merge is deterministic (open-time sort + dedup). Underfill is boot-fatal (WarmupError::Underfill) — a half-warmed strategy is never permitted.
Database access
| Action | Detail |
|---|---|
| Writer | trading.order_intents, trading.bot_leases, trading.signals |
| Reader | No direct market_data.* reads — candles come from gordon-data REST + NATS |
| DB role | gordon_bot (story 16.9) |
Prometheus metrics
gordon_bot_candles_processed_totalgordon_bot_candles_skipped_totalgordon_bot_intents_emitted_totalgordon_bot_intent_emit_errors_totalgordon_bot_strategy_evaluation_duration_secondsgordon_bot_ws_connectedgordon_bot_ws_reconnects_totalgordon_bot_fallback_engagedgordon_bot_lease_liveness_last_ok_secondsgordon_bot_lease_stategordon_bot_fencegordon_bot_drain_in_progressgordon_bot_role_probe_passedgordon_bot_signals_emitted_totalgordon_bot_overlay_vetoes_totalgordon_bot_warmup_freshness_seconds_per_datasetgordon_bot_upgordon_errors_total
Alert: GordonBotLivenessStale fires when time() - gordon_bot_lease_liveness_last_ok_seconds > 90 for 30s (severity critical).
Key env vars
| Variable | Purpose |
|---|---|
GORDON_BOT_BIND_ADDR | HTTP bind address (default :8084) |
GORDON_DATABASE_URL | Postgres connection string |
GORDON_BUS_NATS_URL | NATS JetStream URL |
GORDON_BOT_STRATEGY | Strategy name (supertrend, or manual for test mode) |
GORDON_BOT_SYMBOL | Trading pair (e.g. BTCUSDT) |
GORDON_BOT_TIMEFRAME | Candle timeframe (e.g. 1h) |
GORDON_BOT_CANDLE_SOURCE | data-ws (default) or scripted (e2e only) |
GORDON_BOT_SCRIPT | Fixture path for scripted source (e2e only) |
GORDON_BOT_SCRIPT_TICK_MS | Replay cadence in ms for scripted source |
BINANCE_* env vars are startup-fatal if present.
Invariants
- Zero exchange credentials. Bots never authenticate to Binance. Startup aborts if
BINANCE_*env is detected. - One strategy per container. Strategy is baked at container start via
GORDON_BOT_STRATEGY. No runtime switching. - Lease-guarded. Before emitting any intent, the bot verifies it holds a valid lease and fence token. Lost lease → stop emitting, exit.
- Self-fence on shadow swap. Green/blue cutover uses shadow lease key
lease_key + 1; shadow bot emits to a compare buffer, never to executor, until atomic swap. - Pure math in strategies.
Strategy::evaluatein gordon-strategy crate — no I/O, no DB, no HTTP. The same code path runs in gordon-manager backtests (backtest = live invariant). - Warmup completeness is boot-fatal. Partial warmup (
WarmupError::Underfill) exits non-zero; manager respawns with backoff. - SIGTERM drain in ≤30s preserving SL. Story 16.8.
Status
Phase 6 Bot: 11/13 stories done. Code-complete + pre-cutover. Remaining gate: 72-hour paper-parity soak on testnet. Story 24 (staged cutover) gates production bots.
Related
- Architecture: event-bus-topology
- Reference: error codes
- gordon-strategy — strategy trait + engine registry
- gordon-executor — intent consumer
- gordon-manager — lifecycle orchestrator
- gordon-data — warmup REST source + NATS kline publisher