Marius Mutu 2c1dae14fc fix: forțează SetForegroundWindow prin ALT-key pentru anti focus-stealing
pygetwindow.activate() raporta succes dar Windows refuza silent ridicarea
ferestrei TS (confirmat în audit: window_focused cu titlul corect, dar
screenshot-ul manual tot prindea app-ul de deasupra). Trucul standard:
keybd_event(ALT) resetează focus-lock-ul, apoi SetForegroundWindow via
ctypes pe hwnd direct. Fallback la win.activate() dacă lipsește _hWnd.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:22:47 +03:00
2026-04-18 12:30:57 +03:00

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)
├── calibration/          # corpus auto-suficient pentru validare + regresie
│   ├── calibration_labels.json    # etichete per-frame pentru atm validate-calibration
│   ├── scenarios.json             # secvențe FSM (arm→prime→trigger etc.) pentru test_scenarios_regression.py
│   ├── frames/                    # PNG-uri numite {ts}_{color}.png, izolate de logs/fires și samples
│   └── README.md
├── 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 (tranzitoriu, se poate goli)
│   └── calibrate_capture_*.png / debug_*.png  # artefacte debug (gitignored)
├── samples/              # frame complet salvat automat la fiecare schimbare de culoare (tranzitoriu)
├── 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/                # 192 teste pytest (184 core + 8 scenarii regresie)
└── TODOS.md              # backlog P1/P2/P3

Instalare

Python 3.11+.

Windows (producție)

python -m venv .venv
.venv\Scripts\activate
pip install -e ".[windows]"
# → creează .venv\Scripts\atm.exe
atm --help

[windows] aduce mss, pygetwindow, pywin32. Fără venv, pip install -e ".[windows]" direct în Python-ul global funcționează la fel.

Pornire rapidă cu scriptul inclus — instalează automat la primul run:

atm.bat                        # prima rulare: pip install + atm run
atm.bat run --stop-at 23:00
atm.bat debug

WSL / Linux (dev + teste)

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

[dev] aduce pytest, pytest-cov, pytest-asyncio. Nu include dependențele Windows (mss, pygetwindow, pywin32) — nu rulează capture live.


Secrets

Credențialele Discord/Telegram NU se țin în TOML — trăiesc în .env la rădăcina proiectului:

cp .env.example .env
# apoi editezi .env cu valorile reale

Variabile necesare:

Variabilă Ce e
ATM_DISCORD_URL Webhook URL-ul Discord (canalul unde vin alertele)
ATM_TG_TOKEN Token-ul bot-ului Telegram (de la @BotFather)
ATM_TG_CHAT Chat ID (group-ul sau user-ul; prefix - pentru group)

