verificare backtest mentor: M2D DOW una pe seara
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.
This commit is contained in:
BIN
data/M2D DOW UNA PE SEARA.xlsx
Normal file
BIN
data/M2D DOW UNA PE SEARA.xlsx
Normal file
Binary file not shown.
BIN
data/backtest-atm-m2d-una-pe-seara.xlsx
Normal file
BIN
data/backtest-atm-m2d-una-pe-seara.xlsx
Normal file
Binary file not shown.
601
scripts/generate_mentor_check.py
Normal file
601
scripts/generate_mentor_check.py
Normal file
@@ -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())
|
||||
Reference in New Issue
Block a user