diff --git a/src/atm/main.py b/src/atm/main.py index efeab2e..c6ce6c2 100644 --- a/src/atm/main.py +++ b/src/atm/main.py @@ -58,6 +58,10 @@ def main(argv=None) -> None: "--capture-stub", action="store_true", help="Use stub capture (reads PNGs from samples/); useful for smoke-testing on Linux", ) + p_run.add_argument( + "--startup-delay", type=float, default=5.0, metavar="SEC", + help="Seconds to wait before the loop starts (bring TradeStation to front). Default 5.", + ) # journal p_journal = sub.add_parser("journal", help="Add a trade journal entry interactively") @@ -139,6 +143,15 @@ def _cmd_run(args) -> None: cfg = Config.load_current(Path("configs")) duration_s = args.duration * 3600 if args.duration is not None else None capture_stub = args.capture_stub or bool(os.environ.get("ATM_STUB_CAPTURE")) + + delay = getattr(args, "startup_delay", 0.0) + if delay > 0 and not capture_stub: + print(f"Bring TradeStation to front, minimize PowerShell/VS Code. Starting in {delay:.0f}s...", flush=True) + for i in range(int(delay), 0, -1): + print(f" {i}...", flush=True) + time.sleep(1.0) + print("RUN", flush=True) + run_live(cfg, duration_s=duration_s, capture_stub=capture_stub) @@ -282,15 +295,29 @@ def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None: ] notifier = FanoutNotifier(backends, Path(cfg.dead_letter_path)) - # Startup ping — confirms both channels work before the first bar closes. + # Sanity check: capture one frame, confirm canary matches calibration. 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" + first_frame = capture() + if first_frame is None: + print("WARN: first capture returned None — window/region missing", flush=True) + canary_status = "capture_failed" + else: + first_check = canary.check(first_frame) + canary_status = f"drift={first_check.distance}/{cfg.canary.drift_threshold}" + if first_check.drifted: + print(f"WARN: canary drift at startup ({canary_status}). Wrong window in front?", flush=True) + canary.resume() # clear the auto-pause so user can Ctrl+C and fix + + dur_note = f" dur=∞" if duration_s is None else f" dur={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')}", + body=( + f"cfg={cfg.config_version}{dur_note} @ {datetime.now().isoformat(timespec='seconds')}\n" + f"canary: {canary_status}" + ), )) - audit.log({"event": "started", "config": cfg.config_version}) + audit.log({"event": "started", "config": cfg.config_version, "canary": canary_status}) start = time.monotonic() heartbeat_due = start + cfg.heartbeat_min * 60