Skip to main content

Volatility Trading on Equity Options

CI Pages Python Ruff Pyright pre-commit License: MIT

This project develops and evaluates daily options-volatility strategies on index and single-stock underlyings. Research spans the full pipeline: data engineering and quality checks, implied-volatility surface modelling, volatility forecasting, and strategy backtesting. Backtests use realistic execution assumptions (bid/ask, slippage, commissions, position sizing, and risk limits) and are documented with reproducible notebooks and published reports.

Notebook reports (GitHub Pages): https://anthonymakarewicz.github.io/volatility-trading/

Quickstart

  1. Clone the repository:
git clone https://github.com/anthonymakarewicz/volatility-trading.git
cd volatility_trading
  1. Install uv and create a virtual environment (Python 3.12+):
brew install uv  # or: pipx install uv
uv venv --python 3.12 .venv
source .venv/bin/activate
  1. Install dependencies:

Primary contributor setup (editable package + dev tooling):

uv pip install -e ".[dev]"

Secondary options:

  • Runtime-only install (users running package code without dev tools):
uv pip install .
  • Editable runtime-only install (local source edits, no dev tools):
uv pip install -e .

pip remains supported as a fallback if you do not want to use uv:

python -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
pip install -e ".[dev]"
  1. Optional: set credentials for ORATS data access:
cp .env.example .env

Then set ORATS_API_KEY, ORATS_FTP_USER, and ORATS_FTP_PASS in .env if you plan to run the ORATS download/extract pipeline. You can skip this if you are using already-prepared data or a different options data source.

ORATS ETL Pipeline (End-to-End)

Pipeline steps:

  • API download
  • API extract
  • FTP download
  • FTP extract
  • Build options chain
  • Build daily features
  • QC options chain
  • QC daily features

Use --dry-run to validate config, paths, and credentials before running writes/network.

orats-api-download --config config/orats/api_download.yml --dry-run

For the full command sequence, see Data pipeline.

Current Data Support Status

  • Options ETL (options chain + daily features) is currently supported through the ORATS pipeline.
  • External feed sync (fred-sync, yfinance-sync) currently covers rates/market time series, not a full generic options ETL path.
  • The options backtesting runtime expects the current project options-chain schema (for example quotes/Greeks fields used by entry, sizing, and lifecycle).
  • You can run backtests with non-ORATS data if it is pre-normalized to the expected schema.

Stability

  • Current release line is 0.7.x (alpha / pre-1.0).
  • Public APIs, data contracts, and configuration surfaces may evolve between minor versions.
  • For reproducible research, pin exact package versions and review CHANGELOG.md before upgrading.
  • Public vs internal boundaries are defined in API Scope.

Data Contract / Supported Inputs

Source Input expected by backtester Support status Adapter path
ORATS ETL output Processed ORATS panel normalized to canonical long format First-class load_orats_options_chain_for_backtest(...)
OptionsDX ETL output Cleaned long-format panel (reshape='long') Supported OptionsDxOptionsChainAdapter
Custom/vendor dataset Long-format panel mapped to canonical fields Supported with mapping ColumnMapOptionsChainAdapter

Notes:

  • load_orats_options_chain_for_backtest(...) is the preferred notebook/helper path for ORATS backtests.
  • OratsOptionsChainAdapter is for raw ORATS-style dataframes already loaded in memory, not the repo's processed ORATS parquet outputs.
  • Raw wide OptionsDX vendor format (c_* / p_*) is not accepted directly by the backtester.
  • Contract details and adapter behavior: Options Data Adapters
  • ORATS pipeline reference: Data Pipeline
  • OptionsDX onboarding: OptionsDX Setup

Quick VRP Backtest Example

Assume you already prepared:

  • options: long-format pandas options panel indexed by trade_date

For notebook-style data preparation, prefer the backtesting helpers:

from volatility_trading.backtesting import (
    load_fred_rate_series,
    load_orats_options_chain_for_backtest,
    load_yfinance_close_series,
    spot_series_from_options_chain,
)

