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) <noreply@anthropic.com>
This commit is contained in:
Marius
2026-05-31 11:36:11 +03:00
parent 08adabe4e9
commit 8e51b7dc46
3 changed files with 472 additions and 0 deletions

View File

@@ -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. `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, ianmai 2026, doar `hybrid_be` e pozitiv pe ansamblu ~+0.05R):** edge-ul vine din CÂND, nu din management; 18:0019:00 RO = zonă moartă; ora de start optimă = 19:15. Trei configurații recomandate: **A** 19:1520:15 (1h, edge max/timp min), **B** 19:4521:45 prima (cea mai robustă pe toate validările), **W** 19:1522: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 ## Reference docs
- `strategie_M2D.md` — M2D setup rules (color-coded dot bands on TF mare/mic, SL/TP placement, session filters). - `strategie_M2D.md` — M2D setup rules (color-coded dot bands on TF mare/mic, SL/TP placement, session filters).

BIN
data/Ferestre_v2.xlsx Normal file

Binary file not shown.

View File

@@ -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:0021:00 RO. Ora 18:0019: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:1520:15 (1h, edge maxim/tranzacție, timp minim) · B = 19:4521:45 (2h, cel mai bun edge robust, trece pragul 0.20R) · W = 19:1522: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 9899% 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.100.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.11.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ă (617) → 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 (00.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. "
"Δ (testtrain) aproape de 0 sau pozitiv = stabil; foarte negativ = overfit.")
headers(["Variantă", "Fereastră", "N train", "ExpR train", "N test", "ExpR test (OOS)", "Δ (testtrain)", "", "", "", "", ""])
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:0019: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()