Lab notebook · chapter 1 of 7 · page 1 of 2

The harness

One design rule, the broker connection, paper-only safeguards, risk limits and alerting.

The one design decision

The LLM never touches the order API.

market data ──> Context Builder (deterministic)
                     │
                     v
             Agent (LLM) ──> Proposal
                     │
                     v
             Risk Gate (pure Python) ──reject──> journal + reason
                     │ accept
                     v
             Broker (ib_async) ──> IBKR paper

A proposal is journalled before the gate runs, so agent judgment is scored separately from execution luck. A rejected proposal that would have been a winner is a finding about the limits, not about the agent.

First connection: run preflight

python preflight.py --port 4002

Checks connection, paper-account prefix, NAV, contract lookup, whether quotes come back live or delayed (i.e. whether subscription sharing actually took effect), historical-data permission, and the volume-units question below. Places no orders. Each check catches its own failure so one break doesn't mask the rest.

Market data on a paper account

Sharing is opt-in: Client Portal → Settings → Account Settings → Paper Trading Account → Yes, then choose which live username's subscriptions to share. You cannot consume the shared data on live and paper simultaneously.

reqHistoricalData requires a Level 1 streaming subscription — delayed mode does not bypass it, and without one you get error 162. IBKR's free US Real-Time Non-Consolidated Streaming Quotes (Cboe One + IEX) satisfies this, but it is not the NBBO, and the SEC Vendor Display Rule means TWS won't show it in the order ticket without a paid consolidated subscription.

The volume-units question — unresolved

ExecutionModel.max_participation caps orders at a share of bar volume, which assumes that number is consolidated share volume. Two things can make it wrong, neither of which announces itself:

Either would make the model far more restrictive than intended — 100× or 10×. preflight.py pulls a liquid name and reports which case it's looking at. Settle this before running any strategy whose results you intend to believe.

Paper-only enforcement

Two real checks and one weak one, because this is the only bug here that costs money: