Commit Graph

31 Commits

Author SHA1 Message Date
Claude Agent
51e98ae3d3 fix(notify): switch Telegram parse_mode from Markdown to HTML
Underscores in alert text (dark_green, FIRE_BUY) broke Telegram's
legacy Markdown parser, causing ok:false → retries exhausted → failed.
HTML parse_mode is more robust and doesn't treat _ as italic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 23:01:28 +00:00
Claude Agent
840c23f74c feat(run): screenshot attach, Telegram ok:false fix, post-FIRE catchup guard
Three bundled fixes on the dispatch + FSM + notifier triangle:

1. Telegram silent-success bug: parse JSON body after 200 OK, raise on
   ok:false so FanoutNotifier retries + DLQs + stats surface the failure.
   Previously Discord succeeded while Telegram silently dropped.

2. Per-kind screenshot attach: new AlertsCfg dataclass with per-kind toggle
   (late_start, catchup, arm, prime, trigger). _save_annotated_frame helper
   extracted from inline FIRE block, threaded via Snapshot closure into
   _handle_tick. Failures audit-logged, never silent.

3. Post-FIRE catchup regression (d7305fb): residual dark_green/dark_red dots
   after a FIRE cycle look like startup-catchup from (color, state) alone.
   New fsm.fired_in_session(direction) gate suppresses synth-arm after a
   cycle already fired in that direction. Opposite direction unaffected.

Also: queue-overflow on_drop audit callback, periodic + shutdown heartbeat
stats per-backend, config back-compat (bool or dict for attach_screenshots).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 22:40:17 +00:00
Claude Agent
d7305fbbfc fix(run): drop first_accepted gate from catchup synth-arm
Catchup branch gated on first_accepted, but an earlier accepted gray tick
consumes the flag before a dark_* arrives, so the real prime-phase color
falls through to noise classification and no alert fires. Gate on
IDLE + dark_* alone — self-sufficient and correct.

Regression: 2 unit tests for _handle_tick + 1 integration test feeding
run_live a scripted gray→gray→dark_red→dark_red→light_red sequence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 18:54:03 +00:00
Claude Agent
f4b9000100 feat(run): per-frame detection log at logs/detections/YYYY-MM-DD.jsonl
Writes one JSONL line per detector.step() with ts, rgb, match_name,
distance, confidence, dot_found, window_found, accepted, color.
Captures UNKNOWN classifications and no-dot frames that today's
audit log skips, so the user can verify post-session what colors
the program actually saw.

Reuses AuditLog for daily rotation + buffering. Separate subdir
keeps audit.jsonl uncluttered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:48:27 +00:00
Claude Agent
e7369ca632 feat(run): arm + prime alerts, mid-session catchup, late-start guard
Notify on IDLE→ARMED and ARMED→PRIMED so the user gets staged warnings
before FIRE. If atm run starts mid-cycle on dark_green/dark_red, synth
a catchup arm so the prime alert still fires (audit tagged catchup:true).
If it starts on light_green/light_red, emit one late_start warn and skip
the FSM feed — FIRE already passed.

Extracted _handle_tick() as a pure helper (takes fsm + duck-typed
notifier/audit Protocols) so the dispatch is unit-testable without
mocking FanoutNotifier. 9 new tests cover arm, prime, refresh silence,
catchup synth-arm (+ audit), and late-start on both directions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:30:01 +00:00
Claude Agent
34fde8328c docs: rewrite README to match current CLI + workflow
Reflects everything that shipped after the initial teammate pass:
calibrate region-select flow, startup delays, auto-focus, canary startup
check, --start-at/--stop-at, atm debug, samples/ auto-save, fires/
annotated screenshots, Task Scheduler setup, current troubleshooting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:06:12 +00:00
Claude Agent
ec86f52f1f feat(run): auto-save corpus samples + annotated FIRE screenshots
- samples/: full frame saved every time accepted colour changes (enough
  diversity for the labelled corpus, no constant-N-seconds flood).
- logs/fires/: annotated frame saved on every BUY/SELL trigger, attached
  to the Discord/Telegram Alert so the push includes a visual.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:33:54 +00:00
