Skip to content

Commit 78745c4

Browse files
committed
Route SOXL market regime signals to notification only
1 parent e68b868 commit 78745c4

6 files changed

Lines changed: 54 additions & 31 deletions

File tree

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ send notifications; plugin research and signal generation live here.
3838
index are kept as watch-only evidence and do not contribute to the actionable
3939
trading score.
4040
- `market_regime_control`: unified deterministic facade for crisis, macro, and
41-
TACO signals. Levered strategies can consume position controls directly;
42-
stock/ETF rotation strategies should consume the same artifact through their
43-
local risk-scaling policy and keep TACO as notification-only.
41+
TACO signals. Only strategies with positive backtest evidence should mount
42+
position controls for automated consumption; SOXL/SOXX currently receives
43+
broad macro/crisis signals as general notifications only. Stock/ETF rotation
44+
strategies should consume the same artifact through their local risk-scaling
45+
policy and keep TACO as notification-only.
4446
- `taco_rebound_shadow`: TQQQ-only event-rebound context notifier. It writes
4547
manual-review artifacts and never recommends position size or changes
4648
allocations. Softening/de-escalation events stay watch-only until post-event

README.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Brokers、Schwab、LongBridge、Firstrade 等平台仓库只负责加载 artifac
2626
- `crisis_response_shadow`:面向杠杆美股策略的黑天鹅防守观察插件。它只写入 shadow-mode artifact,不调用券商接口。
2727
可选启用 AI shadow audit:AI 只审计证据一致性和数据缺口,不改写确定性路线、不下单、不改仓位;默认优先尝试本机 Codex,失败后可走 OpenAI-compatible 或 Anthropic fallback endpoint。
2828
- `macro_risk_governor`:面向 TQQQ 的确定性宏观降杠杆插件。它按价格趋势、实现波动、VIX、信用相对压力和可选外部金融压力字段打分,输出 `leverage_scalar` / `risk_asset_scalar` 给显式 opt-in 的策略运行时消费。五角大楼比萨指数这类 OSINT 字段只作为 watch-only 证据,不进入可执行分数。
29-
- `market_regime_control`:统一确定性 facade,汇总 crisis、macro 和 TACO 信号,输出版本化的 `notification``position_control`杠杆策略可直接消费仓位控制,股票/ETF 轮动策略通过本地风险缩放策略消费;TACO 在统一插件里保持通知-only,并会被危机和宏观降风险路线 veto。设计说明见 [Market Regime Control 统一插件方案](docs/market-regime-control-plan.zh-CN.md)
29+
- `market_regime_control`:统一确定性 facade,汇总 crisis、macro 和 TACO 信号,输出版本化的 `notification``position_control`只有经过回测证明自动消费有效的策略才挂载仓位控制;SOXL/SOXX 这类未通过统一宏观插件复核的高波动行业杠杆策略只接收通用通知,人工决定是否干预。股票/ETF 轮动策略通过本地风险缩放策略消费;TACO 在统一插件里保持通知-only,并会被危机和宏观降风险路线 veto。设计说明见 [Market Regime Control 统一插件方案](docs/market-regime-control-plan.zh-CN.md)
3030
- `taco_rebound_shadow`:仅适用于 TQQQ 的事件反弹上下文通知插件。它只写入人工复核 artifact,不给仓位大小建议,也不改动配置或账户分配。缓和/降温事件会先保持 watch-only,只有事件后价格反弹确认通过后才触发人工复核通知,以减少过早抄底提醒。
3131
该插件也可选启用同样的 shadow-only AI audit,但 AI 只复核事件来源和反弹证据质量。
3232
- TACO panic-rebound 研究、组合回测和 overlay 对比也归属本仓库;snapshot pipeline 仓库只保留兼容入口。

docs/examples/strategy_plugins.example.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ taco_enabled = true
3333
output_dir = "data/output/tqqq_growth_income/plugins/market_regime_control"
3434

