Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions application/signal_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@
"dual_drive_volatility_delever_retention_context_found",
"dual_drive_volatility_delever_retention_reason_codes",
"dual_drive_volatility_delever_redirect_symbol",
"dual_drive_volatility_delever_source_value",
"dual_drive_volatility_delever_retained_value",
"dual_drive_volatility_delever_removed_value",
"dual_drive_volatility_delever_retained_ratio",
"dual_drive_volatility_delever_redirected_ratio",
"dual_drive_macro_risk_governor_enabled",
"dual_drive_macro_risk_governor_found",
"dual_drive_macro_risk_governor_route",
Expand Down
4 changes: 4 additions & 0 deletions decision_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@
"dual_drive_volatility_delever_retention_context_found",
"dual_drive_volatility_delever_retention_reason_codes",
"dual_drive_volatility_delever_redirect_symbol",
"dual_drive_volatility_delever_source_value",
"dual_drive_volatility_delever_retained_value",
"dual_drive_volatility_delever_removed_value",
"dual_drive_volatility_delever_retained_ratio",
"dual_drive_volatility_delever_redirected_ratio",
"dual_drive_macro_risk_governor_enabled",
"dual_drive_macro_risk_governor_found",
"dual_drive_macro_risk_governor_route",
Expand Down
36 changes: 36 additions & 0 deletions notifications/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ def _format_percent(value) -> str:
return "n/a"


def _as_float_or_none(value):
try:
return float(value)
except (TypeError, ValueError):
return None


def _format_percentile(value) -> str:
try:
percentile = float(value) * 100
Expand Down Expand Up @@ -255,6 +262,27 @@ def _format_volatility_delever_threshold_detail(execution, *, prefix: str, trans
)


def _format_tqqq_volatility_delever_allocation_detail(
execution,
*,
prefix: str,
redirect_symbol: str,
translator,
) -> str:
retained_ratio = _as_float_or_none(execution.get(f"{prefix}_retained_ratio"))
redirected_ratio = _as_float_or_none(execution.get(f"{prefix}_redirected_ratio"))
if retained_ratio is None:
retained_ratio = _as_float_or_none(execution.get(f"{prefix}_retention_ratio"))
if redirected_ratio is None and retained_ratio is not None:
redirected_ratio = max(0.0, min(1.0, 1.0 - retained_ratio))
return translator(
"tqqq_volatility_delever_allocation_detail",
retained_ratio=_format_percent(retained_ratio),
redirected_ratio=_format_percent(redirected_ratio),
redirect_symbol=redirect_symbol or "QQQ",
)


