VolLab is a Python research project for daily long-gamma studies on China equity index options, focused on MO options on 000852.SH.
Version 0.3 extends the original ATM straddle backtest into an event-driven research lab with:
- Wind-backed signal-date option quality fields
- horizon labels
- event classification
- DTE research
- exit-policy experiments
- ATM straddle vs OTM strangle comparison
- Streamlit dashboard pages for the v0.3 research outputs
VolLab does not place orders, consume real-time market data, or model full volatility surfaces.
Detailed research reports:
- v0.2 timing research:
reports/research_findings.md - v0.3 Wind-backed event research:
reports/v03_research_findings.md
VolLab 到 v0.3 可以作为一个完整的阶段性研究项目收尾。它完成了两件事:
- v0.2 回答了“是否存在稳定、单调、可直接交易的 ATM straddle 买入前置信号”。
- v0.3 补齐了 Wind 真实期权数据质量,并把研究拆成 data quality、event type、DTE、exit policy、straddle vs strangle 几个可审计模块。
最终结论很明确:当前证据不支持把 MO ATM straddle 或 OTM strangle 做成一个稳定的机械化 long-gamma 买入策略。long gamma 的赢家是真实存在的,但收益分布主要由少数冲击事件贡献;多数普通交易日仍然承受 theta decay 和负中位收益。
v0.2 的核心发现:
- 过去 top 10% straddle return 事件没有稳定共同前置信号。
IV - RV20并没有在大行情前稳定偏低,因此“IV 相对 RV 越便宜越好”不是可靠单调规则。range_percentile_252不稳定偏低,RV20_change_5d也没有稳定提前抬升。- 旧数据缺少 signal-date call/put close,所以当时无法严肃判断真实
straddle_premium_to_spot。 - DTE 是最值得继续研究的变量之一,但也只是弱倾向,不是独立交易规则。
- 2024-09/10 与 2025-04 的大行情不是同一种事件,不能混成一个统一的稳定信号。
- 默认 timing rules 在 daily rolling、non-overlapping、one-position-at-a-time 三种执行模式下都不稳健。
- 没有核心 feature 通过严格单调性检查。
v0.3 的核心发现:
- Wind-backed v0.3 回测已补齐 signal-date option quality:
signal_call_close、signal_put_close、signal volume、signal open interest 完整率均为 100%。 - ATM straddle baseline:1,880 horizon trades,胜率 31.8%,平均收益 0.4%,中位收益 -5.6%,总净 PnL -805,129.8。
up_shock和down_shock能解释赢家来自哪里,但它们是事后标签,不是入场信号。- post-warmup 的
7-10DTE / 5d 是唯一明显为正的 DTE 切片,但只有 11 笔,不能视作稳定规律。 stop_loss_30pct改善了尾部亏损和总 PnL,但中位收益仍为负,且 total PnL 依赖少数大赢家。- 3%/5% OTM strangle 已经是真实 provider-chain 结果。它们更便宜,平均收益弹性更高,但所有 OTM strangle 的中位收益仍为负,说明尾部依赖更强,而不是策略质量更稳。
项目收尾判断:
- 可以接受的结论:MO long gamma 在冲击行情里有显著 convex payoff;短 DTE、真实 signal premium、volume/open interest、近期 realized-vol regime 值得作为下一阶段研究变量。
- 不可以接受的结论:目前不能声称已经找到稳定买入规则,也不能只根据平均收益、top-event 表现或少数 spike 来上线交易。
- 本仓库现在更适合作为研究归档和 dashboard 展示,不应被理解为交易信号系统。
如果未来继续研究,重点不应是直接优化 PnL,而应是验证:能否在事前用 signal premium、DTE、option activity、realized-vol regime 和事件日历识别 shock-prone windows。任何新规则都必须同时报告 trade count、win rate、average return、median return、max loss、max win 和 top-winners concentration。
VolLab v0.3 is a complete research checkpoint. It answered two separate questions:
- v0.2 tested whether MO ATM straddle winners shared stable, monotonic, directly tradable pre-signal features.
- v0.3 repaired the real-data foundation with Wind-backed signal-date option snapshots, then decomposed the problem into data quality, event type, DTE, exit policy, and straddle-versus-strangle panels.
The final conclusion is conservative and clear: the current evidence does not support a stable mechanical long-gamma buying strategy in MO ATM straddles or OTM strangles. Long-gamma winners are real, but the payoff distribution is dominated by a small number of shock events. Most ordinary trading days still suffer from theta decay and negative median returns.
Core v0.2 findings:
- Top-decile straddle-return events did not share one stable pre-signal.
IV - RV20was not reliably lower before the biggest winners, so “cheaper IV versus RV is better” did not hold as a monotonic rule.range_percentile_252was not reliably low, andRV20_change_5dwas not reliably rising before the events.- The old backtest did not store signal-date call/put closes, so true
straddle_premium_to_spotcould not be tested properly at that stage. - DTE was one of the more useful weak tendencies, but it was not an independent rule.
- The 2024-09/10 and 2025-04 spikes were different event types, not one stable repeatable pattern.
- Default timing rules were not robust under daily rolling, non-overlapping, or one-position-at-a-time execution modes.
- No core feature passed a strict monotonicity check.
Core v0.3 findings:
- Wind-backed v0.3 trade rows now have 100% completeness for signal-date call/put closes, signal volume, and signal open interest.
- ATM straddle baseline: 1,880 horizon trades, 31.8% win rate, 0.4% average return, -5.6% median return, and -805,129.8 total net PnL.
up_shockanddown_shockexplain where winners came from, but they are ex-post labels, not entry signals.- Post-warmup
7-10DTE / 5d is the only clearly positive DTE slice, but it has only 11 trades. stop_loss_30pctimproves tail loss and total PnL, but median return remains negative and total PnL depends on a small number of large winners.- 3% and 5% OTM strangles are now real provider-chain results. They are cheaper and more convex in average-return terms, but all OTM median returns remain negative, which points to stronger tail dependence rather than a more stable strategy.
Final project assessment:
- Acceptable conclusion: MO long gamma has meaningful convex payoff in shock regimes; short DTE, true signal premium, volume/open interest, and recent realized-volatility regime are useful variables for future research.
- Unacceptable conclusion: this project has not found a robust production trading rule, and results should not be judged only by average return, top-event behavior, or a handful of spikes.
- The repository should now be treated as a research archive and dashboard, not as a live trading signal engine.
If the project continues, the next question should not be “how do we optimize PnL?” It should be: can pre-signal premium, DTE, option activity, realized-volatility regime, and event calendars identify shock-prone windows before the event? Any new rule must report trade count, win rate, average return, median return, max loss, max win, and top-winners concentration.
Run the dashboard locally:
streamlit run app.pyDashboard sections:
- Overview
- Feature Lab
- Event Study
- Rule Lab
- Score Components
- Data Quality
- Event Type Lab
- DTE Lab
- Exit Policy Lab
- Straddle vs Strangle
When data_mode=wind and the committed data/processed/*.csv files exist, the dashboard reads the precomputed CSV outputs instead of initializing WindPy. This is intentional so Streamlit Cloud can deploy without Wind Terminal access.
The repository includes the current Wind-backed processed CSV outputs:
data/processed/trade_details.csvdata/processed/feature_table.csvdata/processed/label_table.csvdata/processed/label_table_by_horizon.csvdata/processed/event_classification.csvreports/*.csv
On Streamlit Cloud, the app should display these committed numbers directly. It should not need WindPy or a Wind Terminal session unless the processed CSV files are removed.
If you rerun Wind locally and want the deployed dashboard to update, commit and push the regenerated data/processed/*.csv and reports/*.csv files.
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txtRun tests:
pytestFor Wind mode, keep the Wind terminal logged in, then run Python from a normal local shell. In restricted Codex sandboxes, w.start() may return login failure even when Wind works in a normal shell.
Full v0.3 rebuild:
python scripts/run_backtest.py
python scripts/audit_data_quality.py
python scripts/build_feature_table.py
python scripts/build_label_table_by_horizon.py
python scripts/run_event_classification.py
python scripts/run_dte_research.py
python scripts/run_exit_policy_experiments.py
python scripts/run_straddle_vs_strangle.py
pytestMain outputs:
data/processed/trade_details.csvdata/processed/trade_details_v03_quality_fields.csvdata/processed/feature_table.csvdata/processed/label_table.csvdata/processed/label_table_by_horizon.csvdata/processed/event_classification.csvreports/data_quality_report.mdreports/event_type_summary.csvreports/dte_research_summary.csvreports/exit_policy_summary.csvreports/straddle_vs_strangle_summary.csvreports/strangle_trade_details.csvreports/v03_research_findings.md
Generate deterministic mock data:
python scripts/generate_mock_data.pyRun the ATM straddle backtest and write reports to data/processed/:
python scripts/run_backtest.pyMain parameters live in config.yaml. The default instrument is:
underlying_code: "000852.SH"option_prefix: "MO"data_mode: "wind"
Set data_mode to mock, csv, or wind.
src/wind_data_provider.py uses:
w.tdaysfor the trading calendarw.wsdfor underlying daily bars and selected option daily barsw.wset("optionchain", ...)for option metadata when configuredw.wss(..., tradeDate=...)to merge same-day option OHLC, volume, and open interest into the option chain
Current config uses synthetic MO contract-code generation with Wind quote snapshots, which avoids depending entirely on w.wset("optionchain", ...) availability.
Before changing instruments or vendors, confirm the exact Wind field names with the Wind code generator and place them in config.yaml.
Expected unified option fields:
date, option_code, call_put, strike, expire_date, open, high, low, close, volume, open_interest
Expected unified underlying fields:
date, open, high, low, close, volume