feat: calibration/ corpus + scenarii regresie FSM
- calibration/frames/: 16 PNG-uri ground-truth numite {ts}_{color}.png,
copiate din logs/fires (izolate de samples/ și logs/fires/ care se pot goli)
- calibration/calibration_labels.json: mutat din samples/, curățat de entries
cu fișiere inexistente, extins la acoperire completă 7 culori → 16/16 PASS
- calibration/scenarios.json: 8 secvențe FSM (BUY/SELL full cycle, phase_skip,
catchup, post-fire suppression) pe frame-uri reale
- tests/test_scenarios_regression.py: parametrizat pe scenarios.json, asertează
color+state+reason+trigger+alerts+scheduler prin pipeline-ul
Detector → _handle_tick
- docs: README + CLAUDE reflectă noua structură, incidentul 2026-04-20/21
(pixel saturat UNKNOWN → FSM blocat în PRIMED → polling continuu) +
troubleshooting pentru trigger UNKNOWN
Pytest: 184 → 192 passed (+8 scenarii regresie).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
83
README.md
83
README.md
@@ -11,12 +11,17 @@ Fără execuție automată. Faza 2 (auto-execute) e blocată de auditul TOS prop
|
||||
```
|
||||
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
|
||||
│ ├── 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
|
||||
├── 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
|
||||
@@ -33,7 +38,7 @@ atm/
|
||||
│ ├── journal.py # înregistrări trade-uri
|
||||
│ ├── report.py # raport săptămânal PnL în R
|
||||
│ └── main.py # CLI unificat
|
||||
├── tests/ # 184 teste pytest
|
||||
├── tests/ # 192 teste pytest (184 core + 8 scenarii regresie)
|
||||
└── TODOS.md # backlog P1/P2/P3
|
||||
```
|
||||
|
||||
@@ -77,8 +82,9 @@ pip install -e ".[dev]"
|
||||
## Dev
|
||||
|
||||
```bash
|
||||
pytest -q # toate testele (184+)
|
||||
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
|
||||
```
|
||||
|
||||
@@ -97,6 +103,7 @@ Structura testelor:
|
||||
| `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) |
|
||||
|
||||
---
|
||||
|
||||
@@ -173,18 +180,23 @@ Deschizi PNG-ul adnotat: dreptunghi galben = `dot_roi`, cerc roșu = dot detecta
|
||||
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
|
||||
atm validate-calibration calibration/calibration_labels.json
|
||||
```
|
||||
|
||||
Format input (`samples/calibration_labels.json`):
|
||||
Format input (`calibration/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"}
|
||||
{"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:
|
||||
@@ -192,25 +204,42 @@ Exit code:
|
||||
- `1` — cel puțin un FAIL
|
||||
- `2` — input invalid/lipsă
|
||||
|
||||
### Două corpus-uri, două scopuri
|
||||
### Trei surse de frame-uri, roluri distincte
|
||||
|
||||
| Corpus | Unde se salvează | Cum se populează | Folosit de |
|
||||
| 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 | `atm validate-calibration` |
|
||||
| `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 `samples/calibration_labels.json` pentru fiecare screenshot relevant, cu culoarea pe care ai văzut-o TU pe chart:
|
||||
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:
|
||||
```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
|
||||
atm validate-calibration calibration/calibration_labels.json
|
||||
```
|
||||
4. **Interpretează rezultatul:**
|
||||
- **Toate PASS** → calibrarea ține, continui live fără modificări.
|
||||
@@ -237,7 +266,7 @@ Scenariu: ai rulat o sesiune live, ai văzut pe chart o culoare pe care bot-ul n
|
||||
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).
|
||||
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
|
||||
@@ -257,9 +286,30 @@ yellow, turquoise, gray, background — lăsate neschimbate (nu am dovezi live c
|
||||
|
||||
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:
|
||||
```bash
|
||||
echo "2026-04-16-0703.toml" > configs/current.txt
|
||||
echo "2026-04-18-1220.toml" > configs/current.txt # sau 2026-04-16-0703.toml
|
||||
```
|
||||
|
||||
---
|
||||
@@ -385,6 +435,7 @@ atm report --week 2026-16 # win rate săptămânal + PnL în R + slippage
|
||||
| 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. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user