Skip to content

Commit 19944dc

Browse files
committed
Show TQQQ delever allocation in notifications
1 parent a117e9b commit 19944dc

6 files changed

Lines changed: 76 additions & 11 deletions

File tree

application/signal_snapshot.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@
7676
"dual_drive_volatility_delever_retention_context_found",
7777
"dual_drive_volatility_delever_retention_reason_codes",
7878
"dual_drive_volatility_delever_redirect_symbol",
79+
"dual_drive_volatility_delever_source_value",
80+
"dual_drive_volatility_delever_retained_value",
7981
"dual_drive_volatility_delever_removed_value",
82+
"dual_drive_volatility_delever_retained_ratio",
83+
"dual_drive_volatility_delever_redirected_ratio",
8084
"dual_drive_macro_risk_governor_enabled",
8185
"dual_drive_macro_risk_governor_found",
8286
"dual_drive_macro_risk_governor_route",

decision_mapper.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@
5252
"dual_drive_volatility_delever_retention_context_found",
5353
"dual_drive_volatility_delever_retention_reason_codes",
5454
"dual_drive_volatility_delever_redirect_symbol",
55+
"dual_drive_volatility_delever_source_value",
56+
"dual_drive_volatility_delever_retained_value",
5557
"dual_drive_volatility_delever_removed_value",
58+
"dual_drive_volatility_delever_retained_ratio",
59+
"dual_drive_volatility_delever_redirected_ratio",
5660
"dual_drive_macro_risk_governor_enabled",
5761
"dual_drive_macro_risk_governor_found",
5862
"dual_drive_macro_risk_governor_route",