def _build_risk_control_lines(execution, *, translator):
if _is_truthy(execution.get("dual_drive_volatility_delever_applied")):
redirect_symbol = str(execution.get("dual_drive_volatility_delever_redirect_symbol") or "QQQ").strip().upper()
Expand All @@ -268,6 +296,12 @@ def _build_risk_control_lines(execution, *, translator):
prefix="dual_drive_volatility_delever",
translator=translator,
)
allocation_detail = _format_tqqq_volatility_delever_allocation_detail(
execution,
prefix="dual_drive_volatility_delever",
redirect_symbol=redirect_symbol or "QQQ",
translator=translator,
)
if str(execution.get("dual_drive_volatility_delever_trigger_reason") or "").strip() == "hysteresis_hold":
return [
translator(
Expand All @@ -279,6 +313,7 @@ def _build_risk_control_lines(execution, *, translator):
threshold_detail=threshold_detail,
source_symbol="TQQQ",
redirect_symbol=redirect_symbol or "QQQ",
allocation_detail=allocation_detail,
)
]
return [
Expand All @@ -290,6 +325,7 @@ def _build_risk_control_lines(execution, *, translator):
threshold_detail=threshold_detail,
source_symbol="TQQQ",
redirect_symbol=redirect_symbol or "QQQ",
allocation_detail=allocation_detail,
)
]
return []
Expand Down
34 changes: 26 additions & 8 deletions notifications/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,19 @@
"buy_deferred_non_usd_cash": "检测到非 USD 现金({currencies}),但美股策略可用 USD 现金为 ${available}、可投资现金为 ${investable};请先换汇或入金 USD 后再买入",
"buy_deferred_small_target_gap": "{symbol} 目标差额 ${diff} 未超过 1 股价格 ${price};为避免超过目标仓位,本轮不买入",
"buy_deferred_small_account_cash_substitution": "{symbol} 目标金额 ${diff} 低于 1 股价格 ${price};为避免超过目标仓位,本轮保留现金(现金替代:{cash_symbols})",
"risk_control_tqqq_volatility_delever_applied": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 高于 {threshold},{source_symbol} 转向 {redirect_symbol}",
"risk_control_tqqq_volatility_delever_applied_dynamic": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 高于实际阈值 {threshold}({threshold_detail}),{source_symbol} 转向 {redirect_symbol}",
"risk_control_tqqq_volatility_delever_hysteresis": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 仍高于退出阈值 {exit_threshold},维持 {source_symbol} 转向 {redirect_symbol}",
"risk_control_tqqq_volatility_delever_hysteresis_dynamic": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 仍高于退出阈值 {exit_threshold};入场实际阈值 {threshold}({threshold_detail}),维持 {source_symbol} 转向 {redirect_symbol}",
"risk_control_tqqq_volatility_delever_applied": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 高于 {threshold},{source_symbol} 转向 {redirect_symbol}({allocation_detail})",
"risk_control_tqqq_volatility_delever_applied_dynamic": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 高于实际阈值 {threshold}({threshold_detail}),{source_symbol} 转向 {redirect_symbol}({allocation_detail})",
"risk_control_tqqq_volatility_delever_hysteresis": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 仍高于退出阈值 {exit_threshold},维持 {source_symbol} 转向 {redirect_symbol}({allocation_detail})",
"risk_control_tqqq_volatility_delever_hysteresis_dynamic": "🛡️ 风控: QQQ {window} 日年化波动率 {volatility} 仍高于退出阈值 {exit_threshold};入场实际阈值 {threshold}({threshold_detail}),维持 {source_symbol} 转向 {redirect_symbol}({allocation_detail})",
"tqqq_volatility_delever_allocation_detail": "杠杆仓位:TQQQ 保留 {retained_ratio},{redirect_symbol} {redirected_ratio}",
"tqqq_signal_reason_entry_trend": "原因:QQQ 高于 MA200,MA20 斜率为正",
"tqqq_signal_reason_entry_pullback": "原因:QQQ 低于 MA200,但站上 MA20 且回撤反弹确认",
"tqqq_signal_reason_hold_trend": "原因:已持有风险仓位,QQQ 仍高于 MA200",
"tqqq_signal_reason_exit_ma200": "原因:QQQ 跌破 MA200 退出线",
"tqqq_signal_reason_idle_waiting": "原因:等待 QQQ 站上 MA200 且 MA20 斜率转正",
"tqqq_signal_reason_macro_delever": "原因:宏观风控降低杠杆",
"tqqq_signal_reason_macro_defense": "原因:宏观风控转入防守",
"tqqq_signal_reason_crisis_defense": "原因:危机防御转入避险仓位",
"buy_deferred_small_cash": "{symbol} 目标差额 ${diff},但可投资现金 ${investable} 不足买入 1 股(价格 ${price})",
"buy_deferred_cash_limit": "{symbol} 目标差额 ${diff},预算可买 {budget_qty} 股,但券商估算可买数量为 0;可能有未完成挂单、结算或购买力占用",
"buy_deferred_cash_sweep_cash_limit": "{symbol} 剩余可投资现金 ${investable},预算可回补 {budget_qty} 股,但券商估算可买数量为 0;可能有未完成挂单、结算或购买力占用",
Expand Down Expand Up @@ -219,10 +228,19 @@
"buy_deferred_non_usd_cash": "Non-USD cash is present ({currencies}), but this US-equity strategy has USD cash ${available} and investable cash ${investable}; convert or deposit USD before buying",
"buy_deferred_small_target_gap": "{symbol} target gap ${diff} does not exceed the 1-share price ${price}; skipped to avoid exceeding the target allocation",
"buy_deferred_small_account_cash_substitution": "{symbol} target ${diff} is below the 1-share price ${price}; to avoid exceeding the target allocation, this cycle keeps cash (cash substitute: {cash_symbols})",
"risk_control_tqqq_volatility_delever_applied": "🛡️ Risk control: QQQ {window}d annualized volatility {volatility} is above {threshold}; {source_symbol} redirects to {redirect_symbol}",
"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}",
"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}",
"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}",
"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})",
"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})",
"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})",
"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})",
"tqqq_volatility_delever_allocation_detail": "leveraged sleeve: TQQQ retained {retained_ratio}, {redirect_symbol} {redirected_ratio}",
"tqqq_signal_reason_entry_trend": "reason: QQQ is above MA200 and MA20 slope is positive",
"tqqq_signal_reason_entry_pullback": "reason: QQQ is below MA200 but above MA20 with a confirmed pullback rebound",
"tqqq_signal_reason_hold_trend": "reason: existing risk sleeve remains active while QQQ stays above MA200",
"tqqq_signal_reason_exit_ma200": "reason: QQQ fell below the MA200 exit line",
"tqqq_signal_reason_idle_waiting": "reason: waiting for QQQ to reclaim MA200 with positive MA20 slope",
"tqqq_signal_reason_macro_delever": "reason: macro risk governor reduced leverage",
"tqqq_signal_reason_macro_defense": "reason: macro risk governor moved the strategy defensive",
"tqqq_signal_reason_crisis_defense": "reason: crisis defense moved the strategy to the safe sleeve",
"buy_deferred_small_cash": "{symbol} target gap ${diff}, but investable cash ${investable} is not enough for 1 share at ${price}",
"buy_deferred_cash_limit": "{symbol} target gap ${diff}, budget supports {budget_qty} shares, but broker estimate returned 0; an open order, settlement, or buying-power hold may still be blocking funds",
"buy_deferred_cash_sweep_cash_limit": "{symbol} residual investable cash ${investable}, budget supports {budget_qty} tail-rebuy shares, but broker estimate returned 0; an open order, settlement, or buying-power hold may still be blocking funds",
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
flask
gunicorn
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@2a711adf60b585ca02932bab9ee1bac7ce1df7c6
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@085f6010883b3f7c66a1c16f96749c0251410f93
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@fdd39ef0313181bee9083319b87b7175c32b364d
hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@02af62bc7af7b8ffdbe8575421434e455ab00d66
pandas
requests
Expand Down
16 changes: 12 additions & 4 deletions tests/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ def test_heartbeat_renders_tqqq_volatility_delever_risk_control(self):
"dual_drive_volatility_delever_dynamic_floor": 0.24,
"dual_drive_volatility_delever_dynamic_cap": 0.36,
"dual_drive_volatility_delever_redirect_symbol": "QQQ",
"dual_drive_volatility_delever_retained_ratio": 0.25,
"dual_drive_volatility_delever_redirected_ratio": 0.75,
},
skip_logs=(),
note_logs=(),
Expand All @@ -218,6 +220,8 @@ def test_heartbeat_renders_tqqq_volatility_delever_risk_control(self):
"dual_drive_volatility_delever_dynamic_floor": 0.24,
"dual_drive_volatility_delever_dynamic_cap": 0.36,
"dual_drive_volatility_delever_redirect_symbol": "QQQ",
"dual_drive_volatility_delever_retained_ratio": 0.25,
"dual_drive_volatility_delever_redirected_ratio": 0.75,
},
skip_logs=(),
note_logs=(),
Expand All @@ -228,11 +232,11 @@ def test_heartbeat_renders_tqqq_volatility_delever_risk_control(self):
)

