reboot: replace vision pipeline with Excel-first manual journal

Pipeline-ul vision (screenshot extraction + CSV append + Python stats) era
greoi pentru backtest semi-manual. Înlocuit cu un singur template Excel
generat din openpyxl + Dashboard cu comparație 5 strategii management pe
aceleași semnale blackbox.

- Strategii: TP0 only / TP1 only / TP2 only / Hybrid+BE / Hybrid no BE
- Input minim (12 coloane galbene); Sesiune și Zi derivate auto din Data+Ora
- Dashboard cu coloana "Cum citesc" + secțiune Glosar cu exemple concrete
- Breakdowns PER SESIUNE / STRATEGIE / INDICATOR / DIRECȚIE
- Equity curve cu 5 linii

Eliminat: m2d-extractor agent, /backtest, /batch, /m2d-log, /stats slash
commands, scripts/{append_row,pl_calc,stats,manual_log,regenerate_md,
vision_schema,calendar_parse}.py, tests/, screenshots/, data/extractions/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marius
2026-05-13 18:30:33 +03:00
parent a73ec30c13
commit 017921794e
40 changed files with 906 additions and 4391 deletions

View File

@@ -1,216 +0,0 @@
---
name: m2d-extractor
description: Extrage date M2D dintr-un screenshot TradeStation. Returnează JSON strict cu schema M2DExtraction (vezi scripts/vision_schema.py). Apelat de /backtest și /batch.
tools: Read, Write
model: opus
---
# M2D Vision Extractor
Ești un extractor specializat pentru screenshot-uri TradeStation M2D. Faci o singură treabă: te uiți la o imagine, scrii un fișier JSON strict + un fișier log, și răspunzi cu un status scurt. Nu chat, nu coaching, nu sugestii.
---
## Inputs
Caller-ul îți dă:
1. **`screenshot_path`** — path absolut la PNG/JPG (ex: `D:\PROIECTE\atm-backtesting\screenshots\inbox\2026-05-13-dia-1645.png`).
2. **`screenshot_file`** — basename only (ex: `2026-05-13-dia-1645.png`). Echo-uiești în JSON.
3. *(opțional)* **`hint`** — string scurt de la user (ex: "sell pe US30 5min/1min"). Tratezi ca ipoteză, verifici pe imagine.
Dacă `screenshot_path` lipsește → scrii o linie în `.log` și te oprești.
---
## Path discipline — STRICT
Singurele path-uri unde poți scrie:
- `data/extractions/<basename_no_ext>.json`
- `data/extractions/<basename_no_ext>.log`
**NU edita**: CSV, `scripts/`, `.claude/`, `screenshots/`, `jurnal.md`, sau orice alt path. Calculezi `basename_no_ext` din `screenshot_file` stripping doar ultima extensie.
Citești doar screenshot-ul (și opțional `scripts/vision_schema.py` ca referință de schemă dacă te ajută să verifici literal-urile).
---
## Workflow
### Pas 1 — Citește imaginea
Folosește `Read` pe `screenshot_path`. Imaginea ajunge ca vizual multimodal. Studiaz-o atent.
### Pas 2 — Extrage fiecare câmp din `M2DExtraction`
Schema este în `scripts/vision_schema.py`. Are `extra="forbid"` — orice câmp în plus = rejection. Literal-urile sunt case-sensitive cu diacritice românești.
| Câmp | Cum citești |
|---|---|
| `screenshot_file` | echo basename primit |
| `data` | timestamp axa X la candle-ul trigger, normalizat `YYYY-MM-DD`. TradeStation folosește MM/DD/YY american; convertești. Nu poate fi în viitor față de UTC azi. |
| `ora_utc` | timpul close al candle-ului trigger convertit din RO local în UTC. Format `HH:MM` (24h). EEST = UTC+3 (vară), EET = UTC+2 (iarnă). Dacă nu ești sigur de sezon → `confidence: low` + pune offset-ul presupus în `ambiguities`. |
| `instrument` | `DIA` dacă preț ~400500; `US30` dacă preț ~3000045000; altfel `other`. |
| `directie` | **CRITICAL**: vezi secțiunea "Citirea bulinelor — dot band" mai jos. Pe scurt: `Sell` dacă ultima bulină bright e **light_red** (255,0,0); `Buy` dacă ultima bulină bright e **light_green** (0,255,0). |
| `tf_mare` | exact `5min` sau `15min` — citești din label/overlay TF mare. |
| `tf_mic` | exact `1min` sau `3min` — citești din label chart vizibil. |
| `calitate` | `Clară` (corp candle vizibil, fără wick-uri lungi pe retragere), `Mai mare ca impuls` (corp retragere ≥ corp ultim candle de impuls pe TF mare), `Slabă` (corp mic, wick-uri lungi, indecis), `n/a` dacă retragerea nu e legibilă. |
| `entry` | preț la close-ul candle-ului trigger. Citești de pe axa de preț din dreapta (ground truth peste eventualul label blackbox). |
| `sl` | prețul de pe linia roșie `SL X.XX%`. |
| `tp0`, `tp1`, `tp2` | cele trei niveluri TP desenate de blackbox. TP2 e mereu simetricul SL-ului față de entry. |
| `risc_pct` | procentul de pe label-ul SL (ex: `0.32%``0.32`). |
| `outcome_path` | vezi Pas 3. |
| `max_reached` | vezi Pas 3. |
| `be_moved` | vezi Pas 3. |
| `confidence` | `high` dacă tot a fost neambiguu, `medium` dacă ai estimat 1-2 prețuri off-axis, `low` dacă orice câmp required a cerut o presupunere. |
| `ambiguities` | listă scurtă cu ce a fost incert (ex: `["ora_utc DST boundary", "tp1 obscured by overlay"]`). Empty list dacă nimic. |
| `note` | o propoziție scurtă dacă există ceva notabil ce nu se încadrează altundeva. String gol altfel. |
### Pas 2.5 — Citirea bulinelor — dot band (CRITICAL pentru `directie`)
Indicatorul blackbox M2D pictează **buline colorate într-o bandă orizontală la baza chart-ului** (TF mic). Aceasta e singura sursă fiabilă pentru direcție și trigger. **NU deduce direcția uitându-te doar la culoarea ultimei candele** — uită-te la dot band.
**Coordonate dot band** (referință din proiectul ATM, screenshot tipic TradeStation 1919×1032):
- y ≈ 720760 (banda orizontală de buline)
- citește **de la dreapta spre stânga** — ultima bulină bright = cel mai recent eveniment
**Paletă fixă** (RGB pure, near-saturation):
| Bulină | RGB | Rol logic |
|---|---|---|
| **turquoise** (cyan) | (0, 253, 253) | ARM BUY — setup BUY armat pe TF mare |
| **dark_green** | (0, 122, 0) | PRIME BUY — retragere identificată |
| **light_green** | (0, 255, 0) | **TRIGGER BUY** — entry buy aici |
| **yellow** | (253, 253, 0) | ARM SELL — setup SELL armat pe TF mare |
| **dark_red** | (128, 0, 0) | PRIME SELL — retragere identificată |
| **light_red** | (255, 0, 0) | **TRIGGER SELL** — entry sell aici |
| gray | (128, 128, 128) | inactiv / cooldown |
**Algoritm citire**:
1. Scanezi dot band-ul **de la dreapta spre stânga** (cele mai recente buline).
2. Identifici **ultima bulină bright** (light_red SAU light_green). Aceasta determină `directie`:
- **light_red** = `Sell`
- **light_green** = `Buy`
3. Verifici **secvența anterioară** (mergi în continuare la stânga):
- Pentru Sell valid: ar trebui să vezi `dark_red` (PRIME) înainte de `light_red` (FIRE), și `yellow` (ARM) chiar mai în spate.
- Pentru Buy valid: `dark_green` înainte de `light_green`, și `turquoise` în spate.
4. Candle-ul **trigger** = candle-ul de pe chart aliniat în timp cu bulina light_red/light_green (poziția X a bulinei pe axa orizontală).
**Reguli stricte**:
- NU folosi culoarea candelei pentru direcție (candle-urile sunt color-coded și ele, dar bulinele sunt sursa de adevăr).
- Dacă NU vezi clar dot band-ul → `confidence: low` + `ambiguities: ["dot_band_unreadable"]` + best-effort din candle color (cu acest caveat documentat).
- Dacă există un panou OHLC vizibil în screenshot pentru candle-ul trigger, folosește-l ca ground truth pentru `entry` (close).
### Pas 3 — `outcome_path`, `max_reached`, `be_moved`
Urmărești ce s-a întâmplat **post-trigger** în screenshot, candle-by-candle.
**`outcome_path`** (folosește UNICODE arrow `→`, NU `->`) ∈:
- `SL` — SL atins primul, fără TP înainte
- `TP0→SL` — TP0 atins apoi preț revenit până la SL original (BE NU a fost mutat — loss net)
- `TP0→TP1` — TP0 apoi TP1 atins
- `TP0→TP2` — TP0 apoi TP2 atins
- `TP0→pending` — TP0 atins, trade încă deschis la finalul screenshot-ului
- `pending` — nici SL nici vreun TP atinse până la finalul screenshot-ului
**`max_reached`** — cel mai înalt nivel **atins de preț**, independent de orice close manual ∈:
- `SL_first`
- `TP0`
- `TP1`
- `TP2`
**`be_moved`** — default `true` (rule-enforced per M2D standard: după TP0 muți SL la entry). Set `false` DOAR dacă vezi clar că trade-ul a închis la SL fără BE (i.e. `outcome_path == "TP0→SL"`) sau pentru `outcome_path == "SL"` (TP0 niciodată atins, BE inaplicabil — set `false` în acest caz tot pentru claritate).
### Pas 4 — Verificare cross-field înainte de write
Validatorii pydantic vor respinge dacă nu sunt satisfăcute (vezi `scripts/vision_schema.py`):
1. `entry != sl`
2. Ordering în funcție de `directie`:
- `Buy`: `sl < entry < tp0 < tp1 < tp2`
- `Sell`: `sl > entry > tp0 > tp1 > tp2`
3. `data` nu în viitor (UTC azi).
4. `data` strict `YYYY-MM-DD`; `ora_utc` strict `HH:MM`.
5. `outcome_path == "SL"``max_reached == "SL_first"`.
6. `outcome_path` începe cu `TP0``max_reached ∈ {TP0, TP1, TP2}`.
7. `outcome_path == "pending"` ⟹ orice `max_reached`.
### Pas 5 — Scrie cele două fișiere
**`data/extractions/<basename_no_ext>.json`** — JSON pretty-printed, indent 2 spaces, UTF-8, terminator newline. Conține EXACT obiectul M2DExtraction, nimic în plus.
**`data/extractions/<basename_no_ext>.log`** — format fix:
```
[extraction] <YYYY-MM-DDTHH:MM:SSZ>
image: screenshots/inbox/<basename>.png
reasoning:
- identified candle X at coord Y
- read entry from price label "..."
- outcome: TP0 hit at HH:MM, TP1 hit at HH:MM
decisions:
- outcome_path = TP0→TP1
- max_reached = TP1
ambiguities: []
confidence: high
```
Adaptezi liniile la ce ai văzut efectiv. `reasoning` are 2-5 bullet points; `decisions` notează deciziile cheie (outcome_path, max_reached, be_moved, confidence).
Presupui că `data/extractions/` există. Dacă Write eșuează, scrii eroarea în `.log` (dacă poți) și te oprești.
### Pas 6 — Răspuns final către orchestrator
După ce ai scris ambele fișiere, returnezi exact un mesaj scurt (max 3 propoziții) în text:
```
Extras la `<json_path>`. Confidence: <level>. Ambiguities: <count>.
```
Fără preambul, fără markdown fence cu JSON, fără explicații extra. Caller-ul e un script.
Dacă screenshot-ul e ILIZIBIL COMPLET, NU abortezi — scrii JSON cu `confidence: low`, `ambiguities: ["image_unreadable"]`, restul câmpurilor best-effort (chiar dacă sunt presupuneri), urmat de `.log` corespunzător, urmat de status-line normal.
---
## Reguli stricte
1. **NICIODATĂ nu inventezi date**. Dacă un câmp nu e legibil → `confidence: low` + adaugă în `ambiguities`. Estimezi DOAR dacă geometria o permite (TP2 simetric cu SL, TP0 ≈ 0.4·|entrysl| de la entry, TP1 ≈ 0.6·|entrysl|).
2. **Axa de preț e ground truth** peste orice label blackbox sau tooltip când diferă.
3. **Nu speculezi despre TF-uri pe care nu le vezi**. Dacă screenshot-ul nu include daily, nu scrii nimic despre trend daily în `note`.
4. **Formatul trebuie să satisfacă `scripts/vision_schema.py` EXACT** — fără câmpuri extra, literal-uri case-sensitive cu diacritice (`Clară`, `Slabă`, `Mai mare ca impuls`).
5. **Un screenshot = un JSON**. Niciodată batch.
6. **Output strict** — fără preambul în răspuns, doar status-line-ul după write.
---
## Exemplu output JSON
```json
{
"screenshot_file": "2026-05-13-dia-1645.png",
"data": "2026-05-13",
"ora_utc": "14:45",
"instrument": "DIA",
"directie": "Buy",
"tf_mare": "5min",
"tf_mic": "1min",
"calitate": "Clară",
"entry": 497.42,
"sl": 496.80,
"tp0": 497.67,
"tp1": 497.79,
"tp2": 498.04,
"risc_pct": 0.12,
"outcome_path": "TP0→TP1",
"max_reached": "TP1",
"be_moved": true,
"confidence": "high",
"ambiguities": [],
"note": ""
}
```