notifications/telegram.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,19 @@ def format_small_account_cash_substitution_notes(
228228
"signal_blend_gate_risk_on": "{trend_symbol} 站上 {window} 日门槛线,持有 SOXL {soxl_ratio} + SOXX {soxx_ratio}",
229229
"signal_blend_gate_defensive": "{trend_symbol} 跌破门槛线,防守持有 SOXX {soxx_ratio}",
230230
"signal_blend_gate_overlay_capped": "{trend_symbol} 仍在 {window} 日门槛线上方,但触发风控降档({reasons}),目标仓位 {allocation_text}",
231-
"risk_control_tqqq_volatility_delever_applied": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 高于 {threshold},{source_symbol} 转向 {redirect_symbol}",
232-
"risk_control_tqqq_volatility_delever_applied_dynamic": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 高于实际阈值 {threshold}({threshold_detail}),{source_symbol} 转向 {redirect_symbol}",
233-
"risk_control_tqqq_volatility_delever_hysteresis": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 仍高于退出阈值 {exit_threshold},维持 {source_symbol} 转向 {redirect_symbol}",
234-
"risk_control_tqqq_volatility_delever_hysteresis_dynamic": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 仍高于退出阈值 {exit_threshold};入场实际阈值 {threshold}({threshold_detail}),维持 {source_symbol} 转向 {redirect_symbol}",
231+
"risk_control_tqqq_volatility_delever_applied": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 高于 {threshold},{source_symbol} 转向 {redirect_symbol}({allocation_detail})",
232+
"risk_control_tqqq_volatility_delever_applied_dynamic": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 高于实际阈值 {threshold}({threshold_detail}),{source_symbol} 转向 {redirect_symbol}({allocation_detail})",
233+
"risk_control_tqqq_volatility_delever_hysteresis": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 仍高于退出阈值 {exit_threshold},维持 {source_symbol} 转向 {redirect_symbol}({allocation_detail})",
234+
"risk_control_tqqq_volatility_delever_hysteresis_dynamic": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 仍高于退出阈值 {exit_threshold};入场实际阈值 {threshold}({threshold_detail}),维持 {source_symbol} 转向 {redirect_symbol}({allocation_detail})",
235+
"tqqq_volatility_delever_allocation_detail": "杠杆仓位:TQQQ 保留 {retained_ratio},{redirect_symbol} {redirected_ratio}",
236+
"tqqq_signal_reason_entry_trend": "原因:QQQ 高于 MA200,MA20 斜率为正",
237+
"tqqq_signal_reason_entry_pullback": "原因:QQQ 低于 MA200,但站上 MA20 且回撤反弹确认",
238+
"tqqq_signal_reason_hold_trend": "原因:已持有风险仓位,QQQ 仍高于 MA200",
239+
"tqqq_signal_reason_exit_ma200": "原因:QQQ 跌破 MA200 退出线",
240+
"tqqq_signal_reason_idle_waiting": "原因:等待 QQQ 站上 MA200 且 MA20 斜率转正",
241+
"tqqq_signal_reason_macro_delever": "原因:宏观风控降低杠杆",
242+
"tqqq_signal_reason_macro_defense": "原因:宏观风控转入防守",
243+
"tqqq_signal_reason_crisis_defense": "原因:危机防御转入避险仓位",
235244
"market_status_risk_on": "🚀 风险开启({asset})",
236245
"market_status_delever": "🛡️ 降杠杆({asset})",
237246
"signal_risk_on": "SOXL 站上 {window} 日均线,持有 SOXL,交易层风险仓位 {ratio}",
@@ -369,10 +378,19 @@ def format_small_account_cash_substitution_notes(
369378
"signal_blend_gate_risk_on": "{trend_symbol} is above the {window}-day gate; hold SOXL {soxl_ratio} + SOXX {soxx_ratio}",
370379
"signal_blend_gate_defensive": "{trend_symbol} is below the gate; hold SOXX {soxx_ratio}",
371380
"signal_blend_gate_overlay_capped": "{trend_symbol} remains above the {window}-day gate, but risk cap is active ({reasons}); target {allocation_text}",
372-
"risk_control_tqqq_volatility_delever_applied": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} is above {threshold}; {source_symbol} redirects to {redirect_symbol}",
373-
"risk_control_tqqq_volatility_delever_applied_dynamic": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} is above effective threshold {threshold} ({threshold_detail}); {source_symbol} redirects to {redirect_symbol}",
374-
"risk_control_tqqq_volatility_delever_hysteresis": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} remains above the exit threshold {exit_threshold}; keep {source_symbol} redirected to {redirect_symbol}",
375-
"risk_control_tqqq_volatility_delever_hysteresis_dynamic": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} remains above exit threshold {exit_threshold}; entry effective threshold {threshold} ({threshold_detail}); keep {source_symbol} redirected to {redirect_symbol}",
381+
"risk_control_tqqq_volatility_delever_applied": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} is above {threshold}; {source_symbol} redirects to {redirect_symbol} ({allocation_detail})",
382+
"risk_control_tqqq_volatility_delever_applied_dynamic": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} is above effective threshold {threshold} ({threshold_detail}); {source_symbol} redirects to {redirect_symbol} ({allocation_detail})",
383+
"risk_control_tqqq_volatility_delever_hysteresis": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} remains above the exit threshold {exit_threshold}; keep {source_symbol} redirected to {redirect_symbol} ({allocation_detail})",
384+
"risk_control_tqqq_volatility_delever_hysteresis_dynamic": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} remains above exit threshold {exit_threshold}; entry effective threshold {threshold} ({threshold_detail}); keep {source_symbol} redirected to {redirect_symbol} ({allocation_detail})",
385+
"tqqq_volatility_delever_allocation_detail": "leveraged sleeve: TQQQ retained {retained_ratio}, {redirect_symbol} {redirected_ratio}",
386+
"tqqq_signal_reason_entry_trend": "reason: QQQ is above MA200 and MA20 slope is positive",
387+
"tqqq_signal_reason_entry_pullback": "reason: QQQ is below MA200 but above MA20 with a confirmed pullback rebound",
388+
"tqqq_signal_reason_hold_trend": "reason: existing risk sleeve remains active while QQQ stays above MA200",
389+
"tqqq_signal_reason_exit_ma200": "reason: QQQ fell below the MA200 exit line",
390+
"tqqq_signal_reason_idle_waiting": "reason: waiting for QQQ to reclaim MA200 with positive MA20 slope",
391+
"tqqq_signal_reason_macro_delever": "reason: macro risk governor reduced leverage",
392+
"tqqq_signal_reason_macro_defense": "reason: macro risk governor moved the strategy defensive",
393+
"tqqq_signal_reason_crisis_defense": "reason: crisis defense moved the strategy to the safe sleeve",
376394
"market_status_risk_on": "Risk on ({asset})",
377395
"market_status_delever": "Delever ({asset})",
378396
"signal_risk_on": "SOXL is above the {window}-day average; hold SOXL at risk sleeve {ratio}",
@@ -763,6 +781,13 @@ def _format_percent(value: Any) -> str:
763781
return "n/a"
764782

765783

784+
def _as_float_or_none(value: Any) -> float | None:
785+
try:
786+
return float(value)
787+
except (TypeError, ValueError):
788+
return None
789+
790+
766791
def _format_percentile(value: Any) -> str:
767792
try:
768793
percentile = float(value) * 100
@@ -829,6 +854,27 @@ def _format_volatility_delever_threshold_detail(
829854
)
830855

