Skip to content

Commit 81ce082

Browse files
authored
Suppress IBKR no-op window notifications (#68)
1 parent 7c17bdd commit 81ce082

2 files changed

Lines changed: 48 additions & 19 deletions

File tree

application/rebalance_service.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,23 @@ def _render_notification_context_text(
7878
return fallback if rendered == key else str(rendered)
7979

8080

81+
def _should_suppress_noop_notification(signal_metadata: Mapping[str, object] | None) -> bool:
82+
metadata = signal_metadata if isinstance(signal_metadata, Mapping) else {}
83+
no_op_reason = str(metadata.get("no_op_reason") or "").strip()
84+
if no_op_reason.startswith(("outside_execution_window", "outside_monthly_execution_window")):
85+
return True
86+
notification_context = metadata.get("notification_context")
87+
if not isinstance(notification_context, Mapping):
88+
return False
89+
status_context = notification_context.get("status")
90+
if not isinstance(status_context, Mapping):
91+
return False
92+
return str(status_context.get("code") or "").strip() in {
93+
"status_monthly_snapshot_waiting_window",
94+
"status_no_execution_window_after_snapshot",
95+
}
96+
97+
8198
def _translate_snapshot_guard_decision(decision: object, *, translator) -> str:
8299
value = str(decision or "").strip()
83100
if not value:
@@ -632,22 +649,36 @@ def run_strategy_core(
632649
+ json.dumps({"path": str(record_path), "status": record.get("execution_status"), "no_op_reason": record.get("no_op_reason")}, ensure_ascii=False),
633650
flush=True,
634651
)
635-
notification_publisher.publish(
636-
notification_renderers.render_heartbeat_notification(
637-
dashboard=dashboard,
638-
strategy_dashboard=strategy_dashboard,
639-
no_op_text=no_op_text,
640-
signal_desc=signal_desc,
641-
status_desc=status_desc,
642-
status_icon=signal_metadata.get("status_icon", "🐤"),
643-
translator=config.translator,
644-
separator=config.separator,
645-
strategy_display_name=config.strategy_display_name,
646-
extra_notification_lines=config.extra_notification_lines,
652+
notification_suppressed = _should_suppress_noop_notification(signal_metadata)
653+
if notification_suppressed:
654+
print(
655+
"notification_suppressed "
656+
+ json.dumps(
657+
{
658+
"reason": no_op_reason or fail_reason or decision,
659+
"strategy_profile": signal_metadata.get("strategy_profile"),
660+
},
661+
ensure_ascii=False,
662+
),
663+
flush=True,
664+
)
665+
else:
666+
notification_publisher.publish(
667+
notification_renderers.render_heartbeat_notification(
668+
dashboard=dashboard,
669+
strategy_dashboard=strategy_dashboard,
670+
no_op_text=no_op_text,
671+
signal_desc=signal_desc,
672+
status_desc=status_desc,
673+
status_icon=signal_metadata.get("status_icon", "🐤"),
674+
translator=config.translator,
675+
separator=config.separator,
676+
strategy_display_name=config.strategy_display_name,
677+
extra_notification_lines=config.extra_notification_lines,
678+
)
647679
)
648-
)
649680
return StrategyCycleResult(
650-
result="OK - heartbeat",
681+
result="OK - no-op" if notification_suppressed else "OK - heartbeat",
651682
signal_metadata=dict(signal_metadata or {}),
652683
target_weights=None,
653684
execution_summary={},

tests/test_rebalance_service.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ def disconnect(self):
360360
reconciliation_output_path=output_root,
361361
)
362362

363-
assert result.result == "OK - heartbeat"
363+
assert result.result == "OK - no-op"
364364
candidate_paths = [
365365
output_root,
366366
output_root / "2026-04-01" / "reconciliation.json",
@@ -416,7 +416,5 @@ def disconnect(self):
416416
strategy_display_name="Mega Cap Top50 平衡龙头轮动",
417417
)
418418

419-
assert result.result == "OK - heartbeat"
420-
assert observed["messages"]
421-
assert "当前不在月度执行窗口" in observed["messages"][0]
422-
assert "决策=proceed" not in observed["messages"][0]
419+
assert result.result == "OK - no-op"
420+
assert observed["messages"] == []

0 commit comments

Comments
 (0)