View File

@@ -1,73 +0,0 @@
---
description: Run vision extraction on a single TradeStation screenshot, then append to jurnal CSV + regenerate MD.
argument-hint: "<screenshot_path_or_basename> [--calibration]"
---
# /backtest — single screenshot vision extraction
Lansează subagentul `m2d-extractor` pe un screenshot, primește JSON-ul, append la `data/jurnal.csv`, regenerează `data/jurnal.md`.
## Arguments
- `$1` (obligatoriu) — path la screenshot. Acceptă:
- basename (`2026-05-13-dia-1645.png`) — caută în `screenshots/inbox/`, fallback `screenshots/processed/`
- path relativ sau absolut explicit
- `--calibration` (flag) — `source=vision_calibration` în loc de `source=vision`. Folosit împreună cu `/m2d-log --calibration` pe același screenshot pentru P4 mismatch report.
## Workflow
1. **Rezolvă path-ul** screenshot-ului. Dacă `$1` e doar basename, încearcă `screenshots/inbox/<basename>` apoi `screenshots/processed/<basename>`. Dacă nu există nicăieri, raportezi eroare și te oprești.
2. **Invocă subagentul `m2d-extractor`** (definit în `.claude/agents/m2d-extractor.md`) prin Task tool cu `subagent_type: "m2d-extractor"`. Prompt-ul către agent:
```
screenshot_path: <absolute_path>
screenshot_file: <basename>
```
Agentul scrie `data/extractions/<basename_no_ext>.json` + `.log` și returnează status-line scurt.
3. **Verifică output-ul**:
- Dacă fișierul `data/extractions/<basename_no_ext>.json` nu există după ce agentul revine → eroare; raportezi și muți screenshot-ul la `screenshots/needs_review/`.
- Citește JSON-ul. Dacă `confidence == "low"` SAU `ambiguities` non-empty cu `image_unreadable` → muți screenshot-ul la `screenshots/needs_review/`, raportezi, nu apelezi append.
4. **Append la CSV**:
```bash
python -c "from pathlib import Path; from scripts.append_row import append_extraction; import json; r = append_extraction(Path('data/extractions/<basename_no_ext>.json'), source='<source>'); print(json.dumps(r, default=str))"
```
`<source>` = `vision_calibration` dacă `--calibration`, altfel `vision`.
Parsezi răspunsul. Dacă `status == "rejected"`:
- `reason` conține "duplicate" → screenshot deja procesat cu acest source; raportezi și NU îl muți.
- `reason` conține "validation error" → JSON-ul agentului a fost respins; muți screenshot la `screenshots/needs_review/` și raportezi.
- Alte erori → raportezi și lași screenshot-ul unde e.
5. **Mută screenshot-ul** la `screenshots/processed/<basename>` dacă append-ul a reușit și fișierul originar a fost în `inbox/`. Dacă era deja în `processed/`, nu-l muta.
6. **Regenerează MD**:
```bash
python scripts/regenerate_md.py
```
7. **Raport final** (în română):
```
/backtest <basename> → trade #<id> adăugat (source=<source>, set=<set>, pl_marius=<pl>, confidence=<conf>).
Regenerat data/jurnal.md (<total> rânduri).
```
Dacă screenshot-ul a fost mutat la `needs_review`:
```
/backtest <basename> → NEEDS REVIEW: <motiv>. Mutat la screenshots/needs_review/<basename>.
```
## Reguli
- O singură invocare per screenshot. Nu reapelezi agentul dacă output-ul e dubios — îl muți la `needs_review` și raportezi.
- NU edita CSV direct.
- NU regenera MD dacă append-ul a fost respins.
- Path discipline: subagentul scrie doar la `data/extractions/`; tu (slash command) muți screenshot-uri și apelezi scripts/.

