manual_log: helper Python + simplified /m2d-log (6 fields obligatorii, restul derivate)

This commit is contained in:
Marius
2026-05-13 13:10:02 +03:00
parent d19e0331d8
commit 68f14095e1
3 changed files with 420 additions and 68 deletions

View File

@@ -1,99 +1,142 @@
---
description: Adaugă manual un rând în jurnal.csv (source=manual sau manual_calibration). Pentru calibrare P4 sau forward paper.
argument-hint: "[--calibration] <screenshot_path>"
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 — manual M2D trade entry
# /m2d-log — quick manual M2D entry
Marius extrage manual TOATE câmpurile trade-ului. Folosit pentru calibration P4 (împreună cu `/backtest --calibration` pe același screenshot) sau ca log direct fără vision.
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`** detectează flag `--calibration` și `<screenshot_path>`. Dacă `<screenshot_path>` lipsește, întreabă user-ul. Calculează `basename = basename(<screenshot_path>)` și `basename_no_ext = basename` minus ultima extensie.
1. **Parse `$ARGUMENTS`** — flag `--calibration` produce `source=manual_calibration`; altfel `source=manual`.
2. **Promptează user-ul în română**, pe rând, pentru fiecare câmp din schema `M2DExtraction` (vezi `scripts/vision_schema.py`). Ordinea + opțiuni valide:
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``YYYY-MM-DD`
- `ora_utc``HH:MM` (conversie din RO local: EEST=UTC+3 vară, EET=UTC+2 iarnă; întreabă user-ul direct dacă nu e clar)
- `instrument``DIA` / `US30` / `other`
- `directie``Buy` / `Sell`
- `tf_mare``5min` / `15min`
- `tf_mic``1min` / `3min`
- `calitate``Clară` / `Mai mare ca impuls` / `Slabă` / `n/a`
- `entry`, `sl`, `tp0`, `tp1`, `tp2` — float-uri
- `risc_pct` — float (ex: `0.12` pentru 0.12%)
- `outcome_path``SL` / `TP0→SL` / `TP0→TP1` / `TP0→TP2` / `TP0→pending` / `pending` (UNICODE `→`)
- `max_reached``SL_first` / `TP0` / `TP1` / `TP2`
- `be_moved``true` / `false`
- `confidence` — default `high` (manual e by definition high)
- `note` — string opțional, default `""`
`screenshot_file` se setează automat la `basename`; `ambiguities` se setează automat la `[]`. Dacă user-ul dă valoare invalidă, repetă întrebarea.
3. **Construiește JSON-ul** complet, valid contra `M2DExtraction`.
4. **Scrie JSON-ul** la `data/extractions/<basename_no_ext>.manual.json` — pretty-print indent 2, UTF-8, newline final. Sufixul `.manual` previne coliziunea cu output-ul vision (`<basename_no_ext>.json`).
5. **Determină source**: `manual_calibration` dacă `--calibration` e prezent, altfel `manual`.
6. **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>.manual.json'), source='<source>'); print(json.dumps(r, default=str))"
```
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)
```
Parsezi răspunsul JSON.
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)
```
7. **Dacă `status == "ok"`**:
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șezi:
Apoi afișează concis:
```
✅ Trade adăugat la jurnal. ID: <id>. Set: <set>. P/L Marius: <pl_marius>. outcome_path: <outcome_path>.
✅ Trade #<id> adăugat — set=<set>, outcome=<outcome_path>, pl_marius=<pl>, pl_theoretical=<pl_t>
```
8. **Dacă `status == "rejected"`**:
7. **Dacă `status == "rejected"`**:
```
Trade respins: <reason>
Respins: <reason>
```
NU regenera MD. Dacă `reason` conține "duplicate":
- pentru `--calibration`: spui user-ului că există deja rând `manual_calibration` pentru acest screenshot; nu poți avea două leg-uri manual de calibrare pe același screenshot.
- pentru `source=manual` simplu: user-ul decide dacă suprascrie (atunci șterge manual rândul din `data/jurnal.csv` și re-rulează).
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 — folosește `append_extraction`.
- NU regenera MD dacă append-ul a fost respins.
- 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.
## Output skeleton JSON
## Exemple
```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": ""
}
**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.