Skip to content

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

FieldValue
Version4.6.0
Port8084 (container-internal)
Env overrideGORDON_BOT_BIND_ADDR
DB rolegordon_bot
Imageghcr.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

MethodPathPurpose
GET/healthzLiveness probe
GET/readyzReadiness — only releases after warmup completes
GET/metricsPrometheus metrics
GET/configRedacted config dump
GET/strategiesRegistered strategy names
GET/strategies/{name}/schemaJSON schema for a strategy's parameters

Test mode only

MethodPathCondition
POST/test/emit-intentOnly registered when GORDON_BOT_STRATEGY=manual
bash
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

SubjectDirectionDurable consumer
market.klines.binance.spot.{symbol_lc}.{tf}Subscribes — live tail after warmupbot-{bot_id}-{symbol_lc}-{tf}-klines
intents.executorPublishes 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

ActionDetail
Writertrading.order_intents, trading.bot_leases, trading.signals
ReaderNo direct market_data.* reads — candles come from gordon-data REST + NATS
DB rolegordon_bot (story 16.9)

Prometheus metrics

  • gordon_bot_candles_processed_total
  • gordon_bot_candles_skipped_total
  • gordon_bot_intents_emitted_total
  • gordon_bot_intent_emit_errors_total
  • gordon_bot_strategy_evaluation_duration_seconds
  • gordon_bot_ws_connected
  • gordon_bot_ws_reconnects_total
  • gordon_bot_fallback_engaged
  • gordon_bot_lease_liveness_last_ok_seconds
  • gordon_bot_lease_state
  • gordon_bot_fence
  • gordon_bot_drain_in_progress
  • gordon_bot_role_probe_passed
  • gordon_bot_signals_emitted_total
  • gordon_bot_overlay_vetoes_total
  • gordon_bot_warmup_freshness_seconds_per_dataset
  • gordon_bot_up
  • gordon_errors_total

Alert: GordonBotLivenessStale fires when time() - gordon_bot_lease_liveness_last_ok_seconds > 90 for 30s (severity critical).

Key env vars

VariablePurpose
GORDON_BOT_BIND_ADDRHTTP bind address (default :8084)
GORDON_DATABASE_URLPostgres connection string
GORDON_BUS_NATS_URLNATS JetStream URL
GORDON_BOT_STRATEGYStrategy name (supertrend, or manual for test mode)
GORDON_BOT_SYMBOLTrading pair (e.g. BTCUSDT)
GORDON_BOT_TIMEFRAMECandle timeframe (e.g. 1h)
GORDON_BOT_CANDLE_SOURCEdata-ws (default) or scripted (e2e only)
GORDON_BOT_SCRIPTFixture path for scripted source (e2e only)
GORDON_BOT_SCRIPT_TICK_MSReplay 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::evaluate in 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.

Gordon — keep compounding without blowing up