The Problem
Evaluating options‑and‑equity portfolio strategies over historical data demands a reproducible pipeline that handles realistic execution, Greeks‑aware risk, and tail‑risk hedging. The repo supplies those capabilities, but its module graph contains circular imports, oversized files, and deep nesting that make changes risky and hard to reason about.
What This Does
The package is a full‑stack backtester built around a Rust compute core (rust/ob_core) and Python orchestration (options_portfolio_backtester).
- Data ingestion –
options_portfolio_backtester/data/providers.pysuppliesHistoricalOptionsDataandTiingoData; raw parquet/CSV is fetched withscripts/fetch_data.py. - Strategy construction –
options_portfolio_backtester/strategy/strategy.pyandstrategy_leg.pylet legs be added, filtered, and cleared; the canonical Spitznagel tail‑hedge leg is built withdeep_otm_put. - Engine execution –
options_portfolio_backtester/engine/engine.py(BacktestEngine) coordinates rebalancing, cost models (execution/cost_model.py), and risk checks (portfolio/risk.py). - Analysis & reporting –
analytics/tearsheet.pyandanalytics/charts.pyproduce HTML reports, equity curves, and options‑specific panels fromengine.balanceandengine.trade_log.
A single run call (entry point options_portfolio_backtester/engine/engine.py:345) drives the backtest from data load through trade execution to results, reaching 28 internal functions.
How It Is Wired
- Entry points –
setup(test‑only, reaches 47 functions, called from nothing else),maininresearch/spitznagel_spy/experiments/cross_underlying.py:159(reaches 148 functions, invoked once), andruninengine.py:345(reaches 28 functions, called once). - Call graph – 3 239 resolved edges; the most‑connected symbols are
_ctx(116 callers),from_balance(105), andBacktestEngine(70). - Hubs & cycles –
options_portfolio_backtester/core/typesis imported by 59 modules (instability 0);options_portfolio_backtester/engine/engine.pyandoptions_portfolio_backtester/__init__.pyform a circular‑import cycle. - Blast‑radius files –
engine/engine.py(864 loc, 3 classes, 32 functions) andengine/pipeline.py(106 functions) are change‑propagation points; a modification ripples widely. - Outside‑process effects – execution paths can write CSV (
to_csv), run external commands, make a single outbound network call (traced frommain → to_csv), and perform a cryptographic operation.
How To Use It
Setup
# With Nix
nix develop
# Without Nix (Python ≥ 3.12)
python -m venv .venv && source .venv/bin/activate
make install-dev # installs from pyproject.toml
Get data
python scripts/fetch_data.py all --symbols SPY
# pulls SPY options & underlying parquet into data/processed/
Run a first backtest (example from the README)
from options_portfolio_backtester import (
BacktestEngine, Stock,
HistoricalOptionsData, TiingoData,
deep_otm_put,
)
options_data = HistoricalOptionsData("data/processed/options.parquet")
stocks_data = TiingoData("data/processed/stocks.csv")
strategy = deep_otm_put(options_data.schema, "SPY")
engine = BacktestEngine({"stocks": 1.0, "options": 0.0, "cash": 0.0},
initial_capital=1_000_000)
engine.use_external_budget(annual_pct=0.005)
engine.stocks = [Stock("SPY", 1.0)]
engine.stocks_data = stocks_data
engine.options_data = options_data
engine.options_strategy = strategy
engine.run(rebalance_freq=1, rebalance_unit="BMS")
results = engine.get_results()
print(results.summary())
# {'annual_return': 13.5, 'max_drawdown': -46.4, 'sharpe': 0.72, 'trades': 327, ...}
Generate a tearsheet
from options_portfolio_backtester.analytics.tearsheet import build_tearsheet
report = build_tearsheet(
engine.balance,
budget_annual_pct=0.033,
trade_log=engine.trade_log,
)
report.to_file("tearsheet.html")
Real‑World Use
A portfolio manager seeking a tail‑hedge can instantiate the Spitznagel leg (deep_otm_put) and allocate a small options budget (e.g., engine.use_allocation(stocks=0.99, options=0.01, cash=0.0)). The backtest then simulates monthly rebalancing, tracks premium spend against the budget, and produces a crash‑window P&L attribution—all within the same framework used for equity‑only sweeps.
Code Health & Issues
Measured analysis (static, 171 files): 66 issues across 7 categories
- High/cognitive_load – deep nesting in
engine/engine.py,strategy/strategy.py,portfolio/risk.py(max indent 8). - High/soundness – import‑cycle member in
engine/engine.py&__init__.py. - High/clarity – hub module
core/types(59 importers) and duplicated 6‑line blocks (173 repeats across 53 files). - Medium/resilience – broad
exceptinengine/engine.py,analytics/results.py,analytics/tearsheet.py. - Medium/resource_safety – file opened without context manager in
tests/engine/test_rust_parity.py.
Code‑health audit (8 findings, 0 critical):
- HIGH – Pin GitHub Actions to commit SHA (
.github/workflows,dtolnay/rust-toolchain@stable,Swatinem/rust-cache@v2). - HIGH – Commit lockfile beside
pyproject.toml. - HIGH – Remove
continue-on-errorfrom correctness‑gating steps (.github/workflows/ci.yml:40). - MEDIUM – Declare least‑privilege
permissions: contents: readforGITHUB_TOKEN. - MEDIUM – Enable Dependabot or Renovate (cover Python, Rust, GitHub‑Actions).
- MEDIUM – Gate PRs on dependency vulnerability scan (add
dependency-review-action). - MEDIUM – Set
persist-credentials: falseon checkout. - LOW – Add
timeout-minutesto workflow jobs.
The Bottom Line
This repo delivers a capable, reproducible options‑backtesting platform with a Rust core and rich analytics, but its module interdependencies, oversized files, and circular imports raise maintenance risk. Teams that need fast strategy sweeps and tail‑risk hedging will find value here, provided they invest in cleaning the identified cycles and pinning CI dependencies.