self.assertIn(
"🛡️ 风控: QQQ 5 日年化波动率 31.2% 高于实际阈值 30.0%(动态 p90,252日窗口,范围 24.0%-36.0%,样本 252),TQQQ 转向 QQQ",
"🛡️ 风控: QQQ 5 日年化波动率 31.2% 高于实际阈值 30.0%(动态 p90,252日窗口,范围 24.0%-36.0%,样本 252),TQQQ 转向 QQQ(杠杆仓位:TQQQ 保留 25.0%,QQQ 75.0%)",
zh_rendered.compact_text,
)
self.assertIn(
"🛡️ Risk control: QQQ 5d annualized volatility 31.2% is above effective threshold 30.0% (dynamic p90, 252d lookback, bounded 24.0%-36.0%, samples 252); TQQQ redirects to QQQ",
"🛡️ Risk control: QQQ 5d annualized volatility 31.2% is above effective threshold 30.0% (dynamic p90, 252d lookback, bounded 24.0%-36.0%, samples 252); TQQQ redirects to QQQ (leveraged sleeve: TQQQ retained 25.0%, QQQ 75.0%)",
en_rendered.compact_text,
)

Expand All @@ -255,6 +259,8 @@ def test_heartbeat_renders_tqqq_volatility_delever_hysteresis_risk_control(self)
"dual_drive_volatility_delever_dynamic_cap": 0.36,
"dual_drive_volatility_delever_trigger_reason": "hysteresis_hold",
"dual_drive_volatility_delever_redirect_symbol": "QQQM",
"dual_drive_volatility_delever_retained_ratio": 0.0,
"dual_drive_volatility_delever_redirected_ratio": 1.0,
},
skip_logs=(),
note_logs=(),
Expand All @@ -281,6 +287,8 @@ def test_heartbeat_renders_tqqq_volatility_delever_hysteresis_risk_control(self)
"dual_drive_volatility_delever_dynamic_cap": 0.36,
"dual_drive_volatility_delever_trigger_reason": "hysteresis_hold",
"dual_drive_volatility_delever_redirect_symbol": "QQQM",
"dual_drive_volatility_delever_retained_ratio": 0.0,
"dual_drive_volatility_delever_redirected_ratio": 1.0,
},
skip_logs=(),
note_logs=(),
Expand All @@ -291,11 +299,11 @@ def test_heartbeat_renders_tqqq_volatility_delever_hysteresis_risk_control(self)
)