Claude Agent
dec3b03d53 feat(run): auto-focus TradeStation by window_title at startup
pygetwindow.activate() brings the calibrated window to the foreground so
the user doesn't need to alt-tab during the startup-delay. Largest window
matching cfg.window_title (case-insensitive substring) wins. If minimized,
restore first. Failures are warnings, not errors — user can still focus
it manually during the countdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:24:52 +00:00
Claude Agent
3c4bc887c3 feat(run): --start-at HH:MM + --stop-at HH:MM for wall-clock scheduling
Usage: atm run --start-at 16:30 --stop-at 23:00
Sleeps until next occurrence of the start time, runs until stop-at. If
start-at is in the past today, rolls over to tomorrow. Duration flag is
overridden when --stop-at is given.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:21:44 +00:00
Claude Agent
e114941bb7 feat(run): --startup-delay + canary sanity check at startup
- 5s countdown before the loop starts so user can alt-tab TradeStation to
  the foreground and minimize whatever covers it.
- First frame triggers a canary phash check. Drift → WARN printed, clears
  auto-pause so user can Ctrl+C without the loop going silent. Canary
  status ('drift=X/Y' or 'capture_failed') is included in the startup
  ping so it's visible on Discord/Telegram.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:17:28 +00:00
Claude Agent
be7c4f82e8 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>
2026-04-16 07:16:14 +00:00
Claude Agent
4dac21b7c0 fix(vision): erode mask pre-CC to sever anti-aliasing bridges
With tol=25 all dots in the strip fused into one blob via 1px anti-aliased
bridges between adjacent dots → centroid landed mid-strip instead of on the
rightmost dot. Erosion (3x3 kernel, 2 iters) cleanly separates discrete
dots before connected-components labelling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:12:57 +00:00
Claude Agent
74b5d33c86 fix(vision): connected-components for rightmost-dot detection
Dots on M2D MAPS strip are so close that anti-aliased edge pixels bridge
adjacent dots column-counts → the previous walk-left approach merged the
entire strip into one cluster and picked its midpoint.

Connected components (8-connectivity) treats each dot as a separate blob
even when antialiased edges touch. We pick the blob with the largest
right-edge, then return its centroid. Robust, O(pixels), one opencv call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:08:28 +00:00
Claude Agent
0f430dae21 fix(vision): find_rightmost_dot returns cluster centre, not edge
Also: calibrate._sample_rgb now snaps to the most-saturated pixel within 15px
of the click, so rough clicks still pick up the dot's pure colour. Default
dot-colour tolerance bumped 30→60 to absorb anti-aliasing.

