IBKR paper
The same strategy code now runs against a live market feed and a paper brokerage account for at least two weeks — the first time model meets reality. Getting here requires clearing a hard safety guard that refuses to start against a live account.
In plain terms
Now the robot trades a pretend account with real, live prices. This is where you find out whether the fills, timing, and connections behave like the backtest assumed — before any real money.
A safety officer refuses to even start if anything about the setup points at a real-money account.
Technically
live.py assembles a TradingNode: execution is always IBKR; data is
IBKR or Databento. validate_paper_mode() cross-checks both the self-declared
TRADING_MODE and the actual gateway port before the node builds. A
MonitorActor logs status every five minutes.
Assembling the live node
Configuration is read from the environment, gated by the paper-mode check, then wired into a node with a data client, an IBKR execution client, and the monitoring actor.
The mode string is self-declared, but the port is what actually routes orders. So
validate_paper_mode refuses to start unless TRADING_MODE == "paper" and
the port is not one of the live gateway/TWS ports (IB_LIVE_PORTS = {4001, 7496}). A
paper-labelled session on a live port would route real orders.
if trading_mode.lower() != "paper": raise RuntimeError("LIVE mode requires explicit authorization") if port in IB_LIVE_PORTS: # {4001, 7496} raise RuntimeError("paper mode but port is a LIVE gateway port")
The live loop & monitoring
Live bars run the identical per-bar path as the backtest — size, risk-check, submit — but now orders route through the IBKR adapter to the Gateway, and the execution engine reconciles fills. A monitor actor watches the book.
| Metric | Healthy | Warning | Action |
|---|---|---|---|
| Gross exposure | < 80% | 60–80% | gatekeeper caps at 80% |
| Net exposure | < 60% | 40–60% | gatekeeper caps at 60% |
| Drawdown | < 5% | 5–8% | monitor; kill-switch region beyond |
| Fill count | > 0 / week | 0 for 3+ days | check feed & signal thresholds |
Operating it
$ docker compose up -d ibgateway # start IB Gateway (port 4002) $ python -m src.runners.live --dry-run # validate config, no connection $ python -m src.runners.live --strategy cross_sectional_momentum --data-source databento $ python -m src.dashboard # read-only overview at :8787
On stop, each strategy cancels working orders and — because flatten_on_stop defaults to
True — market-closes every position. To stop while keeping positions open, set
flatten_on_stop=False before starting. Control actions stay on the CLI; the dashboard is
deliberately read-only so no web button can liquidate a live book.
Gate 3 criteria
| Criterion | Requirement |
|---|---|
| Duration on live market data | ≥ 2 weeks |
| Fill rates & slippage vs backtest | match assumptions |
| Disconnects / missed signals | none unexpected |
| P&L trajectory vs backtest | consistent |
| Kill-switch | tested & functional |
| Runbook | reviewed & up to date |
References
- src/runners/live.py —
build_live_node_config, IBKR exec + IBKR/Databento data,MonitorActorwiring,--dry-run. - src/config/node.py —
get_ibkr_config,validate_paper_mode,IB_LIVE_PORTS, adapter config builders. - src/monitoring/monitor.py —
MonitorActor: 5-minute status, drawdown warning, paper-session record. - docs/runbook.md — pre-flight checklist, start/stop, monitoring table, troubleshooting.