Skip to content

Testnet validation

When to use: before promoting any strategy to live capital — verify real Binance wire protocol behaviour with fake money.

The paper-trading simulator was removed at gordon-exchange 3.0.0. Binance testnet is the only forward-test gate. There is no paper mode anywhere in v7.

Prerequisites

  • gordon-executor built and deployable.
  • A Binance testnet account (separate from production).
  • BINANCE_TESTNET_API_KEY and BINANCE_TESTNET_API_SECRET available.
  • Phase 6 Bot stories 11/13+ done (strategy loop validated in e2e).

Setup

1. Provision testnet API keys

  1. Register at https://testnet.binancefuture.com — separate account from production, GitHub sign-in works.
  2. Generate an API key pair: right column, "API Key" tab, "Create API Key".
  3. Save the public key and secret. Secret is shown once. Mode 600 on the env file.
  4. Verify can_withdraw is false — testnet keys never have withdrawal rights. The Binance conformance suite asserts this on startup and refuses to run if can_withdraw=true (guards against pointing a production key here by mistake).

2. Configure gordon-executor for testnet

gordon-executor has a testnet: bool runtime field. When true, all REST and WS connections target the testnet endpoints:

  • REST: https://testnet.binancefuture.com
  • WS: wss://stream.binancefuture.com

Set the env vars in the executor's compose/deploy config:

bash
BINANCE_TESTNET_API_KEY=<your-testnet-key>
BINANCE_TESTNET_API_SECRET=<your-testnet-secret>
GORDON_EXECUTOR_TESTNET=true

Verify the executor is running against testnet — GET /readyz returns 200 and the logs show testnet=true at startup.

3. Verify Binance conformance

Before starting the soak, run the conformance suite to confirm mock-binance and testnet shapes are in sync:

bash
export BINANCE_TESTNET_API_KEY=...
export BINANCE_TESTNET_API_SECRET=...
make e2e-binance-conformance

Expected output: all 9 matrix calls pass on both targets. Any drift row means either mock-binance needs updating or gordon-exchange deserializers are stale.

Soak procedure

4. Start the testnet stack

bash
make e2e-soak-up

Soak mode runs the full Docker stack with MOCK_BINANCE_KLINE_TICK_MS=60000 (one tick per real minute) to avoid filling the DB at e2e speed. Log verbosity drops to warn.

For production testnet soak against real Binance instead of mock-binance, set GORDON_EXECUTOR_TESTNET=true and provide real testnet keys before starting the stack.

5. Monitor the soak

bash
make e2e-logs
docker compose logs -f gordon-executor

Watch for:

  • No halt-latch trips (trading.risk_state.halted stays false).
  • trading.orders rows appearing with status='filled' or status='partially_filled'.
  • trading.fills rows cross-referencing correct order_id.
  • Expected vs actual fill prices within tolerance (slippage within configured limits).
  • No quarantined_at set on trading.bot_configs.

6. Query soak progress

bash
docker compose exec postgres psql -U gordon -d gordon -c "
  SET search_path = trading;
  SELECT
    COUNT(*) AS total_orders,
    COUNT(*) FILTER (WHERE status='filled') AS filled,
    COUNT(*) FILTER (WHERE status='rejected') AS rejected,
    COUNT(*) FILTER (WHERE status='cancelled') AS cancelled
  FROM orders;
"

Promotion criteria

All of the following must hold before promoting to live:

CriterionThresholdHow to verify
Soak duration72 hours continuousStack uptime + trading.runs timestamps
Trade count30+ filled ordersSELECT COUNT(*) FROM trading.orders WHERE status='filled'
Fill toleranceActual fills within 0.05% of expected priceCompare order.avg_price vs order.price
No halt tripsZero halt-latch eventsSELECT * FROM trading.risk_state WHERE halted=true returns empty
No quarantinesNo bot quarantinedSELECT * FROM trading.bot_configs WHERE quarantined_at IS NOT NULL returns empty
Reconcile cleanStartup reconcile on restart shows zero anomaliesRestart executor; check logs for reconcile_outcome=ok
SL attachedEvery filled order has a linked stop-loss orderQuery trading.orders for order_type='stop_market' count equals filled entry count

Validation checklist before live promotion

[ ] 72-hour testnet soak completed without stack restart
[ ] 30+ trades filled
[ ] Fill prices within tolerance on all orders
[ ] No halt-latch trips during soak
[ ] No bot quarantine events
[ ] Executor startup reconcile tested (restart executor mid-soak, verify clean reconcile)
[ ] SL attached on every filled entry
[ ] Daily notional caps configured (DP-01) and tested — oversized intent rejected
[ ] Operator tokens issued and tested (POST /risk/emergency-flatten requires x-operator-token)
[ ] Breaker thresholds reviewed for live account size
[ ] make e2e passes at HEAD (no regressions)
[ ] Postmortem filed for any soak incident

Flipping to live

Once all promotion criteria are met:

  1. Stop the testnet executor.
  2. Update the executor's env: set GORDON_EXECUTOR_TESTNET=false, replace testnet keys with production keys.
  3. Inject production keys via Ansible vault — never commit them.
  4. Start with micro-live allocation ($500 cap) — see live-trading for the full progression.

Gordon — keep compounding without blowing up