diff --git a/README.md b/README.md index 39c3114..61ba264 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,9 @@ To research alternative SOXL delever gates without changing production, use `--soxl-delever-symbol`, `--soxl-delever-window`, `--soxl-delever-threshold`, `--soxl-delever-retention-ratio`, and `--soxl-delever-redirect-symbol`. The current best research candidate is a -SOXX 10-day volatility gate at `0.50` that redirects SOXL into SOXX. +SOXX 20-day volatility gate at `0.50` that redirects SOXL into SOXX. See +`docs/tqqq-soxl-optimization-research.md` for the TQQQ/SOXL no-regression +optimization sweep. For long-history core SOXL/SOXX validation, provide a BOXX-compatible cash proxy such as BIL under the `BOXX` symbol and add `--disable-income-layer`. @@ -749,9 +751,9 @@ underlying strategy artifact build remains independent; downstream systems read the plugin's `latest_signal.json`, verify its `strategy` / `plugin` identity, and treat the plugin as notification-only shadow context. Plugin mounts are strategy-limited by code and tests. Keep the crisis plugin -scoped to defensive TQQQ-style risk-off behavior. Keep MAGS/TACO experiments in -research commands until a future PR adds a validated TQQQ TACO overlay plugin or -explicitly promotes another strategy mount. +scoped to defensive TQQQ/SOXL-style risk-off behavior. Keep MAGS/TACO +experiments in research commands until a future PR adds a validated TQQQ TACO +overlay plugin or explicitly promotes another strategy mount. The only supported plugin mode is `shadow`. The runner rejects `paper`, `advisory`, and `live` plugin modes; the current product decision is that the diff --git a/docs/crisis-response-live-promotion-spec.md b/docs/crisis-response-live-promotion-spec.md index 32f03f4..da5bfec 100644 --- a/docs/crisis-response-live-promotion-spec.md +++ b/docs/crisis-response-live-promotion-spec.md @@ -128,10 +128,10 @@ registers it. A plugin is mounted to a strategy through each `[[strategy_plugins]]` entry; the strategy core must remain independent of the runner and must not import plugin code. Plugins are strategy-limited in the runner: a mount is rejected unless the plugin is explicitly declared compatible -with that strategy. This keeps `crisis_response_shadow` scoped to the TQQQ -black-swan strategy and blocks `taco_rebound_shadow` from runtime mounts while -MAGS remains research-only. A future TQQQ TACO overlay plugin must update the -compatibility table and tests in its own PR. +with that strategy. This keeps `crisis_response_shadow` scoped to TQQQ/SOXL +black-swan defense strategies and blocks `taco_rebound_shadow` from runtime +mounts while MAGS remains research-only. A future TQQQ TACO overlay plugin must +update the compatibility table and tests in its own PR. The runner accepts only `mode = "shadow"` and writes that mode into each plugin artifact. Downstream platform adapters must read the artifact as notification diff --git a/docs/crisis-response-v1.md b/docs/crisis-response-v1.md index 34adfa5..14a1ebb 100644 --- a/docs/crisis-response-v1.md +++ b/docs/crisis-response-v1.md @@ -7,9 +7,9 @@ auditable. The production-facing plugin contract is now split: -- `crisis_response_shadow` is defense-only for TQQQ-style black-swan risk. It - may emit `true_crisis` / `defend` or `no_action` / `watch_only`; it must not - emit TACO routes or sleeves. +- `crisis_response_shadow` is defense-only for TQQQ/SOXL-style leveraged + equity black-swan risk. It may emit `true_crisis` / `defend` or `no_action` / + `watch_only`; it must not emit TACO routes or sleeves. - TACO rebound work is separate from crisis defense and remains research-only for MAGS-style pullback strategies. A TQQQ overlay candidate should be promoted through a separate validation path before any runner mount. @@ -56,13 +56,14 @@ The production-facing crisis shadow plugin narrows this to: - TACO sleeve: start with `0.05` account sleeve in historical research. It is not part of `crisis_response_shadow`, and the MAGS path is not a promoted runner plugin. -- Crisis drawdown: `QQQ` drawdown from trailing high <= `-0.20`. +- Crisis drawdown: configured benchmark drawdown from trailing high <= `-0.20` + (`QQQ` for TQQQ, `SOXX` for SOXL). - Crisis confirmation: `5` trading days. - Crisis risk multiplier: `0.25`; do not full-clear risk exposure in V1. - Defense-only shadow crisis plugin default: `0.0` risk multiplier when a true-crisis route is emitted for manual review. -- Bubble proxy: `QQQ` 252-trading-day return >= `0.75`, remembered for 126 - trading days. +- Bubble proxy: configured benchmark 252-trading-day return >= `0.75`, + remembered for 126 trading days. - Financial proxy: `XLF` drawdown and relative weakness vs `SPY`. - Safe asset in long history tests: `SHY`; live safe asset can be mapped by the execution platform, for example BOXX/SGOV/CASH, but that is not part of this diff --git a/docs/examples/strategy_plugins.example.toml b/docs/examples/strategy_plugins.example.toml index 4ff3598..0a70a03 100644 --- a/docs/examples/strategy_plugins.example.toml +++ b/docs/examples/strategy_plugins.example.toml @@ -6,7 +6,7 @@ strategy = "tqqq_growth_income" plugin = "crisis_response_shadow" enabled = true # The runner enforces plugin/strategy compatibility. This plugin is scoped to -# the TQQQ black-swan defense strategy. +# TQQQ/SOXL leveraged equity black-swan defense strategies. # mode is optional when it matches default_mode; only shadow notification mode is supported. [strategy_plugins.inputs] diff --git a/docs/tqqq-soxl-optimization-research.md b/docs/tqqq-soxl-optimization-research.md new file mode 100644 index 0000000..5f7616e --- /dev/null +++ b/docs/tqqq-soxl-optimization-research.md @@ -0,0 +1,101 @@ +# TQQQ / SOXL Optimization Research + +Research date: 2026-05-10 + +This note records a bounded optimization sweep for the TQQQ and SOXL leveraged +equity profiles. The acceptance rule is intentionally strict to avoid fitting a +single crisis window: + +1. CAGR must not decrease versus baseline in every comparison window. +2. Max drawdown must not worsen versus baseline in every comparison window. +3. A passing candidate remains research evidence only unless a later PR promotes + it into strategy configuration. + +The `crisis_response_shadow` plugin remains notification-only. It can be mounted +to TQQQ/SOXL defense contexts, but it does not place trades or mutate live +allocations. + +## TQQQ + +Data and model: + +- Source prices: `data/output/crisis_response_1999_synthetic_v2_context/input/crisis_response_price_history.csv` +- Benchmark: `QQQ` +- Attack leg: synthetic daily-reset `SYNTH_TQQQ` from `QQQ` at 3x, 1% annual + expense assumption +- Safe asset: `SHY` +- Turnover cost: 5 bps + +Tested candidates: + +- Pullback-rebound windows: 10, 20, 30 trading days. +- Pullback-rebound volatility multipliers: 1.0, 1.5, 2.0, 2.5, 3.0. +- Fixed pullback-rebound thresholds: 0%, 2%, 4%, 6%. +- Small allocation variants around the baseline 45% QQQ / 45% synthetic TQQQ / + 8% SHY / 2% cash active mix. +- Moving the 2% cash sleeve into SHY for active and/or idle states. + +Result: + +- No TQQQ candidate passed the strict no-regression rule across all tested + windows. +- Higher pullback thresholds improved full-sample CAGR and drawdown in some + cases, but regressed at least one stress or recent period. +- Moving cash into SHY was close, but still caused small regressions in rate + stress windows. + +Conclusion: keep the current TQQQ strategy parameters. Do not promote a TQQQ +optimization from this sweep. + +## SOXL + +Data and model: + +- Source prices: `/tmp/soxl_synthetic_long/synthetic_soxl_from_soxx_price_history.csv` +- Benchmark: `SOXX` +- Attack leg: synthetic daily-reset `SOXL` from `SOXX` +- Strategy: `soxl_soxx_trend_income` research backtest with income layer + disabled for long-history core replay +- Turnover cost: 5 bps + +Tested candidates: + +- SOXX realized-volatility gates with 10, 15, 20, and 30 trading-day windows. +- Annualized realized-vol thresholds: 45%, 50%, 55%, 60%, 65%. +- SOXL retention ratios: 0% and 25%. +- Redirect destinations: mostly `SOXX`, with selected `BOXX` defensive checks. + +Baseline: + +| Period | CAGR | Max Drawdown | +| --- | ---: | ---: | +| Full 2001-2026 | 19.34% | -60.24% | +| Dotcom tail 2001-2003 | 25.30% | -51.24% | +| GFC 2007-2009 | 2.85% | -55.78% | +| Real SOXL era 2010-2026 | 27.13% | -36.84% | +| 2022 rate bear | -27.32% | -34.53% | +| 2024-2026 live-full proxy | 64.63% | -36.84% | + +Passing candidates: + +| Candidate | Stops | Full CAGR | Full MDD | Min CAGR Delta | Min MDD Delta | +| --- | ---: | ---: | ---: | ---: | ---: | +| SOXX 20d vol >= 50%, retain 0% SOXL, redirect to SOXX | 19 | 20.02% | -59.52% | +0.05 pp | +0.08 pp | +| SOXX 15d vol >= 50%, retain 0% SOXL, redirect to SOXX | 18 | 20.01% | -59.52% | +0.05 pp | +0.08 pp | +| SOXX 20d vol >= 50%, retain 25% SOXL, redirect rest to SOXX | 19 | 19.86% | -59.66% | +0.01 pp | +0.06 pp | +| SOXX 15d vol >= 50%, retain 25% SOXL, redirect rest to SOXX | 18 | 19.85% | -59.66% | +0.01 pp | +0.06 pp | + +Selected `BOXX` redirect variants improved full-sample CAGR and drawdown, but +failed the strict no-CAGR-regression rule in at least one window. + +Conclusion: the strongest SOXL research candidate is the 20-day SOXX +realized-volatility gate at 50%, redirecting triggered SOXL target exposure into +SOXX. It passed this bounded sweep, but should remain research-only until a +separate PR promotes it with implementation tests and operator review. + +## Local Research Outputs + +- `/tmp/tqqq_soxl_optimization_research/tqqq_variant_summary.csv` +- `/tmp/tqqq_soxl_optimization_research/tqqq_variant_periods.csv` +- `/tmp/tqqq_soxl_optimization_research/soxl_variant_summary.csv` +- `/tmp/tqqq_soxl_optimization_research/soxl_variant_periods.csv` diff --git a/src/us_equity_snapshot_pipelines/strategy_plugin_runner.py b/src/us_equity_snapshot_pipelines/strategy_plugin_runner.py index eb33df1..d2d08bf 100644 --- a/src/us_equity_snapshot_pipelines/strategy_plugin_runner.py +++ b/src/us_equity_snapshot_pipelines/strategy_plugin_runner.py @@ -27,7 +27,7 @@ PLUGIN_TACO_REBOUND_SHADOW = TACO_REBOUND_PROFILE SUPPORTED_PLUGIN_MODES = (SHADOW_MODE,) PLUGIN_COMPATIBLE_STRATEGIES: dict[str, tuple[str, ...]] = { - PLUGIN_CRISIS_RESPONSE_SHADOW: ("tqqq_growth_income",), + PLUGIN_CRISIS_RESPONSE_SHADOW: ("tqqq_growth_income", "soxl_soxx_trend_income"), } PLUGIN_RESEARCH_ONLY_REASONS: dict[str, str] = { PLUGIN_TACO_REBOUND_SHADOW: ( diff --git a/tests/test_strategy_plugin_runner.py b/tests/test_strategy_plugin_runner.py index 18d8d12..2d21a0c 100644 --- a/tests/test_strategy_plugin_runner.py +++ b/tests/test_strategy_plugin_runner.py @@ -16,6 +16,7 @@ ) STRATEGY_NAME = "tqqq_growth_income" +SOXL_STRATEGY_NAME = "soxl_soxx_trend_income" LEFT_SIDE_STRATEGY_NAME = "mega_cap_leader_rotation_top50_balanced" @@ -35,6 +36,23 @@ def _quiet_prices() -> pd.DataFrame: return pd.DataFrame(rows) +def _soxl_quiet_prices() -> pd.DataFrame: + dates = pd.bdate_range("2025-01-02", periods=230) + rows: list[dict[str, object]] = [] + for symbol in ("SOXX", "SOXL", "SPY"): + for offset, as_of in enumerate(dates): + multiplier = 3.0 if symbol == "SOXL" else 1.0 + rows.append( + { + "symbol": symbol, + "as_of": as_of, + "close": 100.0 + offset * 0.01 * multiplier, + "volume": 1_000_000, + } + ) + return pd.DataFrame(rows) + + def _financial_crisis_prices() -> pd.DataFrame: dates = pd.bdate_range("2007-01-02", periods=310) rows: list[dict[str, object]] = [] @@ -226,6 +244,45 @@ def test_strategy_plugin_runner_uses_default_mode_when_entry_mode_is_omitted(tmp assert payload["execution_controls"]["notification_profile"] == "shadow_only" +def test_strategy_plugin_runner_mounts_crisis_shadow_to_soxl_strategy(tmp_path) -> None: + prices_path = tmp_path / "soxl_prices.csv" + output_dir = tmp_path / SOXL_STRATEGY_NAME / "plugins" / PLUGIN_CRISIS_RESPONSE_SHADOW + _soxl_quiet_prices().to_csv(prices_path, index=False) + config = { + "output_dir": str(tmp_path / "runner"), + "default_mode": "shadow", + "strategy_plugins": [ + { + "strategy": SOXL_STRATEGY_NAME, + "plugin": PLUGIN_CRISIS_RESPONSE_SHADOW, + "enabled": True, + "inputs": { + "prices": str(prices_path), + "as_of": "2025-11-19", + "start_date": "2025-01-02", + "benchmark_symbol": "SOXX", + "attack_symbol": "SOXL", + "financial_symbols": [], + "credit_pairs": [], + "rate_symbols": [], + }, + "outputs": {"output_dir": str(output_dir)}, + } + ], + } + + summary = run_configured_plugins(config) + + result = summary["strategy_plugins"][0] + assert result["strategy"] == SOXL_STRATEGY_NAME + assert result["plugin"] == PLUGIN_CRISIS_RESPONSE_SHADOW + assert result["effective_mode"] == "shadow" + payload = json.loads((output_dir / "latest_signal.json").read_text(encoding="utf-8")) + assert payload["strategy"] == SOXL_STRATEGY_NAME + assert payload["evidence"]["metrics"]["benchmark_symbol"] == "SOXX" + assert payload["execution_controls"]["broker_order_allowed"] is False + + def test_strategy_plugin_runner_filters_by_strategy(tmp_path) -> None: config = _shadow_plugin_config(tmp_path) config["strategy_plugins"].append(