Adaugat generate_mentor_check.py care transpune backtest-ul mentorului (M2D DOW UNA PE SEARA.xlsx) in schema noastra (Config + Trades + Dashboard) ca sa verificam ca WR, R:R si profit factor ies la fel cu cifrele declarate de mentor. Output: backtest-atm-m2d-una-pe-seara.xlsx.
602 lines
24 KiB
Python
602 lines
24 KiB
Python
"""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())
|