self.assertIn(
"🛡️ 风控: QQQ 5 日年化波动率 26.2% 仍高于退出阈值 24.0%;入场实际阈值 30.0%(动态 p90,252日窗口,范围 24.0%-36.0%,样本 252),维持 TQQQ 转向 QQQM",
"🛡️ 风控: QQQ 5 日年化波动率 26.2% 仍高于退出阈值 24.0%;入场实际阈值 30.0%(动态 p90,252日窗口,范围 24.0%-36.0%,样本 252),维持 TQQQ 转向 QQQM(杠杆仓位:TQQQ 保留 0.0%,QQQM 100.0%)",
zh_rendered.compact_text,
)
self.assertIn(
"🛡️ Risk control: QQQ 5d annualized volatility 26.2% remains above exit threshold 24.0%; entry effective threshold 30.0% (dynamic p90, 252d lookback, bounded 24.0%-36.0%, samples 252); keep TQQQ redirected to QQQM",
"🛡️ Risk control: QQQ 5d annualized volatility 26.2% remains above exit threshold 24.0%; entry effective threshold 30.0% (dynamic p90, 252d lookback, bounded 24.0%-36.0%, samples 252); keep TQQQ redirected to QQQM (leveraged sleeve: TQQQ retained 0.0%, QQQM 100.0%)",
en_rendered.compact_text,
)

Expand Down