diff --git a/application/rebalance_service.py b/application/rebalance_service.py index 27d45ac..195daac 100644 --- a/application/rebalance_service.py +++ b/application/rebalance_service.py @@ -343,6 +343,28 @@ def _strategy_dashboard_text(signal_metadata, *, translator) -> str: or metadata.get("dashboard") or "" ) + cash_only_execution = bool(metadata.get("cash_only_execution", True)) + if cash_only_execution: + dashboard_text = dashboard_text.replace("总资产(策略净值)", "总资产(策略标的+现金,不含融资额度)") + dashboard_text = dashboard_text.replace( + "Total assets (strategy net liquidation)", + "Total assets (strategy symbols + cash, ex-margin)", + ) + dashboard_text = dashboard_text.replace("购买力", "可用现金") + dashboard_text = dashboard_text.replace("Buying power", "Available cash") + else: + dashboard_text = dashboard_text.replace("总资产(策略标的+现金,不含融资额度)", "总资产(策略净值)") + dashboard_text = dashboard_text.replace("总资产(策略标的+现金)", "总资产(策略净值)") + dashboard_text = dashboard_text.replace( + "Total assets (strategy symbols + cash, ex-margin)", + "Total assets (strategy net liquidation)", + ) + dashboard_text = dashboard_text.replace( + "Total assets (strategy symbols + cash)", + "Total assets (strategy net liquidation)", + ) + dashboard_text = dashboard_text.replace("可用现金", "购买力") + dashboard_text = dashboard_text.replace("Available cash", "Buying power") timing_lines = _build_timing_audit_lines(metadata, translator=translator) if not timing_lines: return dashboard_text diff --git a/constraints.txt b/constraints.txt index 2a6081a..80824cb 100644 --- a/constraints.txt +++ b/constraints.txt @@ -4,7 +4,7 @@ # Auto-updated by update-qpk-pin.yml on every push to QPK main. quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@d786c1140967f0e96e35599d057f0655e5a9ba25 -us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@c8df5f9659340965bd7f53998892ed1018ed4254 +us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@c57fb20528d40f363ae149ae6e3f6b1a240ec237 hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@700c8b19c46336d3d8fcba687e58553afcf0235f cn-equity-strategies @ git+https://github.com/QuantStrategyLab/CnEquityStrategies.git@e09fd557c2bd9ae5f4d44228915c4e52c4b0dd21 crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@565be005e4186fd6d750884e7b0bc59f779f9549 diff --git a/notifications/renderers.py b/notifications/renderers.py index ef88f13..a4bcaea 100644 --- a/notifications/renderers.py +++ b/notifications/renderers.py @@ -444,6 +444,47 @@ def _format_dashboard_text(text) -> str: return "\n".join(lines) +def _relabel_dashboard_cash_labels(text: str, *, cash_only_execution: bool) -> str: + value = str(text or "") + if cash_only_execution: + value = value.replace("总资产(策略净值)", "总资产(策略标的+现金,不含融资额度)") + value = value.replace( + "Total assets (strategy net liquidation)", + "Total assets (strategy symbols + cash, ex-margin)", + ) + value = value.replace("购买力", "可用现金") + value = value.replace("Buying power", "Available cash") + return value + value = value.replace("总资产(策略标的+现金,不含融资额度)", "总资产(策略净值)") + value = value.replace("总资产(策略标的+现金)", "总资产(策略净值)") + value = value.replace( + "Total assets (strategy symbols + cash, ex-margin)", + "Total assets (strategy net liquidation)", + ) + value = value.replace( + "Total assets (strategy symbols + cash)", + "Total assets (strategy net liquidation)", + ) + value = value.replace("可用现金", "购买力") + value = value.replace("Available cash", "Buying power") + return value + + +def _format_compact_dashboard_text(text) -> str: + lines = [] + for line in _format_dashboard_text(text).splitlines(): + stripped = line.strip() + lowered = stripped.lower() + if stripped.startswith(("⏱", "🧾", "🛡️", "📊", "🎯")): + continue + if lowered.startswith(("signal:", "signal:", "market status:")): + continue + if stripped.startswith(("信号:", "信号:", "市场状态:", "市场状态:")): + continue + lines.append(line) + return "\n".join(lines) + + def _build_timing_audit_lines(signal_metadata, *, translator) -> list[str]: metadata = signal_metadata if isinstance(signal_metadata, Mapping) else {} raw_annotations = metadata.get("execution_annotations") @@ -646,6 +687,10 @@ def _strategy_dashboard_text(signal_metadata, *, translator) -> str: or metadata.get("dashboard") or "" ) + dashboard_text = _relabel_dashboard_cash_labels( + dashboard_text, + cash_only_execution=bool(metadata.get("cash_only_execution", True)), + ) risk_control_lines = _build_tqqq_risk_control_lines(risk_source, translator=translator) timing_lines = _build_timing_audit_lines(metadata, translator=translator) snapshot_line = _format_signal_snapshot_line(metadata.get("signal_snapshot"), translator=translator) @@ -791,7 +836,7 @@ def _build_compact_message( lines.append(" | ".join(str(e).strip() for e in extra if str(e).strip())) lines.append(separator) # Positions - dashboard = _format_dashboard_text(dashboard_text) + dashboard = _format_compact_dashboard_text(dashboard_text) if dashboard: lines.extend(dashboard.splitlines()) lines.append(separator) diff --git a/requirements.txt b/requirements.txt index 586f758..b125af0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,5 +12,5 @@ google-cloud-secret-manager google-cloud-storage yfinance quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@d786c1140967f0e96e35599d057f0655e5a9ba25 -us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@c8df5f9659340965bd7f53998892ed1018ed4254 +us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@c57fb20528d40f363ae149ae6e3f6b1a240ec237 hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@700c8b19c46336d3d8fcba687e58553afcf0235f diff --git a/tests/test_rebalance_service.py b/tests/test_rebalance_service.py index 00b0ad6..eafb276 100644 --- a/tests/test_rebalance_service.py +++ b/tests/test_rebalance_service.py @@ -1,6 +1,6 @@ import json -from application.rebalance_service import _resolve_reconciliation_mode, run_strategy_core +from application.rebalance_service import _resolve_reconciliation_mode, _strategy_dashboard_text, run_strategy_core from application.runtime_dependencies import IBKRRebalanceConfig from notifications.renderers import ( _build_notification_trade_lines, @@ -233,8 +233,8 @@ def test_trade_notification_localizes_compact_signal_state_for_zh_and_en(): translator=build_translator("en"), ) - assert "🎯 入场信号" in zh_notification.compact_text - assert "🎯 Entry Signal" in en_notification.compact_text + assert "🎯 入场信号" not in zh_notification.compact_text + assert "🎯 Entry Signal" not in en_notification.compact_text def test_run_strategy_core_passes_signal_metadata_to_execution(): @@ -308,8 +308,9 @@ def fake_execute_rebalance( assert "📌 Strategy portfolio" in observed["messages"][0] assert "Total assets (strategy symbols + cash): $1,000.00" in observed["messages"][0] assert "💼 Strategy holdings" in observed["messages"][0] - assert "📏 breadth=60.0%" in observed["messages"][0] - assert "⏱ Timing: 2026-04-01 -> 2026-04-02 (next trading day)" in observed["messages"][0] + assert "📏 breadth=60.0%" not in observed["messages"][0] + assert "⏱ Timing:" not in observed["messages"][0] + assert "Signal snapshot:" not in observed["messages"][0] assert "Target Weights" not in observed["messages"][0] @@ -423,11 +424,29 @@ def test_trade_notification_keeps_detailed_logs_out_of_compact_message(): assert "execution_profile=tqqq_growth_income" in notification.detailed_text assert "execution_profile=tqqq_growth_income" not in notification.compact_text assert "📌 Strategy portfolio" in notification.compact_text - assert "⏱ Timing: 2026-04-01 -> 2026-04-02 (next trading day)" in notification.compact_text + assert "⏱ Timing:" not in notification.compact_text assert "no_order_plan_reason reason=min_notional:QQQ,TQQQ" in notification.compact_text assert notification.compact_text.index("🆔 Account: U1234567") < notification.compact_text.index("🧩 Plugin:") +def test_strategy_dashboard_relabels_total_assets_when_margin_is_enabled(): + dashboard = _strategy_dashboard_text( + { + "cash_only_execution": False, + "dashboard_text": ( + "📌 策略账户概览\n" + " - 总资产(策略标的+现金,不含融资额度): $50,000.00\n" + " - 可用现金: $75,000.00" + ), + }, + translator=build_translator("zh"), + ) + + assert "总资产(策略净值): $50,000.00" in dashboard + assert "购买力: $75,000.00" in dashboard + assert "不含融资额度" not in dashboard + + def test_trade_notification_includes_abnormal_order_batch(): notification = render_trade_notification( dashboard="dashboard",