# ATM — Automated Trading Monitor Personal Faza-1 tool for the **M2D strategy**. Watches the M2D MAPS colored-dot strip on a TradeStation chart, runs a phased state machine (ARMED→PRIMED→FIRE), pushes Discord + Telegram alerts with an annotated screenshot on BUY/SELL. You execute the trade manually in TradeLocker. No auto-execution. Faza 2 (auto-execute) is blocked on prop-firm TOS audit — see `docs/phase2-prop-firm-audit.md`. --- ## Project layout ``` atm/ ├── configs/ # calibration outputs + current.txt marker ├── logs/ │ ├── YYYY-MM-DD.jsonl # per-cycle audit log, rotates at local midnight │ ├── dead_letter.jsonl # alerts that failed after retries │ ├── fires/ # annotated screenshots, one per BUY/SELL trigger │ └── calibrate_capture_*.png / debug_*.png # gitignored debug artifacts ├── samples/ # full frames saved automatically on each colour change ├── src/atm/ # package │ ├── config.py # frozen dataclass + TOML loader │ ├── vision.py # ROI crop, phash, pixel↔price, Hough, connected-components │ ├── state_machine.py # 5-state phased FSM, per-direction lockout │ ├── detector.py # capture → crop → find rightmost dot → classify → debounce │ ├── canary.py # layout phash drift watchdog with pause-file gating │ ├── levels.py # Phase-B SL/TP line extraction │ ├── notifier/ # FanoutNotifier + Discord webhook + Telegram bot │ ├── audit.py # line-buffered JSONL, daily rotation │ ├── calibrate.py # Tk wizard (region-select + click-sample) │ ├── labeler.py # Tk UI → labels.json │ ├── dryrun.py # replay corpus, precision/recall gate │ ├── journal.py # trade entries │ ├── report.py # weekly R-multiple PnL │ └── main.py # unified CLI ├── tests/ # 105 pytest cases └── TODOS.md # P1/P2/P3 backlog, Faza 2 items ``` --- ## Install Python 3.11+ required. Clone, then: ```bash pip install -e ".[windows]" # Windows: live capture + window focus pip install -e . # Linux / macOS: dev / dryrun only (no live) atm --help ``` `[windows]` pulls `mss`, `pygetwindow`, `pywin32`. --- ## Calibration One-time per chart layout. Run on the machine that will do live capture. ```powershell atm calibrate # 3s default countdown; use --delay 10 if you want more time ``` Flow: 1. Dialog: substring of the chart window title (e.g. `TradeStation` or `DIA`). Stored in config for later auto-focus. 2. **"Ready?" message** → click OK → 3s countdown in terminal. Alt-tab TradeStation to the foreground and minimize anything covering it. 3. Full-desktop screenshot is captured and shown in a scaled Tk window. 4. **Drag a rectangle** over the chart (include the M2D MAPS strip). Enter = confirm. Esc = cancel. 5. Step-by-step clicks on the selected region: - M2D MAPS strip: top-left + bottom-right corners - One click on each of: turquoise, yellow, dark_green, dark_red, light_green, light_red, gray dot + chart background (8 total — "Skip" if a colour isn't currently visible) - Chart area: top-left + bottom-right (for Phase-B line detection) - Two known price levels on the y-axis (pixel y → enter price) - Canary region: top-left + bottom-right on a stable UI element (axis label, title bar) 6. **Save** → writes `configs/YYYY-MM-DD-HHMM.toml` + marker `configs/current.txt`. Pulls Discord/Telegram creds from env (`ATM_DISCORD_URL`, `ATM_TG_TOKEN`, `ATM_TG_CHAT`) if set; otherwise `REPLACE_ME` placeholders — edit the TOML manually. What gets written: - `chart_window_region = {x, y, w, h}` — virtual-desktop absolute rectangle. Runtime capture crops the same box, so the window must stay in that position. - `dot_roi`, `chart_roi`, `canary.roi` — coords relative to the selected region. - Per-colour RGB (sampled via saturation-snap within 15px of the click, mean of 5x5 around the snapped centre). - `y_axis` linear-interp pair. - `canary.baseline_phash` of the canary ROI. Sampling tips: - Click colours that are **actually present** in the current dot history. If a colour isn't visible, skip it — `atm dryrun` will tell you if the skipped value doesn't match real dots. - Default tolerance is 60 for dot colours, 25 for background. Tighten via TOML after dryrun if misclassifications creep in. --- ## Smoke-test after calibration ```powershell atm debug --delay 5 ``` Captures one frame. Saves `logs/debug_full_.png`, `logs/debug_dot_roi_.png`, `logs/debug_annotated_.png`. Prints: ``` window_found: True dot_found: True rgb: (114, 114, 114) classified: gray distance=24 confidence=0.79 accepted: True color=gray ``` Open the annotated PNG: yellow rectangle = `dot_roi`, red circle = detected dot. The circle should land on the ACTUAL rightmost colored dot in the M2D MAPS strip. If not: - Circle mid-strip → wrong window under the capture region (bring TradeStation to front). - Circle on a non-dot UI element → `dot_roi` boundaries capture too much; recalibrate narrower. - `color=None` + `UNKNOWN` → tolerances too tight OR sampled RGBs don't match real dots; recalibrate clicking on actual dots. --- ## Live run ```powershell # Today's session 16:30–23:00 Romania local atm run --start-at 16:30 --stop-at 23:00 # Indefinite atm run # Fixed duration (hours) atm run --duration 2 # Linux / headless smoke (reads samples/*.png in a loop) atm run --capture-stub --duration 0.05 ``` Startup sequence: 1. Wall-clock wait until `--start-at` (if set). 2. `pygetwindow.activate()` on the first window matching `cfg.window_title` — brings TradeStation to the foreground automatically (restores if minimised). 3. 5s countdown (`--startup-delay`). 4. Capture first frame + canary check. Status (`drift=X/Y` or `capture_failed`) is included in the startup ping. 5. **"ATM started" ping** on Discord + Telegram. 6. Main loop: every `loop_interval_s` (default 5s) — capture → canary → detect → state machine → maybe notify → maybe Phase-B. 7. At `--stop-at` (or `--duration`): **"ATM stopped" ping**, then exit. Per-cycle behaviour: - Canary drift → auto-pause (logs `paused`, skips detection). Clear by running `atm run` again with the pause-file removed. - Detector reports UNKNOWN → stays in current state (logged as `noise`). - Colour change → full frame saved to `samples/YYYYMMDD_HHMMSS_.png` (for corpus). - FIRE (BUY/SELL, not locked) → annotated PNG saved to `logs/fires/`, attached to the alert, `LevelsExtractor` armed. - Phase-B complete → "Levels SL=… TP1=… TP2=…" push. - Heartbeat every `heartbeat_min` minutes. Keep PowerShell minimized during the session so it doesn't cover TradeStation. --- ## After the session ```powershell atm label samples # Tk UI — label each saved frame with true dot colour atm dryrun samples # replay through detector + FSM; exits 0 if precision=100%, recall>=95% ``` If the gate fails, tune per-colour `tolerance` in `configs/.toml`, or recalibrate colour samples that didn't match. Re-run `atm dryrun` until it passes. Only then do you trust live signals. Trade record-keeping: ```powershell atm journal # interactive entry after a real trade atm report --week 2026-16 # weekly win rate + R PnL + slippage ``` --- ## DPI / multi-monitor notes - Calibration region is virtual-desktop-absolute; runtime capture uses the same rectangle. **Don't move the TradeStation window** after calibrating. Canary will catch drift and pause automatically. - Changing DPI scaling or moving to a different monitor with different DPI → recalibrate. - RDP / virtual desktops: `mss` can return black frames over RDP. Run locally on the same physical machine as TradeStation. --- ## Troubleshooting | Symptom | Likely cause | Fix | |---|---|---| | `capture_failed` in startup ping | `chart_window_region` references coords off-screen (different monitor layout) | Recalibrate. | | Startup canary `drift=X/8` with X >> 8 | Wrong window is in the capture region | Make sure TradeStation is the window at `cfg.chart_window_region`. Relaunch. | | `WARN: no window contains 'xxx'` at startup | `cfg.window_title` substring matches nothing | Edit `window_title` in TOML to a substring that's unique to TradeStation. | | No alerts even after trigger ought to fire | Check `logs/YYYY-MM-DD.jsonl` for `event=tick` entries — are colours accepted? Is `trigger` ever set? | If always UNKNOWN → tolerances too tight. If `trigger` but `locked=true` → lockout from prior fire, normal. | | Discord OK, Telegram silent (or vice versa) | `logs/dead_letter.jsonl` contains failed alerts with error | Fix credentials in TOML, restart. | | Heartbeat shows `telegram: failed > 0` | Telegram returned `ok:false` (bot blocked, invalid chat_id, parse error) | Check `logs/dead_letter.jsonl` for the `error_str` / `description` field. Common: bot never started by user in Telegram, or wrong `chat_id` flavor (channel vs group vs DM). | | Debug circle on mid-strip instead of right edge | Anti-aliasing bridges dots in the mask | Already fixed via erosion+connected-components — ensure `git pull` is current. | | Wizard window is tiny / image not visible | Tk geometry default on Windows | Already fixed — `git pull`. Image is scaled to fit screen. | --- ## Windows Task Scheduler (production) For hands-off daily runs surviving reboots: 1. Task Scheduler → Create Task → name `ATM M2D Monitor` 2. **General**: "Run only when user is logged on", "Run with highest privileges" 3. **Triggers**: New → Daily, Start `16:30` 4. **Actions**: New → Program `C:\path\to\python.exe`, Arguments `-m atm run --stop-at 23:00`, Start in `D:\PROIECTE\atm` 5. **Conditions**: uncheck "Start only if AC power" (if laptop) 6. **Settings**: "If task runs longer than 7 hours → stop" Click-right → Run to test manually. Manual DST-change check twice a year (Mar / Oct first week). --- ## Quick command reference ``` atm calibrate [--screenshot PATH] [--delay SEC] # Tk wizard atm debug [--delay SEC] # one-shot capture + detect atm label SAMPLES_DIR # Tk labeling atm dryrun SAMPLES_DIR # corpus gate atm run [--duration H] [--start-at HH:MM] [--stop-at HH:MM] [--startup-delay SEC] [--capture-stub] atm journal [--file PATH] # interactive trade entry atm report [--week YYYY-WW] [--file PATH] # weekly summary ``` Exit code: `atm dryrun` exits 0 if gate passes, 1 otherwise. Other commands follow standard convention.