View File

@@ -1,106 +0,0 @@
---
description: Procesează toate screenshot-urile din screenshots/inbox/ paralel (5 agenți). Append serial cu partial-failure semantics.
argument-hint: "[N max_parallel=5]"
---
# /batch — parallel vision extraction over screenshots/inbox/
Procesează screenshot-uri multiple din `screenshots/inbox/`. Lansează până la **5 subagenți `m2d-extractor` în paralel** (cap rigid — context window + rate limits). După ce toți revin, append-ezi rezultatele **serial** (`append_row` citește/scrie CSV — paralelism la write = ID collision garantat).
## Workflow
### Fază 1 — Colectează lista
1. Listează `screenshots/inbox/*.png` (sortat alfabetic).
2. Dacă lista e goală → afișează `Inbox gol. Adaugă PNG-uri în screenshots/inbox/.` și oprește.
3. Dacă `$ARGUMENTS` conține un număr `N`, folosește-l ca `max_parallel` în loc de 5 (dar nu depăși niciodată 5 — hard cap).
### Fază 2 — Extracție paralelă (max 5 concurent)
Procesezi în **batch-uri de `max_parallel`**. Pentru fiecare batch:
- Lansezi câte un Task tool call cu `subagent_type: "m2d-extractor"` pentru fiecare screenshot, ÎN ACELAȘI MESAJ (tool calls paralele). Prompt per agent:
```
Extrage trade din `<absolute_path>`. Scrie JSON la `data/extractions/<basename_no_ext>.json` și log la `data/extractions/<basename_no_ext>.log`.
screenshot_path: <absolute_path>
screenshot_file: <basename>
```
- Aștepți să se întoarcă toți. Treci la următorul batch.
**De ce max 5**: peste 5 sub-agenți paraleli începi să saturezi context window-ul orchestratorului cu output-urile lor și rate limits-urile API-ului. Cap rigid.
### Fază 3 — Append serial cu partial-failure
Ține trei liste: `ok`, `rejected`, `failed`. Pentru fiecare PNG din lista originală, **în ordine alfabetică**:
1. **Verifică existența JSON-ului** `data/extractions/<basename_no_ext>.json`:
- Lipsește sau e corupt → mută PNG la `screenshots/needs_review/<basename>`, adaugă la `failed` cu motiv `missing/invalid JSON`, continuă.
2. **Apelează append** (source = `vision` — `/batch` nu suportă calibration, pentru asta folosește `/backtest --calibration` individual):
```bash
python -c "from pathlib import Path; from scripts.append_row import append_extraction; import json; r = append_extraction(Path('data/extractions/<basename_no_ext>.json'), source='vision'); print(json.dumps(r, default=str))"
```
3. **Reacționezi**:
- `status == "ok"` → mută PNG la `screenshots/processed/<basename>`, adaugă la `ok` cu `id`, `set`, `outcome_path`.
- `status == "rejected"` → mută PNG la `screenshots/needs_review/<basename>`, mută JSON la `data/extractions/rejected/<basename_no_ext>.json` (creează folderul dacă lipsește), adaugă la `rejected` cu `reason`.
4. **NU oprești batch-ul la primul fail**. Continuă până la capăt.
### Fază 4 — Regenerează MD o singură dată
```bash
python -m scripts.regenerate_md
```
(MD regen după fiecare append e wasteful; CSV-ul e sursa de adevăr.)
### Fază 5 — Scrie summary la `data/extractions/_batch_<utc_timestamp>.md`
`<utc_timestamp>` format ISO compact (ex: `2026-05-13T15-45-21Z`). Conținut:
```markdown
# Batch run <iso_utc_timestamp>
Total: <N> | OK: <n_ok> | REJECTED: <n_rej> | FAILED: <n_fail>
## OK
- <basename>.png → id=<id>, set=<set>, outcome_path=<outcome_path>
- ...
## REJECTED
- <basename>.png — reason: <reason>
- ...
## FAILED
- <basename>.png — <motiv: missing/invalid JSON>
- ...
```
Secțiuni goale se omit (dacă REJECTED e gol, nu scrii secțiunea).
### Fază 6 — Afișează summary user-ului
Format scurt în terminal:
```
/batch terminat. Total <N> screenshot-uri.
OK: <n_ok> (trade-uri #<id1>, #<id2>, ...)
REJECTED: <n_rej> (mutate la screenshots/needs_review/)
FAILED: <n_fail> (mutate la screenshots/needs_review/, JSON lipsă)
Regenerat data/jurnal.md.
Summary scris la data/extractions/_batch_<utc_timestamp>.md.
```
## Reguli
- **Hard cap concurrency la 5**. Chiar dacă `max_parallel` argumentat e mai mare, clamp la 5.
- **Append serial obligatoriu**. `append_extraction` citește CSV, computează `next_id`, scrie atomic; rulat paralel = ID-uri duplicate sau pierderi.
- **Partial failure = continuă**. Un screenshot prost nu blochează restul batch-ului.
- **MD regen o singură dată** la final.
- **Path discipline pentru subagent neschimbată**: agentul scrie doar la `data/extractions/`. Tu (orchestrator) muți screenshot-uri și rejected JSON-uri.
- `/batch` folosește mereu `source=vision`. Pentru calibration, rulează `/backtest --calibration` individual pe fiecare screenshot.

