Skip to content

Commit c258bca

Browse files
authored
Wire SOXL dynamic volatility diagnostics (#143)
1 parent 7b2f9ee commit c258bca

5 files changed

Lines changed: 110 additions & 3 deletions

File tree

application/signal_snapshot.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@
3030
"trend_rsi14_dynamic_threshold",
3131
"trend_rsi14_effective_threshold",
3232
"trend_bb_upper",
33+
"blend_gate_volatility_delever_symbol",
34+
"blend_gate_volatility_delever_window",
35+
"blend_gate_volatility_delever_threshold_mode",
36+
"blend_gate_volatility_delever_threshold",
37+
"blend_gate_volatility_delever_dynamic_threshold",
38+
"blend_gate_volatility_delever_dynamic_sample_count",
39+
"blend_gate_volatility_delever_dynamic_lookback",
40+
"blend_gate_volatility_delever_dynamic_percentile",
41+
"blend_gate_volatility_delever_dynamic_min_periods",
42+
"blend_gate_volatility_delever_dynamic_floor",
43+
"blend_gate_volatility_delever_dynamic_cap",
3344
"blend_gate_volatility_delever_metric",
3445
"blend_gate_volatility_delever_triggered",
3546
)

decision_mapper.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,25 @@
5454
"dual_drive_volatility_delever_vetoed",
5555
"dual_drive_volatility_delever_redirect_symbol",
5656
)
57+
_SOXL_RISK_CONTROL_EXECUTION_FIELDS = (
58+
"blend_gate_volatility_delever_enabled",
59+
"blend_gate_volatility_delever_symbol",
60+
"blend_gate_volatility_delever_window",
61+
"blend_gate_volatility_delever_threshold_mode",
62+
"blend_gate_volatility_delever_threshold",
63+
"blend_gate_volatility_delever_dynamic_threshold",
64+
"blend_gate_volatility_delever_dynamic_sample_count",
65+
"blend_gate_volatility_delever_dynamic_lookback",
66+
"blend_gate_volatility_delever_dynamic_percentile",
67+
"blend_gate_volatility_delever_dynamic_min_periods",
68+
"blend_gate_volatility_delever_dynamic_floor",
69+
"blend_gate_volatility_delever_dynamic_cap",
70+
"blend_gate_volatility_delever_metric",
71+
"blend_gate_volatility_delever_triggered",
72+
"blend_gate_volatility_delever_retention_ratio",
73+
"blend_gate_volatility_delever_redirect_symbol",
74+
"blend_gate_volatility_delever_removed_ratio",
75+
)
5776

5877

