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_KEYandBINANCE_TESTNET_API_SECRETavailable.- Phase 6 Bot stories 11/13+ done (strategy loop validated in e2e).
Setup
1. Provision testnet API keys
- Register at https://testnet.binancefuture.com — separate account from production, GitHub sign-in works.
- Generate an API key pair: right column, "API Key" tab, "Create API Key".
- Save the public key and secret. Secret is shown once. Mode 600 on the env file.
- Verify
can_withdrawisfalse— testnet keys never have withdrawal rights. The Binance conformance suite asserts this on startup and refuses to run ifcan_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:
BINANCE_TESTNET_API_KEY=<your-testnet-key>
BINANCE_TESTNET_API_SECRET=<your-testnet-secret>
GORDON_EXECUTOR_TESTNET=trueVerify 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:
export BINANCE_TESTNET_API_KEY=...
export BINANCE_TESTNET_API_SECRET=...
make e2e-binance-conformanceExpected 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
make e2e-soak-upSoak 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
make e2e-logs
docker compose logs -f gordon-executorWatch for:
- No halt-latch trips (
trading.risk_state.haltedstaysfalse). trading.ordersrows appearing withstatus='filled'orstatus='partially_filled'.trading.fillsrows cross-referencing correctorder_id.- Expected vs actual fill prices within tolerance (slippage within configured limits).
- No
quarantined_atset ontrading.bot_configs.
6. Query soak progress
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:
| Criterion | Threshold | How to verify |
|---|---|---|
| Soak duration | 72 hours continuous | Stack uptime + trading.runs timestamps |
| Trade count | 30+ filled orders | SELECT COUNT(*) FROM trading.orders WHERE status='filled' |
| Fill tolerance | Actual fills within 0.05% of expected price | Compare order.avg_price vs order.price |
| No halt trips | Zero halt-latch events | SELECT * FROM trading.risk_state WHERE halted=true returns empty |
| No quarantines | No bot quarantined | SELECT * FROM trading.bot_configs WHERE quarantined_at IS NOT NULL returns empty |
| Reconcile clean | Startup reconcile on restart shows zero anomalies | Restart executor; check logs for reconcile_outcome=ok |
| SL attached | Every filled order has a linked stop-loss order | Query 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 incidentFlipping to live
Once all promotion criteria are met:
- Stop the testnet executor.
- Update the executor's env: set
GORDON_EXECUTOR_TESTNET=false, replace testnet keys with production keys. - Inject production keys via Ansible vault — never commit them.
- Start with micro-live allocation (
$500cap) — see live-trading for the full progression.
Related
- Live trading — promotion progression and safety rules
- E2E testing — Binance conformance suite details
- Incident response — halt-latch and emergency-flatten procedures