Test fixture _SAMPLED_RGB recomputed for the new 36/49 dilution (was 24/49
when sampling at the trailing edge).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:59:47 +00:00
Claude Agent
0a4f9793e9 fix(detector): source bg_rgb/bg_tol from cfg.colors.background when present
User's chart background is pure black (0,0,0) but detector hardcoded (18,18,18)
with tol=15. Gap pixels between dots (0,0,0) fell outside background tolerance,
so find_rightmost_dot locked onto a gap pixel rather than a dot. Now falls back
to the config's background spec if defined.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:53:55 +00:00
Claude Agent
e7189742bf feat(calibrate): --delay countdown + messagebox warning before capture
Same fix as atm debug: desktop snapshot happens several seconds after user
confirms, giving time to alt-tab TradeStation to the foreground and get
rid of terminal/IDE windows covering it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:49:07 +00:00
Claude Agent
780b1d67dd chore: ignore debug PNGs + logs/*.png
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:48:22 +00:00
Claude Agent
ecd1b23e11 feat(debug): add --delay countdown so user can bring target to front
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:48:07 +00:00
Claude Agent
f43f5ce93b feat(cli): atm debug — one-shot capture + detect + annotated PNG
Quick sanity check after calibration. Prints window/dot detection state,
classified colour + confidence, saves full/cropped/annotated frames to logs/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:45:01 +00:00
Claude Agent
562df0c395 feat(capture): save calibration region; runtime crops same area
Problem: region-select crop is in its own coord-frame, but runtime capture
used pygetwindow+mss with the window's coords → ROI coords mismatch, chart
not captured correctly.

Fix: region-select now returns (image, virtual-desktop region). Wizard saves
'chart_window_region' in config. At runtime, _build_capture() prefers region
mode: grabs the same virtual-desktop rectangle, so ROI coords line up.

Users who calibrated before this commit must re-run 'atm calibrate' OR add
chart_window_region to their TOML manually. If window moves, canary will
detect drift and auto-pause.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:38:02 +00:00
Claude Agent
c314fe0584 chore: ignore calibrate_capture debug PNGs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:29:22 +00:00
Claude Agent
cabe1634bc feat(calibrate): region-select mode as default capture
Window-level PrintWindow/mss returns blank on GPU-accelerated apps
(TradeStation, some trading terminals). Switch to full-desktop screenshot
+ drag-rectangle as the default method — reliable across all apps.

- Title prompt still asked (needed by 'atm run' to locate window at runtime).
- Captured region saved to logs/calibrate_capture_<ts>.png for verification.
- Old window-capture path kept as fallback if region-select is cancelled.
- Minor: silence pyright windll attr warning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:29:16 +00:00
Claude Agent
602fdbbc6e chore: ignore image*.png screenshots
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:25:10 +00:00
Claude Agent
f90e4477ed fix(calibrate): robust window capture with PrintWindow fallback + picker
- Enumerate all matching windows, let user pick the largest one if multiple.
- Try PrintWindow first (works with GPU-accelerated apps like TradeStation
  that render blank under plain mss capture).
- Detect blank/uniform captures and fall back to mss automatically.
- Save captured frame to logs/calibrate_capture_<ts>.png for debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:25:02 +00:00
Claude Agent
d19666ba0e fix(calibrate): force wizard window geometry; image now fills canvas
Root cause: Tk created default-size (~200x200) window; canvas rendered but cropped.
Fix: compute display dims + call root.geometry() explicitly + fill=both expand=True.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:19:32 +00:00
Claude Agent
f6ffeb22ec fix(calibrate): case-insensitive window title match; show visible titles on miss
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:15:22 +00:00
Claude Agent
c23a66fd0b feat(calibrate): full interactive Tk wizard
Replace skeletal wizard (window title only) with complete click-to-sample flow:
- Screenshot target window via mss+pygetwindow (Windows) or --screenshot PNG
- Single-canvas Tk wizard with scaled display + back/skip/save controls
- Stepwise clicks collect: dot_roi, all 7 dot colours (+ optional background),
  chart_roi, 2 y-axis reference points with prices, canary region
- Auto-compute canary baseline phash at save time
- Pull Discord/Telegram creds from ATM_DISCORD_URL/ATM_TG_TOKEN/ATM_TG_CHAT env
- Add --screenshot flag to 'atm calibrate' for non-Windows dev testing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:06:45 +00:00
Claude Agent
6cf4b7a570 chore: fix pyright type errors; add pyright config
- Cast res.color to DotColor in run_live loop.
- Add [tool.pyright] extraPaths to pyproject.toml for IDE resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:26:57 +00:00
Claude Agent
51dc1e403f docs: add example config + TODOS.md for Faza 2/P2 backlog
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:18:31 +00:00
Claude Agent
bf70ca3ac7 feat: complete Faza 1 implementation (105 tests green)
All 12 modules built per reviewed plan:
- detector, state_machine (5-state phased FSM), canary, levels Phase B
- notifier fanout (Discord + Telegram, bounded queue, retry, dead-letter)
- audit (JSONL daily rotation), journal, report (weekly R-multiple PnL)
- calibrate + labeler (Tk, lazy-imported), dryrun with acceptance gate
- unified CLI: atm calibrate|label|dryrun|run|journal|report

README + Phase 2 prop-firm TOS audit checklist included.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:17:41 +00:00
Claude Agent
9207197a56 initial: scaffold atm trading monitor (Faza 1)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:03:36 +00:00