# ATM — Monitor Automat de Trading Tool personal pentru strategia **M2D**. Urmărește banda de puncte colorate M2D MAPS de pe un chart TradeStation, rulează o mașină de stări pe faze (ARMED → PRIMED → FIRE) și trimite alerte pe Discord + Telegram cu screenshot adnotat la fiecare semnal BUY/SELL. **Execuția trade-ului o faci tu manual în TradeLocker.** Fără execuție automată. Faza 2 (auto-execute) e blocată de auditul TOS prop-firm — vezi `docs/phase2-prop-firm-audit.md`. --- ## Cum e organizat proiectul ``` atm/ ├── configs/ # calibrări + current.txt (marcaj care config e activ) ├── logs/ │ ├── YYYY-MM-DD.jsonl # audit zilnic, se rotește la miezul nopții local │ ├── dead_letter.jsonl # alerte care au eșuat după retries │ ├── fires/ # screenshot-uri adnotate, unul per trigger BUY/SELL │ └── calibrate_capture_*.png / debug_*.png # artefacte debug (gitignored) ├── samples/ # frame complet salvat automat la fiecare schimbare de culoare ├── src/atm/ # pachetul Python │ ├── config.py # dataclass + loader TOML │ ├── vision.py # crop ROI, phash, pixel↔preț, Hough, componente conectate │ ├── state_machine.py # FSM 5 stări + lockout per direcție │ ├── detector.py # capture → crop → găsește dot-ul rightmost → clasifică → debounce │ ├── canary.py # watchdog layout via phash drift + flag de pauză │ ├── levels.py # extracție SL/TP pe Faza-B │ ├── notifier/ # FanoutNotifier + webhook Discord + bot Telegram │ ├── audit.py # JSONL line-buffered, rotație zilnică │ ├── calibrate.py # wizard Tk (selectează regiune + click pe culori) │ ├── labeler.py # UI Tk → labels.json │ ├── dryrun.py # replay pe corpus, gate precision/recall │ ├── validate.py # gate offline de clasificare a culorilor │ ├── journal.py # înregistrări trade-uri │ ├── report.py # raport săptămânal PnL în R │ └── main.py # CLI unificat ├── tests/ # 184 teste pytest └── TODOS.md # backlog P1/P2/P3 ``` --- ## Instalare Python 3.11+. ```bash pip install -e ".[windows]" # Windows: capture live + focus fereastră pip install -e ".[dev]" # Linux/macOS/WSL: doar dev + teste (fără capture) atm --help ``` **WSL/Linux:** recomandat să folosești un virtualenv local: ```bash python3 -m venv .venv source .venv/bin/activate pip install -e ".[dev]" ``` `[windows]` aduce `mss`, `pygetwindow`, `pywin32` (nu le pune pe WSL). --- ## Calibrare Se face o singură dată per layout de chart. Trebuie să ruleze pe mașina pe care face capture live (Windows, fizic — nu RDP/virtual). ```powershell atm calibrate # countdown 3s default; pune --delay 10 dacă vrei mai mult timp ``` Flow: 1. Dialog: substring din titlul ferestrei chart-ului (ex. `TradeStation` sau `DIA`). Se salvează în config pentru auto-focus ulterior. 2. **Mesaj "Ready?"** → click OK → countdown 3s în terminal. Alt-tab pe TradeStation, minimizează tot ce-l acoperă. 3. Se face screenshot full-desktop, apare o fereastră Tk scalată. 4. **Trage un dreptunghi** peste chart (include și banda M2D MAPS). Enter = confirmă. Esc = anulează. 5. Click pas cu pas pe regiunea selectată: - M2D MAPS strip: colț stânga-sus + colț dreapta-jos - Un click pe fiecare culoare: turquoise, yellow, dark_green, dark_red, light_green, light_red, gray + background (8 total — "Skip" dacă o culoare nu-i vizibilă acum) - Chart: colț stânga-sus + colț dreapta-jos (pentru detecția de linii în Faza-B) - Două prețuri cunoscute pe axa Y (pixel y → introduci prețul) - Canary: colț stânga-sus + colț dreapta-jos pe un element UI **stabil** (etichetă axă, bară titlu) 6. **Save** → scrie `configs/YYYY-MM-DD-HHMM.toml` + marcaj `configs/current.txt`. Preia credențialele Discord/Telegram din env (`ATM_DISCORD_URL`, `ATM_TG_TOKEN`, `ATM_TG_CHAT`) dacă sunt setate; altfel pune `REPLACE_ME` — editezi TOML-ul manual. ### ⚠️ Reguli critice la calibrare (evită incidentul 2026-04-17) **1. Click EXCLUSIV pe dot-ul din DREAPTA al strip-ului.** Banda M2D MAPS e istoric: dot-ul din dreapta = activ/curent, restul sunt mai vechi. TradeStation desenează dot-ul activ mai strălucitor decât cele vechi. Detector-ul live citește MEREU dot-ul din dreapta. Dacă dai click pe unul din stânga, culoarea calibrată e mai întunecată decât realitatea → clasificare greșită live (dark_red poate ajunge citit ca light_red, de exemplu). **2. Canary pe un pixel STATIC.** NU pune regiunea canary peste: volume bar, preț curent, ceas/timestamp. Orice se schimbă natural în acea zonă declanșează drift-pause silent → bot-ul se oprește din detecție fără alertă vizibilă (asta s-a întâmplat la 22:25 pe 17.04, drift=129). Alege: o etichetă de axă, un titlu de panel, un colț de bordură. **3. Calibrează în mijlocul unei sesiuni active**, nu dimineața înainte de deschidere. Dot-urile sunt clar vizibile și reflectă exact aceleași setări de rendering ca la live. ### Ce scrie în TOML - `chart_window_region = {x, y, w, h}` — dreptunghi absolut virtual-desktop. Capture-ul la runtime crop-ează exact aceeași cutie, deci fereastra **nu trebuie mutată** după calibrare. - `dot_roi`, `chart_roi`, `canary.roi` — coordonate relative la regiunea selectată. - RGB per culoare (eșantionat cu saturation-snap într-o rază de 15px de click, media unui box 5x5 în jurul pixelului snapped). - `y_axis` — pereche de interpolare liniară. - `canary.baseline_phash` al ROI-ului canary. Tips de sampling: - Click pe culori **chiar vizibile acum** în istoricul dot-urilor. Dacă o culoare nu-i vizibilă, skip — `atm dryrun` îți zice dacă valoarea ratată nu se potrivește cu dot-uri reale. - Tolerance default: 60 pentru dot-uri, 25 pentru background. Strângi în TOML după dryrun dacă apar misclasificări. --- ## Smoke-test după calibrare ```powershell atm debug --delay 5 ``` Ia un frame. Salvează `logs/debug_full_.png`, `logs/debug_dot_roi_.png`, `logs/debug_annotated_.png`. Tipărește: ``` window_found: True dot_found: True rgb: (114, 114, 114) classified: gray distance=24 confidence=0.79 accepted: True color=gray ``` Deschizi PNG-ul adnotat: dreptunghi galben = `dot_roi`, cerc roșu = dot detectat. Cercul trebuie să pice pe **dot-ul colorat cel mai din dreapta** din banda M2D MAPS. Dacă nu: - Cerc la mijloc de strip → alt window e sub regiunea de capture (adu TradeStation în față). - Cerc pe element UI non-dot → `dot_roi` prea larg; recalibrează mai îngust. - `color=None` + `UNKNOWN` → tolerances prea strânse SAU RGB-urile eșantionate nu se potrivesc cu dot-urile reale; recalibrează cu click pe dot-uri reale. --- ## Validare offline a calibrării Verifici dacă calibrarea actuală clasifică corect un set de frame-uri etichetate manual, **fără să aștepți sesiunea live**. Esențial după orice recalibrare. ```bash atm validate-calibration samples/calibration_labels.json ``` Format input (`samples/calibration_labels.json`): ```json [ {"path": "logs/fires/20260417_201500_arm_sell.png", "expected": "yellow", "note": "primul arm"}, {"path": "logs/fires/20260417_205302_ss.png", "expected": "dark_red"}, {"path": "logs/fires/20260417_210441_ss.png", "expected": "light_red"} ] ``` Output: per fiecare frame PASS/FAIL + culoarea detectată + top 3 candidați după distanță RGB + sugestii de pixel pentru misclasificări. Exit code: - `0` — 100% PASS (poți porni live în siguranță) - `1` — cel puțin un FAIL - `2` — input invalid/lipsă ### Două corpus-uri, două scopuri | Corpus | Unde se salvează | Cum se populează | Folosit de | |---|---|---|---| | `samples/` | frame complet la fiecare **schimbare de culoare** detectată | automat de `atm run` | `atm label` + `atm dryrun` | | `logs/fires/` | screenshot adnotat la fiecare alertă BUY/SELL, `/ss` manual, **interval automat `/3`** | manual sau scheduler | `atm validate-calibration` | **Flow A — calibrare fină cu screenshots automate (`/3`)** Util când vrei să acumulezi repede frame-uri din culori reale, fără să aștepți schimbări de culoare. 1. **În sesiunea live**, trimite `/3` în Telegram → bot-ul face screenshot automat la 3 minute și îl salvează în `logs/fires/*_ss.png`. Oprești cu `/stop`. 2. **După sesiune**, adaugi intrări în `samples/calibration_labels.json` pentru fiecare screenshot relevant, cu culoarea pe care ai văzut-o TU pe chart: ```json {"path": "logs/fires/20260420_151234_ss.png", "expected": "dark_green", "note": "văzut live, ratat de bot"} ``` 3. **Rulează validarea:** ```bash atm validate-calibration samples/calibration_labels.json ``` 4. **Interpretează rezultatul:** - **Toate PASS** → calibrarea ține, continui live fără modificări. - **Măcar un FAIL** → output-ul îți arată pixelul real (ex. `RGB(128, 0, 0)`), centrul curent din TOML (ex. `dark_red RGB(83, 0, 0)`) și distanța. Două opțiuni: - **Fix tactic rapid:** editezi TOML-ul direct, muți centrul culorii aproape de pixelul observat. Rulezi iar `validate-calibration`. Te oprești când e PASS. - **Fix complet:** la următoarea sesiune live completă, rulezi `atm calibrate` de la zero pe Windows, cu **disciplina cele 3 reguli critice de mai sus** (rightmost dot, pixel static pentru canary, în timpul unei sesiuni active). 5. **Acumulezi mai multe samples în timp.** Obiectiv: 2-3 intrări per culoare în `calibration_labels.json`. Cu cât fișierul are mai multe etichete, cu atât calibrarea următoare e validată mai solid. **Flow B — gate de precizie pe corpus de schimbări de culoare** `atm run` salvează automat în `samples/` un frame complet la fiecare schimbare de culoare detectată. După sesiune: ```powershell atm label samples # UI Tk — etichetezi fiecare frame cu culoarea reală văzută pe chart atm dryrun samples # replay prin detector + FSM; exit 0 dacă precision=100%, recall≥95% ``` Dacă gate-ul pică, ajustezi `tolerance` per culoare în TOML sau corectezi eșantioanele nepotrivite, apoi rulezi iar `atm dryrun` până trece. ### Workflow de corectare iterativă (când apare o alertă greșită live) Scenariu: ai rulat o sesiune live, ai văzut pe chart o culoare pe care bot-ul n-a detectat-o (sau a detectat greșit). 1. **În timpul sesiunii** — două opțiuni pentru a captura dovezi: - `/ss` în Telegram → un screenshot instant în `logs/fires/` - `/3` în Telegram → screenshots automate la 3 min în `logs/fires/` (util dacă nu ești la monitor continuu); oprești cu `/stop` 2. **După sesiune**, adaugi intrările relevante în `samples/calibration_labels.json` cu culoarea corectă și rulezi `atm validate-calibration` (Flow A de mai sus). 3. Dacă apar FAIL-uri, aplici fix tactic în TOML sau recalibrezi complet. ### Exemplu real — incidentul 2026-04-17 La 20:53 s-a afișat un dark_red pe chart dar bot-ul l-a citit ca light_red (alertă ratată). Root cause: calibrarea anterioară (`2026-04-16-0703.toml`) a fost făcută dând click pe dot-uri istorice (mai întunecate), nu pe dot-ul activ din dreapta. Fix aplicat în `2026-04-18-1220.toml`, pe bază de evidență live: | Culoare | Centru vechi | Pixel live observat | Centru nou | |---|---|---|---| | dark_red | (83, 0, 0) | (128, 0, 0) | **(128, 0, 0)** | | light_red | (153, 0, 0) | (171, 0, 0) | **(171, 0, 0)** | | dark_green | (0, 77, 0) | — | **(0, 122, 0)** (ajustat proporțional: +45 pe G) | | light_green | (0, 153, 0) | — | **(0, 171, 0)** (ajustat proporțional: +18 pe G) | yellow, turquoise, gray, background — lăsate neschimbate (nu am dovezi live care să justifice ajustarea). După fix: `atm validate-calibration` → 3/3 PASS, confidence 1.00 pe ambele roșuri. **Rollback** dacă ceva merge prost: ```bash echo "2026-04-16-0703.toml" > configs/current.txt ``` --- ## Sesiunea live ```powershell # Sesiunea de azi 16:30–23:00 România local atm run --start-at 16:30 --stop-at 23:00 # Fără limită atm run # Durată fixă (ore) atm run --duration 2 # Linux/WSL smoke (rulează pe fișiere din samples/) atm run --capture-stub --duration 0.05 ``` Startup: 1. Așteptare wall-clock până la `--start-at` (dacă e setat). 2. `pygetwindow.activate()` pe prima fereastră care conține `cfg.window_title` — aduce TradeStation în față (restaurează dacă-i minimizată). 3. Countdown 5s (`--startup-delay`). 4. Primul frame + check canary. Status (`drift=X/Y` sau `capture_failed`) e inclus în ping-ul de start. 5. **Ping "ATM started"** pe Discord + Telegram. 6. Loop principal: la fiecare `loop_interval_s` (default 5s) — capture → canary → detect → FSM → poate notifică → poate Faza-B. 7. La `--stop-at` (sau `--duration`): **ping "ATM stopped"**, apoi exit. Comportament per ciclu: - Drift canary → auto-pause + **alertă Telegram single-shot** (`⚠️ Canary drift=N — monitorizare pauzată`). Anulezi cu `/resume force` în Telegram, sau repornești cu flag-ul de pauză șters. - Detector raportează UNKNOWN → rămâne în starea curentă (loghează `noise`). - Schimbare de culoare → frame complet salvat în `samples/YYYYMMDD_HHMMSS_.png` (pentru corpus). - FIRE (BUY/SELL, nu locked) → PNG adnotat salvat în `logs/fires/`, atașat la alertă, `LevelsExtractor` armed. - **Phase-skip backstop** (`fire_on_phase_skip=true` default) → ARMED → light_red/light_green direct (dark_* ratat) emite totuși alertă `⚠️ PHASE SKIP` cu screenshot. Lockout-ul FSM previne spam. - Faza-B completă → push "Levels SL=… TP1=… TP2=…". - Heartbeat la fiecare `heartbeat_min` minute. Ține PowerShell minimizat în timpul sesiunii ca să nu acopere TradeStation. ### Fereastra orelor de trading Configurezi din TOML (sursă adevăr: NYSE local, timezone-aware — DST-ul e gestionat automat): ```toml [options.operating_hours] enabled = true timezone = "America/New_York" # validat fail-fast la load weekdays = ["MON", "TUE", "WED", "THU", "FRI"] start_hhmm = "09:30" # deschidere NYSE stop_hhmm = "16:00" # închidere NYSE ``` Tick-urile din afara ferestrei sunt skipped (logged doar la tranziție). La traversarea boundary-ului bot-ul emite `market_open` / `market_closed` în Telegram — o singură dată per tranziție. **Pornirea în-fereastră nu emite alertă spurioasă.** Override din CLI (bat TOML-ul): ``` atm run --tz America/New_York --weekdays MON,TUE,WED,THU,FRI --oh-start 09:30 --oh-stop 16:00 ``` > `--oh-start / --oh-stop` sunt **diferite** de `--start-at / --stop-at`. > `--start-at / --stop-at` = wall-clock session bounds (când pornește procesul și când se oprește). > `--oh-start / --oh-stop` = fereastra NYSE în care detecția rulează efectiv în interiorul sesiunii. > Se combină. ### Comenzi Telegram Trimiți în chat-ul bot-ului: | Comandă | Efect | |---|---| | `/ss` sau `/screenshot` | Screenshot acum | | `/status` | Stare FSM + motiv pauză + fereastră open/closed | | `/pause` | Suspendă detecția (heartbeat-urile continuă) | | `/resume` | Elimină DOAR pauza user. Dacă Canary e drift-paused, **rămâne paused** — folosește `/resume force` | | `/resume force` | Elimină și drift-pause-ul canary (după recalibrare) | | `/3` sau `/interval 3` | Interval auto-screenshot = 3 min | | `/stop` | Oprește scheduler-ul de screenshot | Doar `allowed_chat_ids` sunt acceptate. După 3 `401` consecutive, poller-ul intră în mod degradat. --- ## După sesiune ```powershell atm label samples # UI Tk — etichetezi fiecare frame salvat cu culoarea reală atm dryrun samples # replay prin detector + FSM; exit 0 dacă precision=100%, recall≥95% ``` Dacă gate-ul pică, ajustezi `tolerance` per culoare în `configs/.toml`, sau recalibrezi eșantioanele care n-au potrivit. Rulezi iar `atm dryrun` până trece. **Numai atunci ai încredere în semnalele live.** Pentru calibrare fină a clasificării de culori (Flow A cu `/3`), vezi secțiunea **Validare offline a calibrării** de mai sus. Evidență trade-uri: ```powershell atm journal # înregistrare interactivă după un trade real atm report --week 2026-16 # win rate săptămânal + PnL în R + slippage ``` --- ## Note DPI / multi-monitor - Regiunea din calibrare e absolută virtual-desktop; runtime capture folosește același dreptunghi. **Nu muta fereastra TradeStation** după calibrare. Canary prinde drift-ul și pauzează automat. - Schimbi DPI scaling sau muți pe un alt monitor cu DPI diferit → recalibrezi. - RDP / desktop virtual: `mss` poate returna frame-uri negre peste RDP. Rulează local pe aceeași mașină fizică pe care e TradeStation. --- ## Troubleshooting | Simptom | Cauză probabilă | Fix | |---|---|---| | `capture_failed` în ping-ul de start | `chart_window_region` referă coords off-screen (alt layout monitor) | Recalibrează. | | Canary la startup arată `drift=X/8` cu X ≫ 8 | Alt window e în regiunea de capture | TradeStation trebuie să fie ferestra la `cfg.chart_window_region`. Relansează. | | `WARN: no window contains 'xxx'` la start | `cfg.window_title` nu prinde nimic | Editează `window_title` în TOML cu un substring unic pentru TradeStation. | | Nu vin alerte deși ar trebui | Verifică `logs/YYYY-MM-DD.jsonl` — `event=frame` au culoare acceptată? `trigger` setat? | Dacă mereu UNKNOWN → tolerances prea strânse SAU RGB-urile calibrate nu se potrivesc. Rulează `atm validate-calibration`. Dacă `trigger` dar `locked=true` → lockout de la fire anterior, normal. | | Alertă pe culoare greșită (ex. dark_red → light_red) | Calibrarea a luat dot istoric, nu activ | Rulează `atm validate-calibration`. Corectezi tactic în TOML sau recalibrezi cu regula rightmost dot. | | Discord OK, Telegram tace (sau invers) | `logs/dead_letter.jsonl` are alertele eșuate + eroarea | Fixezi credențiale în TOML, restart. | | Heartbeat arată `telegram: failed > 0` | Telegram a răspuns `ok:false` | Check `logs/dead_letter.jsonl` pentru `error_str` / `description`. Comun: bot-ul nu-a fost pornit de user în Telegram, sau `chat_id` greșit (channel vs group vs DM). | | Bot-ul "moare" după N ore, heartbeat merge dar comenzile nu răspund | Era bug-ul de hang din 2026-04-17 — drain coadă de comenzi sărit când Canary paused | Fixat în `c5024ce`. Update git pull. | --- ## Windows Task Scheduler (producție) Pentru rulare automată zilnică care supraviețuiește reboot-urilor: 1. Task Scheduler → Create Task → nume `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**: debifează "Start only if AC power" (dacă e laptop) 6. **Settings**: "If task runs longer than 7 hours → stop" Click-right → Run, să testezi manual. Check DST schimbare de două ori pe an (prima săptămână din martie / octombrie). --- ## Referință rapidă comenzi ``` atm calibrate [--screenshot PATH] [--delay SEC] # wizard Tk atm debug [--delay SEC] # one-shot capture + detect atm label SAMPLES_DIR # etichetare Tk atm dryrun SAMPLES_DIR # gate pe corpus atm validate-calibration LABEL_FILE.json # gate offline clasificare culori atm run [--duration H] [--start-at HH:MM] [--stop-at HH:MM] [--startup-delay SEC] [--capture-stub] [--tz TZNAME] [--weekdays MON,TUE,...] [--oh-start HH:MM] [--oh-stop HH:MM] atm journal [--file PATH] # înregistrare interactivă atm report [--week YYYY-WW] [--file PATH] # raport săptămânal ``` Exit codes: - `atm dryrun` — 0 pass, 1 fail. - `atm validate-calibration` — 0 toate PASS, 1 orice FAIL, 2 input invalid. - Restul: standard. --- ## Evenimente audit Scrise în `logs/YYYY-MM-DD.jsonl`. Cele adăugate recent: | Event | Payload | Când | |---|---|---| | `canary_drift_paused` | `distance` | Primul tick cu drift după o stare curată; emite alertă Telegram | | `user_paused` | — | `/pause` primit | | `user_resumed` | `was_drift`, `was_user`, `force` | `/resume` sau `/resume force` | | `market_open` / `market_closed` | `reason` | Boundary fereastră operating-hours (o dată per tranziție; **nu** la startup) | | `phase_skip_fire` | `direction` | Alertă backstop când ARMED→light_* direct | | `command_error` | `action`, `error` | Excepție la dispatch (izolată de loop-ul de detecție) |