Gate 3

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.

flowchart TD ENV["env: IBKR_HOST / PORT / TRADING_MODE"] --> CFG["get_ibkr_config"] CFG --> GUARD{"validate_paper_mode<br/>paper AND port not live?"} GUARD -->|fail| STOP["RuntimeError: SAFETY CHECK FAILED"] GUARD -->|ok| BUILD["build_live_node_config"] BUILD --> DATA{"data_source?"} DATA -->|ibkr| IBD["IB data client"] DATA -->|databento| DBD["Databento data client"] BUILD --> EXEC["IBKR exec client (always)"] BUILD --> MON["MonitorActor<br/>status every 300s"] IBD --> NODE["TradingNode.run()"] DBD --> NODE EXEC --> NODE MON --> NODE NODE --> GW["IB Gateway (Docker, port 4002)"]
Live assembly. Execution is IBKR-only (Databento is data-only). The kill-switch state path is injected here so the switch persists across restarts — a safety concern unique to live.
The paper-mode guard (Hard Rule 2)

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.

src/config/node.py — validate_paper_mode()
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.

sequenceDiagram autonumber participant F as Data feed (IBKR/Databento) participant S as Strategy participant R as Risk layer participant O as OMS participant A as IBKR adapter participant M as MonitorActor F->>S: Bar (live close) S->>R: size + check_order R-->>S: allowed S->>O: submit_order O->>A: route order A->>A: IB Gateway to exchange A-->>O: OrderFilled / reconcile M-->>M: every 300s log equity, exposure, drawdown, slippage
Live trading loop. Reconciliation is enabled on the execution engine so restarts recover open orders and positions. The monitor writes a paper-session record on stop for the Gate 4 review.
MetricHealthyWarningAction
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 / week0 for 3+ dayscheck feed & signal thresholds

Operating it

zsh — start / verify / stop (paper)
$ 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
Ctrl+C liquidates the book by default

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

CriterionRequirement
Duration on live market data≥ 2 weeks
Fill rates & slippage vs backtestmatch assumptions
Disconnects / missed signalsnone unexpected
P&L trajectory vs backtestconsistent
Kill-switchtested & functional
Runbookreviewed & up to date

References