From 8e51b7dc469298cc02b1a2dc340778639740fe23 Mon Sep 17 00:00:00 2001 From: Marius Date: Sun, 31 May 2026 11:36:11 +0300 Subject: [PATCH] analiza Ferestre v2: fereastra optima pe edge/durata/fiabilitate Script nou generate_ferestre_v2.py (citeste backtest.xlsx read-only, scrie data/Ferestre_v2.xlsx separat). Tabel unic cu toate variantele + validari forward (lunar, train/test 70/30, walk-forward 3 felii) + bootstrap CI + grafic echitate. Recomandari A (19:15-20:15) / B (19:45-21:45) / W (19:15-22:15). Ghid de reluare in CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 14 + data/Ferestre_v2.xlsx | Bin 0 -> 17132 bytes scripts/generate_ferestre_v2.py | 458 ++++++++++++++++++++++++++++++++ 3 files changed, 472 insertions(+) create mode 100644 data/Ferestre_v2.xlsx create mode 100644 scripts/generate_ferestre_v2.py diff --git a/CLAUDE.md b/CLAUDE.md index 791af13..8297030 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,6 +57,20 @@ The `Sesiune` column is computed by `_f_session` from `Data` + `Ora RO` (Romania `STOPPING_RULE.md` is a **signed document** (the user committed it as a commitment). It defines GO LIVE / EXTEND / ABANDON thresholds: `N≥40`, `WR≥55%`, `Expectancy≥+0.20R`. Treat these numbers as fixed unless the user explicitly asks to renegotiate them — do not "improve" them in passing. +## Ferestre v2 — analiză edge/fereastră (scripts/generate_ferestre_v2.py) + +Analiză separată care găsește **fereastra de timp (ora RO) cu cel mai bun raport edge / nr. tranzacții / durată**, fără să breach-uiască contul prop. Citește `data/backtest.xlsx` **read-only** și scrie un fișier nou `data/Ferestre_v2.xlsx` (NU atinge workbook-ul cu tranzacții; date_grafic rămâne sheet vizibil ca să se randeze chart-ul). + +**Reluare după ce Marius adaugă tranzacții noi:** +```powershell +python scripts/generate_ferestre_v2.py +``` +Totul se recalculează automat din `backtest.xlsx` (R/$ deja calculate de Excel; scriptul nu recalculează formule). Conține: Concluzii, Tabel unic cu toate variantele, validări Forward 1 (lunar) / Forward 2 (train-test 70/30) / Walk-forward (3 felii) pe toate ferestrele, bootstrap CI, calendar, grafic echitate. + +**ÎNAINTE de analiză — verifică typo-uri de tastare în Trades** (TP%/SL% cu zecimală lipsă umflă fals edge-ul). Cele găsite și corectate manual: #314 (TP2 17→0.17), #298 (TP0 0.5→0.05), #240 (TP1 0.8→0.08). La date noi, caută valori TP/SL ≥1 sau TP0>TP1>TP2 inversate și confirmă cu Marius înainte de a corecta. + +**Findings curente (330 trade-uri, ian–mai 2026, doar `hybrid_be` e pozitiv pe ansamblu ~+0.05R):** edge-ul vine din CÂND, nu din management; 18:00–19:00 RO = zonă moartă; ora de start optimă = 19:15. Trei configurații recomandate: **A** 19:15–20:15 (1h, edge max/timp min), **B** 19:45–21:45 prima (cea mai robustă pe toate validările), **W** 19:15–22:15 prima (volum/bani max raportat la timp; +30 min până la 22:45 aduc doar ~+$61). Filtrele direcționale (buy) par mai bune dar pică out-of-sample. Edge subțire → ipoteze de confirmat live. + ## Reference docs - `strategie_M2D.md` — M2D setup rules (color-coded dot bands on TF mare/mic, SL/TP placement, session filters). diff --git a/data/Ferestre_v2.xlsx b/data/Ferestre_v2.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..24e27320b317f95c19fb88b8faec46e585f4c213 GIT binary patch literal 17132 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 literal 0 HcmV?d00001 diff --git a/scripts/generate_ferestre_v2.py b/scripts/generate_ferestre_v2.py new file mode 100644 index 0000000..32dcaeb --- /dev/null +++ b/scripts/generate_ferestre_v2.py @@ -0,0 +1,458 @@ +# -*- coding: utf-8 -*- +"""Generează data/Ferestre_v2.xlsx — analiză edge × durată × fiabilitate. + +CITEȘTE backtest.xlsx (read-only) și SCRIE un fișier nou separat. +NU atinge backtest.xlsx (păstrează dropdown-urile, chart-ul, tranzacțiile). +Reruleaza oricând după ce adaugi tranzacții noi: + python scripts/generate_ferestre_v2.py +""" +import statistics +import random +from collections import defaultdict +from datetime import datetime, time, date, timedelta +import openpyxl +from openpyxl.styles import Font, PatternFill, Alignment, Border, Side +from openpyxl.utils import get_column_letter +from openpyxl.chart import LineChart, Reference +from openpyxl.drawing.line import LineProperties +from openpyxl.chart.shapes import GraphicalProperties + +SRC = "D:/PROIECTE/atm-backtesting/data/backtest.xlsx" +OUT = "D:/PROIECTE/atm-backtesting/data/Ferestre_v2.xlsx" +STRATS = ['tp0only', 'tp1only', 'tp2only', 'hybrid_be', 'hybrid_nobe'] +ACCT, DAILY, MAXL = 50000.0, 2000.0, 3500.0 +ROLUNI = {1: 'Ian', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'Mai', 6: 'Iun', + 7: 'Iul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Noi', 12: 'Dec'} + + +# ---------- load ---------- +def load(): + wb = openpyxl.load_workbook(SRC, read_only=True, data_only=True) + ws = wb['Trades'] + it = ws.iter_rows(min_row=1, values_only=True) + hdr = next(it) + h = {x: i for i, x in enumerate(hdr) if x is not None} + rows = [] + for row in it: + o = row[h['Outcome']] + if o is None or str(o).strip() == '': + continue + t = row[h['Ora RO']] + if isinstance(t, (int, float)): + hh = int(t); mm = round((t - hh) * 100); tt = time(hh, mm) + else: + p = str(t).split(':'); tt = time(int(p[0]), int(p[1])) + d = datetime.fromisoformat(str(row[h['Data']])).date() + rec = {'num': row[h['#']], 'd': d, 'min': tt.hour * 60 + tt.minute, + 't': tt.strftime('%H:%M'), 'dir': row[h['Direcție']]} + for s in STRATS: + rec['R_' + s] = row[h['R_' + s]] + rec['$_' + s] = row[h['$_' + s]] + rows.append(rec) + rows.sort(key=lambda r: (r['d'], r['min'])) + return rows + + +# ---------- engine ---------- +def in_window(rows, s, e): + return [r for r in rows if s <= r['min'] < e] + + +def apply_filter(rows, f): + if f == 'toate': + return rows + byd = defaultdict(list) + for r in rows: + byd[r['d']].append(r) + out = [] + if f == 'prima': + for d, rs in byd.items(): + out.append(rs[0]) + elif f == 'prima2': + for d, rs in byd.items(): + out.extend(rs[:2]) + elif f == 'buy': + out = [r for r in rows if r['dir'] == 'Buy'] + elif f == 'sell': + out = [r for r in rows if r['dir'] == 'Sell'] + elif f == 'prima_buy': + for d, rs in byd.items(): + b = [r for r in rs if r['dir'] == 'Buy'] + if b: + out.append(b[0]) + elif f == 'prima_sell': + for d, rs in byd.items(): + x = [r for r in rs if r['dir'] == 'Sell'] + if x: + out.append(x[0]) + out.sort(key=lambda r: (r['d'], r['min'])) + return out + + +def metrics(rows, strat): + n = len(rows) + if n == 0: + return None + R = [r['R_' + strat] for r in rows] + D = [r['$_' + strat] for r in rows] + eq = ACCT; peak = ACCT; maxdd = 0.0 + cur = None; daycum = 0.0; daymin = 0.0; dbreach = False; mbreach = False + for r in rows: + if r['d'] != cur: + cur = r['d']; daycum = 0.0; daymin = 0.0 + daycum += r['$_' + strat]; daymin = min(daymin, daycum) + if -daymin >= DAILY: + dbreach = True + eq += r['$_' + strat]; peak = max(peak, eq); maxdd = max(maxdd, peak - eq) + if peak - eq >= MAXL: + mbreach = True + return dict(n=n, wr=sum(1 for x in R if x > 0) / n * 100, exp=statistics.mean(R), + totR=sum(R), totD=sum(D), maxdd=maxdd, breach=dbreach or mbreach) + + +def expr_on(rows, cfg): + """ExpR (R mediu) pentru cfg pe un subset de rânduri; None dacă nu sunt tranzacții.""" + sel = apply_filter(in_window(rows, cfg['s'], cfg['e']), cfg['filt']) + if not sel: + return None + return statistics.mean([r['R_' + cfg['strat']] for r in sel]) + + +def bootstrap(rows, strat, nboot=10000, seed=12345): + """Re-eșantionare cu înlocuire: distribuția lui ExpR și a $ total pe N tranzacții.""" + rnd = random.Random(seed) + R = [r['R_' + strat] for r in rows] + D = [r['$_' + strat] for r in rows] + n = len(R) + if n == 0: + return dict(n=0, expR_med=0, expR_lo=0, expR_hi=0, p_pos=0, p_02=0, totD_med=0, totD_lo=0, totD_hi=0) + means_R = []; tot_D = [] + for _ in range(nboot): + sample = [rnd.randrange(n) for _ in range(n)] + means_R.append(sum(R[i] for i in sample) / n) + tot_D.append(sum(D[i] for i in sample)) + means_R.sort(); tot_D.sort() + + def pct(arr, p): + return arr[min(len(arr) - 1, int(p * len(arr)))] + + return dict( + n=n, + expR_med=means_R[len(means_R) // 2], + expR_lo=pct(means_R, 0.025), expR_hi=pct(means_R, 0.975), + p_pos=sum(1 for x in means_R if x > 0) / nboot * 100, + p_02=sum(1 for x in means_R if x >= 0.20) / nboot * 100, + totD_med=tot_D[len(tot_D) // 2], + totD_lo=pct(tot_D, 0.025), totD_hi=pct(tot_D, 0.975), + ) + + +def fmt(m): + return f"{m // 60:02d}:{m % 60:02d}" + + +# ---------- styles ---------- +TITLE = Font(bold=True, size=14, color="1F4E78") +SUB = Font(bold=True, size=11, color="1F4E78") +HF = Font(bold=True, color="FFFFFF") +HFILL = PatternFill("solid", fgColor="1F4E78") +GOOD = PatternFill("solid", fgColor="C6EFCE") +WARNF = PatternFill("solid", fgColor="FFEB9C") +BAD = PatternFill("solid", fgColor="FFC7CE") +GREY = PatternFill("solid", fgColor="F2F2F2") +CTR = Alignment(horizontal="center") +LEFT = Alignment(horizontal="left", wrap_text=True) +THIN = Border(left=Side(style="thin", color="BFBFBF"), right=Side(style="thin", color="BFBFBF"), + top=Side(style="thin", color="BFBFBF"), bottom=Side(style="thin", color="BFBFBF")) + + +def build(): + T = load() + d0 = min(r['d'] for r in T); d1 = max(r['d'] for r in T) + alld = sorted(set(r['d'] for r in T)) + + A = dict(s=19 * 60 + 15, e=20 * 60 + 15, filt='toate', strat='hybrid_be') + B = dict(s=19 * 60 + 45, e=21 * 60 + 45, filt='prima', strat='hybrid_be') + W = dict(s=19 * 60 + 15, e=22 * 60 + 15, filt='prima', strat='hybrid_be') + cut = alld[int(len(alld) * 0.70)] + tr = [r for r in T if r['d'] < cut] + te = [r for r in T if r['d'] >= cut] + + # toate variantele analizate (folosite în tabelul unic ȘI în toate validările) + VARIANTS = [ + ("BRUT (referință)", dict(s=0, e=1440, filt='toate', strat='hybrid_be'), GREY), + ("A ⭐ edge/timp", A, GOOD), + ("familia 19:15", dict(s=19 * 60 + 15, e=20 * 60 + 45, filt='prima', strat='hybrid_be'), None), + ("familia 19:15", dict(s=19 * 60 + 15, e=21 * 60 + 15, filt='prima', strat='hybrid_be'), None), + ("B ⭐ echilibru", B, GOOD), + ("C direcție (fragil)", dict(s=19 * 60 + 15, e=21 * 60 + 15, filt='prima_buy', strat='tp1only'), WARNF), + ("familia 19:15", dict(s=19 * 60 + 15, e=21 * 60 + 45, filt='prima', strat='hybrid_be'), None), + ("W ⭐ volum/bani", W, GOOD), + ("fereastra ta", dict(s=19 * 60 + 30, e=22 * 60 + 45, filt='prima', strat='hybrid_be'), WARNF), + ("alt. mai lungă", dict(s=19 * 60 + 15, e=22 * 60 + 45, filt='prima', strat='hybrid_be'), None), + ("familia 19:15", dict(s=19 * 60 + 15, e=23 * 60, filt='prima', strat='hybrid_be'), None), + ] + + def msel(rows, cfg): + return metrics(apply_filter(in_window(rows, cfg['s'], cfg['e']), cfg['filt']), cfg['strat']) + + def wlabel(cfg): + dd = cfg['e'] - cfg['s'] + return "(fără fer.)" if dd >= 1440 else f"{fmt(cfg['s'])}-{fmt(cfg['e'])}" + + def expcolor(e): + return GOOD if e >= 0.10 else (WARNF if e >= 0 else BAD) + + wb = openpyxl.Workbook(); ws = wb.active; ws.title = "Ferestre v2" + ws.sheet_view.showGridLines = False + for i, w in enumerate([21, 15, 8, 13, 9, 9, 9, 16, 9, 10, 10, 9], 1): + ws.column_dimensions[get_column_letter(i)].width = w + state = {'R': 1} + + def put(r, c, v, font=None, fill=None, fmtn=None, align=None, border=False): + cell = ws.cell(row=r, column=c, value=v) + if font: cell.font = font + if fill: cell.fill = fill + if fmtn: cell.number_format = fmtn + if align: cell.alignment = align + if border: cell.border = THIN + return cell + + def title(txt): + put(state['R'], 1, txt, SUB); state['R'] += 1 + + def headers(hs): + for j, hh in enumerate(hs, 1): + put(state['R'], j, hh, HF, HFILL, align=CTR, border=True) + state['R'] += 1 + + def row(vals, fills=None, fmts=None): + for j, v in enumerate(vals, 1): + f = fills[j - 1] if fills else None + nf = fmts[j - 1] if fmts else None + put(state['R'], j, v, fill=f, fmtn=nf, border=True, align=CTR if j > 1 else LEFT) + state['R'] += 1 + + def note(txt): + put(state['R'], 1, txt, align=LEFT); state['R'] += 1 + + def blank(): + state['R'] += 1 + + def pad(vals, fills, fmts, n=12): + while len(vals) < n: + vals.append(""); fills.append(None); fmts.append(None) + return vals, fills, fmts + + put(state['R'], 1, "FERESTRE v2 — edge × durată × fiabilitate", TITLE); state['R'] += 1 + note(f"Sursă: backtest.xlsx · {len(T)} tranzacții M2D/DIA · {d0:%d.%m.%Y}–{d1:%d.%m.%Y} · ora RO · cont prop $50k (daily $2k / max $3.5k)") + note("Date corectate (typo #314/#298/#240). ExpR = R mediu/tranzacție · maxDD = drawdown maxim pe traseu · 'breach' = ar fi omorât contul prop.") + blank() + + title("CONCLUZII (citește întâi astea)") + for c in [ + "1. Edge real dar MODEST. Pe toate cele 330 de tranzacții, doar managementul hybrid_be e pozitiv (~+0.05R). Edge-ul vine din CÂND tranzacționezi, nu din ce management alegi.", + "2. Fereastra de aur = ~19:00–21:00 RO. Ora 18:00–19:00 e zonă moartă (−0.10R); orice fereastră care o include își diluează edge-ul. Ora de START optimă = 19:15.", + "3. Trei opțiuni recomandate: A = 19:15–20:15 (1h, edge maxim/tranzacție, timp minim) · B = 19:45–21:45 (2h, cel mai bun edge robust, trece pragul 0.20R) · W = 19:15–22:15 (3h, cei mai mulți bani raportat la timp: +$1.3k vs B, N=89, edge 0.17R sub prag). A prelungi până la 22:45 aduce doar ~+$61 marginal.", + "4. Pt durate SCURTE (≤2h) plasarea B (19:45-21:45) bate start-ul 19:15; 19:15 câștigă DOAR pe ferestre lungi (3h+). B rămâne cea mai de încredere (pozitiv în fiecare lună, cel mai puternic out-of-sample, cel mai bun interval bootstrap).", + "5. Bootstrap (10.000 scenarii): edge-ul e pozitiv în 98–99% din cazuri → e REAL, nu noroc. DAR mărimea lui e incertă: ~50% șansă să fie efectiv peste 0.20R. Adevărul probabil e 0.10–0.21R.", + "6. Filtrele direcționale (doar Buy — rândul C) dau ExpR mai mare, dar interval bootstrap mai larg cu limita de jos lângă 0 și depind de regimul bull → fragile. Vezi validările: edge-ul direcției se clatină pe felii, A/B/W nu. Opțiunile A/B/W nu depind de direcție.", + "7. Calendarul de evenimente (FOMC/NFP) NU influențează negativ; prea puține zile pt o regulă de news-filter.", + "8. Avertisment: ~5000 configurații scanate pe eșantion mic → tratează totul ca IPOTEZE de confirmat live, nu certitudini.", + ]: + note(c) + blank() + + # ===================== TABEL UNIC ===================== + title("TABEL UNIC — toate variantele (management hybrid_be, dacă nu scrie altfel în Filtru)") + note("Sortate după durată. Rol: ⭐ = recomandate (A edge/timp · B echilibru · W volum-bani) · BRUT = referință fără fereastră (sparge contul!) · " + "'fereastra ta' = 19:30-22:45 · C = variantă pe direcție (mai fragilă). CI 95% ExpR = interval bootstrap (dacă e tot peste 0 → edge robust). " + "OOS = edge pe ultimele ~6 săpt. (verde ≥0.10). Δ$ vs B = bani față de B. Toate sunt non-breach (maxDD ~$1.1–1.9k) EXCEPTÂND BRUT.") + bD = metrics(apply_filter(in_window(T, B['s'], B['e']), B['filt']), B['strat'])['totD'] + + def mrow(rol, cfg, fill): + sel = apply_filter(in_window(T, cfg['s'], cfg['e']), cfg['filt']) + m = metrics(sel, cfg['strat']); b = bootstrap(sel, cfg['strat']) + oosm = msel(te, cfg); oosx = oosm['exp'] if oosm else 0.0 + foos = expcolor(oosx) + dd = cfg['e'] - cfg['s'] + durs = "—" if dd >= 1440 else f"{dd // 60}h{dd % 60:02d}" + flt = cfg['filt'] + ("·tp1only" if cfg['strat'] == 'tp1only' else "") + row([rol, wlabel(cfg), durs, flt, m['n'], m['wr'], round(m['exp'], 3), + f"[{b['expR_lo']:+.2f};{b['expR_hi']:+.2f}]", round(oosx, 3), + round(m['totD']), round(m['totD'] - bD), round(m['maxdd'])], + fills=[fill, None, None, None, None, None, None, None, foos, None, None, None], + fmts=[None, None, None, None, '0', '0.0"%"', '0.000', None, '0.000', '$#,##0', '$#,##0', '$#,##0']) + + headers(["Rol", "Fereastră RO", "Durată", "Filtru", "N", "WR%", "ExpR", "CI 95% ExpR", "OOS", "$ total", "Δ$ vs B", "maxDD$"]) + for rol, cfg, fill in VARIANTS: + mrow(rol, cfg, fill) + blank() + note("Cum citești: B face $%d în 2h. W (19:15-22:15) face cu ~$%d mai mult dar în 3h și cu edge/tranzacție mai mic. " + "Fereastra ta (19:30-22:45) face MAI PUȚIN decât B — problema e start-ul la 19:30 (pierzi slotul tare 19:15-19:30). " + "BRUT (fără fereastră) sparge contul prop. C (direcție) are edge mai mare dar fragil." + % (round(bD), round(metrics(apply_filter(in_window(T, W['s'], W['e']), 'prima'), 'hybrid_be')['totD'] - bD))) + blank() + + # ===================== EXPLICAȚII VALIDĂRI ===================== + title("CE ÎNSEAMNĂ VALIDĂRILE (citește înainte de tabelele de mai jos)") + note("• ExpR = R mediu pe tranzacție = EDGE-ul. 1R = riscul tău pe o tranzacție (SL). +0.20R înseamnă că, în medie, câștigi 0.2× riscul pe fiecare tranzacție. Negativ = pierzi în medie.") + note("• Forward 1 (LUNAR): edge-ul calculat în FIECARE lună separat. Vrei pozitiv în toate lunile = edge constant, nu noroc concentrat într-o lună. Atenție: N mic/lună (6–17) → o singură tranzacție mișcă mult media.") + note("• Forward 2 (TRAIN/TEST): 'antrenez' pe primele 70% din zile, apoi verific pe ultimele 30% pe care nu le-am folosit la alegere (out-of-sample). ExpR test ≈ ExpR train → edge robust. ExpR test mult mai mic sau negativ → era 'potrivit pe trecut' (overfit).") + note("• Walk-forward (3 FELII): împart perioada în 3 bucăți cronologice egale. P1 = început, P2 și P3 = 'viitorul' față de P1. O regulă bună rămâne pozitivă în toate trei feliile — nu doar la început.") + note("• Culori în toate validările: VERDE = bun (≥0.10R) · GALBEN = slab (0–0.10R) · ROȘU = negativ. Gol = nicio tranzacție în acea felie/lună.") + blank() + + # ===================== FORWARD 1 — LUNAR (toate variantele) ===================== + months = sorted({f"{r['d']:%Y-%m}" for r in T}) + mlabels = [ROLUNI[int(m[5:7])] for m in months] + title("VALIDARE FORWARD 1 — consistență LUNARĂ (ExpR pe fiecare lună), TOATE variantele") + headers(pad(["Variantă", "Fereastră"] + mlabels, [None] * (2 + len(mlabels)), [None] * (2 + len(mlabels)))[0]) + for rol, cfg, fill in VARIANTS: + sel = apply_filter(in_window(T, cfg['s'], cfg['e']), cfg['filt']) + bym = defaultdict(list) + for r in sel: + bym[f"{r['d']:%Y-%m}"].append(r) + vals = [rol, wlabel(cfg)]; fills = [fill, None]; fmts = [None, None] + for m in months: + rr = bym.get(m, []) + if rr: + e = statistics.mean([x['R_' + cfg['strat']] for x in rr]) + vals.append(round(e, 3)); fills.append(expcolor(e)); fmts.append('0.000') + else: + vals.append(""); fills.append(GREY); fmts.append(None) + row(*pad(vals, fills, fmts)) + blank() + + # ===================== FORWARD 2 — TRAIN/TEST (toate variantele) ===================== + title("VALIDARE FORWARD 2 — TRAIN/TEST 70/30, TOATE variantele") + note(f"Train: {tr[0]['d']:%d.%m}–{cut:%d.%m} · Test/OOS: {cut:%d.%m}–{d1:%d.%m}. Verde la 'ExpR test' = edge-ul a ținut pe datele nevăzute. " + "Δ (test−train) aproape de 0 sau pozitiv = stabil; foarte negativ = overfit.") + headers(["Variantă", "Fereastră", "N train", "ExpR train", "N test", "ExpR test (OOS)", "Δ (test−train)", "", "", "", "", ""]) + for rol, cfg, fill in VARIANTS: + mtr = msel(tr, cfg); mte = msel(te, cfg) + etr = mtr['exp'] if mtr else 0.0; ete = mte['exp'] if mte else 0.0 + ntr = mtr['n'] if mtr else 0; nte = mte['n'] if mte else 0 + row([rol, wlabel(cfg), ntr, round(etr, 3), nte, round(ete, 3), round(ete - etr, 3), "", "", "", "", ""], + fills=[fill, None, None, None, None, expcolor(ete), None, None, None, None, None, None], + fmts=[None, None, '0', '0.000', '0', '0.000', '0.000', None, None, None, None, None]) + blank() + + # ===================== WALK-FORWARD — 3 FELII (toate variantele) ===================== + n3 = len(alld) // 3 + P = [set(alld[:n3]), set(alld[n3:2 * n3]), set(alld[2 * n3:])] + pr = [(alld[0], alld[n3 - 1]), (alld[n3], alld[2 * n3 - 1]), (alld[2 * n3], alld[-1])] + title("VALIDARE WALK-FORWARD — edge pe 3 FELII cronologice, TOATE variantele") + note("P1=%s–%s · P2=%s–%s · P3=%s–%s. Vrei pozitiv (verde) în toate trei = edge stabil în timp, nu doar la început." % ( + pr[0][0].strftime('%d.%m'), pr[0][1].strftime('%d.%m'), + pr[1][0].strftime('%d.%m'), pr[1][1].strftime('%d.%m'), + pr[2][0].strftime('%d.%m'), pr[2][1].strftime('%d.%m'))) + headers(["Variantă", "Fereastră", "P1 ExpR", "P2 ExpR", "P3 ExpR", "N total", "", "", "", "", "", ""]) + for rol, cfg, fill in VARIANTS: + sel = apply_filter(in_window(T, cfg['s'], cfg['e']), cfg['filt']) + vals = [rol, wlabel(cfg)]; fills = [fill, None]; fmts = [None, None] + for ps in P: + rr = [r for r in sel if r['d'] in ps] + if rr: + e = statistics.mean([x['R_' + cfg['strat']] for x in rr]) + vals.append(round(e, 3)); fills.append(expcolor(e)); fmts.append('0.000') + else: + vals.append(""); fills.append(GREY); fmts.append(None) + vals.append(len(sel)); fills.append(None); fmts.append('0') + row(*pad(vals, fills, fmts)) + note("Observă: A/B/W rămân verzi (pozitive) pe toate feliile = edge stabil. C (direcție) și fereastra ta se clatină mai mult de la o felie la alta. " + "Capcana overfit: dacă ai alege ORBEȘTE fereastra cu edge maxim pe P1, ea tinde să se prăbușească pe P2/P3 — de-aia preferăm stabilitatea, nu vârful.") + blank() + + # ===================== CALENDAR ===================== + title("CALENDAR EVENIMENTE — influență?") + FOMC = {date(2026, 1, 28), date(2026, 3, 18), date(2026, 4, 29)} + + def first_fri(y, mo): + d = date(y, mo, 1) + while d.weekday() != 4: + d += timedelta(days=1) + return d + + NFP = {first_fri(2026, mo) for mo in range(1, 6)} + headers(["Grup", "N", "WR%", "ExpR (hybrid_be)", "", "", "", "", "", "", "", ""]) + + def grp(rows): + Rm = [r['R_hybrid_be'] for r in rows] + return len(Rm), (sum(1 for x in Rm if x > 0) / len(Rm) * 100 if Rm else 0), (statistics.mean(Rm) if Rm else 0) + + A_all = apply_filter(in_window(T, A['s'], A['e']), 'toate') + for label, rows in (("Zile FOMC", [r for r in T if r['d'] in FOMC]), + ("Restul zilelor", [r for r in T if r['d'] not in FOMC]), + ("Zile NFP (prima vineri)", [r for r in T if r['d'] in NFP]), + ("Config A — toate zilele", A_all), + ("Config A — fără FOMC+NFP", [r for r in A_all if r['d'] not in FOMC | NFP])): + n, wr, ex = grp(rows) + row([label, n, wr, round(ex, 3), "", "", "", "", "", "", "", ""], + fmts=[None, '0', '0.0"%"', '0.000', None, None, None, None, None, None, None, None]) + note("Verdict: fără efect negativ măsurabil. FOMC/NFP au fost ușor POZITIVE; prea puține zile (3 FOMC, 5 NFP) pt o regulă de news-filter.") + blank() + + title("NOTE") + for n in [ + "• Edge real subțire: pe toate tranzacțiile, doar hybrid_be e pozitiv (~+0.05R). Edge-ul vine din CÂND, nu din management.", + "• 18:00–19:00 RO = zonă moartă (−0.10R). Ora de start optimă = 19:15.", + "• ~5000 configurații scanate → top-by-ExpR supraestimează. De-aia validăm cu lunar + train/test + walk-forward + bootstrap. ExpR ~0.2R pe N~50-94 = interval de încredere larg.", + "• Filtrele direcționale (buy/sell) dau edge nominal mai mare dar pică out-of-sample (regim). A/B/W nu depind de direcție.", + "• Reruleaza după ce adaugi tranzacții: python scripts/generate_ferestre_v2.py", + ]: + note(n) + blank() + + # ===================== GRAFIC ===================== + title("GRAFIC — curbă de echitate ($ cumulativ): B vs W (19:15-22:15)") + note("Ambele cu filtru Prima + management hybrid_be, pe contul prop $50k. Aliniate pe dată, ca să compari câștigul și 'netezimea'.") + chart_anchor = f"A{state['R'] + 1}" + + def daily_sum(cfg): + sel = apply_filter(in_window(T, cfg['s'], cfg['e']), cfg['filt']) + byd = defaultdict(float) + for r in sel: + byd[r['d']] += r['$_' + cfg['strat']] + return byd + + cumB = daily_sum(B); cumW = daily_sum(W) + ds = wb.create_sheet("date_grafic") + ds.append(["Data", "B 19:45-21:45", "W 19:15-22:15"]) + accB = 0.0; accW = 0.0 + for d in alld: + accB += cumB.get(d, 0.0); accW += cumW.get(d, 0.0) + ds.append([d, round(accB), round(accW)]) + nrows = len(alld) + for r in range(2, nrows + 2): + ds.cell(row=r, column=1).number_format = 'dd.mm' + + chart = LineChart() + chart.title = "Curbă de echitate ($ cumulativ) — B vs W (19:15-22:15)" + chart.style = 2 + chart.height = 9.5; chart.width = 24 + chart.y_axis.title = "$ cumulativ (cont prop)" + chart.x_axis.title = "Data" + chart.x_axis.number_format = 'dd.mm' + chart.x_axis.majorTimeUnit = "days" + chart.x_axis.delete = False + chart.y_axis.delete = False + data = Reference(ds, min_col=2, max_col=3, min_row=1, max_row=nrows + 1) + cats = Reference(ds, min_col=1, min_row=2, max_row=nrows + 1) + chart.add_data(data, titles_from_data=True) + chart.set_categories(cats) + for s, color in zip(chart.series, ("2E7D32", "1F4E78")): + s.graphicalProperties = GraphicalProperties() + s.graphicalProperties.line = LineProperties(solidFill=color, w=20000) + s.smooth = False + ws.add_chart(chart, chart_anchor) + ds.column_dimensions['A'].width = 11 + for col in ('B', 'C'): + ds.column_dimensions[col].width = 15 + + wb.save(OUT) + print(f"Scris {OUT} ({state['R']} rânduri, grafic la {chart_anchor}).") + + +if __name__ == "__main__": + build()