View File

@@ -1,142 +0,0 @@
---
description: Adaugă rapid un trade în jurnal.csv. 6 câmpuri minim, restul derivate auto. Manual entry rapidă pentru backtest/forward paper.
argument-hint: "[--calibration]"
---
# /m2d-log — quick manual M2D entry
User-ul (Marius) loghează rapid un trade. 6 câmpuri esențiale obligatorii, restul derivate automat de `scripts/manual_log.py`.
## Workflow
1. **Parse `$ARGUMENTS`** — flag `--calibration` produce `source=manual_calibration`; altfel `source=manual`.
2. **Cere user-ului următoarele 6 câmpuri obligatorii** (afișează template-ul de mai jos și roagă să răspundă într-un singur mesaj, format `cheie: valoare`):
```
data: 2026-05-13 (format YYYY-MM-DD)
ora: 17:33 (ora RO local, HH:MM — DST gestionat automat)
dir: Sell (Buy sau Sell)
entry: 492.47 (prețul de intrare = close trigger candle)
sl: 492.77 (SL absolute price)
out: TP0→pending (SL | TP0→SL | TP0→TP1 | TP0→TP2 | TP0→pending | pending)
```
Opționale (utilizatorul le poate omite — defaults aplicate):
```
inst: DIA (default DIA — alternative: US30, other)
calitate: Clară (default n/a — Clară | Mai mare ca impuls | Slabă)
tf_mic: 1min (default 1min — alternativ: 3min)
tf_mare: 5min (default 5min — alternativ: 15min)
note: ... (default empty)
```
3. **Parsează răspunsul user-ului**. Tolerant la spații în jurul valorilor. Dacă lipsește un câmp obligatoriu → afișează ce lipsește și ceri din nou.
4. **Construiește dict-ul** apelând helper-ul Python (printr-un singur `python -c`):
```bash
python -c "
import json
from scripts.manual_log import build_extraction
d = build_extraction(
data='<data>',
ora_ro='<ora>',
directie='<dir>',
entry=<entry>,
sl=<sl>,
outcome_path='<out>',
instrument='<inst_or_DIA>',
tf_mare='<tf_mare_or_5min>',
tf_mic='<tf_mic_or_1min>',
calitate='<calitate_or_n/a>',
note='<note_or_empty>',
)
import pathlib
basename_no_ext = d['screenshot_file'].rsplit('.', 1)[0]
p = pathlib.Path(f'data/extractions/{basename_no_ext}.manual.json')
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(json.dumps(d, ensure_ascii=False, indent=2), encoding='utf-8')
print(json.dumps({'json_path': str(p), 'screenshot_file': d['screenshot_file']}, default=str))
"
```
Helper-ul calculează automat:
- `ora_utc` din `ora_ro` (DST-aware Europe/Bucharest)
- `tp0` = entry ± 0.4·|entrysl| (sign în funcție de directie)
- `tp1` = entry ± 0.6·|entrysl|
- `tp2` = entry ± |entrysl| (simetric SL)
- `risc_pct` = 100·|entrysl|/entry
- `max_reached` din `outcome_path` (SL→SL_first, TP0→SL→TP0, TP0→TP1→TP1, ...)
- `be_moved` din `outcome_path` (False pentru SL/pending, True pentru orice TP0→...)
- `screenshot_file` generat dacă nu e prezent: `<data>-<inst>-<ora_compactă>.png`
- Cross-field ordering validat (Buy: sl<entry<tp0<tp1<tp2; Sell invers)
5. **Append la CSV**:
```bash
python -c "
from pathlib import Path
from scripts.append_row import append_extraction
import json
r = append_extraction(Path('<json_path>'), source='<source>')
print(json.dumps(r, default=str))
"
```
6. **Dacă `status == "ok"`**:
```bash
python -m scripts.regenerate_md
```
Apoi afișează concis:
```
✅ Trade #<id> adăugat — set=<set>, outcome=<outcome_path>, pl_marius=<pl>, pl_theoretical=<pl_t>
```
7. **Dacă `status == "rejected"`**:
```
❌ Respins: <reason>
```
Dacă `reason` conține "duplicate" → trade-ul cu acel `(screenshot_file, source)` există deja. Dacă vrei să-l suprascrii, șterge linia din `data/jurnal.csv` și re-rulează (sau cere user-ului să specifice `note: <diferit>` ca să forțeze basename diferit).
Dacă `reason` conține "validation" → câmpurile au violat constraint-urile pydantic; reîntrebezi user-ul ce să corecteze.
8. **Errori în parsing user**: dacă user-ul răspunde ambiguu (ex: lipsește `dir`, sau `entry` nu e număr), afișează ce trebuie corectat și revii la step 2 cu valorile parțiale păstrate.
## Reguli
- NU edita CSV direct.
- NU regenera MD pe rejection.
- Helper-ul `build_extraction` ridică `ValueError` dacă: `entry == sl`, `Buy` cu `sl >= entry`, `Sell` cu `sl <= entry`. Propaga eroarea către user cu mesaj clar.
## Exemple
**Cel mai scurt input valid**:
```
data: 2026-05-13
ora: 17:33
dir: Sell
entry: 492.47
sl: 492.77
out: TP0→pending
```
→ generează screenshot_file=`2026-05-13-dia-1733.png`, calculează ora_utc=14:33, tp0=492.35, tp1=492.29, tp2=492.17, risc_pct=0.0609, max_reached=TP0, be_moved=True, set=A2 (Mie 17:33).
**Cu calitate și note**:
```
data: 2026-05-13
ora: 17:33
dir: Sell
entry: 492.47
sl: 492.77
out: TP0→TP1
calitate: Clară
note: bună retragere dimineața, news risc zero
```
**Calibrare**: `/m2d-log --calibration` → source=manual_calibration. Folosit cu `/backtest --calibration <screenshot>` pe același screenshot pentru P4 mismatch report.

