Options research platform

shadow-options-trading-lab

Live-data options strategies that trade on paper only, graded on the worst fill they could have gotten.

Shadow mode, no orders 20 strategies armed Python 3.14 726 tests passing Work in progress
Read this first. This is a snapshot in time of a system that is currently running. It is a work in progress, running roughly 20 strategies in unison in shadow mode, for fine-tuning. It places no broker orders and it is not trying to look finished.

What it does

An apparatus for grading strategies honestly

On live 1-minute market data the system decides what options position it would open, tracks that hypothetical position mark by mark, decides when it would close, and writes every step to append-only ledgers.

The point is not a trading bot. The point is measurement that refuses to flatter itself. The apparatus exists. The verdict does not: no strategy has reached the sample size needed to clear the evidence bar, so every verdict currently reads UNPROVEN. That is the honest state of the work.

20

Strategies armed

Published independently, each scanning on its own interval against a shared quote hub.

3

Fill ledgers per trade

Worst, base and optimistic recorded at once. Grading runs on worst only.

0

Orders, structurally

No code path in the options import closure can submit one. A test proves it.

The load-bearing property

It cannot trade

Shadow mode here is not a flag. The order-placement machinery from the project's earlier equity phase was deleted from the tree, not disabled. Going live would be a build, not a flip.

That claim is mechanically enforced. A test spawns a clean subprocess, imports the whole live decision path the way production imports it, and asserts that none of the archived order machinery came back.

# tests/test_keep_imports.py::test_no_order_machinery_reachable_from_options
banned = [b for b in ('atlas.execution.order_lifecycle',
                      'atlas.execution.broker_adapter', 'atlas.execution.guardian',
                      'atlas.execution.robinhood_adapter', 'atlas.execution.rh_mcp_client',
                      'atlas.orchestrator', 'atlas.app')
          if b in sys.modules]
assert not banned, f'order machinery reachable: {banned}'

The subprocess is the load-bearing part. Inside a shared pytest process, sys.modules is polluted by whatever a sibling test imported first, so the same check would pass vacuously.

How the grading works

Three ledgers, and a test you cannot game by looking

Every entry and every exit is recorded three times at once. Grading runs exclusively on the worst ledger. The other two bound the answer and falsify the recorder: on net P&L the identity worst <= base <= optimistic must hold on every row, and rows that violate it are quarantined out of the statistics rather than averaged in.

LedgerAssumption
WORSTbuy the ask, sell the bid
BASEmid, moved 0.35 x half-spread against you
OPTIMISTICmid

Architecture

Two shadow engines, side by side

The main options shadow runs five lane classes into a contract selector with 20 hard gates and an expected-value stage. The strategy lab runs twenty published strategies against a shared quote hub under a rolling-minute budget governor. Both write into the same three-ledger shadow record, and both feed the nightly grader.

Lane classLooks forState
IndexTrendLaneindex trend breaklive
Last30Lanelast-30-minute 1DTE continuationlive
InPlayORBLanein-play opening-range breaklive
MacroReactionLanemacro-print reactionlive
PreEarningsStubLaneevent straddlepermanent stub

The exit ladder carries a standing prohibition: no rule may fire on a premium threshold. What you paid is not evidence about what the contract is worth. Where AI appears at all, it appears at the edges: free-tier cloud models propose a premarket watchlist, their replies pass a drop-never-repair allowlist, and no module under atlas/options/ imports or calls a model or a provider. The only LLM-derived data reaching the options package is a news-flag tail read as a graded entry covariate and a re-mark cadence trigger; it gates nothing and never enters an EV or probability computation. The live decision path is pure math.

Quickstart

Runs on a fresh clone with zero credentials

py -3 -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt
.venv\Scripts\python.exe -m pip install httpx pyarrow alpaca-py

# Nothing here touches a broker or the network.
.venv\Scripts\python.exe -m pytest -q
# 731 tests: 726 pass, 5 skip (live-deployment canaries)

Live-data loops need your own Tradier token in config/tradier.local.yaml. Tradier is the only feed the live loop actually needs. Alpaca, Finnhub and the cloud-model keys are optional, and every source fails open. runtime/ is gitignored and created on first run, so a clone starts without it.

Documentation in the repo

Read the primary sources