Files
atm-backtesting/scripts/generate_ferestre_v2.py
Marius c7fe08c088 analiza Ferestre v2 pe 407 trade-uri (dec 2025 inclus)
- corectii typo #182 (ordine TP0/TP1) si #338 (TP1 0.011->0.11)
- repara calea hardcodata D: -> derivata din locatia scriptului
- regenereaza Ferestre_v2.xlsx; B (19:45-21:45) atinge pragul 0.20R,
  cea mai robusta; A/W slabesc usor dupa decembrie
- actualizeaza Findings curente in CLAUDE.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 23:39:05 +03:00

461 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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
import os
_DATA = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data")
SRC = os.path.join(_DATA, "backtest.xlsx")
OUT = os.path.join(_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 [
f"1. Edge real dar MODEST. Pe toate cele {len(T)} 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()