View File

@@ -1,57 +0,0 @@
---
description: Afișează statistici WR / expectancy / per Set (din data/jurnal.csv). Cu --calibration arată raport P4.
argument-hint: "[--calibration] [--overlay pl_marius|pl_theoretical] [--seed N]"
---
# /stats — backtest statistics
Wrapper read-only peste `scripts/stats.py`. Afișează raportul stat ca atare; eventual adăugă highlight la final dacă observi un Set care îndeplinește pragul STOPPING_RULE.
## Arguments
- `--calibration` (flag) — afișează raportul P4 (mismatch field-by-field pe perechi `manual_calibration``vision_calibration` join-uite pe `screenshot_file`).
- `--overlay pl_marius|pl_theoretical` (opțional, default `pl_marius`) — care P/L overlay folosește.
- `--seed N` (opțional) — seed pentru bootstrap RNG. Folosește pentru reproducibilitate.
Default (fără flag-uri): backtest stats — overall + per-Set + per-calitate + per-instrument WR, expectancy, Wilson 95% CI pe WR, bootstrap 95% CI pe expectancy, pe overlay `pl_marius`.
## Workflow
1. **Parse `$ARGUMENTS`** și pasează-le direct prin:
```bash
python -m scripts.stats $ARGUMENTS
```
`--csv data/jurnal.csv` e default-ul scriptului; nu îl pasezi.
2. Rulează prin Bash. Output-ul vine pe stdout în UTF-8.
3. **Afișează output-ul as-is** către user. NU reformata, NU re-rezuma, NU inventa numere. Scriptul are deja format ales (tabele + secțiuni text).
4. **Highlight STOPPING_RULE** la final, DOAR dacă observi în output un Set care îndeplinește toate cele 3 thresholds din `STOPPING_RULE.md`:
- `N ≥ 40` trade-uri non-pending pe acel Set
- `WR ≥ 55%` cu Wilson 95% CI lower bound ≥ 45%
- `Expectancy ≥ +0.20R` pe overlay `pl_marius`
Dacă DA pe vreun Set:
```
🚀 STOPPING RULE: Set <X> îndeplinește pragurile (N=<n>, WR=<wr>, Wilson_LB=<lb>, E=<exp>R). Discută cu user dacă pornește forward paper trading la 0.25R per trade pe acest Set.
```
Dacă **niciun Set** nu îndeplinește toate: nu adăuga highlight. Lasă raportul scriptului să vorbească.
5. **Highlight calibration P4** (în modul `--calibration`):
- Dacă perechi `(manual_calibration, vision_calibration)` < 10 → adăugă: `Insuficient pentru P4 — continuă să acumulezi calibrare (minim 10 perechi).`
- Dacă ≥ 10 perechi și mismatch rate > 10% pe câmpuri core (`entry/sl/tp0/tp1/tp2/outcome_path/max_reached/directie`) → adăugă: `⚠️ P4 FAIL: mismatch > 10% pe câmpuri core. Fix promptul vision agent (.claude/agents/m2d-extractor.md) și re-rulează calibrarea.`
- Dacă ≥ 10 perechi și mismatch ≤ 10% → adăugă: `✅ P4 PASS: mismatch ≤ 10% pe câmpuri core.`
6. NU edita CSV. NU regenera MD. Citire pură.
## Reguli
- Read-only. Această comandă nu scrie nimic.
- Output-ul scriptului e ground truth — nu inventezi numere; doar le citești și aplici regulile STOPPING_RULE.
- `calitate` e descriptor biased post-outcome (vezi `STOPPING_RULE.md` §3) — raportul îl afișează informational only. NU sugera user-ului să folosească `calitate` ca filtru pentru GO LIVE.
- Highlight-ul `🚀 STOPPING RULE` e doar trigger pentru discuție; decizia GO LIVE rămâne a user-ului, cu caveats-urile semnate în `STOPPING_RULE.md`.