.env e în .gitignore — secretele nu ajung pe git. configs/*.toml pot fi comise pe git (calibrare pură, safe to version).

La pornire, dacă .env e găsit, loader-ul printează pe stderr:

[atm.config] .env: loaded 3 vars (0 overridden by shell)

⚠️ Shell env wins peste .env. Dacă ai făcut export ATM_TG_TOKEN=... cândva în .bashrc / profil, aceasta override-uiește .env — verifică cu printenv | grep ATM_. Mesajul (N overridden by shell) te avertizează când se întâmplă.

Config-ul se refuză să pornească dacă:

  • lipsește oricare din cele 3 variabile (mesaj cu numele variabilei + hint către .env.example);
  • ATM_DISCORD_URL sau ATM_TG_TOKEN conține REPLACE_ME (ai copiat .env.example dar n-ai editat);
  • ATM_TG_CHAT nu-i numeric (opțional cu - la început pentru group).

Dev

pytest -q                        # toate testele (192+)
pytest tests/test_commands.py    # un modul specific
pytest tests/test_scenarios_regression.py -v   # scenarii FSM pe imagini reale
pytest -q --cov=atm --cov-report=term-missing   # cu coverage

Smoke-test fără Windows (stub de captură din samples/):

atm run --capture-stub --duration 0.05

Structura testelor:

Fișier Ce acoperă
test_commands.py parsing comenzi Telegram
test_config.py loader TOML, attach_screenshots
test_handle_tick.py loop principal, snapshot, FSM
test_main.py lifecycle, operating hours, canary, dispatcher
test_validate.py gate offline clasificare culori
test_canary.py drift + callback pauză
test_scenarios_regression.py secvențe FSM pe frame-uri reale (arm→prime→trigger, phase_skip, catchup, post-fire suppression)

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).

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. TOML-ul conține doar calibrare — secretele Discord/Telegram se țin în .env la rădăcina proiectului (vezi secțiunea Secrets de mai jos).

⚠️ 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

atm debug --delay 5

Ia un frame. Salvează logs/debug_full_<ts>.png, logs/debug_dot_roi_<ts>.png, logs/debug_annotated_<ts>.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.

atm validate-calibration calibration/calibration_labels.json

Format input (calibration/calibration_labels.json):

[
  {"path": "calibration/frames/20260420_171501_yellow.png",    "expected": "yellow"},
  {"path": "calibration/frames/20260420_172104_dark_red.png",  "expected": "dark_red"},
  {"path": "calibration/frames/20260420_173004_light_red.png", "expected": "light_red"}
]

Frame-urile sunt copiate în calibration/frames/ cu numele {timestamp}_{culoare}.png — numele reflectă ground truth-ul vizibil pe dot, nu label-ul de eveniment din logs/fires/. Directorul e auto-suficient: samples/ și logs/fires/ se pot goli oricând fără să afecteze validarea.

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ă

Trei surse de frame-uri, roluri distincte

Sursă Unde se salvează Cum se populează Folosit de
calibration/frames/ PNG-uri curate {ts}_{color}.png manual — copii din logs/fires/ doar cele verificate atm validate-calibration + test_scenarios_regression.py
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 sursă pentru calibration/frames/

calibration/ e singurul director persistent. Celelalte două se pot goli după ce ai extras ce-ți trebuie — tranzitorii prin natură.

Regresie FSM pe frame-uri reale

calibration/scenarios.json definește secvențe ordonate (arm → prime → trigger, phase_skip, catchup, suprimare dark_* post-fire) care refolosesc aceleași frame-uri. tests/test_scenarios_regression.py rulează fiecare secvență prin pipeline-ul real Detector → _handle_tick, asertând per pas: culoarea detectată, tranziția FSM (prev→next + reason + trigger), alertele emise prin notifier, și starea scheduler-ului (running/stopped).

Extensii fără cod nou: adaugi un scenariu în JSON și pytest-ul îl consumă automat (parametrizat pe id). Dacă scenariul cere o combinație de culori noi, copii frame-ul în calibration/frames/ cu numele {timestamp}_{culoare}.png.

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 calibration/calibration_labels.json pentru fiecare screenshot relevant, cu culoarea pe care ai văzut-o TU pe chart:
    {"path": "logs/fires/20260420_151234_ss.png", "expected": "dark_green", "note": "văzut live, ratat de bot"}
    
  3. Rulează validarea:
    atm validate-calibration calibration/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:

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 calibration/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.

Exemplu real — incidentul 2026-04-20/21 (culori saturate)

User a observat screenshot-uri poll periodice după ce un trigger BUY/SELL părea deja declanșat. Dovadă: logs/fires/20260420_214908_poll.png avea pixel verde pur (0, 255, 0) (trigger light_green) dar detector-ul îl clasifica UNKNOWN. Investigație: 27/114 PNG-uri din corpus ieșeau UNKNOWN pentru că paleta din 2026-04-18-1220.toml avea centrele celor patru culori luminoase prea întunecate — distanța până la pixelul real depășea toleranța de 60.

Fix aplicat în 2026-04-21-recalib.toml:

Culoare Centru vechi Pixel live observat Centru nou d(vechi)
turquoise (0, 153, 153) (0, 253, 253) (0, 253, 253) 141
yellow (153, 153, 0) (253, 253, 0) (253, 253, 0) 141
light_green (0, 171, 0) (0, 255, 0) (0, 255, 0) 84
light_red (171, 0, 0) (255, 0, 0) (255, 0, 0) 84

dark_green, dark_red, gray, background — neschimbate (nu ieșeau UNKNOWN).

Consecință invizibilă pentru user: fără trigger acceptat de FSM, starea rămânea blocată în PRIMED_*ScreenshotScheduler nu primea reason=fire/cooled/phase_skip/opposite_rearm → polling continuu la 3 min ore în șir.

După fix: corpus 27→0 UNKNOWN pe culorile luminoase (restul 9 sunt pixeli off-ROI crem, nu dot-uri). atm validate-calibration calibration/calibration_labels.json → 16/16 PASS.

Lesson learned: la recalibrare cu wizard-ul Tk, dacă folosești o imagine screenshot (nu captură live), pipeline-ul de saturation-snap poate rata pixelul cel mai saturat și să ia un dot ușor desaturat. Regulă: după wizard, verifică imediat cu atm validate-calibration pe un corpus cu toate 7 culorile. Dacă vreo culoare iese UNKNOWN, corectează manual în TOML cu pixelul real observat.

Rollback dacă ceva merge prost:

echo "2026-04-18-1220.toml" > configs/current.txt   # sau 2026-04-16-0703.toml

Sesiunea live

# Sesiunea de azi 16:3023: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_<color>.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):

[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
/h sau /help Listă scurtă a tuturor comenzilor disponibile

Doar allowed_chat_ids sunt acceptate. După 3 401 consecutive, poller-ul intră în mod degradat.


După sesiune

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/<current>.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:

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.jsonlevent=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.
Poll-uri periodice continuă deși un trigger BUY/SELL s-a afișat pe chart Trigger-ul a ieșit UNKNOWN (pixel saturat, paletă întunecată) → FSM blocat în PRIMED → scheduler nu primește fire/cooled/phase_skip Rulează atm validate-calibration calibration/calibration_labels.json. Dacă vreo culoare luminoasă iese UNKNOWN, actualizezi centrul RGB în TOML la pixelul real observat. Vezi incidentul 2026-04-20/21.

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)
Description
No description provided
Readme 10 MiB
Languages
Python 99.9%