options = load_orats_options_chain_for_backtest("SPY")
rf = load_fred_rate_series("dgs3mo")
benchmark = load_yfinance_close_series("SP500TR")
hedge_mid = spot_series_from_options_chain(options)

Common backtest-window filters can be applied directly in the ORATS loader:

options = load_orats_options_chain_for_backtest(
    "SPY",
    start="2011-01-01",
    end="2017-12-31",
    dte_min=5,
    dte_max=60,
)
from volatility_trading.backtesting import (
    AccountConfig,
    Backtester,
    BacktestRunConfig,
    BrokerConfig,
    MarginConfig,
    OptionsBacktestDataBundle,
    OptionsMarketData,
    print_performance_report,
)
from volatility_trading.options import RegTMarginModel
from volatility_trading.signals import ShortOnlySignal
from volatility_trading.strategies import VRPHarvestingSpec, make_vrp_strategy

data = OptionsBacktestDataBundle(
    options_market=OptionsMarketData(chain=options),
)
strategy = make_vrp_strategy(
    VRPHarvestingSpec(
        signal=ShortOnlySignal(),
        rebalance_period=10,
        risk_budget_pct=0.03,
        margin_budget_pct=0.4,
    )
)
cfg = BacktestRunConfig(
    account=AccountConfig(initial_capital=50_000),
    broker=BrokerConfig(
        margin=MarginConfig(model=RegTMarginModel(broad_index=False))
    ),
)

bt = Backtester(data=data, strategy=strategy, config=cfg)
trades, mtm = bt.run()
print_performance_report(
    trades=trades,
    mtm_daily=mtm,
    risk_free_rate=0.02,
)

Backtester.run() returns the closed-trade table plus the dense trading-session MTM used by the standard reporting path.

Quick Skew Backtest Example

If you are using ORATS daily features, load them with the public helper:

  • features: daily ORATS features indexed by trade_date, including iv_dlt25_30d and iv_dlt75_30d
from volatility_trading.backtesting import load_daily_features_frame
from volatility_trading.strategies import (
    SkewMispricingSpec,
    make_skew_mispricing_strategy,
)

features = load_daily_features_frame(
    "SPY",
    start="2011-01-01",
    end="2017-12-31",
)

data = OptionsBacktestDataBundle(
    options_market=OptionsMarketData(chain=options),
    features=features,
)

strategy = make_skew_mispricing_strategy(
    SkewMispricingSpec(
        risk_budget_pct=0.03,
        margin_budget_pct=0.4,
        max_holding_period=30,
    )
)

bt = Backtester(data=data, strategy=strategy, config=cfg)
trades, mtm = bt.run()

For a full scriptable workflow (data loading + backtest run), see VRP end-to-end example. For the skew counterpart using daily features, see Skew end-to-end example. For config-driven CLI runs, see Backtest Runner Workflows, which points to the shipped config/backtesting/vrp_harvesting.yml and config/backtesting/skew_mispricing.yml templates. For focused backtesting configuration examples (execution, margin, adapters, hedging), see examples/README.md. For hedging model semantics and WW/fixed-band configuration details, see hedging.md. For option execution model behavior and option-cost attribution fields, see option_execution.md. For the research-style workflow and reporting exploration, see VRP notebook.

Preferred imports for common backtesting usage come from volatility_trading.backtesting. The narrower volatility_trading.backtesting.options_engine namespace remains available for advanced engine-specific helpers. Narrower advanced subpackages such as volatility_trading.backtesting.performance and volatility_trading.backtesting.reporting, plus lower-level modules such as volatility_trading.backtesting.rates, are only needed when you intentionally want the narrower advanced surface.

Advanced Execution And Hedging Setup

Backtester intentionally keeps a stable high-level API and does not expose execution-model arguments directly.

