Cerebro · system documentation

Architecture & Workflow

How a market idea travels from raw price data to a live order — through a backtest, out-of-sample validation, weeks of paper trading, and a human sign-off — with a risk layer standing guard at every bar. Read each idea twice: once in plain terms, once precisely.

paper-first · Nautilus Trader · IBKR

In plain terms

Cerebro is a robot that tests trading ideas honestly and only lets the survivors touch real money.

It gathers price history, tries a strategy on the past, then re-tests it on data it never "saw" while being tuned. If it still works, it trades a pretend account for a few weeks. Only after a person reviews the results can it go live — and even then, at the smallest size.

Every step has a safety officer that can halt trading if losses get too deep.

Technically

Python strategies run on the Rust-core nautilus_trader engine (CASH account, NETTING OMS). Bars flow from a ParquetDataCatalog into a Strategy, through a composed risk layer, to the engine's OMS and out via the IBKR adapter.

Promotion is gated: backtest (in-sample) → walk_forward (OOS) + param_sensitivity + dsr → IBKR paper → gate_review → live. Metrics come from a reconstructed net-liquidation curve, never the engine's cash analyzer.

The big picture

Data on the left, market on the right. The strategy never talks to the broker directly — it goes through the risk layer and the engine's order-management system, and the same code path runs in both backtest and live.

flowchart LR subgraph SRC["Data sources"] IB[(IBKR)] DB[(Databento)] YF[(yfinance)] end CAT[["Parquet catalog<br/>data/catalog"]] UNI["Point-in-time universe<br/>data/universe.py"] SRC -->|ingest.py| CAT UNI -. membership .-> CAT subgraph ENGINE["Nautilus engine"] STRAT["Strategy (Python)<br/>strategies/*"] RISK["Risk layer<br/>sizer / gatekeeper / kill-switch"] OMS["Engine / OMS<br/>NETTING / CASH"] BUS{{"message bus"}} STRAT --> RISK --> OMS STRAT <--> BUS OMS <--> BUS end CAT --> STRAT OMS -->|orders| ADP["IBKR adapter"] ADP --> GW["IB Gateway (Docker)"] GW -->|paper / live| MKT((Market))
High-level architecture. Ingestion writes a Parquet catalog; strategies read bars from it, pass every order through the risk layer and OMS, and reach the market only via the IBKR adapter and a Dockerized IB Gateway. The message bus links strategy, engine, and actors.
data & catalog
strategy / engine
execution / market

How a strategy actually runs

Everything happens on each bar. The order is deliberate: check the kill-switch first, then manage risk, then (maybe) trade. Safety precedes signal.

sequenceDiagram autonumber participant E as Engine participant S as Strategy.on_bar participant K as KillSwitch participant P as PositionSizer participant V as VolTarget participant G as RiskGatekeeper participant O as OMS / Venue E->>S: Bar (close) S->>S: append close, compute equity (NLV) S->>K: update_equity(equity) alt drawdown >= max K-->>S: is_triggered = true S->>O: flatten_all (cancel + close) else within limits S->>S: rebalance? (monthly / signal) S->>P: calculate(equity, entry, stop) P-->>S: qty S->>V: vol_scale(realized, target) V-->>S: scale S->>G: check_order(exposure, trade_risk) alt allowed G-->>S: RiskCheck(allowed=true) S->>O: submit_order(MARKET BUY) O-->>S: OrderFilled (next bar close) else blocked G-->>S: RiskCheck(allowed=false) end end
The per-bar control flow shared by every strategy. The kill-switch is consulted before any trading logic; sizing, vol-targeting, and the exposure gatekeeper all run before an order is submitted; fills land on the next bar's close (no look-ahead). Detailed on the Risk Layer page.

The promotion pipeline

A strategy may not advance until it passes every criterion at its current stage (docs/gates.md). A failure sends it back to research — it does not get waved through.

flowchart LR I["Stage 0<br/>Data & Universe"] G1["Gate 1<br/>Backtest (in-sample)"] G2["Gate 2<br/>Walk-forward (OOS)"] G3["Gate 3<br/>IBKR paper (2+ weeks)"] G4["Gate 4<br/>Human review"] L(["Live<br/>minimum size"]) X["Reject / iterate"] I --> G1 --> G2 --> G3 --> G4 --> L G1 -. fail .-> X G2 -. fail .-> X G3 -. fail .-> X X -. revise .-> I
Promotion order. Runners by stage: ingest/catalog_checkbacktest/promote_checkwalk_forward/param_sensitivity/dsrlive/monitorgate_review.

Why so many gates?

Most backtest numbers are lies — a strategy tuned on the past can look brilliant and still be worthless. Each gate is a different way of asking "is this real, or did we fool ourselves?"

What each gate defends against

Gate 1: baseline quality + look-ahead. Gate 2: overfitting (OOS consistency, ±20% param perturbation, deflated Sharpe for N trials). Gate 3: model-vs-reality (fills, slippage, disconnects). Gate 4: the human veto before capital is at risk.

Module map

How the code fits together. Runners orchestrate; strategies decide; the risk package and the equity-curve reconstructor are shared machinery.

flowchart TD live["runners/live.py"] bt["runners/backtest.py"] node["config/node.py"] mon["monitoring/monitor.py"] strat["strategies/*"] risk["risk/*"] eq["runners/equity_curve.py"] pc["runners/promote_check.py"] wf["runners/walk_forward.py"] ps["runners/param_sensitivity.py"] dsr["runners/dsr.py"] gr["runners/gate_review.py"] ing["runners/ingest.py"] uni["data/universe.py"] con["data/constituents.py"] cc["data/catalog_check.py"] live --> node live --> bt live --> mon bt --> ing bt --> eq bt --> strat strat --> risk pc --> dsr pc --> bt pc --> eq wf --> pc ps --> pc gr --> pc mon --> pc ing --> con con --> uni cc --> bt
Static dependency map. backtest.py holds the strategy registry and the execution model; promote_check.py is the evaluation hub the validation runners share; every strategy composes the same risk/* components.

Explore each stage

Stage 0
DB

Data & Universe

Ingesting survivorship-free price history and point-in-time membership into the Parquet catalog.

Gate 1
BT

Backtest

Assembling a backtest, the no-look-ahead execution model, and honest NLV measurement.

Cross-cutting
RX

Risk Layer

Position sizing, vol-targeting, exposure caps, and the drawdown kill-switch.

Gate 2
WF

Validation

Walk-forward windows, parameter perturbation, and the deflated Sharpe ratio.

Gate 3
PP

IBKR Paper

The live TradingNode, IBKR/Databento wiring, monitoring, and operational safety.

Gate 4
LV

Review & Live

Divergence analysis, the human sign-off, emergency flatten, and going live at minimum size.

References

Primary sources in the Cerebro repository (paths relative to the repo root).