3535
[[strategy_plugins]]
36-
strategy = "soxl_soxx_trend_income"
36+
strategy = "market_regime_notification"
3737
plugin = "market_regime_control"
3838
enabled = true
39-
# SOXL uses the same macro/crisis contract. TACO stays disabled because the
40-
# current rebound-event playbook is calibrated for TQQQ/QQQ.
39+
# General market-regime notification. This artifact is not mounted into the
40+
# SOXL/SOXX strategy runtime; sector-levered SOXL keeps its own validated SOXX
41+
# volatility gate and humans decide whether broad macro/crisis notices matter.
4142

4243
[strategy_plugins.inputs]
4344
prices = "data/output/market_regime_control/input/soxl_price_history.csv"
@@ -58,7 +59,7 @@ crisis_enabled = true
5859
macro_enabled = true
5960

6061
[strategy_plugins.outputs]
61-
output_dir = "data/output/soxl_soxx_trend_income/plugins/market_regime_control"
62+
output_dir = "data/output/market_regime_notification/plugins/market_regime_control"
6263

6364
# Deprecated compatibility mounts. They remain runnable for historical
6465
# backtests and downstream consumers that have not migrated to

docs/market-regime-control-plan.zh-CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
- TQQQ 杠杆增长收益策略
6464
默认消费 `position_control``risk_off` 降到现金类或非风险资产;`risk_reduced` 按策略配置降低杠杆或风险预算;TACO 只触发人工复核和本地 veto。
6565
- SOXL/SOXX 趋势收益策略
66-
默认挂载统一插件。`risk_off` 生效;`risk_reduced` 可按配置选择通知-only 或部分降风险,避免过度干扰高波动半导体策略
66+
不默认挂载统一插件,也不消费 `position_control`。SOXL 继续只使用已经通过复核的 SOXX 自身趋势和波动率降杠杆门;宏观、危机和 OSINT 信号只进入通用通知,由人工决定是否干预
6767
- Global ETF、Russell 1000、Tech/Communication、Mega Cap 类轮动策略
6868
默认支持统一插件。`risk_reduced` 建议做 50% 风险预算缩放,`risk_off` 建议归零风险资产预算。
6969
- DCA 或收入型低频策略
@@ -112,7 +112,7 @@ TQQQ 2010-2026 真实产品窗口:
112112
## 当前推荐默认值
113113

114114
- 杠杆策略:默认挂载统一插件,允许 `risk_off` 生效。
115-
- 高波动行业杠杆策略:默认挂载统一插件,但 `risk_reduced` 是否影响仓位由策略配置控制
115+
- 高波动行业杠杆策略:除非回测证明自动消费能提升收益/回撤组合,否则不默认挂载统一插件;SOXL 当前只接收通用通知
116116
- 轮动策略:默认开启 50% risk scaling 和 `risk_off` 归零。
117117
- TACO:默认通知-only;只有没有危机和宏观降风险时才允许提示机会。
118118
- AI audit:默认不参与交易权限,只能写审计结论和通知证据。

src/quant_strategy_plugins/strategy_plugin_runner.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,17 @@
3333
)
3434

