diff --git a/data/M2D DOW UNA PE SEARA.xlsx b/data/M2D DOW UNA PE SEARA.xlsx new file mode 100644 index 0000000..1088ba6 Binary files /dev/null and b/data/M2D DOW UNA PE SEARA.xlsx differ diff --git a/data/backtest-atm-m2d-una-pe-seara.xlsx b/data/backtest-atm-m2d-una-pe-seara.xlsx new file mode 100644 index 0000000..db886c1 Binary files /dev/null and b/data/backtest-atm-m2d-una-pe-seara.xlsx differ diff --git a/scripts/generate_mentor_check.py b/scripts/generate_mentor_check.py new file mode 100644 index 0000000..fcd3f25 --- /dev/null +++ b/scripts/generate_mentor_check.py @@ -0,0 +1,601 @@ +"""Generator pentru data/backtest-atm-m2d-una-pe-seara.xlsx. + +Transpune backtest-ul mentorului (M2D DOW UNA PE SEARA.xlsx) în schema noastră +completă (Config + Trades + Dashboard) ca să verificăm dacă WR, R:R, profit +factor etc. ies la fel cu cifrele declarate de mentor. + +Reguli de transpunere: + - Mentor TP1 → TP0 nostru ; Mentor TP2 → TP1 nostru ; Mentor TP3 → TP2 nostru + - Regula R:R 1:1: SL plan = TP3 plan (când e atins) sau |valoarea negativă| + - Pentru cazurile intermediare (TP3=0), SL = ultimul TP atins × 1.36 + +Output: data/backtest-atm-m2d-una-pe-seara.xlsx +Rulare: + python scripts/generate_mentor_check.py +""" + +from __future__ import annotations + +from datetime import date, datetime, time +from pathlib import Path + +from openpyxl import load_workbook +from openpyxl.styles import Alignment, Border, Font, PatternFill, Side + +# Reutilizare directă: schema completă (Config, Trades cu formule, Dashboard) +from generate_template import ( + BORDER, + CENTER, + COL, + DERIVED_FILL, + HEADER_FILL, + HEADER_FONT, + INPUT_FILL, + STRAT_KEYS, + STRAT_LABELS, + SUBTITLE_FONT, + TITLE_FONT, + build_workbook, +) + + +MENTOR_FILE = Path(__file__).resolve().parent.parent / "data" / "M2D DOW UNA PE SEARA.xlsx" +OUTPUT = Path(__file__).resolve().parent.parent / "data" / "backtest-atm-m2d-una-pe-seara.xlsx" + +# Raport mediu TP3/TP2 observat în datele complete ale mentorului +RATIO = 1.36 + +# Cutoff calcule mentor: mentorul a actualizat statisticile doar pe trade-urile +# până la 24.04.2026 INCLUSIV; restul (post-24.04) au fost doar înregistrate. +CUTOFF_DATE = date(2026, 4, 24) + +# Stiluri suplimentare pentru sheet-ul Comparatie +OK_FILL = PatternFill("solid", fgColor="C6EFCE") +DIFF_FILL = PatternFill("solid", fgColor="FFC7CE") +NOTE_FONT = Font(name="Calibri", size=10, italic=True, color="595959") + + +# --------------------------------------------------------------------------- +# Derivare per trade +# --------------------------------------------------------------------------- + + +def _round2(x: float) -> float: + return round(x, 2) + + +def derive(tp1m, tp2m, tp3m, ls): + """Mapează (TP1, TP2, TP3, L/S) mentor → (sl, tp0, tp1, tp2, outcome, direction). + + Cele 4 cazuri din planul aprobat: + 1. Valoare negativă pe orice coloană → SL hit + 2. TP3 > 0 → a atins maximul (R:R 1:1) + 3. TP2 > 0, TP3 = 0 → intermediar 2 + 4. TP1 > 0, TP2 = TP3 = 0 → intermediar 1 + """ + direction = "Buy" if ls == "L" else "Sell" + vals = [tp1m or 0, tp2m or 0, tp3m or 0] + + if min(vals) < 0: + sl = abs(min(vals)) + tp2 = sl + tp1 = sl / RATIO + tp0 = tp1 / 1.65 + outcome = "SL" + elif tp3m and tp3m > 0: + sl = tp3m + tp0, tp1, tp2 = tp1m, tp2m, tp3m + outcome = "TP2" + elif tp2m and tp2m > 0: + sl = tp2m * RATIO + tp0, tp1, tp2 = tp1m, tp2m, sl + outcome = "TP1" + elif tp1m and tp1m > 0: + sl = tp1m * RATIO + tp0 = tp1m + tp2 = sl + tp1 = (tp0 + tp2) / 2 + outcome = "TP0" + else: + # Toate 0 — BE (nu ar trebui să apară pentru rânduri cu L/S non-null, dar fallback) + sl = 0.10 + tp0, tp1, tp2 = 0.04, 0.07, 0.10 + outcome = "" + + return _round2(sl), _round2(tp0), _round2(tp1), _round2(tp2), outcome, direction + + +# --------------------------------------------------------------------------- +# Citire mentor file +# --------------------------------------------------------------------------- + + +def _parse_date(v): + if isinstance(v, datetime): + return v.date() + if isinstance(v, date): + return v + if isinstance(v, str): + try: + return datetime.strptime(v, "%d.%m.%Y").date() + except ValueError: + return None + return None + + +def read_mentor(): + """Returnează lista trade-urilor și statistici (subset vs total). + + Important: mentorul a actualizat calculele finale (rândurile 167-175) DOAR + pe trade-urile până la CUTOFF_DATE (24.04.2026 inclusiv). Tot ce e după + e doar înregistrat, dar nu intră în statisticile lui declarate. + """ + wb = load_workbook(MENTOR_FILE, data_only=True) + ws = wb.active + + trades = [] # toate trade-urile (114) — populate în Trades sheet + raw_pl_tp1 = [] # P/L TP1 mentor pentru fiecare trade, pentru reconciliere + in_subset_flags = [] # bool: True dacă data <= CUTOFF_DATE + days_no_trade_total = 0 + days_no_trade_subset = 0 + + for r in range(2, 167): + data_v = _parse_date(ws.cell(r, 2).value) + if data_v is None: + continue + ls = ws.cell(r, 3).value + if ls is None: + days_no_trade_total += 1 + if data_v <= CUTOFF_DATE: + days_no_trade_subset += 1 + continue + + ora = ws.cell(r, 4).value + tp1m = ws.cell(r, 6).value + tp2m = ws.cell(r, 7).value + tp3m = ws.cell(r, 8).value + + sl, tp0, tp1, tp2, outcome, direction = derive(tp1m, tp2m, tp3m, ls) + + # Sanity check: dacă SL < TP0 sau ordinea TP-urilor e inversată, marchează + notes = "" + if sl > 0 and tp0 > sl: + notes = f"ANOMALIE: TP0={tp0} > SL={sl}. Verifică mentor file (probabil zecimale lipsă): TP1m={tp1m}, TP2m={tp2m}, TP3m={tp3m}" + elif sl > 0 and tp1 > sl: + notes = f"ANOMALIE: TP1={tp1} > SL={sl}. Verifică mentor file: TP1m={tp1m}, TP2m={tp2m}, TP3m={tp3m}" + + trades.append({ + "data": data_v, + "ora": ora, + "direction": direction, + "sl": sl, + "tp0": tp0, + "tp1": tp1, + "tp2": tp2, + "outcome": outcome, + "notes": notes, + }) + raw_pl_tp1.append(tp1m or 0) + in_subset_flags.append(data_v <= CUTOFF_DATE) + + return trades, raw_pl_tp1, in_subset_flags, days_no_trade_total, days_no_trade_subset + + +# --------------------------------------------------------------------------- +# Override Config + parametri suplimentari +# --------------------------------------------------------------------------- + + +def override_config(wb): + """Configurează cont prop $100K cu scaling teoretic 1% = $1000 (poziție = cont). + + Vezi nota din plan: cifrele declarate de mentor sunt scalate la nominal $100K + (poziție full-leverage), nu la cele 0.8 contracte CFD reale. + """ + ws = wb["Config"] + ws["B4"] = 100000 # Account Size Start ($) + ws["B9"] = 100000 # Account Prop Start ($) + ws["B10"] = 1 # Contracte per trade (teoretic 1 = poziție = cont) + ws["B11"] = 1000 # $ per 1% per contract ($100K nominal × 1%) + ws["C11"] = "Setup mentor: 1% pe $100K nominal = $1,000; deci 0.10% = $100" + + # Parametri suplimentari pentru sheet Comparatie (B18-B20) + ws["A18"] = "Nominal cont prop ($)" + ws["B18"] = 100000 + ws["C18"] = "Folosit în Comparatie pentru conversia P/L % → $" + + ws["A19"] = "Conturi prop simultane" + ws["B19"] = 8 + ws["C19"] = "D175 din fișierul mentorului (= 8 conturi $100K)" + + ws["A20"] = "Luni operare" + ws["B20"] = 7 + ws["C20"] = "Divizor din H175 (oct-mai = 7 luni complete)" + + for r in (18, 19, 20): + ws.cell(row=r, column=2).fill = INPUT_FILL + ws.cell(row=r, column=2).border = BORDER + ws["B18"].number_format = "$#,##0" + + +# --------------------------------------------------------------------------- +# Populare Trades sheet +# --------------------------------------------------------------------------- + + +def populate_trades(wb, trades): + """Populează celulele input ale Trades pentru fiecare trade transpus. + + Formulele derivate (Zi, Sesiune, R_*, $_*, Bal_*, etc.) sunt deja prezente + pe toate rândurile (puse de build_trades în generate_template) și citesc + celulele input. Doar input cells trebuie completate. + """ + ws = wb["Trades"] + + # Şterg sample rows existente (rândurile 2-6 sunt sample din build_trades) + # Voi suprascrie primele len(trades) rânduri, dar trebuie să curăţ Notes + # pe sample rows ca să nu rămână comentarii vechi. + for r in range(2, 2 + len(trades)): + ws[f'{COL["Notes"]}{r}'] = None + + for offset, t in enumerate(trades): + r = 2 + offset + ws[f"B{r}"] = t["data"] + ws[f"C{r}"] = t["ora"] + ws[f'{COL["Strategie"]}{r}'] = "M2D" + ws[f'{COL["Indicator"]}{r}'] = "DIA" + ws[f'{COL["TF"]}{r}'] = "3min" + ws[f'{COL["Direcție"]}{r}'] = t["direction"] + ws[f'{COL["SL %"]}{r}'] = t["sl"] + ws[f'{COL["TP0 %"]}{r}'] = t["tp0"] + ws[f'{COL["TP1 %"]}{r}'] = t["tp1"] + ws[f'{COL["TP2 %"]}{r}'] = t["tp2"] + ws[f'{COL["Outcome"]}{r}'] = t["outcome"] + if t.get("notes"): + ws[f'{COL["Notes"]}{r}'] = t["notes"] + + +# --------------------------------------------------------------------------- +# Sheet Comparatie +# --------------------------------------------------------------------------- + + +def _style_header(cell): + cell.font = HEADER_FONT + cell.fill = HEADER_FILL + cell.alignment = CENTER + cell.border = BORDER + + +def _style_ok_diff(cell, threshold_cell_addr): + """Aplică OK/DIFF în funcție de coloana Diff.""" + cell.alignment = CENTER + cell.border = BORDER + + +def build_comparatie(wb, trades, raw_pl_tp1, in_subset_flags, + days_no_trade_subset, days_no_trade_total): + """Construieşte sheet-ul Comparatie cu 3 secţiuni clare. + + Cheia: mentorul a actualizat calculele finale DOAR pe primele 104 trade-uri + (până la 24.04.2026 inclusiv). Restul (10 trade-uri post-cutoff) au fost + înregistrate dar NU intră în statistici. Secțiunea A reconciliază exact + pe subset, deci toate metricile trebuie să iasă OK. + """ + ws = wb.create_sheet("Comparatie") + ws.sheet_view.showGridLines = False + + n_trades = len(trades) + n_subset = sum(in_subset_flags) # 104 + last_row = 1 + n_trades # rândul ultimei tranzacții (header = row 1) + last_row_subset = 1 + n_subset # ultimul rând din subset (105) + + # ---------- TITLU + CONTEXT ---------- + ws["A1"] = "COMPARATIE — programul reproduce calculele mentorului?" + ws["A1"].font = TITLE_FONT + ws.merge_cells("A1:F1") + + context = ( + "Mentorul a actualizat statisticile finale (rândurile 167-175 din fișierul lui) " + "DOAR pe primele 104 trade-uri (până la 24.04.2026 inclusiv). " + "Trade-urile #105-114 (29.04 → 15.05.2026) au fost înregistrate dar NU intră în " + "statisticile lui declarate.\n" + "→ Secțiunea A reconciliază pe SUBSET (104 trade-uri = ce a calculat mentorul). " + "Dacă programul îi reproduce calculele, toate trebuie să iasă OK.\n" + "→ Secțiunea A2 arată cifrele pe TOATE 114 (informativ — diff-ul față de subset = " + "rezultatul trade-urilor post-cutoff).\n" + "→ Secțiunea B simulează profitul lunar pe cont prop (replică formula H175 a mentorului)." + ) + ws["A2"] = context + ws["A2"].font = NOTE_FONT + ws["A2"].alignment = Alignment(wrap_text=True, vertical="top") + ws.merge_cells("A2:F2") + ws.row_dimensions[2].height = 85 + + # Stochez P/L TP1 mentor în coloana AC pentru formule (ascunsă) + # Primele n_subset valori (rândurile 2..1+n_subset) sunt subset + # Restul (1+n_subset+1..1+n_trades) sunt post-cutoff + ws["AC1"] = "P/L TP1 mentor (stocat — primele 104 = subset, restul = post-24.04)" + ws["AC1"].font = Font(name="Calibri", size=9, italic=True, color="9E9E9E") + + # Reordonez: pun întâi cele din subset, apoi cele din rest, ca să corespundă + # cu range-urile AC2:AC105 (subset) și AC106:AC115 (rest) + subset_vals = [v for v, in_s in zip(raw_pl_tp1, in_subset_flags) if in_s] + rest_vals = [v for v, in_s in zip(raw_pl_tp1, in_subset_flags) if not in_s] + all_ordered = subset_vals + rest_vals + for offset, v in enumerate(all_ordered): + ws.cell(row=2 + offset, column=29, value=v) + ws.column_dimensions["AC"].hidden = True + + rng_subset = f"$AC$2:$AC${last_row_subset}" # 104 trade-uri + rng_total = f"$AC$2:$AC${1 + n_trades}" # 114 trade-uri + + # ============================================================ + # SECŢIUNE A: Reconciliere cifre mentor (pe subset 104) + # ============================================================ + row = 4 + ws.cell(row=row, column=1, + value="A. Reconciliere cifre mentor pe subset (primele 104 trade-uri, până 24.04.2026)").font = SUBTITLE_FONT + ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=6) + row += 1 + + headers = ["Metric", "Mentor declarat", "Program (subset 104)", "Diff", "OK?", "Notă"] + for c, h in enumerate(headers, start=1): + _style_header(ws.cell(row=row, column=c, value=h)) + row += 1 + + rows_A = [ + ("Total trade-uri", 104, + f"=COUNT({rng_subset})", "D170 din mentor"), + ("Wins (P/L TP1 > 0)", 81, + f"=COUNTIF({rng_subset},\">0\")", "D168"), + ("Losses (P/L TP1 < 0)", 23, + f"=COUNTIF({rng_subset},\"<0\")", "D167"), + ("Zile fără trade (în subset)", 36, days_no_trade_subset, "D169"), + ("Win Rate", 0.7788, + f"=COUNTIF({rng_subset},\">0\")/(COUNTIF({rng_subset},\">0\")+COUNTIF({rng_subset},\"<0\"))", + "D171 = wins/(wins+losses)"), + ("Avg Win (%)", 0.0774, + f"=AVERAGEIF({rng_subset},\">0\")", "D173"), + ("Avg Loss (%) abs", 0.1243, + f"=-AVERAGEIF({rng_subset},\"<0\")", "D172"), + ("Profit Factor (avg_win/avg_loss)", 0.6227, + f"=AVERAGEIF({rng_subset},\">0\")/(-AVERAGEIF({rng_subset},\"<0\"))", + "D174 — definiție mentor: raport medii"), + ] + + section_A_first_row = row + for label, mentor_val, prog_formula, note in rows_A: + ws.cell(row=row, column=1, value=label).border = BORDER + ws.cell(row=row, column=2, value=mentor_val).border = BORDER + ws.cell(row=row, column=3, value=prog_formula).border = BORDER + ws.cell(row=row, column=4, value=f"=C{row}-B{row}").border = BORDER + tolerance = 0.5 if isinstance(mentor_val, int) and mentor_val > 10 else 0.001 + ok_formula = f'=IF(ABS(D{row})<{tolerance},"OK","DIFF")' + ws.cell(row=row, column=5, value=ok_formula).border = BORDER + ws.cell(row=row, column=5).alignment = CENTER + ws.cell(row=row, column=6, value=note).font = NOTE_FONT + ws.cell(row=row, column=6).border = BORDER + ws.cell(row=row, column=6).alignment = Alignment(wrap_text=True, vertical="center") + row += 1 + + # Format procentual pentru rândurile de procent (oraș 4=WR, 5=AvgWin, 6=AvgLoss) + for pct_row_offset in (4, 5, 6): + for c in (2, 3): + ws.cell(row=section_A_first_row + pct_row_offset, column=c).number_format = "0.0000" + for c in (2, 3): + ws.cell(row=section_A_first_row + 7, column=c).number_format = "0.0000" # PF + + row += 2 + + # ============================================================ + # SECŢIUNE A2: Cifre pe TOTAL 114 trade-uri (informativ) + # ============================================================ + ws.cell(row=row, column=1, + value="A2. Cifre pe TOATE 114 trade-uri (informativ — include și trade-urile post-24.04)").font = SUBTITLE_FONT + ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=6) + row += 1 + + headers_A2 = ["Metric", "Subset (104, până 24.04)", "Total (114)", "Delta post-cutoff", "", "Notă"] + for c, h in enumerate(headers_A2, start=1): + _style_header(ws.cell(row=row, column=c, value=h)) + row += 1 + + rows_A2 = [ + ("Total trade-uri", + f"=COUNT({rng_subset})", f"=COUNT({rng_total})", + "Mentor a omis 10 trade-uri post-cutoff."), + ("Wins", + f"=COUNTIF({rng_subset},\">0\")", f"=COUNTIF({rng_total},\">0\")", + "+8 wins în post-cutoff."), + ("Losses", + f"=COUNTIF({rng_subset},\"<0\")", f"=COUNTIF({rng_total},\"<0\")", + "+2 losses în post-cutoff."), + ("Win Rate", + f"=COUNTIF({rng_subset},\">0\")/(COUNTIF({rng_subset},\">0\")+COUNTIF({rng_subset},\"<0\"))", + f"=COUNTIF({rng_total},\">0\")/(COUNTIF({rng_total},\">0\")+COUNTIF({rng_total},\"<0\"))", + "Variație minoră."), + ("Sum P/L TP1 (%)", + f"=SUM({rng_subset})", f"=SUM({rng_total})", + "Cumulativ în %."), + ("Avg Win (%)", + f"=AVERAGEIF({rng_subset},\">0\")", f"=AVERAGEIF({rng_total},\">0\")", + ""), + ("Avg Loss (%) abs", + f"=-AVERAGEIF({rng_subset},\"<0\")", f"=-AVERAGEIF({rng_total},\"<0\")", + ""), + ] + + section_A2_first_row = row + for label, subset_f, total_f, note in rows_A2: + ws.cell(row=row, column=1, value=label).border = BORDER + ws.cell(row=row, column=2, value=subset_f).border = BORDER + ws.cell(row=row, column=3, value=total_f).border = BORDER + ws.cell(row=row, column=4, value=f"=C{row}-B{row}").border = BORDER + ws.cell(row=row, column=6, value=note).font = NOTE_FONT + ws.cell(row=row, column=6).border = BORDER + ws.cell(row=row, column=6).alignment = Alignment(wrap_text=True, vertical="center") + row += 1 + + # Format pentru WR și avg-uri (3, 5, 6 = wr, avg_win, avg_loss... rândurile 3-6 din A2) + for pct_row_offset in (3, 4, 5, 6): + for c in (2, 3, 4): + ws.cell(row=section_A2_first_row + pct_row_offset, column=c).number_format = "0.0000" + + row += 2 + + # ============================================================ + # SECŢIUNE B: Simulare PROP (formula H175) + # ============================================================ + ws.cell(row=row, column=1, + value="B. Simulare profit lunar pe cont prop (replică formula H175 a mentorului)").font = SUBTITLE_FONT + ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=6) + row += 1 + + note_B = ( + "Formula H175 a mentorului: (avg_win$ × N_conturi × wins − N_conturi × avg_loss$ × zile_fără_trade) / 7_luni.\n" + "Surface o eroare: formula folosește 'zile fără trade' (D169=36) în loc de 'losses' (D167=23). " + "Cu cifrele declarate ale mentorului, asta dă $2,050.97/lună (= ce arată H175 în fișierul lui). " + "Corectând D169→D167, ar fi ieșit $3,897.71/lună.\n" + "Scaling: cifrele $ sunt teoretice — poziție = cont prop $100K (Config!B10=1, B11=$1000). " + "Pentru P&L real pe 0.8 contracte CFD ($40K poziție efectivă), setați B10=0.8 și B11=$500 (cifrele se vor scala × 0.4)." + ) + ws.cell(row=row, column=1, value=note_B).font = NOTE_FONT + ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=6) + ws.cell(row=row, column=1).alignment = Alignment(wrap_text=True, vertical="top") + ws.row_dimensions[row].height = 90 + row += 2 + + headers_B = ["Variantă", "Rezultat ($/lună)", "", "", "", "Notă"] + for c, h in enumerate(headers_B, start=1): + _style_header(ws.cell(row=row, column=c, value=h)) + row += 1 + + # Cifrele mentorului DECLARATE (coloana B din Secțiunea A) + avg_win_decl = f"B{section_A_first_row + 5}" + avg_loss_decl = f"B{section_A_first_row + 6}" + wins_decl = f"B{section_A_first_row + 1}" + losses_decl = f"B{section_A_first_row + 2}" + days_decl = f"B{section_A_first_row + 3}" + + rows_B = [ + ("Mentor H175 as-is (cu D169 = zile_fără_trade)", + f"=(({avg_win_decl}*Config!$B$18/100)*Config!$B$19*{wins_decl} - " + f"Config!$B$19*({avg_loss_decl}*Config!$B$18/100)*{days_decl})/Config!$B$20", + "Replică EXACT cifra mentorului. Așteptat: $2,050.97."), + ("Mentor H175 corectat (D169 → D167 = losses)", + f"=(({avg_win_decl}*Config!$B$18/100)*{wins_decl} - " + f"({avg_loss_decl}*Config!$B$18/100)*{losses_decl}) * Config!$B$19 / Config!$B$20", + "Așteptat: ~$3,897.71. Cu cifrele declarate, formula corectă."), + ] + + # Plus pentru fiecare strategie: Sum $_strat × N_accounts / N_luni + # Folosim subset (primele 104 = până la 24.04.2026) ca să fie comparabil + for strat in STRAT_KEYS: + sum_dollar_subset = ( + f"SUM(Trades!{COL[f'$_{strat}']}2:{COL[f'$_{strat}']}{last_row_subset})" + ) + rows_B.append(( + f"Program — {STRAT_LABELS[strat]} (pe subset 104)", + f"={sum_dollar_subset} * Config!$B$19 / Config!$B$20", + f"Sum $_{strat} pe primele 104 trade-uri × N_conturi / N_luni.", + )) + + for label, formula, note in rows_B: + ws.cell(row=row, column=1, value=label).border = BORDER + ws.cell(row=row, column=2, value=formula).border = BORDER + ws.cell(row=row, column=2).number_format = '"$"#,##0.00' + ws.cell(row=row, column=6, value=note).font = NOTE_FONT + ws.cell(row=row, column=6).border = BORDER + ws.cell(row=row, column=6).alignment = Alignment(wrap_text=True, vertical="center") + row += 1 + + row += 2 + + # ============================================================ + # SECŢIUNE C: Per strategie pe TOATE trade-urile + # ============================================================ + ws.cell(row=row, column=1, + value="C. Metrici per strategie (pe TOATE 114 trade-uri)").font = SUBTITLE_FONT + ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=6) + row += 1 + + headers_C = ["Strategie", "Sum R", "Expectancy R", "Balance final ($)", "Max DD ($)", "Win Rate"] + for c, h in enumerate(headers_C, start=1): + _style_header(ws.cell(row=row, column=c, value=h)) + row += 1 + + for strat in STRAT_KEYS: + r_range = f"Trades!{COL[f'R_{strat}']}2:{COL[f'R_{strat}']}{last_row}" + dd_range = f"Trades!{COL[f'DD_{strat}']}2:{COL[f'DD_{strat}']}{last_row}" + win_range = f"Trades!{COL[f'Win_{strat}']}2:{COL[f'Win_{strat}']}{last_row}" + bal_last_cell = f"Trades!{COL[f'Bal_{strat}']}{last_row}" + + ws.cell(row=row, column=1, value=STRAT_LABELS[strat]).border = BORDER + ws.cell(row=row, column=2, value=f"=SUM({r_range})").border = BORDER + ws.cell(row=row, column=2).number_format = "+0.00;-0.00;0.00" + ws.cell(row=row, column=3, value=f"=AVERAGE({r_range})").border = BORDER + ws.cell(row=row, column=3).number_format = "+0.000;-0.000;0.000" + ws.cell(row=row, column=4, value=f"={bal_last_cell}").border = BORDER + ws.cell(row=row, column=4).number_format = '"$"#,##0.00' + ws.cell(row=row, column=5, value=f"=MAX({dd_range})").border = BORDER + ws.cell(row=row, column=5).number_format = '"$"#,##0.00' + ws.cell(row=row, column=6, value=f"=SUM({win_range})/COUNT({win_range})").border = BORDER + ws.cell(row=row, column=6).number_format = "0.00%" + row += 1 + + # Column widths + widths_comparatie = { + "A": 48, "B": 24, "C": 24, "D": 18, "E": 10, "F": 50, + } + for col_letter, w in widths_comparatie.items(): + ws.column_dimensions[col_letter].width = w + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + + +def main() -> int: + print(f"Citesc mentor: {MENTOR_FILE}") + trades, raw_pl_tp1, in_subset_flags, days_total, days_subset = read_mentor() + n_subset = sum(in_subset_flags) + print(f" {len(trades)} trade-uri ({n_subset} în subset până 24.04.2026, " + f"{len(trades) - n_subset} post-cutoff)") + print(f" Zile fără trade: {days_total} total ({days_subset} în subset)") + + # Sanity check + from collections import Counter + oc = Counter(t["outcome"] for t in trades) + print(f" Outcome distribution: {dict(oc)}") + + # Reordonez trade-urile: subset întâi, post-cutoff după. + # Asta aliniază cu coloana AC din Comparatie (subset_vals + rest_vals). + subset_trades = [t for t, in_s in zip(trades, in_subset_flags) if in_s] + rest_trades = [t for t, in_s in zip(trades, in_subset_flags) if not in_s] + trades_ordered = subset_trades + rest_trades + + print("Construiesc workbook (schema completă)...") + wb = build_workbook() + + print("Override Config pentru cont prop $100K...") + override_config(wb) + + print("Populez Trades cu datele transpuse...") + populate_trades(wb, trades_ordered) + + print("Construiesc sheet Comparatie...") + build_comparatie(wb, trades_ordered, raw_pl_tp1, in_subset_flags, + days_subset, days_total) + + wb.active = wb.sheetnames.index("Comparatie") + + OUTPUT.parent.mkdir(parents=True, exist_ok=True) + wb.save(OUTPUT) + print(f"Wrote {OUTPUT}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())