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
49 changes: 46 additions & 3 deletions application/rebalance_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,48 @@ def _translate_snapshot_guard_decision(decision: object, *, translator) -> str:
return value if translated == key else str(translated)


def _localize_timing_contract(contract: str, *, translator) -> str:
value = str(contract or "").strip()
if not value:
return ""
if value == "same_trading_day":
return "当日执行" if _translator_uses_zh(translator) else "same trading day"
if value == "next_trading_day":
return "次一交易日执行" if _translator_uses_zh(translator) else "next trading day"
match = re.fullmatch(r"next_(\d+)_trading_days", value)
if match:
count = int(match.group(1))
if _translator_uses_zh(translator):
return f"{count}个交易日后执行"
return f"next {count} trading days"
return _localize_notification_text(value, translator=translator)


def _render_notification_context_text(
notification_context: Mapping[str, object] | None,
*,
translator,
fallback: str = "",
) -> str:
if not isinstance(notification_context, Mapping):
return fallback
key = str(notification_context.get("code") or "").strip()
if not key:
return fallback
params = dict(notification_context.get("params") or {})
rendered = translator(key, **params)
return fallback if rendered == key else str(rendered)


def _translate_snapshot_guard_decision(decision: object, *, translator) -> str:
value = str(decision or "").strip()
if not value:
return ""
key = f"snapshot_guard_decision_{value}"
translated = translator(key)
return value if translated == key else str(translated)


def _split_detail_segment(text: str) -> list[str]:
value = str(text or "").strip()
if not value:
Expand Down Expand Up @@ -283,12 +325,13 @@ def _build_timing_audit_lines(signal_metadata, *, translator) -> list[str]:
if not signal_date and not effective_date and not contract:
return []
label = "⏱ 执行时点" if _translator_uses_zh(translator) else "⏱ Timing"
localized_contract = _localize_timing_contract(contract, translator=translator)
if signal_date and effective_date:
value = f"{signal_date} -> {effective_date}"
else:
value = signal_date or effective_date or contract
if contract and contract not in value:
value = f"{value} ({contract})" if value else contract
value = signal_date or effective_date or localized_contract
if localized_contract and localized_contract not in value:
value = f"{value} ({localized_contract})" if value else localized_contract
return [f"{label}: {value}"]


Expand Down
24 changes: 21 additions & 3 deletions notifications/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ def _localize_notification_text(text: str, *, translator) -> str:
)


def _localize_timing_contract(contract: str, *, translator) -> str:
value = str(contract or "").strip()
if not value:
return ""
if value == "same_trading_day":
return "当日执行" if _translator_uses_zh(translator) else "same trading day"
if value == "next_trading_day":
return "次一交易日执行" if _translator_uses_zh(translator) else "next trading day"
match = re.fullmatch(r"next_(\d+)_trading_days", value)
if match:
count = int(match.group(1))
if _translator_uses_zh(translator):
return f"{count}个交易日后执行"
return f"next {count} trading days"
return _localize_notification_text(value, translator=translator)


def _split_detail_segment(text: str) -> list[str]:
value = str(text or "").strip()
if not value:
Expand Down Expand Up @@ -246,12 +263,13 @@ def _build_timing_audit_lines(signal_metadata, *, translator) -> list[str]:
if not signal_date and not effective_date and not contract:
return []
label = "⏱ 执行时点" if _translator_uses_zh(translator) else "⏱ Timing"
localized_contract = _localize_timing_contract(contract, translator=translator)
if signal_date and effective_date:
value = f"{signal_date} -> {effective_date}"
else:
value = signal_date or effective_date or contract
if contract and contract not in value:
value = f"{value} ({contract})" if value else contract
value = signal_date or effective_date or localized_contract
if localized_contract and localized_contract not in value:
value = f"{value} ({localized_contract})" if value else localized_contract
return [f"{label}: {value}"]


Expand Down
4 changes: 4 additions & 0 deletions notifications/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"strategy_label": "🧭 策略: {name}",
"equity": "净值",
"buying_power": "购买力",
"reserved_cash": "预留现金",
"investable_cash": "可投资现金",
"empty_positions": " (空仓)",
"empty_target_weights": " (无目标持仓)",
"account_summary_title": "📊 账户摘要",
Expand Down Expand Up @@ -100,6 +102,8 @@
"strategy_label": "🧭 Strategy: {name}",
"equity": "Equity",
"buying_power": "Buying Power",
"reserved_cash": "Reserved Cash",
"investable_cash": "Investable Cash",
"empty_positions": " (No positions)",
"empty_target_weights": " (No target positions)",
"account_summary_title": "📊 Account Summary",
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@c24d4c52e84c8c696006532590b15e9be92c8d89
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@a56c2adcb04beaf5f3dffb3a7e564497f97d0df1
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@fcffc7e3901089ddc10e161d4e5703417c0a03d3
pandas
numpy
requests
Expand Down
2 changes: 1 addition & 1 deletion tests/test_rebalance_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def fake_execute_rebalance(
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 "⏱ Timing: 2026-04-01 -> 2026-04-02 (next trading day)" in observed["messages"][0]
assert "Target Weights" not in observed["messages"][0]


Expand Down