If you want explicit transaction-cost models for both options and hedge trades, configure them on BacktestRunConfig.execution. If the strategy uses dynamic delta hedging, also provide hedge_market:

# Reuse the imports from the quick VRP example above, then add:
from volatility_trading.backtesting import (
    BidAskFeeOptionExecutionModel,
    DeltaHedgePolicy,
    ExecutionConfig,
    FixedDeltaBandModel,
    FixedBpsHedgeExecutionModel,
    HedgeMarketData,
    HedgeTriggerPolicy,
)

hedge_mid = options.groupby(level=0)["spot_price"].first().astype(float)
data = OptionsBacktestDataBundle(
    options_market=OptionsMarketData(chain=options),
    hedge_market=HedgeMarketData(mid=hedge_mid),
)
strategy = make_vrp_strategy(
    VRPHarvestingSpec(
        signal=ShortOnlySignal(),
        rebalance_period=10,
        delta_hedge=DeltaHedgePolicy(
            enabled=True,
            target_net_delta=0.0,
            trigger=HedgeTriggerPolicy(
                band_model=FixedDeltaBandModel(half_width_abs=25.0),
                rebalance_every_n_days=5,
                combine_mode="or",
            ),
            min_rebalance_qty=1.0,
        ),
    )
)
cfg = BacktestRunConfig(
    execution=ExecutionConfig(
        option_execution_model=BidAskFeeOptionExecutionModel(
            commission_per_leg=0.0,
        ),
        hedge_execution_model=FixedBpsHedgeExecutionModel(
            fee_bps=1.0,
        ),
    ),
)
bt = Backtester(data=data, strategy=strategy, config=cfg)
trades, mtm = bt.run()

Tests

Run unit tests (default):

pytest -q

Run integration tests:

pytest -q -m integration

See Testing guide for layout and conventions.

Continuous Integration (CI)

GitHub Actions runs:

  • Ruff lint + format checks
  • Pyright type checks
  • Unit tests by default
  • Integration tests on PRs and pushes to main (and manual runs)

See CI workflow.

Developer Workflow

Common commands are available via Makefile:

make lint
make format
make check
make typecheck
make test
make test-unit
make test-integration
make package-check
make sync-nb
make sync-nb-all
make ci

For full setup and tooling details, see the Documentation index. Notebook HTML reports are built in GitHub Actions and published to GitHub Pages.

Docs

See Documentation index for the full docs map. Most-used pages:

Research Highlights

We publish research notebooks and strategy diagnostics as HTML reports on GitHub Pages.

  • RV forecasting: HAR-RV-VIX reaches about 30% OOS R² vs a naive baseline.
  • IV surface modelling: parametric vs non-parametric surface comparison workflows.
  • Skew trading: delta-hedged RR strategy with realistic costs and risk controls.

Explore details here:

Metadata

Release files for volatility-trading 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for volatility-trading 0.7.0
File Size Uploaded
volatility_trading-0.7.0.tar.gz 241.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for volatility-trading 0.7.0
File Interpreter ABI Platform
volatility_trading-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 603.9 kB

Release files / volatility_trading-0.7.0.tar.gz

Download URL volatility_trading-0.7.0.tar.gz
Size 241.7 kB
Tags Source
SHA-256 checksum
How to use checksums
66dc53ec351178fb9c3b22aff94109a78441702be23a567e02d599cd5a7df3e7
BLAKE2b-256 checksum
How to use checksums
32afee38a81d0d0bfc288023110eccb81ce9434362926a361dc03f8422337cae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 16, 2026.

Transparency log

Release files / volatility_trading-0.7.0-py3-none-any.whl

Download URL volatility_trading-0.7.0-py3-none-any.whl
Size 362.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b39d684719a93eaa592f5626e729d8a7ed598d01bda09fa620ac36b1f8fd2761
BLAKE2b-256 checksum
How to use checksums
3ba50bffcec343ebf7cd927dfef1698ddb5fe2ee18569ce3b3cd89c41c466edc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.7.0 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page