diff --git a/src/atm/main.py b/src/atm/main.py index e2e3483..4067ba2 100644 --- a/src/atm/main.py +++ b/src/atm/main.py @@ -123,6 +123,10 @@ def main(argv=None) -> None: "--delay", type=float, default=3.0, metavar="SEC", help="Seconds to wait before capture (bring TradeStation to front first). Default 3.", ) + p_debug.add_argument( + "--canary", action="store_true", + help="Diagnostic-only: capture canary ROI, compute phash distance vs baseline, save crop.", + ) # report p_report = sub.add_parser("report", help="Print weekly performance report") @@ -208,6 +212,10 @@ def _focus_window_by_title(needle: str) -> str | None: (protecția anti focus-stealing introdusă în Win2000). Trucul standard: trimiți un ALT press/release ca să resetezi lock-ul, apoi restore + SetForegroundWindow prin ctypes pe hwnd direct. + + Forțează maximize dacă fereastra nu e deja zoomed — `chart_window_region` + din calibrare presupune fereastra fullscreen, orice altă dimensiune + produce canary drift imediat. """ try: import pygetwindow as gw # type: ignore[import-untyped] @@ -232,7 +240,12 @@ def _focus_window_by_title(needle: str) -> str | None: # VK_MENU=0x12 (ALT), KEYEVENTF_KEYUP=0x02 — unlocks focus protection. user32.keybd_event(0x12, 0, 0, 0) user32.keybd_event(0x12, 0, 0x02, 0) - user32.ShowWindow(hwnd, 9) # SW_RESTORE + # Dacă e minimizată o scoatem; apoi forțăm maximize necondiționat. + # SW_RESTORE pe o fereastră maximizată o de-maximizează — niciodată nu-l + # folosim aici (chart_window_region presupune fullscreen). + if user32.IsIconic(hwnd): + user32.ShowWindow(hwnd, 9) # SW_RESTORE (doar unminimize) + user32.ShowWindow(hwnd, 3) # SW_SHOWMAXIMIZED user32.BringWindowToTop(hwnd) user32.SetForegroundWindow(hwnd) return win.title @@ -370,9 +383,16 @@ def _cmd_debug(args) -> None: cfg = Config.load_current(Path("configs")) capture = _build_capture(cfg) + if cfg.window_title: + title = _focus_window_by_title(cfg.window_title) + if title: + print(f"Focused window: {title!r}", flush=True) + else: + print(f"WARN: no window contains {cfg.window_title!r} — bring TradeStation to front manually", flush=True) + delay = getattr(args, "delay", 0.0) if delay > 0: - print(f"Bring TradeStation to front. Capturing in {delay:.0f}s...", flush=True) + print(f"Capturing in {delay:.0f}s...", flush=True) for i in range(int(delay), 0, -1): print(f" {i}...", flush=True) time.sleep(1.0) @@ -391,6 +411,27 @@ def _cmd_debug(args) -> None: cv2.imwrite(str(full_path), frame) print(f"captured frame: {frame.shape} → {full_path}") + if getattr(args, "canary", False): + from atm.vision import phash, hamming_hex + try: + canary_crop = crop_roi(frame, cfg.canary.roi) + except Exception as exc: + sys.exit(f"canary ROI crop failed: {exc}") + canary_path = out_dir / f"debug_canary_{ts}.png" + cv2.imwrite(str(canary_path), canary_crop) + live_hash = phash(canary_crop) + distance = hamming_hex(live_hash, cfg.canary.baseline_phash) + threshold = cfg.canary.drift_threshold + verdict = "DRIFT" if distance > threshold else "ok" + roi = cfg.canary.roi + print("---") + print(f"canary ROI: x={roi.x} y={roi.y} w={roi.w} h={roi.h}") + print(f"crop: {canary_crop.shape} → {canary_path}") + print(f"baseline: {cfg.canary.baseline_phash}") + print(f"live phash: {live_hash}") + print(f"distance: {distance}/{threshold} → {verdict}") + return + # crop dot_roi + run detector try: dot_crop = crop_roi(frame, cfg.dot_roi)