From 34fde8328c1f44103d10727a5daa4d306df93eb9 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Thu, 16 Apr 2026 13:06:12 +0000 Subject: [PATCH] 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) --- README.md | 325 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 181 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index 1b0bbaf..63de472 100644 --- a/README.md +++ b/README.md @@ -1,179 +1,216 @@ # ATM — Automated Trading Monitor -Personal tool for the **M2D strategy** on TradeStation DIA/GLD charts with US30/XAUUSD execution on TradeLocker. The bot watches the colored dot strip produced by the *M2D MAPS* custom indicator and sends a Telegram/Discord notification (with chart screenshot + SL/TP levels) when a BUY or SELL trigger fires — so you execute the trade manually in TradeLocker instead of watching two screens. +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. -**Current phase: Faza 1 — notification-only.** No auto-execution until prop firm TOS has been audited (see `docs/phase2-prop-firm-audit.md`). +No auto-execution. Faza 2 (auto-execute) is blocked on prop-firm TOS audit — see `docs/phase2-prop-firm-audit.md`. --- -## Project structure +## Project layout ``` atm/ -├── pyproject.toml -├── configs/ # calibration configs (YYYY-MM-DD-HHMM.toml + current.txt) -├── logs/ # audit JSONL + dead-letter queue -├── samples/ # screenshots for dry-run validation -└── src/atm/ - ├── config.py # Config dataclass + loader - ├── detector.py # screenshot → color → state machine - ├── state_machine.py - ├── vision.py # color matching helpers - ├── levels.py # SL/TP pixel-to-price - ├── calibrate.py # Tkinter calibration wizard - ├── labeler.py # Tkinter sample labeler - ├── journal.py # trade journal (JSONL) - ├── report.py # weekly performance report - ├── audit.py # structured audit log - ├── canary.py # layout drift detection - ├── dryrun.py # replay saved screenshots - ├── notifier/ - │ ├── discord.py - │ └── telegram.py - └── main.py # unified CLI +├── 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 ``` --- -## Setup +## Install -### Prerequisites - -- Python 3.11+ -- Windows 10/11 (required for live capture; dry-run works on any OS) -- TradeStation running with a 3-minute DIA or GLD chart open - -### Install +Python 3.11+ required. Clone, then: ```bash -# Clone (internal repo) -git clone git@gitea.romfast.ro:romfast/atm.git -cd atm - -# Create venv and install -python -m venv .venv -.venv\Scripts\activate # Windows -pip install -e . - -# Windows-only extras (screen capture, window detection) -pip install -e ".[windows]" +pip install -e ".[windows]" # Windows: live capture + window focus +pip install -e . # Linux / macOS: dev / dryrun only (no live) +atm --help ``` -### Environment variables (notifiers) +`[windows]` pulls `mss`, `pygetwindow`, `pywin32`. -Create a `.env` file or set these in your shell before running: +--- + +## 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: ``` -ATM_DISCORD_WEBHOOK=https://discord.com/api/webhooks/... -ATM_TELEGRAM_TOKEN=123456789:AABBcc... -ATM_TELEGRAM_CHAT_ID=-100123456789 +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 ``` --- -## Calibration workflow +## DPI / multi-monitor notes -Calibration maps the TradeStation window layout to the config that drives detection. **Redo calibration whenever you resize the chart window, change DPI, or switch monitors.** - -### Step-by-step - -1. **Open TradeStation** with the 3-minute DIA chart. Maximise or snap to a fixed position. Do not resize it during the session. - -2. **Run the calibration wizard:** - - ```bash - atm calibrate - ``` - -3. **Pick window title** — type the exact string that appears in the TradeStation title bar (e.g. `DIA - 3 Min`). The bot uses this to locate the window via `pygetwindow`. - -4. **Mark the dot ROI** — a screenshot of the current window is shown. Click the top-left corner and the bottom-right corner of the M2D MAPS dot strip to define the region of interest. - -5. **Sample dot colours** — for each of the 7 dot colours (turquoise, yellow, dark green, dark red, light green, light red, gray), click a representative dot in the screenshot. The wizard records the RGB value and sets an initial tolerance of 30. - -6. **Set y-axis calibration points** — click on two price levels that appear as visible horizontal gridlines on the chart, then type the corresponding price for each. This calibrates the pixel-y → price mapping for SL/TP extraction. - -7. **Select canary region** — drag a small rectangle over a stable, unchanging part of the chart border (title bar strip works well). This becomes the baseline for canary drift detection. - -8. **Save** — the wizard writes `configs/YYYY-MM-DD-HHMM.toml` and updates `configs/current.txt`. Future runs load this config automatically. - -### Verify calibration - -```bash -atm dryrun --dir samples/ -``` - -Review the dry-run output. Every sample should classify to the expected dot colour. If misclassifications appear, re-run the calibration wizard or adjust tolerances manually in the TOML file. - ---- - -## Per-session operating checklist - -Before each trading window (NY open 16:30 RO, NY close 21:00 RO): - -- [ ] TradeStation open on 3-minute DIA chart, window not minimised -- [ ] Chart window is in the same position/size as when calibrated -- [ ] TradeLocker open in browser, instrument US30 loaded, position sizing ready -- [ ] Telegram / Discord notification channel visible on mobile or second screen -- [ ] Run `atm canary-check` — confirm no drift alert before starting the bot -- [ ] Start the monitor: `atm run` -- [ ] After the session: `atm journal add` to record trade outcome (or leave `outcome=open` to fill later) -- [ ] At week end: `atm report --week YYYY-WW` to review win rate and PnL in R - ---- - -## DPI and multi-monitor notes - -- **High-DPI displays:** Windows DPI scaling can shift pixel coordinates. Set TradeStation to "System (Enhanced)" DPI compatibility mode (right-click EXE → Properties → Compatibility → Change high DPI settings) OR set Python to DPI-unaware via `SetProcessDPIAware()` in the manifest. The calibration wizard and capture code both call `SetProcessDPIAware()` on start. - -- **Multiple monitors:** `mss` captures the monitor that contains the target window. The ROI offsets in the config are relative to the window's own top-left, so moving the window between sessions (but not resizing) is usually safe. Moving to a different monitor with a different DPI **requires recalibration**. - -- **Virtual desktops / remote desktop:** Screen capture via `mss` does not work through RDP (the window reports on-screen but the pixel data is black). Run the bot locally on the same physical machine as TradeStation. +- 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 -### Window not found +| 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. | +| 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 ``` -WindowNotFoundError: No window matching 'DIA - 3 Min' +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 ``` -**Causes and fixes:** - -- TradeStation is minimised — restore it to a visible state. -- The window title has changed — re-run `atm calibrate` and provide the exact current title string. Copy-paste from the title bar. -- DPI scaling changed the reported title encoding — confirm the title in `pygetwindow.getWindowsWithTitle("")` output. - -### Canary drift alert - -``` -CanaryDriftAlert: phash distance 12 > threshold 8 -``` - -The chart layout has shifted (e.g., chart scroll, zoom change, indicator redraw). **Do not trade until this is resolved.** - -1. Check TradeStation — scroll or zoom may have shifted the DOT ROI out of frame. -2. Reset the chart to the calibrated state (same zoom/scroll as during calibration). -3. If the layout change was intentional, re-run calibration. -4. To suppress a single false alarm without recalibrating, run `atm canary-reset` which re-samples the canary region baseline from the current screenshot. - -### Low confidence warnings - -``` -LowConfidence: cycle 5 — best match 'gray' dist=0.27 (threshold 0.20) -``` - -The sampled pixel is near the edge of a colour tolerance zone. Usually caused by: - -- Screenshot timing during a dot colour transition (rare on 3-min chart). -- Chart re-render artifact (scaling seam at ROI boundary). - -If this persists for more than 3 consecutive cycles, an alert is sent automatically. Verify the dot strip is fully visible and not clipped by another window. - -### Notification not received - -1. Check `logs/audit.jsonl` for the last cycle — look for `"notification_sent": false` and the `reason` field. -2. Verify webhook/token: `atm test-notify`. -3. Check network connectivity from the Windows machine to Discord/Telegram endpoints. +Exit code: `atm dryrun` exits 0 if gate passes, 1 otherwise. Other commands follow standard convention.