5978
def _build_portfolio_inputs(
@@ -176,6 +195,27 @@ def _attach_tqqq_risk_control_execution_fields(
176195
execution[field] = value
177196

178197

198+
def _attach_soxl_risk_control_execution_fields(
199+
plan: dict[str, Any],
200+
*,
201+
decision: StrategyDecision,
202+
runtime_metadata: Mapping[str, Any] | None,
203+
) -> None:
204+
if _resolve_canonical_profile(str(plan.get("strategy_profile") or "")) != "soxl_soxx_trend_income":
205+
return
206+
execution = plan.get("execution")
207+
if not isinstance(execution, dict):
208+
return
209+
diagnostics = {**dict(runtime_metadata or {}), **dict(decision.diagnostics)}
210+
annotations = diagnostics.get("execution_annotations")
211+
if isinstance(annotations, Mapping):
212+
diagnostics = {**diagnostics, **dict(annotations)}
213+
for field in _SOXL_RISK_CONTROL_EXECUTION_FIELDS:
214+
value = diagnostics.get(field)
215+
if value not in (None, ""):
216+
execution[field] = value
217+
218+
179219
def _apply_reserved_cash_policy(
180220
annotations: ValueTargetExecutionAnnotations,
181221
*,
@@ -610,4 +650,9 @@ def map_strategy_decision_to_plan(
610650
decision=normalized_decision,
611651
runtime_metadata=runtime_metadata,
612652
)
653+
_attach_soxl_risk_control_execution_fields(
654+
plan,
655+
decision=normalized_decision,
656+
runtime_metadata=runtime_metadata,
657+
)
613658
return plan

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
flask
22
gunicorn
3-
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@e0f760255232b62481444a8c1d6637546ba2c07e
4-
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@5fe430699e532ee444e6c2370b34da3dc8b01b06
5-
hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@8d539aeef707b3594af4073f4cd4c3b13140b73f
3+
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@023641c88506c732624a7329e48b51b9dbbe3c2a
4+
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@7d35772d1125b534d0bcca557cb6dbaf28914719
5+
hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@2e0075004239e7ede7ba256763a3441d4ec4ca73
66
pandas
77
requests
88
pytz

tests/test_decision_mapper.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ def test_prefers_normalized_execution_annotations_when_present(self):
7272
"active_risk_asset": "SOXL",
7373
"investable_cash": 9000.0,
7474
"current_min_trade": 100.0,
75+
"blend_gate_volatility_delever_threshold_mode": "rolling_percentile",
76+
"blend_gate_volatility_delever_threshold": 0.60,
77+
"blend_gate_volatility_delever_dynamic_threshold": 0.60,
78+
"blend_gate_volatility_delever_dynamic_sample_count": 252,
79+
"blend_gate_volatility_delever_dynamic_lookback": 252,
80+
"blend_gate_volatility_delever_dynamic_percentile": 0.95,
81+
"blend_gate_volatility_delever_dynamic_min_periods": 126,
82+
"blend_gate_volatility_delever_dynamic_floor": 0.50,
83+
"blend_gate_volatility_delever_dynamic_cap": 0.75,
84+
"blend_gate_volatility_delever_metric": 0.61,
85+
"blend_gate_volatility_delever_triggered": True,
7586
}
7687
},
7788
)
@@ -94,6 +105,13 @@ def test_prefers_normalized_execution_annotations_when_present(self):
94105
self.assertEqual(plan["execution"]["signal_display"], "signal")
95106
self.assertEqual(plan["execution"]["dashboard_text"], "strategy dashboard")
96107
self.assertEqual(plan["execution"]["investable_cash"], 9000.0)
108+
self.assertEqual(plan["execution"]["blend_gate_volatility_delever_threshold_mode"], "rolling_percentile")
109+
self.assertEqual(plan["execution"]["blend_gate_volatility_delever_threshold"], 0.60)
110+
self.assertEqual(plan["execution"]["blend_gate_volatility_delever_dynamic_threshold"], 0.60)
111+
self.assertEqual(plan["execution"]["blend_gate_volatility_delever_dynamic_sample_count"], 252)
112+
self.assertEqual(plan["execution"]["blend_gate_volatility_delever_dynamic_percentile"], 0.95)
113+
self.assertEqual(plan["execution"]["blend_gate_volatility_delever_metric"], 0.61)
114+
self.assertIs(plan["execution"]["blend_gate_volatility_delever_triggered"], True)
97115

98116
def test_maps_hybrid_decision_from_snapshot_source(self):
99117
decision = StrategyDecision(

tests/test_signal_snapshot.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,39 @@ def test_uses_price_as_of_as_snapshot_date_fallback(self):
9090
self.assertEqual(snapshot["price_as_of"], "2026-06-01")
9191
self.assertEqual(snapshot["universe_as_of"], "2026-05-14")
9292

93+
def test_includes_soxl_dynamic_volatility_fields(self):
94+
snapshot = build_signal_snapshot(
95+
platform="longbridge",
96+
strategy_profile="soxl_soxx_trend_income",
97+
execution={
98+
"blend_gate_volatility_delever_threshold_mode": "rolling_percentile",
99+
"blend_gate_volatility_delever_threshold": 0.60,
100+
"blend_gate_volatility_delever_dynamic_threshold": 0.60,
101+
"blend_gate_volatility_delever_dynamic_sample_count": 252,
102+
"blend_gate_volatility_delever_dynamic_percentile": 0.95,
103+
"blend_gate_volatility_delever_metric": 0.61,
104+
"blend_gate_volatility_delever_triggered": True,
105+
},
106+
)
107+
108+
self.assertEqual(
109+
snapshot["indicators"]["blend_gate_volatility_delever_threshold_mode"],
110+
"rolling_percentile",
111+
)
112+
self.assertEqual(
113+
snapshot["indicators"]["blend_gate_volatility_delever_dynamic_threshold"],
114+
0.60,
115+
)
116+
self.assertEqual(
117+
snapshot["indicators"][
118+
"blend_gate_volatility_delever_dynamic_sample_count"
119+
],
120+
252,
121+
)
122+
self.assertIs(
123+
snapshot["indicators"]["blend_gate_volatility_delever_triggered"], True
124+
)
125+
93126

94127
if __name__ == "__main__":
95128
unittest.main()

0 commit comments

Comments
 (0)