From 31dcb4abe334328dc897c6c55004892a98419cfd Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 13 May 2026 12:33:24 +0300 Subject: [PATCH] foundation: STOPPING_RULE, WORKFLOW, pyproject, calendar YAML, _meta, directory layout --- .gitignore | 13 +- STOPPING_RULE.md | 74 ++++++++++ WORKFLOW.md | 86 ++++++++++++ calendar_evenimente.yaml | 216 +++++++++++++++++++++++++++++ data/_meta.yaml | 18 +++ data/extractions/.gitkeep | 0 data/extractions/rejected/.gitkeep | 0 pyproject.toml | 25 ++++ screenshots/inbox/.gitkeep | 0 screenshots/needs_review/.gitkeep | 0 screenshots/processed/.gitkeep | 0 11 files changed, 428 insertions(+), 4 deletions(-) create mode 100644 STOPPING_RULE.md create mode 100644 WORKFLOW.md create mode 100644 calendar_evenimente.yaml create mode 100644 data/_meta.yaml create mode 100644 data/extractions/.gitkeep create mode 100644 data/extractions/rejected/.gitkeep create mode 100644 pyproject.toml create mode 100644 screenshots/inbox/.gitkeep create mode 100644 screenshots/needs_review/.gitkeep create mode 100644 screenshots/processed/.gitkeep diff --git a/.gitignore b/.gitignore index 61b57a0..fec7b47 100644 --- a/.gitignore +++ b/.gitignore @@ -11,13 +11,18 @@ venv/ # Data — vision extractions and batch logs (regenerable) data/extractions/*.json data/extractions/*.log -data/extractions/rejected/ +data/extractions/rejected/* data/extractions/_batch_*.md +!data/extractions/.gitkeep +!data/extractions/rejected/.gitkeep # Screenshots — keep originals out of git (large binary, regenerable jurnal from CSV) -screenshots/inbox/ -screenshots/processed/ -screenshots/needs_review/ +screenshots/inbox/* +screenshots/processed/* +screenshots/needs_review/* +!screenshots/inbox/.gitkeep +!screenshots/processed/.gitkeep +!screenshots/needs_review/.gitkeep # OS / editor .DS_Store diff --git a/STOPPING_RULE.md b/STOPPING_RULE.md new file mode 100644 index 0000000..6bbf8a3 --- /dev/null +++ b/STOPPING_RULE.md @@ -0,0 +1,74 @@ +# STOPPING_RULE — M2D Backtesting + +**Versiune**: 1 +**Data**: 2026-05-13 +**Status**: draft — pentru semnătură Marius + +--- + +## Întrebarea de decis + +Pentru fiecare Set candidat (A1, A2, A3, B), decidem una din trei: + +- **GO LIVE** — pornesc forward paper trading cu 0.25R per trade (validare reală) +- **EXTEND COLLECTION** — mai colectez screenshot-uri, încă nu sunt date suficiente +- **ABANDON** — strategia nu are edge măsurabil pe acest Set; renunț la Set sau la întreaga strategie + +--- + +## Threshold-uri obligatorii pentru GO LIVE pe un Set + +Toate condițiile trebuie satisfăcute simultan: + +1. **N ≥ 40** trade-uri non-pending pe acel Set +2. **WR ≥ 55%** (Wilson 95% CI lower bound ≥ 45%) +3. **Expectancy ≥ +0.20R** pe overlay-ul `pl_marius` (50% TP0 + BE + close ~TP1) +4. **Calibration P4 PASS** — pe primele 10 trade-uri double-extracted (manual + vision), mismatch rate ≤10% pe câmpuri core (`entry`, `sl`, `tp0/1/2`, `outcome_path`, `max_reached`, `directie`) + +Dacă oricare condiție pică → **EXTEND COLLECTION** sau **ABANDON** (vezi mai jos). + +--- + +## Threshold-uri pentru ABANDON pe un Set + +Oricare e suficient: + +- N ≥ 40 și WR < 45% → edge negativ; ABANDON acest Set +- N ≥ 40 și Expectancy ≤ −0.10R → ABANDON +- Wilson 95% CI lower bound stabil sub 50% după N ≥ 60 → ABANDON + +--- + +## Caveat metodologic semnat + +**Eu, Marius, înțeleg și accept**: + +1. **N=40 = directional evidence, NU scientific proof**. Intervalul de încredere 95% pentru WR la N=40, WR observat 55%, este aproximativ [40%, 70%]. "Validated" la N=40 înseamnă "merită să tradez cu 0.25R", NU "edge confirmat statistic". Confirmarea reală vine din forward paper trading. + +2. **Selection bias rezidual**. Chiar și cu scroll protocol (vezi `WORKFLOW.md`), eu am ales perioada pe care scroll-uiesc. Acest bias e parțial mitigat, NU eliminat. + +3. **Lookahead bias pe `calitate`**. Câmpul `calitate ∈ {Clară, Mai mare ca impuls, Slabă}` este clasificat post-outcome (am văzut chart-ul întreg). DECI: NU folosesc `calitate` ca filtru în stopping rule. Rămâne descriptor în jurnal, NU criteriu de tradare. + +4. **Backtest = upper bound al expectancy real**. Execuția live va avea slippage, latențe, emoții. Expectancy real probabil 0.05-0.15R sub backtest. De aceea pragul `+0.20R` în backtest = aproximativ break-even-cu-edge-mic în live. + +5. **Indicator drift**. Dacă indicatorul blackbox se update-ează, trade-urile vechi devin istoric irelevant. Trackuit prin coloana `indicator_version` în CSV; reset stat-uri la schimbare. + +--- + +## Post-GO LIVE protocol + +După un Set primește GO LIVE: + +1. Forward paper trading cu 0.25R per trade pe acel Set. +2. Minimum 20 trade-uri live înainte de a urca sizing-ul la 0.5R sau full. +3. Dacă WR live diverge >10pp de backtest în prima 20 trade-uri → review (probabil execuție defectă sau bias subestimat). + +--- + +## Semnătură + +``` +Marius — _________________________ — data: ___________ +``` + +(prin commit-ul acestui fișier cu modificări la status "signed" + nume în istoric git) diff --git a/WORKFLOW.md b/WORKFLOW.md new file mode 100644 index 0000000..6798567 --- /dev/null +++ b/WORKFLOW.md @@ -0,0 +1,86 @@ +# WORKFLOW — colectare screenshot-uri M2D + +**Scop**: minimizează **selection bias** când colectezi screenshot-uri istorice din TradeStation. + +> **Premisa ascunsă pe care o atacăm**: dacă faci screenshot doar la trade-urile care "arată interesant", WR-ul măsurat va fi structural mai mare decât WR-ul real. Setup-urile care arată "dubios" la trigger sunt mai des losers — dacă le filtrezi inconștient, statisticile mint. + +--- + +## Regula de aur + +> **Dacă vezi o bulină verde-deschis (BUY trigger) sau roșu-deschis (SELL trigger) pe TF mic care urmează unei bulin verde-închis/roșu-închis după turquoise/galben pe TF mare → screenshot, indiferent cum arată setupul.** + +NU evaluezi calitatea ÎNAINTE de screenshot. Calitatea o pune extractorul (manual sau vision) pe baza imaginii, nu tu pe baza memoriei. + +--- + +## Scroll protocol + +### 1. Alege perioada în avans + +- Definește o fereastră calendaristică concretă: "Mar/Mie/Joi între 16:35-18:00 RO, din 2025-09-01 până în 2025-12-31". +- **Scrie perioada aleasă într-un comentariu sau fișier înainte de a începe scroll-ul**. Așa nu poți extinde sau restrânge perioada în funcție de ce vezi. + +### 2. Configurare TradeStation + +- TF mic vizibil (1min sau 3min). +- TF mare overlay sau pe ecran separat — important să vezi semnalul direcțional. +- Indicator blackbox activ pe ambele TF-uri. +- Zoom suficient ca să distingi culorile bulinelor clar. + +### 3. Scroll candle-by-candle + +- Începi cu prima zi a perioadei, ora 16:35 (sau începutul fereastrei alese). +- Avansezi candle-by-candle (sau cu pas mic). **NU sări** peste perioade lungi "pentru că arată plat" — chiar și acolo pot fi trigger-e ratate. +- La fiecare bulină verde-deschis / roșu-deschis pe TF mic în fereastra orară a perioadei → **STOP. Screenshot.** +- Verifică post-screenshot că setup-ul respectă pre-condițiile (turquoise/galben pe TF mare anterior, retragere intermediară). Dacă NU respectă → este un trigger fără setup valid; documentează cu nume `-invalid.png` (sau șterge), dar **logează decizia în WORKFLOW_LOG.md de mai jos**. + +### 4. Workflow log (audit trail anti-bias) + +Creează un fișier `data/workflow_log.md` la pornirea fiecărei sesiuni de scroll: + +```markdown +## 2026-05-13 — sesiune scroll perioada 2025-09-01 → 2025-09-15 + +- Start: 14:32 RO +- Perioada scroll-uită: 2025-09-01 → 2025-09-15, Mar/Mie/Joi, 16:35-18:00 +- Trigger-e găsite: 12 +- Screenshot-uri făcute: 12 +- Screenshot-uri NEFĂCUTE (cu motiv): + - 2025-09-04 17:14 — bulina verde-deschis dar fără verde-închis înainte → NU e M2D valid (trigger fals) +- End: 15:48 RO +``` + +**Regula**: dacă rata screenshot/trigger < 95% pe o sesiune, ai un motiv documentat pentru cele lipsă. Dacă nu — ai un bias. + +### 5. Cazuri ambigue (cum eviți cherry-picking) + +- "Nu sunt sigur că e M2D" → screenshot oricum, log la `data/workflow_log.md` ca "ambiguous: motiv". Extractorul (manual/vision) decide final. +- "Imaginea e neclară" → screenshot oricum; vision poate să returneze `confidence:low` și merge la `needs_review/`. +- "Setupul arată slab" → IRRELEVANT la momentul screenshot. Screenshot. Calitate o pune extractorul. (Da, calitate e descriptor biased — vezi STOPPING_RULE.md punct 3 — dar NU folosim calitate ca filtru.) +- "Trade-ul a pierdut clar" → IRRELEVANT. Screenshot. Asta e exact biasul pe care îl evităm. + +--- + +## Anti-pattern-uri (NU FACE asta) + +- ❌ "Scrol până găsesc un trade frumos" — biased. +- ❌ "Sar peste ziua asta, n-a fost nimic interesant" — biased. +- ❌ "Refac screenshot-ul, primul a ieșit prost" → ok dacă primul e ilizibil; NU ok dacă vrei un screenshot "mai clar" pe un trade winning. +- ❌ "Văd că e SL clar, nu merită screenshot" → exact opusul a ce vrei. + +--- + +## Calibration trades (primele 10) + +Pentru cele 10 trade-uri de calibrare (P4 gate): + +- **Tu** (Marius) extragi manual TOATE câmpurile prin `/m2d-log` (source=`manual_calibration`). +- **Apoi** rulezi `/backtest screenshot.png` pentru extracție vision (source=`vision_calibration`). +- `/stats --calibration` compară field-by-field. +- Acceptance: ≤10% mismatch pe câmpurile core. >10% → fix promptul vision agent (`.claude/agents/m2d-extractor.md`) și re-rulează. + +--- + +## Versiune +- v1 (2026-05-13) — draft inițial diff --git a/calendar_evenimente.yaml b/calendar_evenimente.yaml new file mode 100644 index 0000000..2c18d36 --- /dev/null +++ b/calendar_evenimente.yaml @@ -0,0 +1,216 @@ +schema_version: 1 + +# Format: +# - name: str +# cadence: "first_friday_monthly" | "second_wednesday_monthly" | "weekly_" | "scheduled" +# date: YYYY-MM-DD # for scheduled +# day_of_week: Mon|Tue|... # for weekly_* +# time_ro: HH:MM # all times Europe/Bucharest +# severity: extrem | mare | mediu | mic +# window_before_min: int # minutes before event for Set C trigger +# window_after_min: int # minutes after event for Set C trigger +# +# Set C trigger logic: +# severity in {extrem, mare} AND ora_ro in [time - window_before, time + window_after] + +events: + # ========== Lunar recurring ========== + - name: "NFP" + cadence: "first_friday_monthly" + time_ro: "15:30" + severity: extrem + window_before_min: 15 + window_after_min: 15 + + - name: "CPI" + cadence: "monthly_mid" + time_ro: "15:30" + severity: extrem + window_before_min: 15 + window_after_min: 15 + + - name: "PPI" + cadence: "monthly_post_cpi" + time_ro: "15:30" + severity: mare + window_before_min: 15 + window_after_min: 15 + + - name: "Retail Sales" + cadence: "monthly_15" + time_ro: "15:30" + severity: mare + window_before_min: 15 + window_after_min: 15 + + - name: "PCE Price Index" + cadence: "monthly_end" + time_ro: "15:30" + severity: mare + window_before_min: 15 + window_after_min: 15 + + - name: "ADP Employment" + cadence: "wednesday_pre_nfp" + time_ro: "15:15" + severity: mediu + window_before_min: 10 + window_after_min: 10 + + - name: "JOLTS Job Openings" + cadence: "monthly_first_week" + time_ro: "17:00" + severity: mediu + window_before_min: 10 + window_after_min: 10 + + - name: "ISM Manuf/Services" + cadence: "monthly_first_week" + time_ro: "17:00" + severity: mediu + window_before_min: 10 + window_after_min: 10 + + # ========== Săptămânal ========== + - name: "EIA Crude Oil Inventories" + cadence: "weekly_wednesday" + time_ro: "17:30" + severity: mediu + window_before_min: 10 + window_after_min: 10 + + - name: "Initial Jobless Claims" + cadence: "weekly_thursday" + time_ro: "15:30" + severity: mediu + window_before_min: 10 + window_after_min: 10 + + # ========== FOMC 2026 scheduled ========== + - name: "FOMC Statement Jan" + cadence: "scheduled" + date: "2026-01-28" + time_ro: "21:00" + severity: extrem + window_before_min: 15 + window_after_min: 30 + + - name: "FOMC Powell Press Jan" + cadence: "scheduled" + date: "2026-01-28" + time_ro: "21:30" + severity: extrem + window_before_min: 0 + window_after_min: 45 + + - name: "FOMC Statement Mar (DST shift)" + cadence: "scheduled" + date: "2026-03-18" + time_ro: "20:00" + severity: extrem + window_before_min: 15 + window_after_min: 30 + + - name: "FOMC Powell Press Mar" + cadence: "scheduled" + date: "2026-03-18" + time_ro: "20:30" + severity: extrem + window_before_min: 0 + window_after_min: 45 + + - name: "FOMC Statement Apr" + cadence: "scheduled" + date: "2026-04-29" + time_ro: "21:00" + severity: extrem + window_before_min: 15 + window_after_min: 30 + + - name: "FOMC Powell Press Apr" + cadence: "scheduled" + date: "2026-04-29" + time_ro: "21:30" + severity: extrem + window_before_min: 0 + window_after_min: 45 + + - name: "FOMC Statement Jun" + cadence: "scheduled" + date: "2026-06-17" + time_ro: "21:00" + severity: extrem + window_before_min: 15 + window_after_min: 30 + + - name: "FOMC Powell Press Jun" + cadence: "scheduled" + date: "2026-06-17" + time_ro: "21:30" + severity: extrem + window_before_min: 0 + window_after_min: 45 + + - name: "FOMC Statement Jul" + cadence: "scheduled" + date: "2026-07-29" + time_ro: "21:00" + severity: extrem + window_before_min: 15 + window_after_min: 30 + + - name: "FOMC Powell Press Jul" + cadence: "scheduled" + date: "2026-07-29" + time_ro: "21:30" + severity: extrem + window_before_min: 0 + window_after_min: 45 + + - name: "FOMC Statement Sep" + cadence: "scheduled" + date: "2026-09-16" + time_ro: "21:00" + severity: extrem + window_before_min: 15 + window_after_min: 30 + + - name: "FOMC Powell Press Sep" + cadence: "scheduled" + date: "2026-09-16" + time_ro: "21:30" + severity: extrem + window_before_min: 0 + window_after_min: 45 + + - name: "FOMC Statement Oct" + cadence: "scheduled" + date: "2026-10-28" + time_ro: "21:00" + severity: extrem + window_before_min: 15 + window_after_min: 30 + + - name: "FOMC Powell Press Oct" + cadence: "scheduled" + date: "2026-10-28" + time_ro: "21:30" + severity: extrem + window_before_min: 0 + window_after_min: 45 + + - name: "FOMC Statement Dec" + cadence: "scheduled" + date: "2026-12-09" + time_ro: "21:00" + severity: extrem + window_before_min: 15 + window_after_min: 30 + + - name: "FOMC Powell Press Dec" + cadence: "scheduled" + date: "2026-12-09" + time_ro: "21:30" + severity: extrem + window_before_min: 0 + window_after_min: 45 diff --git a/data/_meta.yaml b/data/_meta.yaml new file mode 100644 index 0000000..1404385 --- /dev/null +++ b/data/_meta.yaml @@ -0,0 +1,18 @@ +schema_version: 1 + +# Versions stamped on every CSV row. Update only when behavior changes. +indicator_version: "v-2026-05" +pl_overlay_version: "marius-v1" # 50% TP0 + BE + close ~TP1 + +csv_schema_version: 1 +calendar_schema_version: 1 + +# Tier 1 / Tier 2 trading windows (informational; Set calc uses calendar_parse.py logic) +tier_1_window: + start_ro: "16:35" + end_ro: "18:00" + days: ["Tue", "Wed", "Thu"] +tier_2_window: + start_ro: "22:00" + end_ro: "22:45" + days: ["Tue", "Wed", "Thu"] diff --git a/data/extractions/.gitkeep b/data/extractions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data/extractions/rejected/.gitkeep b/data/extractions/rejected/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2718738 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "atm-backtesting" +version = "0.1.0" +description = "M2D backtesting system — vision extraction + stats" +requires-python = ">=3.11" +dependencies = [ + "pydantic>=2.5", + "pyyaml>=6.0", + "scipy>=1.11", + "numpy>=1.26", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4", + "pytest-cov>=4.1", +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +addopts = "-ra -q" + +[tool.setuptools] +packages = ["scripts"] diff --git a/screenshots/inbox/.gitkeep b/screenshots/inbox/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/screenshots/needs_review/.gitkeep b/screenshots/needs_review/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/screenshots/processed/.gitkeep b/screenshots/processed/.gitkeep new file mode 100644 index 0000000..e69de29