feat(run): startup + shutdown ping on both channels

Answers 'is it even running?' within seconds of 'atm run' — no waiting
30 min for heartbeat + no need for a live trigger to occur.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-16 07:16:14 +00:00
parent 4dac21b7c0
commit be7c4f82e8

View File

@@ -282,6 +282,16 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None:
] ]
notifier = FanoutNotifier(backends, Path(cfg.dead_letter_path)) notifier = FanoutNotifier(backends, Path(cfg.dead_letter_path))
# Startup ping — confirms both channels work before the first bar closes.
from datetime import datetime
dur_note = f" duration={cfg.heartbeat_min:d}m heartbeat" if duration_s is None else f" duration={duration_s/3600:.2f}h"
notifier.send(Alert(
kind="heartbeat",
title="ATM started",
body=f"config={cfg.config_version}{dur_note} at {datetime.now().isoformat(timespec='seconds')}",
))
audit.log({"event": "started", "config": cfg.config_version})
start = time.monotonic() start = time.monotonic()
heartbeat_due = start + cfg.heartbeat_min * 60 heartbeat_due = start + cfg.heartbeat_min * 60
levels_extractor = None levels_extractor = None
@@ -337,6 +347,13 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None:
heartbeat_due = time.time() + cfg.heartbeat_min * 60 heartbeat_due = time.time() + cfg.heartbeat_min * 60
time.sleep(cfg.loop_interval_s) time.sleep(cfg.loop_interval_s)
finally: finally:
try:
notifier.send(Alert(
kind="heartbeat", title="ATM stopped",
body=f"after {time.monotonic() - start:.0f}s",
))
except Exception:
pass
notifier.stop() notifier.stop()
audit.close() audit.close()