diff --git a/data/backtest.xlsx b/data/backtest.xlsx index 5240a82..a1521bb 100644 Binary files a/data/backtest.xlsx and b/data/backtest.xlsx differ diff --git a/scripts/generate_template.py b/scripts/generate_template.py index e513cb6..1d6bd3d 100644 --- a/scripts/generate_template.py +++ b/scripts/generate_template.py @@ -114,7 +114,7 @@ def build_config(wb: Workbook) -> None: ws = wb.create_sheet("Config", 0) ws.sheet_view.showGridLines = False - ws["A1"] = "📋 Config — editează doar celulele galbene" + ws["A1"] = "Config — editează doar celulele galbene" ws["A1"].font = TITLE_FONT ws.merge_cells("A1:C1") @@ -448,20 +448,72 @@ def _range(col_name: str) -> str: METRIC_HINTS: dict[str, str] = { - "Trades Placed": "Numărul total de trade-uri logate", - "Wins": "Trade-uri cu R > 0", - "Win Ratio": "% wins. Singur NU spune mult — vezi împreună cu R:R și Expectancy", - "Average Win ($)": "Câștigul mediu pe trade winning", - "Average Loss ($)": "Pierderea medie pe trade losing", - "Best Trade ($)": "Cel mai mare câștig individual", - "Worst Trade ($)": "Cea mai mare pierdere individuală", - "Profit Factor": ">1.0 profitabil • >1.5 solid • >2.0 foarte bun • <1.0 pierzător", - "Risk:Reward": "Avg Win ÷ |Avg Loss|. >1 = câștig mediu > pierdere medie", - "Expectancy (R)": "★ STEAUA NORDULUI ★ >+0.20R = GO LIVE • negativ = ABANDON", - "Expectancy ($)": "Expectancy R convertit în $ (folosește Risk per Trade)", - "Cumulative P&L ($)": "P&L total în $ pe toate trade-urile", - "HWM Balance ($)": "Highest watermark — balanța de vârf atinsă", - "Max Drawdown ($)": "Cea mai mare cădere ($) din vârf la fund", + "Trades Placed": ( + "Câte trade-uri ai logat în total.\n" + "Cu cât N e mai mare, cu atât celelalte metrici sunt mai de încredere.\n" + "Exemplu: la N=10 Win Ratio e zgomot pur, la N=40 începe să aibă semnal, la N=100 e solid." + ), + "Wins": ( + "Câte trade-uri s-au închis pe plus (R > 0).\n" + "Singur nu spune nimic — privește-l raportat la total (vezi Win Ratio mai jos)." + ), + "Win Ratio": ( + "Procentul de trade-uri câștigătoare. WR = 60% înseamnă 6 wins din 10 trade-uri.\n" + "Singur NU spune dacă strategia e profitabilă — citește-l împreună cu R:R de pe rândul următor." + ), + "Average Win ($)": ( + "Câștigul mediu pe trade-urile pozitive.\n" + "Comparat cu Average Loss îți spune cât de mari sunt câștigurile vs pierderile.\n" + "Exemplu: 4 wins de $50 și 2 wins de $80 — Average Win = $60." + ), + "Average Loss ($)": ( + "Pierderea medie pe trade-urile negative (cifra apare cu minus).\n" + "Cu Risk per Trade fix, ar trebui să fie aproape de −1R în dolari.\n" + "Dacă e mult mai mare decât Risk per Trade, ai SL-uri sărite (slippage, gap-uri)." + ), + "Best Trade ($)": ( + "Cel mai mare câștig individual.\n" + "Dacă majoritatea profitului total vine dintr-un singur trade outlier, edge-ul e fragil — " + "elimini acel trade și strategia devine pierzătoare." + ), + "Worst Trade ($)": ( + "Cea mai mare pierdere individuală.\n" + "Ar trebui să fie aproximativ egală cu −1R (Risk per Trade din Config).\n" + "Dacă e semnificativ mai mare, ai depășit risk-ul plănuit — SL ratat, slippage, gap overnight." + ), + "Profit Factor": ( + "Total bani câștigați împărțit la total bani pierduți (în valoare absolută).\n" + "Sub 1.0 = pierzi pe ansamblu. Peste 1.5 = solid. Peste 2.0 = câștigi de 2× cât pierzi.\n" + "Exemplu: 4 wins de $50 (= $200) + 6 losses de $30 (= $180) — PF = 200÷180 = 1.11, profitabil marginal." + ), + "Risk:Reward": ( + "De câte ori e mai mare câștigul mediu decât pierderea medie.\n" + "R:R = 2 înseamnă: când câștigi, câștigi $2; când pierzi, pierzi $1.\n" + "Cu R:R mare poți avea Win Ratio mic și tot să faci bani." + ), + "Expectancy (R)": ( + "Cât bani câștigi în medie pe UN trade (în R; 1R = Risk per Trade, default $100).\n" + "+0.30R = câștigi $30 pe trade. Pe 100 trade-uri: +$3.000.\n" + "−0.10R = pierzi $10 pe trade. Pe 100 trade-uri: −$1.000.\n" + "Pragul de GO LIVE: +0.20R sau mai mult." + ), + "Expectancy ($)": ( + "Aceeași expectancy convertită în dolari, folosind Risk per Trade din Config.\n" + "Util ca să vezi cât câștigi în medie pe trade în bani reali, nu doar în R." + ), + "Cumulative P&L ($)": ( + "Suma profitului și pierderii pe toate trade-urile logate.\n" + "E ce-ai avea în plus (sau minus) față de balanța de start din Config." + ), + "HWM Balance ($)": ( + "Highest Watermark — cea mai mare balanță atinsă vreodată în jurnal.\n" + "Punct de referință pentru calculul drawdown-ului." + ), + "Max Drawdown ($)": ( + "Cea mai mare cădere ($) din vârf la fundul ulterior al balanței. Măsoară durerea psihologică maximă.\n" + "Exemplu: ai urcat la $11,500, ai coborât la $9,800 — DD = $1,700, adică 17% din peak.\n" + "Un drawdown mare la backtest e foarte greu de tolerat în live cu bani reali — așteaptă-te să renunți." + ), } @@ -469,7 +521,7 @@ def build_dashboard(wb: Workbook) -> None: ws = wb.create_sheet("Dashboard", 2) ws.sheet_view.showGridLines = False - ws["A1"] = "📊 Backtest Dashboard" + ws["A1"] = "Backtest Dashboard" ws["A1"].font = TITLE_FONT ws.merge_cells("A1:G1") @@ -507,13 +559,13 @@ def build_dashboard(wb: Workbook) -> None: # (label, fn(strat_key) -> formula, number_format) ("Trades Placed", lambda s: f'=COUNTA({OUTCOME_RANGE})', "0"), ("Wins", lambda s: f'=COUNTIF({W[s]},1)', "0"), - # Win Ratio: depends on rows above — handled after metrics list (placeholder) - ("Win Ratio", lambda s: None, "0.0%"), ("Average Win ($)", lambda s: f'=IFERROR(AVERAGEIF({D[s]},">0"),0)', '"$"#,##0.00'), ("Average Loss ($)", lambda s: f'=IFERROR(AVERAGEIF({D[s]},"<0"),0)', '"$"#,##0.00'), ("Best Trade ($)", lambda s: f'=IFERROR(MAX({D[s]}),0)', '"$"#,##0.00'), ("Worst Trade ($)", lambda s: f'=IFERROR(MIN({D[s]}),0)', '"$"#,##0.00'), ("Profit Factor", lambda s: f'=IFERROR(SUMIF({D[s]},">0")/ABS(SUMIF({D[s]},"<0")),0)', "0.00"), + # Win Ratio: depends on Wins + Trades Placed — handled after metrics list (placeholder) + ("Win Ratio", lambda s: None, "0.0%"), # Risk:Reward — placeholder; bazat pe rândurile Avg Win/Loss ("Risk:Reward", lambda s: None, "0.00"), ("Expectancy (R)", lambda s: f'=IFERROR(AVERAGE({R[s]}),0)', "+0.000;-0.000;0.000"), @@ -555,65 +607,13 @@ def build_dashboard(wb: Workbook) -> None: cell.fill = DERIVED_FILL cell.border = BORDER cell.alignment = RIGHT - # Coloana G — interpretare scurtă + # Coloana G — interpretare narativă + exemplu numeric hint_cell = ws[f"G{r}"] hint_cell.value = METRIC_HINTS.get(label, "") - hint_cell.font = Font(name="Calibri", size=10, italic=True, color="595959") - hint_cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True) + hint_cell.font = Font(name="Calibri", size=10, color="595959") + hint_cell.alignment = Alignment(horizontal="left", vertical="top", wrap_text=True) hint_cell.border = BORDER - # ---- Glosar section: exemple concrete pentru metricile-cheie ---- - glosar_start = 5 + len(metrics) + 2 # 2 rânduri spațiu după metrici - ws[f"A{glosar_start}"] = "📖 Glosar metrici — exemple concrete" - ws[f"A{glosar_start}"].font = SUBTITLE_FONT - ws.merge_cells(f"A{glosar_start}:G{glosar_start}") - - glosar_entries = [ - ( - "Profit Factor", - "Suma câștigurilor ÷ |suma pierderilor|. Total cumulativ, nu mediu.", - "10 trade-uri: 4 wins de $50 (=$200) + 6 losses de −$30 (=−$180). PF = 200÷180 = 1.11 (marginal profitabil). La PF=2.0 câștigi de 2× cât pierzi în total.", - ), - ( - "Risk:Reward", - "Avg Win ÷ |Avg Loss|. Privește per-trade, nu total.", - "Avg win $50, avg loss −$30 → R:R = 1.67. La R:R=2.0 ești profitabil chiar cu Win Ratio doar 40%. La R:R=0.5 ai nevoie de WR >67%.", - ), - ( - "Expectancy (R)", - "Câștigul mediu per trade exprimat în multipli de risc (R). CEA MAI ONESTĂ metrică — combină WR și R:R într-un singur număr.", - "10 trade-uri cu R = [+0.5, +0.5, +0.5, +0.5, −1, −1, −1, −1, −1, −1] → media = −0.30R (pierdere) chiar dacă WR=40%. Pragul GO LIVE din STOPPING_RULE.md: ≥ +0.20R.", - ), - ( - "Win Ratio (WR)", - "% trade-uri cu R > 0. ÎNȘELĂTOR singur — un WR mare cu R:R mic poate fi pierzător.", - "WR=70% pare excelent, dar dacă R:R=0.3 (câștigi $30, pierzi $100) → Expectancy = 0.7·30 − 0.3·100 = −$9 per trade. Pierzător.", - ), - ( - "Max Drawdown", - "Cea mai mare cădere din vârful balanței la fundul ulterior. Măsoară 'durerea psihologică'.", - "Balance peak $11,500 → fund $9,800 → DD = $1,700 (17% din peak). DD mare la backtest = greu de tolerat în live.", - ), - ] - - row = glosar_start + 1 - for term, definition, example in glosar_entries: - ws[f"A{row}"] = term - ws[f"A{row}"].font = Font(name="Calibri", size=11, bold=True, color="1F3864") - ws[f"A{row}"].alignment = Alignment(horizontal="left", vertical="top", wrap_text=True) - ws[f"B{row}"] = definition - ws[f"B{row}"].font = Font(name="Calibri", size=10) - ws[f"B{row}"].alignment = Alignment(horizontal="left", vertical="top", wrap_text=True) - ws.merge_cells(f"B{row}:C{row}") - ws[f"D{row}"] = f"Exemplu: {example}" - ws[f"D{row}"].font = Font(name="Calibri", size=10, italic=True, color="595959") - ws[f"D{row}"].alignment = Alignment(horizontal="left", vertical="top", wrap_text=True) - ws.merge_cells(f"D{row}:G{row}") - ws.row_dimensions[row].height = 48 - row += 1 - - glosar_end = row # primul rând după glosar - # Helper pentru a emite un block breakdown (per Sesiune / Strategie / etc.) def _emit_breakdown( start_row: int, title: str, first_col_label: str, @@ -651,7 +651,7 @@ def build_dashboard(wb: Workbook) -> None: # Breakdowns — toate folosesc overlay-ul Hybrid+BE (recomandat de trader) overlay = "hybrid_be" - start = glosar_end + 2 # 2 rânduri spațiu după glosar + start = 5 + len(metrics) + 2 # 2 rânduri spațiu după tabelul de metrici after_sess = _emit_breakdown( start, "PER SESIUNE (overlay: Hybrid + BE)", "Sesiune", SESSIONS, _range("Sesiune"), overlay, @@ -670,13 +670,13 @@ def build_dashboard(wb: Workbook) -> None: ) # Column widths - widths = {"A": 22, "B": 14, "C": 14, "D": 14, "E": 16, "F": 16, "G": 50} + widths = {"A": 22, "B": 14, "C": 14, "D": 14, "E": 16, "F": 16, "G": 75} for col, w in widths.items(): ws.column_dimensions[col].width = w - # Row height pentru rândurile cu hint (cu wrap) + # Row height pentru rândurile cu hint (cu wrap) — explicații multi-line for r in range(5, 5 + len(metrics)): - ws.row_dimensions[r].height = 22 + ws.row_dimensions[r].height = 75 # Equity curve chart — 5 linii chart = LineChart()