docs(readme): rescriere completă în română + workflow validate-calibration
- Tradus întreg README-ul din engleză în română, simplu și clar.
- Secțiune nouă "Reguli critice la calibrare" care codifică cele 3 lecții
din incidentul 2026-04-17: click pe rightmost dot, canary pe pixel static,
calibrare în sesiune activă.
- Workflow detaliat de corectare iterativă cu validate-calibration: cum
colectezi samples în timpul sesiunii live (prin /ss), cum actualizezi
calibration_labels.json, ce faci când FAIL, rollback.
- Exemplu real cu tabelul de ajustări aplicate în 2026-04-18-1220.toml
(dark_red, light_red din evidență live; dark_green, light_green
ajustate proporțional +45/+18 pe canalul dominant).
- Troubleshooting: două linii noi (alertă pe culoare greșită, hang după
N ore rezolvat în c5024ce).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
448
README.md
448
README.md
@@ -1,98 +1,117 @@
|
|||||||
# ATM — Automated Trading Monitor
|
# ATM — Monitor Automat de Trading
|
||||||
|
|
||||||
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.
|
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.**
|
||||||
|
|
||||||
No auto-execution. Faza 2 (auto-execute) is blocked on prop-firm TOS audit — see `docs/phase2-prop-firm-audit.md`.
|
Fără execuție automată. Faza 2 (auto-execute) e blocată de auditul TOS prop-firm — vezi `docs/phase2-prop-firm-audit.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Project layout
|
## Cum e organizat proiectul
|
||||||
|
|
||||||
```
|
```
|
||||||
atm/
|
atm/
|
||||||
├── configs/ # calibration outputs + current.txt marker
|
├── configs/ # calibrări + current.txt (marcaj care config e activ)
|
||||||
├── logs/
|
├── logs/
|
||||||
│ ├── YYYY-MM-DD.jsonl # per-cycle audit log, rotates at local midnight
|
│ ├── YYYY-MM-DD.jsonl # audit zilnic, se rotește la miezul nopții local
|
||||||
│ ├── dead_letter.jsonl # alerts that failed after retries
|
│ ├── dead_letter.jsonl # alerte care au eșuat după retries
|
||||||
│ ├── fires/ # annotated screenshots, one per BUY/SELL trigger
|
│ ├── fires/ # screenshot-uri adnotate, unul per trigger BUY/SELL
|
||||||
│ └── calibrate_capture_*.png / debug_*.png # gitignored debug artifacts
|
│ └── calibrate_capture_*.png / debug_*.png # artefacte debug (gitignored)
|
||||||
├── samples/ # full frames saved automatically on each colour change
|
├── samples/ # frame complet salvat automat la fiecare schimbare de culoare
|
||||||
├── src/atm/ # package
|
├── src/atm/ # pachetul Python
|
||||||
│ ├── config.py # frozen dataclass + TOML loader
|
│ ├── config.py # dataclass + loader TOML
|
||||||
│ ├── vision.py # ROI crop, phash, pixel↔price, Hough, connected-components
|
│ ├── vision.py # crop ROI, phash, pixel↔preț, Hough, componente conectate
|
||||||
│ ├── state_machine.py # 5-state phased FSM, per-direction lockout
|
│ ├── state_machine.py # FSM 5 stări + lockout per direcție
|
||||||
│ ├── detector.py # capture → crop → find rightmost dot → classify → debounce
|
│ ├── detector.py # capture → crop → găsește dot-ul rightmost → clasifică → debounce
|
||||||
│ ├── canary.py # layout phash drift watchdog with pause-file gating
|
│ ├── canary.py # watchdog layout via phash drift + flag de pauză
|
||||||
│ ├── levels.py # Phase-B SL/TP line extraction
|
│ ├── levels.py # extracție SL/TP pe Faza-B
|
||||||
│ ├── notifier/ # FanoutNotifier + Discord webhook + Telegram bot
|
│ ├── notifier/ # FanoutNotifier + webhook Discord + bot Telegram
|
||||||
│ ├── audit.py # line-buffered JSONL, daily rotation
|
│ ├── audit.py # JSONL line-buffered, rotație zilnică
|
||||||
│ ├── calibrate.py # Tk wizard (region-select + click-sample)
|
│ ├── calibrate.py # wizard Tk (selectează regiune + click pe culori)
|
||||||
│ ├── labeler.py # Tk UI → labels.json
|
│ ├── labeler.py # UI Tk → labels.json
|
||||||
│ ├── dryrun.py # replay corpus, precision/recall gate
|
│ ├── dryrun.py # replay pe corpus, gate precision/recall
|
||||||
│ ├── journal.py # trade entries
|
│ ├── validate.py # gate offline de clasificare a culorilor
|
||||||
│ ├── report.py # weekly R-multiple PnL
|
│ ├── journal.py # înregistrări trade-uri
|
||||||
│ └── main.py # unified CLI
|
│ ├── report.py # raport săptămânal PnL în R
|
||||||
├── tests/ # 184 pytest cases
|
│ └── main.py # CLI unificat
|
||||||
└── TODOS.md # P1/P2/P3 backlog, Faza 2 items
|
├── tests/ # 184 teste pytest
|
||||||
|
└── TODOS.md # backlog P1/P2/P3
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Install
|
## Instalare
|
||||||
|
|
||||||
Python 3.11+ required. Clone, then:
|
Python 3.11+.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[windows]" # Windows: live capture + window focus
|
pip install -e ".[windows]" # Windows: capture live + focus fereastră
|
||||||
pip install -e . # Linux / macOS: dev / dryrun only (no live)
|
pip install -e ".[dev]" # Linux/macOS/WSL: doar dev + teste (fără capture)
|
||||||
atm --help
|
atm --help
|
||||||
```
|
```
|
||||||
|
|
||||||
`[windows]` pulls `mss`, `pygetwindow`, `pywin32`.
|
**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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Calibration
|
## Calibrare
|
||||||
|
|
||||||
One-time per chart layout. Run on the machine that will do live capture.
|
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
|
```powershell
|
||||||
atm calibrate # 3s default countdown; use --delay 10 if you want more time
|
atm calibrate # countdown 3s default; pune --delay 10 dacă vrei mai mult timp
|
||||||
```
|
```
|
||||||
|
|
||||||
Flow:
|
Flow:
|
||||||
1. Dialog: substring of the chart window title (e.g. `TradeStation` or `DIA`). Stored in config for later auto-focus.
|
1. Dialog: substring din titlul ferestrei chart-ului (ex. `TradeStation` sau `DIA`). Se salvează în config pentru auto-focus ulterior.
|
||||||
2. **"Ready?" message** → click OK → 3s countdown in terminal. Alt-tab TradeStation to the foreground and minimize anything covering it.
|
2. **Mesaj "Ready?"** → click OK → countdown 3s în terminal. Alt-tab pe TradeStation, minimizează tot ce-l acoperă.
|
||||||
3. Full-desktop screenshot is captured and shown in a scaled Tk window.
|
3. Se face screenshot full-desktop, apare o fereastră Tk scalată.
|
||||||
4. **Drag a rectangle** over the chart (include the M2D MAPS strip). Enter = confirm. Esc = cancel.
|
4. **Trage un dreptunghi** peste chart (include și banda M2D MAPS). Enter = confirmă. Esc = anulează.
|
||||||
5. Step-by-step clicks on the selected region:
|
5. Click pas cu pas pe regiunea selectată:
|
||||||
- M2D MAPS strip: top-left + bottom-right corners
|
- M2D MAPS strip: colț stânga-sus + colț dreapta-jos
|
||||||
- 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)
|
- 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 area: top-left + bottom-right (for Phase-B line detection)
|
- Chart: colț stânga-sus + colț dreapta-jos (pentru detecția de linii în Faza-B)
|
||||||
- Two known price levels on the y-axis (pixel y → enter price)
|
- Două prețuri cunoscute pe axa Y (pixel y → introduci prețul)
|
||||||
- Canary region: top-left + bottom-right on a stable UI element (axis label, title bar)
|
- Canary: colț stânga-sus + colț dreapta-jos pe un element UI **stabil** (etichetă axă, bară titlu)
|
||||||
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.
|
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.
|
||||||
|
|
||||||
What gets written:
|
### ⚠️ Reguli critice la calibrare (evită incidentul 2026-04-17)
|
||||||
- `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:
|
**1. Click EXCLUSIV pe dot-ul din DREAPTA al strip-ului.**
|
||||||
- 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.
|
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).
|
||||||
- Default tolerance is 60 for dot colours, 25 for background. Tighten via TOML after dryrun if misclassifications creep in.
|
|
||||||
|
**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 after calibration
|
## Smoke-test după calibrare
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
atm debug --delay 5
|
atm debug --delay 5
|
||||||
```
|
```
|
||||||
|
|
||||||
Captures one frame. Saves `logs/debug_full_<ts>.png`, `logs/debug_dot_roi_<ts>.png`, `logs/debug_annotated_<ts>.png`. Prints:
|
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
|
window_found: True
|
||||||
@@ -102,195 +121,248 @@ classified: gray distance=24 confidence=0.79
|
|||||||
accepted: True color=gray
|
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:
|
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:
|
||||||
- Circle mid-strip → wrong window under the capture region (bring TradeStation to front).
|
- Cerc la mijloc de strip → alt window e sub regiunea de capture (adu TradeStation în față).
|
||||||
- Circle on a non-dot UI element → `dot_roi` boundaries capture too much; recalibrate narrower.
|
- Cerc pe element UI non-dot → `dot_roi` prea larg; recalibrează mai îngust.
|
||||||
- `color=None` + `UNKNOWN` → tolerances too tight OR sampled RGBs don't match real dots; recalibrate clicking on actual dots.
|
- `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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Live run
|
## Validare offline a calibrării
|
||||||
|
|
||||||
```powershell
|
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.
|
||||||
# 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 + **single-shot Telegram alert** (`⚠️ Canary drift=N — monitorizare pauzată`). Clear via `/resume force` in Telegram, or restart with pause-file removed.
|
|
||||||
- Detector reports UNKNOWN → stays in current state (logged as `noise`).
|
|
||||||
- Colour change → full frame saved to `samples/YYYYMMDD_HHMMSS_<color>.png` (for corpus).
|
|
||||||
- FIRE (BUY/SELL, not locked) → annotated PNG saved to `logs/fires/`, attached to the alert, `LevelsExtractor` armed.
|
|
||||||
- **Phase skip backstop** (`fire_on_phase_skip=true` default) → ARMED → light_red/light_green direct (dark_red/dark_green missed) still emits `⚠️ PHASE SKIP` alert with screenshot. FSM lockout suppresses spam.
|
|
||||||
- Phase-B complete → "Levels SL=… TP1=… TP2=…" push.
|
|
||||||
- Heartbeat every `heartbeat_min` minutes.
|
|
||||||
|
|
||||||
### Operating hours window
|
|
||||||
|
|
||||||
Configure via `[options.operating_hours]` in TOML (source of truth: NYSE local time, timezone-aware so DST is handled automatically):
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[options.operating_hours]
|
|
||||||
enabled = true
|
|
||||||
timezone = "America/New_York" # fail-fast validated at config load
|
|
||||||
weekdays = ["MON", "TUE", "WED", "THU", "FRI"]
|
|
||||||
start_hhmm = "09:30" # NYSE open
|
|
||||||
stop_hhmm = "16:00" # NYSE close
|
|
||||||
```
|
|
||||||
|
|
||||||
Out-of-window ticks are skipped (logged only on transition). On boundary crossings the bot emits `market_open` / `market_closed` Telegram status messages exactly once per transition. **Startup in-window does not emit a spurious `market_open` alert.**
|
|
||||||
|
|
||||||
CLI overrides (beat TOML):
|
|
||||||
|
|
||||||
```
|
|
||||||
atm run --tz America/New_York --weekdays MON,TUE,WED,THU,FRI --oh-start 09:30 --oh-stop 16:00
|
|
||||||
```
|
|
||||||
|
|
||||||
> `--oh-start / --oh-stop` are **different** from `--start-at / --stop-at`. The `--start-at / --stop-at` pair controls wall-clock session bounds (when the process starts and quits); `--oh-*` controls the NYSE trading window inside the session (what hours detection actually runs). They compose.
|
|
||||||
|
|
||||||
### Telegram commands
|
|
||||||
|
|
||||||
Send to the bot chat:
|
|
||||||
|
|
||||||
| Command | Effect |
|
|
||||||
|---|---|
|
|
||||||
| `/ss` or `/screenshot` | Take and send a screenshot now |
|
|
||||||
| `/status` | State + pause reason + window open/closed |
|
|
||||||
| `/pause` | Suspend detection (heartbeats continue) |
|
|
||||||
| `/resume` | Clear user pause only. If Canary is drift-paused it **stays paused** — use `/resume force` |
|
|
||||||
| `/resume force` | Also clear Canary drift-pause (use after recalibration) |
|
|
||||||
| `/3` or `/interval 3` | Set auto-screenshot interval to 3 min |
|
|
||||||
| `/stop` | Stop the scheduler |
|
|
||||||
|
|
||||||
Only `allowed_chat_ids` are accepted. After 3 consecutive `401`s the poller enters degraded mode.
|
|
||||||
|
|
||||||
### Calibration validation (offline gate)
|
|
||||||
|
|
||||||
Validate that the current calibration classifies known-labeled frames correctly **without waiting for a live session**:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
atm validate-calibration samples/calibration_labels.json
|
atm validate-calibration samples/calibration_labels.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Input JSON:
|
Format input (`samples/calibration_labels.json`):
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{"path": "logs/fires/20260417_201500_arm_sell.png", "expected": "yellow", "note": "first arm"},
|
{"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_205302_ss.png", "expected": "dark_red"},
|
||||||
{"path": "logs/fires/20260417_210441_ss.png", "expected": "light_red"}
|
{"path": "logs/fires/20260417_210441_ss.png", "expected": "light_red"}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Output: per-sample PASS/FAIL with detected color + top-3 candidates by RGB distance + suggestion pixels for misclassifications.
|
Output: per fiecare frame PASS/FAIL + culoarea detectată + top 3 candidați după distanță RGB + sugestii de pixel pentru misclasificări.
|
||||||
|
|
||||||
Exit code: `0` if 100% PASS, `1` on any FAIL, `2` on malformed/missing input. Suitable for CI or a pre-`atm run` sanity check.
|
Exit code:
|
||||||
|
- `0` — 100% PASS (poți porni live în siguranță)
|
||||||
|
- `1` — cel puțin un FAIL
|
||||||
|
- `2` — input invalid/lipsă
|
||||||
|
|
||||||
Keep PowerShell minimized during the session so it doesn't cover TradeStation.
|
### 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).
|
||||||
|
|
||||||
## After the session
|
1. **În timpul sesiunii**, când observi o culoare nouă pe chart, trimite `/ss` în Telegram. Asta salvează un screenshot în `logs/fires/` cu timestamp.
|
||||||
|
2. **După sesiune**, deschizi `samples/calibration_labels.json` și adaugi o intrare nouă pentru fiecare screenshot relevant:
|
||||||
```powershell
|
```json
|
||||||
atm label samples # Tk UI — label each saved frame with true dot colour
|
{"path": "logs/fires/20260420_151234_ss.png", "expected": "dark_green", "note": "văzut live, ratat de bot"}
|
||||||
atm dryrun samples # replay through detector + FSM; exits 0 if precision=100%, recall>=95%
|
|
||||||
```
|
```
|
||||||
|
Câmpul `expected` = culoarea pe care TU ai văzut-o pe chart (nu ce a zis bot-ul).
|
||||||
|
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.
|
||||||
|
|
||||||
If the gate fails, tune per-colour `tolerance` in `configs/<current>.toml`, or recalibrate colour samples that didn't match. Re-run `atm dryrun` until it passes. Only then do you trust live signals.
|
### Exemplu real — incidentul 2026-04-17
|
||||||
|
|
||||||
Trade record-keeping:
|
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.
|
||||||
|
|
||||||
```powershell
|
Fix aplicat în `2026-04-18-1220.toml`, pe bază de evidență live:
|
||||||
atm journal # interactive entry after a real trade
|
|
||||||
atm report --week 2026-16 # weekly win rate + R PnL + slippage
|
| 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
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## DPI / multi-monitor notes
|
## Sesiunea live
|
||||||
|
|
||||||
- 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.
|
```powershell
|
||||||
- Changing DPI scaling or moving to a different monitor with different DPI → recalibrate.
|
# Sesiunea de azi 16:30–23:00 România local
|
||||||
- RDP / virtual desktops: `mss` can return black frames over RDP. Run locally on the same physical machine as TradeStation.
|
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):
|
||||||
|
|
||||||
|
```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/<current>.toml`, sau recalibrezi eșantioanele care n-au potrivit. Rulezi iar `atm dryrun` până trece. **Numai atunci ai încredere în semnalele live.**
|
||||||
|
|
||||||
|
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
|
## Troubleshooting
|
||||||
|
|
||||||
| Symptom | Likely cause | Fix |
|
| Simptom | Cauză probabilă | Fix |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `capture_failed` in startup ping | `chart_window_region` references coords off-screen (different monitor layout) | Recalibrate. |
|
| `capture_failed` în ping-ul de start | `chart_window_region` referă coords off-screen (alt layout monitor) | Recalibrează. |
|
||||||
| 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. |
|
| 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'` at startup | `cfg.window_title` substring matches nothing | Edit `window_title` in TOML to a substring that's unique to TradeStation. |
|
| `WARN: no window contains 'xxx'` la start | `cfg.window_title` nu prinde nimic | Editează `window_title` în TOML cu un substring unic pentru 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. |
|
| 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. |
|
||||||
| Discord OK, Telegram silent (or vice versa) | `logs/dead_letter.jsonl` contains failed alerts with error | Fix credentials in TOML, restart. |
|
| 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. |
|
||||||
| 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). |
|
| Discord OK, Telegram tace (sau invers) | `logs/dead_letter.jsonl` are alertele eșuate + eroarea | Fixezi credențiale în 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. |
|
| 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). |
|
||||||
| Wizard window is tiny / image not visible | Tk geometry default on Windows | Already fixed — `git pull`. Image is scaled to fit screen. |
|
| 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 (production)
|
## Windows Task Scheduler (producție)
|
||||||
|
|
||||||
For hands-off daily runs surviving reboots:
|
Pentru rulare automată zilnică care supraviețuiește reboot-urilor:
|
||||||
|
|
||||||
1. Task Scheduler → Create Task → name `ATM M2D Monitor`
|
1. Task Scheduler → Create Task → nume `ATM M2D Monitor`
|
||||||
2. **General**: "Run only when user is logged on", "Run with highest privileges"
|
2. **General**: "Run only when user is logged on", "Run with highest privileges"
|
||||||
3. **Triggers**: New → Daily, Start `16:30`
|
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`
|
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)
|
5. **Conditions**: debifează "Start only if AC power" (dacă e laptop)
|
||||||
6. **Settings**: "If task runs longer than 7 hours → stop"
|
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).
|
Click-right → Run, să testezi manual. Check DST schimbare de două ori pe an (prima săptămână din martie / octombrie).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick command reference
|
## Referință rapidă comenzi
|
||||||
|
|
||||||
```
|
```
|
||||||
atm calibrate [--screenshot PATH] [--delay SEC] # Tk wizard
|
atm calibrate [--screenshot PATH] [--delay SEC] # wizard Tk
|
||||||
atm debug [--delay SEC] # one-shot capture + detect
|
atm debug [--delay SEC] # one-shot capture + detect
|
||||||
atm label SAMPLES_DIR # Tk labeling
|
atm label SAMPLES_DIR # etichetare Tk
|
||||||
atm dryrun SAMPLES_DIR # corpus gate
|
atm dryrun SAMPLES_DIR # gate pe corpus
|
||||||
atm validate-calibration LABEL_FILE.json # offline color-classification gate
|
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]
|
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]
|
[--tz TZNAME] [--weekdays MON,TUE,...] [--oh-start HH:MM] [--oh-stop HH:MM]
|
||||||
atm journal [--file PATH] # interactive trade entry
|
atm journal [--file PATH] # înregistrare interactivă
|
||||||
atm report [--week YYYY-WW] [--file PATH] # weekly summary
|
atm report [--week YYYY-WW] [--file PATH] # raport săptămânal
|
||||||
```
|
```
|
||||||
|
|
||||||
Exit codes:
|
Exit codes:
|
||||||
- `atm dryrun` — 0 pass, 1 fail.
|
- `atm dryrun` — 0 pass, 1 fail.
|
||||||
- `atm validate-calibration` — 0 all PASS, 1 any FAIL, 2 bad input.
|
- `atm validate-calibration` — 0 toate PASS, 1 orice FAIL, 2 input invalid.
|
||||||
- Others: standard convention.
|
- Restul: standard.
|
||||||
|
|
||||||
## Audit log events
|
---
|
||||||
|
|
||||||
Events written to `logs/YYYY-MM-DD.jsonl`. Added by the lifecycle+canary work:
|
## Evenimente audit
|
||||||
|
|
||||||
| Event | Payload | When |
|
Scrise în `logs/YYYY-MM-DD.jsonl`. Cele adăugate recent:
|
||||||
|
|
||||||
|
| Event | Payload | Când |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `canary_drift_paused` | `distance` | First drift tick after clean; emits Telegram alert |
|
| `canary_drift_paused` | `distance` | Primul tick cu drift după o stare curată; emite alertă Telegram |
|
||||||
| `user_paused` | — | `/pause` received |
|
| `user_paused` | — | `/pause` primit |
|
||||||
| `user_resumed` | `was_drift`, `was_user`, `force` | `/resume` or `/resume force` |
|
| `user_resumed` | `was_drift`, `was_user`, `force` | `/resume` sau `/resume force` |
|
||||||
| `market_open` / `market_closed` | `reason` | Operating-hours window boundary (once per transition; **not** at startup) |
|
| `market_open` / `market_closed` | `reason` | Boundary fereastră operating-hours (o dată per tranziție; **nu** la startup) |
|
||||||
| `phase_skip_fire` | `direction` | Backstop alert when ARMED→light_* direct |
|
| `phase_skip_fire` | `direction` | Alertă backstop când ARMED→light_* direct |
|
||||||
| `command_error` | `action`, `error` | Dispatch exception (isolated from detection loop) |
|
| `command_error` | `action`, `error` | Excepție la dispatch (izolată de loop-ul de detecție) |
|
||||||
|
|||||||
Reference in New Issue
Block a user