5.9 KiB
Live-Money Cutover Runbook
Staged 2026-07-22. Everything below is pre-built; flip day is configuration, not construction. Target: minimal downtime, zero blending of paper and live records.
Architecture decision
Real money runs as a second, fully isolated compose stack ("momentum-live"): own Postgres, own volumes, own dashboard. Rationale: the ~30 dashboard/P&L projections do not filter by event mode — one missed filter would silently blend money records. A separate database makes blending impossible by construction. The paper stack stays untouched as the research substrate (nightly labeler, experiments, backtests, regime scoring).
What is already staged
| Piece | Where | State |
|---|---|---|
| Live compose (paper keys, current settings) | deploy/docker/truenas-compose.live.local.yaml (LOCAL, secrets, never commit) |
ready; flip checklist in its header |
| DB seed script | deploy/docker/bootstrap_live_db.sh |
ready |
| Equity-floor kill switch | TRADING_EQUITY_FLOOR (commit 5287c8e) |
in image, 0=off |
| Boot order reconciliation | TRADING_BOOT_RECONCILE (default on, 5287c8e) |
in image |
| Stale-session dashboard alarm | commit 2e39b75/5f33799 | in image |
Known dependencies (discovered during staging)
- One paper account cannot serve two stacks. Both composes currently carry the same paper keys. Running both would double-trade one account AND each stack's boot-reconcile would cancel the other's resting orders as orphans. Before first boot: create a second Alpaca paper key pair for the live stack's shakedown (or pause the paper stack's loop during it).
- Fresh DB must be seeded, not born empty. Empty DB = rvol baselines
missing (~1 day to self-heal) and, worse, the regime gate fail-opens for
weeks (no premarket history) — the one validated protection would be inert
exactly at go-live.
bootstrap_live_db.shseeds from the paper stack's nightly dump and truncates the money-record tables (events,telemetry_events, simulated/paper tables), so research history carries over while live P&L starts at zero. The dump also carries every index. data/learned_params.json— copy from the paper stack's data dir intomomentum-live/data/(nightly tune runs only on the paper stack).- Telegram — both stacks share one chat; create a second bot token for
the live stack (
TELEGRAM_BOT_TOKENin its compose) or alerts interleave indistinguishably. - Host prerequisites: dirs
/mnt/Volume/Storage/Apps/momentum-live/data/backupscreated; host ports 8865 (dashboard) and 5434 (postgres) free.
Config model (added 2026-07-22)
Strategy knobs live in the config_overrides database table (commit
495a3fa), edited via the admin panel (paper :8767, live :8867) with a
whitelist (TRADING_*, TRIGGER_*, WATCHER_*, NEWS_*, OLLAMA_*,
DISCOVER_*; secrets refused). Boot precedence: compose env <
data/overrides.env file < database — every service applies the table last;
DB down falls through silently so boot never blocks on config. Compose and
the overrides file retain only infra + secrets (keys, URLs, DB address — the
DB address cannot live inside the DB). One-shot migration inside any stack
container:
python -m runtime.env_overrides seed-from-env
copies that container's effective knobs into the table, after which the
compose knob lines are dead weight (strip at any redeploy). For the live
stack: run the seed after bootstrap_live_db.sh (the seeded table travels
with the DB restore otherwise — which is fine too, since the paper dump's
table carries the same knobs; just re-check flip-day values). Flip-day knob
changes (risk, regime gate, equity floor) happen in the admin UI; only keys +
ALPACA_BASE_URL remain compose edits (secret-marked, deliberately not
UI-editable).
Cutover procedure (~15 min, paper stack keeps running throughout)
mkdir -p /mnt/Volume/Storage/Apps/momentum-live/data/backups- Add the TrueNAS app from
truenas-compose.live.local.yaml; start ONLY the postgres service (or start all and let the loop crash-loop until step 3 — harmless,restart: unless-stopped). - Seed:
./bootstrap_live_db.sh <latest paper backup> <live-pg-container>(paper backups land nightly 20:10 ET in/mnt/Volume/Storage/Apps/momentum/data/backups/). cp .../momentum/data/learned_params.json .../momentum-live/data/andcp deploy/docker/overrides.live.seed.env .../momentum-live/data/overrides.env- Shakedown on the SECOND paper key pair for 1-2 sessions: watch the 8865 dashboard — armed triggers populate, entries fire, no stale-session banner, boot-reconcile quiet.
- The flip (edit live compose env, both loop + dashboard, then redeploy):
ALPACA_BASE_URL: https://api.alpaca.marketsALPACA_API_KEY/ALPACA_SECRET_KEY: live keysTRADING_RISK_PER_TRADE_PCT: '0.0025'(start tiny)TRADING_MAX_RISK_DOLLARS: '250'(hard ceiling back on)TRADING_REGIME_GATE_ENABLED: '1'(the one validated lever)TRADING_EQUITY_FLOOR:0.90 x starting equity (absolute dollars)
- Verify within the first hour: dashboard LIVE badge, one order's audit trail
end-to-end,
python -m research.slippage --since <today>after the first close, Telegram alert received.
Rollback
Point the live compose back at paper keys/URL and redeploy — the stack itself is the same code either way. The paper stack was never touched. If the live DB is ever suspect, it is disposable pre-flip: destroy pgdata, re-run the seed.
Notes
- FINRA PDT rule retired June 4 2026 (Alpaca Intraday Margin Framework) — no $25k floor; standard margin minimums apply.
- Measured fill reality on gate-passing names: 0.3-0.5% median spread, ~12%
profit drag vs paper; p90 tail 2.4-3.6% and book-walks are the real risks
(
research/slippage.pyis the standing report). - Gate for the flip: 2-3 consecutive green regime-gated paper weeks on the fixed stack (week of 07-20: +$1,732, 8-for-8, regime OFF/watch-mode).