3535
DEFAULT_RUNNER_OUTPUT_DIR = "data/output/strategy_plugins"
36+
GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY = "market_regime_notification"
3637
PLUGIN_CRISIS_RESPONSE_SHADOW = "crisis_response_shadow"
3738
PLUGIN_MARKET_REGIME_CONTROL = MARKET_REGIME_CONTROL_PROFILE
3839
PLUGIN_MACRO_RISK_GOVERNOR = MACRO_RISK_GOVERNOR_PROFILE
3940
PLUGIN_TACO_REBOUND_SHADOW = TACO_REBOUND_PROFILE
4041
SUPPORTED_PLUGIN_MODES = (SHADOW_MODE,)
4142
PLUGIN_COMPATIBLE_STRATEGIES: dict[str, tuple[str, ...]] = {
42-
PLUGIN_CRISIS_RESPONSE_SHADOW: ("tqqq_growth_income", "soxl_soxx_trend_income"),
43+
PLUGIN_CRISIS_RESPONSE_SHADOW: ("tqqq_growth_income",),
4344
PLUGIN_MARKET_REGIME_CONTROL: (
45+
GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY,
4446
"tqqq_growth_income",
45-
"soxl_soxx_trend_income",
4647
"global_etf_rotation",
4748
"russell_1000_multi_factor_defensive",
4849
"tech_communication_pullback_enhancement",
@@ -402,6 +403,11 @@ def _apply_plugin_contract(payload: Mapping[str, Any], *, strategy: str, plugin:
402403
execution_controls["effective_mode"] = mode
403404
execution_controls["repository_broker_write_allowed"] = False
404405
execution_controls["repository_allocation_mutation_allowed"] = False
406+
if strategy == GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY:
407+
execution_controls["capital_impact"] = "notification_only"
408+
execution_controls["strategy_runtime_metadata_allowed"] = False
409+
execution_controls["position_control_shadow_only"] = True
410+
execution_controls["intended_strategy_role"] = "general_market_regime_notification"
405411
execution_controls["mode_note"] = (
406412
"Mode is the platform behavior contract; this repository writes artifacts and does not call brokers"
407413
)
@@ -628,6 +634,7 @@ def main(argv: list[str] | None = None) -> int:
628634

629635

630636
__all__ = [
637+
"GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY",
631638
"PLUGIN_CRISIS_RESPONSE_SHADOW",
632639
"PLUGIN_MARKET_REGIME_CONTROL",
633640
"PLUGIN_MACRO_RISK_GOVERNOR",

tests/test_strategy_plugin_runner.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from quant_strategy_plugins.crisis_response_research import ROUTE_TRUE_CRISIS
1010
from quant_strategy_plugins.strategy_plugin_runner import (
11+
GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY,
1112
PLUGIN_COMPATIBLE_STRATEGIES,
1213
PLUGIN_CRISIS_RESPONSE_SHADOW,
1314
PLUGIN_DEPRECATED_SUCCESSORS,
@@ -266,16 +267,16 @@ def test_strategy_plugin_runner_runs_unified_market_regime_control_for_tqqq(tmp_
266267
assert payload["execution_controls"]["live_allocation_mutation_allowed"] is False
267268

268269

269-
def test_strategy_plugin_runner_runs_unified_market_regime_control_for_soxl(tmp_path) -> None:
270+
def test_strategy_plugin_runner_runs_general_market_regime_notification(tmp_path) -> None:
270271
prices_path = tmp_path / "market_regime_prices.csv"
271-
output_dir = tmp_path / SOXL_STRATEGY_NAME / "plugins" / PLUGIN_MARKET_REGIME_CONTROL
272+
output_dir = tmp_path / GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY / "plugins" / PLUGIN_MARKET_REGIME_CONTROL
272273
_soxl_quiet_prices().to_csv(prices_path, index=False)
273274
config = {
274275
"output_dir": str(tmp_path / "runner"),
275276
"default_mode": "shadow",
276277
"strategy_plugins": [
277278
{
278-
"strategy": SOXL_STRATEGY_NAME,
279+
"strategy": GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY,
279280
"plugin": PLUGIN_MARKET_REGIME_CONTROL,
280281
"enabled": True,
281282
"inputs": {
@@ -295,20 +296,42 @@ def test_strategy_plugin_runner_runs_unified_market_regime_control_for_soxl(tmp_
295296
summary = run_configured_plugins(config)
296297

297298
result = summary["strategy_plugins"][0]
298-
assert result["strategy"] == SOXL_STRATEGY_NAME
299+
assert result["strategy"] == GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY
299300
assert result["plugin"] == PLUGIN_MARKET_REGIME_CONTROL
300301
assert result["status"] == "ok"
301302
payload = json.loads((output_dir / "latest_signal.json").read_text(encoding="utf-8"))
302-
assert payload["strategy"] == SOXL_STRATEGY_NAME
303+
assert payload["strategy"] == GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY
303304
assert payload["plugin"] == PLUGIN_MARKET_REGIME_CONTROL
304305
assert payload["schema_version"] in PLUGIN_SCHEMA_VERSIONS[PLUGIN_MARKET_REGIME_CONTROL]
305306
assert payload["canonical_route"] == "no_action"
307+
assert payload["execution_controls"]["capital_impact"] == "notification_only"
308+
assert payload["execution_controls"]["strategy_runtime_metadata_allowed"] is False
309+
310+
311+
def test_strategy_plugin_runner_rejects_soxl_market_regime_control_mount(tmp_path) -> None:
312+
prices_path = tmp_path / "market_regime_prices.csv"
313+
_soxl_quiet_prices().to_csv(prices_path, index=False)
314+
config = {
315+
"output_dir": str(tmp_path / "runner"),
316+
"default_mode": "shadow",
317+
"strategy_plugins": [
318+
{
319+
"strategy": SOXL_STRATEGY_NAME,
320+
"plugin": PLUGIN_MARKET_REGIME_CONTROL,
321+
"enabled": True,
322+
"inputs": {"prices": str(prices_path), "benchmark_symbol": "SOXX", "attack_symbol": "SOXL"},
323+
}
324+
],
325+
}
326+
327+
with pytest.raises(ValueError, match="strategy-limited"):
328+
run_configured_plugins(config)
306329

307330

308331
def test_strategy_plugin_runner_contract_registry_prefers_unified_plugin() -> None:
309332
assert set(PLUGIN_COMPATIBLE_STRATEGIES[PLUGIN_MARKET_REGIME_CONTROL]) == {
333+
GENERAL_MARKET_REGIME_NOTIFICATION_STRATEGY,
310334
STRATEGY_NAME,
311-
SOXL_STRATEGY_NAME,
312335
"global_etf_rotation",
313336
"russell_1000_multi_factor_defensive",
314337
"tech_communication_pullback_enhancement",
@@ -433,9 +456,8 @@ def test_strategy_plugin_runner_uses_default_mode_when_entry_mode_is_omitted(tmp
433456
assert payload["execution_controls"]["notification_profile"] == "shadow_only"
434457

435458

436-
def test_strategy_plugin_runner_mounts_crisis_shadow_to_soxl_strategy(tmp_path) -> None:
459+
def test_strategy_plugin_runner_rejects_crisis_shadow_soxl_strategy_mount(tmp_path) -> None:
437460
prices_path = tmp_path / "soxl_prices.csv"
438-
output_dir = tmp_path / SOXL_STRATEGY_NAME / "plugins" / PLUGIN_CRISIS_RESPONSE_SHADOW
439461
_soxl_quiet_prices().to_csv(prices_path, index=False)
440462
config = {
441463
"output_dir": str(tmp_path / "runner"),
@@ -455,21 +477,12 @@ def test_strategy_plugin_runner_mounts_crisis_shadow_to_soxl_strategy(tmp_path)
455477
"credit_pairs": [],
456478
"rate_symbols": [],
457479
},
458-
"outputs": {"output_dir": str(output_dir)},
459480
}
460481
],
461482
}
462483

463-
summary = run_configured_plugins(config)
464-
465-
result = summary["strategy_plugins"][0]
466-
assert result["strategy"] == SOXL_STRATEGY_NAME
467-
assert result["plugin"] == PLUGIN_CRISIS_RESPONSE_SHADOW
468-
assert result["effective_mode"] == "shadow"
469-
payload = json.loads((output_dir / "latest_signal.json").read_text(encoding="utf-8"))
470-
assert payload["strategy"] == SOXL_STRATEGY_NAME
471-
assert payload["evidence"]["metrics"]["benchmark_symbol"] == "SOXX"
472-
assert payload["execution_controls"]["broker_order_allowed"] is False
484+
with pytest.raises(ValueError, match="strategy-limited"):
485+
run_configured_plugins(config)
473486

474487

475488
def test_strategy_plugin_runner_filters_by_strategy(tmp_path) -> None:

0 commit comments

Comments
 (0)