831856

857+
def _format_tqqq_volatility_delever_allocation_detail(
858+
execution: Mapping[str, Any],
859+
*,
860+
prefix: str,
861+
redirect_symbol: str,
862+
translator: Callable[..., str],
863+
) -> str:
864+
retained_ratio = _as_float_or_none(execution.get(f"{prefix}_retained_ratio"))
865+
redirected_ratio = _as_float_or_none(execution.get(f"{prefix}_redirected_ratio"))
866+
if retained_ratio is None:
867+
retained_ratio = _as_float_or_none(execution.get(f"{prefix}_retention_ratio"))
868+
if redirected_ratio is None and retained_ratio is not None:
869+
redirected_ratio = max(0.0, min(1.0, 1.0 - retained_ratio))
870+
return translator(
871+
"tqqq_volatility_delever_allocation_detail",
872+
retained_ratio=_format_percent(retained_ratio),
873+
redirected_ratio=_format_percent(redirected_ratio),
874+
redirect_symbol=redirect_symbol or "QQQ",
875+
)
876+
877+
832878
def _format_tqqq_risk_control_lines(
833879
execution: Mapping[str, Any],
834880
*,
@@ -845,6 +891,12 @@ def _format_tqqq_risk_control_lines(
845891
prefix=prefix,
846892
translator=translator,
847893
)
894+
allocation_detail = _format_tqqq_volatility_delever_allocation_detail(
895+
execution,
896+
prefix=prefix,
897+
redirect_symbol=redirect_symbol or "QQQ",
898+
translator=translator,
899+
)
848900
if str(execution.get(f"{prefix}_trigger_reason") or "").strip() == "hysteresis_hold":
849901
return [
850902
translator(
@@ -856,6 +908,7 @@ def _format_tqqq_risk_control_lines(
856908
threshold_detail=threshold_detail,
857909
source_symbol="TQQQ",
858910
redirect_symbol=redirect_symbol or "QQQ",
911+
allocation_detail=allocation_detail,
859912
)
860913
]
861914
return [
@@ -867,6 +920,7 @@ def _format_tqqq_risk_control_lines(
867920
threshold_detail=threshold_detail,
868921
source_symbol="TQQQ",
869922
redirect_symbol=redirect_symbol or "QQQ",
923+
allocation_detail=allocation_detail,
870924
)
871925
]
872926

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ authors = [
1515
dependencies = [
1616
"firstrade==0.0.39",
1717
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@2a711adf60b585ca02932bab9ee1bac7ce1df7c6",
18-
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@085f6010883b3f7c66a1c16f96749c0251410f93",
18+
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@fdd39ef0313181bee9083319b87b7175c32b364d",
1919
"google-cloud-storage",
2020
"requests",
2121
]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ flask
22
gunicorn
33
firstrade==0.0.39
44
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@2a711adf60b585ca02932bab9ee1bac7ce1df7c6
5-
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@085f6010883b3f7c66a1c16f96749c0251410f93
5+
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@fdd39ef0313181bee9083319b87b7175c32b364d
66
google-cloud-storage
77
requests
88
pytest

tests/test_notifications_telegram.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ def test_render_cycle_summary_includes_tqqq_volatility_delever_risk_control():
8585
"dual_drive_volatility_delever_dynamic_floor": 0.24,
8686
"dual_drive_volatility_delever_dynamic_cap": 0.36,
8787
"dual_drive_volatility_delever_redirect_symbol": "QQQM",
88+
"dual_drive_volatility_delever_retained_ratio": 0.0,
89+
"dual_drive_volatility_delever_redirected_ratio": 1.0,
8890
},
8991
"submitted_orders": [],
9092
"skipped_orders": [],
@@ -94,5 +96,6 @@ def test_render_cycle_summary_includes_tqqq_volatility_delever_risk_control():
9496

9597
assert (
9698
"🛡️ Risk control: QQQ 5d annualized volatility 31.2% is above effective threshold 30.0% "
97-
"(dynamic p90, 252d lookback, bounded 24.0%-36.0%, samples 252); TQQQ redirects to QQQM"
99+
"(dynamic p90, 252d lookback, bounded 24.0%-36.0%, samples 252); TQQQ redirects to QQQM "
100+
"(leveraged sleeve: TQQQ retained 0.0%, QQQM 100.0%)"
98101
) in message

0 commit comments

Comments
 (0)