This commit is contained in:
Marius
2026-05-21 03:06:49 +03:00
parent 6057a94caa
commit 4fed6d2d59
3 changed files with 177 additions and 63 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -195,13 +195,13 @@ def build_config(wb: Workbook) -> None:
ws["B9"] = 50000
ws["C9"] = "Balanța contului de prop"
ws["A10"] = "Position Usage (%)"
ws["B10"] = 80
ws["C10"] = "% din cont folosit ca notional (max contracte)"
ws["A10"] = "Contracte per trade"
ws["B10"] = 1
ws["C10"] = "Număr de contracte tranzacționate per semnal (TradeLocker)"
ws["A11"] = "Position Size ($)"
ws["B11"] = "=B9*B10/100"
ws["C11"] = "Auto — notional efectiv pe trade"
ws["A11"] = "$ per 1% per contract"
ws["B11"] = 10000
ws["C11"] = "Pe DIA: 0.10% = $1000 ⇒ 1% = $10,000 (1 contract notional ≈ $1M)"
ws["A12"] = "Daily Loss Limit (%)"
ws["B12"] = 4.0
@@ -219,15 +219,15 @@ def build_config(wb: Workbook) -> None:
ws["B15"] = "=B9*B14/100"
ws["C15"] = "Auto — derivat din B9 și B14"
for r in (9, 10, 12, 14): # inputuri galbene
for r in (9, 10, 11, 12, 14): # inputuri galbene
ws.cell(row=r, column=2).fill = INPUT_FILL
ws.cell(row=r, column=2).border = BORDER
for r in (11, 13, 15): # derived blue
for r in (13, 15): # derived blue
ws.cell(row=r, column=2).fill = DERIVED_FILL
ws.cell(row=r, column=2).border = BORDER
ws["B9"].number_format = "$#,##0"
ws["B10"].number_format = '0"%"'
ws["B10"].number_format = "0"
ws["B11"].number_format = "$#,##0"
ws["B12"].number_format = '0.0"%"'
ws["B13"].number_format = "$#,##0"
@@ -351,20 +351,22 @@ R_FN: dict[str, callable] = {
def _f_dollar(r: int, r_col: str) -> str:
"""$ P&L pe contul abstract. Variabil per trade = R × SL%/100 × Account Size."""
"""$ P&L per trade = R × SL% × Contracte × $/1% per contract (TradeLocker real)."""
rc = f"{COL[r_col]}{r}"
sl = f"{COL['SL %']}{r}"
return f'=IF({rc}="","",{rc}*{sl}/100*Config!$B$4)'
return f'=IF({rc}="","",{rc}*{sl}*Config!$B$10*Config!$B$11)'
def _f_sl_dollar(r: int) -> str:
"""SL $ = SL% × Contracte × $/1% per contract."""
sl = f"{COL['SL %']}{r}"
return f'=IF({sl}="","",{sl}/100*Config!$B$4)'
return f'=IF({sl}="","",{sl}*Config!$B$10*Config!$B$11)'
def _f_sl_dollar_prop(r: int) -> str:
"""SL $ pe contul de prop — același cont real, formula identică cu SL $."""
sl = f"{COL['SL %']}{r}"
return f'=IF({sl}="","",{sl}/100*Config!$B$11)'
return f'=IF({sl}="","",{sl}*Config!$B$10*Config!$B$11)'
def _f_balance(r: int, dollar_col: str) -> str:
@@ -395,10 +397,14 @@ def _f_drawdown(r: int, peak_col: str, balance_col: str) -> str:
def _f_dollar_prop(r: int, r_col: str) -> str:
"""$ P&L pe contul de prop. Variabil per trade = R × SL%/100 × Position Size."""
"""$ P&L pe contul de prop — același calcul ca _f_dollar (cont real TradeLocker).
Diferența între cont abstract și prop e doar balanța de start; $-ul per trade
e identic pentru că reflectă realitatea contractelor tranzacționate.
"""
rc = f"{COL[r_col]}{r}"
sl = f"{COL['SL %']}{r}"
return f'=IF({rc}="","",{rc}*{sl}/100*Config!$B$11)'
return f'=IF({rc}="","",{rc}*{sl}*Config!$B$10*Config!$B$11)'
def _f_balance_prop(r: int, dollar_col: str) -> str:
@@ -620,7 +626,7 @@ METRIC_HINTS: dict[str, str] = {
),
"Average Loss ($)": (
"Pierderea medie pe trade-urile negative (cifra apare cu minus).\n"
"În dolari reali, 1R depinde de SL%: pierdere ≈ SL% × Account Size Start.\n"
"În dolari reali, 1R depinde de SL%: pierdere ≈ SL% × Contracte × $/1% per contract.\n"
"Dacă e mult mai mare decât riscul calculat din SL, ai SL-uri sărite (slippage, gap-uri)."
),
"Best Trade ($)": (
@@ -630,8 +636,8 @@ METRIC_HINTS: dict[str, str] = {
),
"Worst Trade ($)": (
"Cea mai mare pierdere individuală.\n"
"Ar trebui să fie aproximativ egală cu 1R calculat din SL% × Account Size Start.\n"
"Dacă e semnificativ mai mare, ai depășit risk-ul plănuit — SL ratat, slippage, gap overnight."
"Ar trebui să fie aproximativ egală cu 1R calculat din SL% × Contracte × $/1% per contract.\n"
"Pe TradeLocker DIA: SL=0.30%, 1 contract → ≈ $3000. Dacă e mai mare, ai slippage/gap."
),
"Profit Factor": (
"Total bani câștigați împărțit la total bani pierduți (în valoare absolută).\n"
@@ -650,8 +656,8 @@ METRIC_HINTS: dict[str, str] = {
"Pragul de GO LIVE: +0.20R sau mai mult."
),
"Expectancy ($)": (
"Aceeași expectancy convertită în dolari, folosind SL% × Account Size Start per trade.\n"
"Util ca să vezi cât câștigi în medie pe trade în bani reali, nu doar în R."
"Aceeași expectancy convertită în dolari, folosind SL% × Contracte × $/1% per contract.\n"
"Util ca să vezi cât câștigi în medie pe trade în bani reali (TradeLocker), nu doar în R."
),
"Cumulative P&L ($)": (
"Suma profitului și pierderii pe toate trade-urile logate.\n"
@@ -672,13 +678,14 @@ METRIC_HINTS: dict[str, str] = {
"Editabil în Config B9."
),
"Position Size ($)": (
"Notional efectiv pe trade = Account Prop × Position Usage %.\n"
"Default: 80% × $50,000 = $40,000. Pe DIA, ≈ 0.8 contracte din prețul curent.\n"
"Pierderea pe SL = SL% × Position Size (NU procent fix din cont)."
"Configurare contract real TradeLocker:\n"
" • Contracte per trade (Config B10) — câte contracte tranzacționezi pe semnal.\n"
" • $ per 1% per contract (Config B11) — pe DIA: 0.10% = $1000 → 1% = $10,000.\n"
"Pierderea pe SL = SL% × Contracte × $/1% per contract. Pentru SL=0.30%, 1 contract → $3000."
),
"Cumulative P&L Prop ($)": (
"Profitul total al contului de prop pe traseul logat.\n"
"Reflectă $ real (SL% × Position Size per trade), NU R-multiple-ul abstract de mai sus.\n"
"Reflectă $ real (SL% × Contracte × $/1% per contract), nu un procent abstract din cont.\n"
"Adunat peste $50,000 dă balanța finală reală."
),
"Final Balance Prop ($)": (
@@ -949,11 +956,13 @@ def build_dashboard(wb: Workbook) -> None:
ws[f"P{row}"] = f'=IF(N{row}>Config!$B$15,"DA","NU")'
ws[f"Q{row}"] = f'=IF(OR(O{row}="DA",P{row}="DA"),"CONT PIERDUT","CONFORM")'
ws[f"R{row}"] = (
f'=IF(AND(E{row}>=40,G{row}>=55%,H{row}>=0.2,'
f'O{row}="NU",P{row}="NU"),"CANDIDAT","NU")'
f'=IF(E{row}<1,"",'
f'IF(OR(O{row}="DA",P{row}="DA"),"BREACH",'
f'IF(AND(E{row}>=40,G{row}>=55%,H{row}>=0.2),"CANDIDAT","PRE-CANDIDAT")))'
)
ws[f"S{row}"] = (
f'=IF(R{row}="CANDIDAT",H{row}*100000+J{row}*1000+K{row}-L{row}-N{row}/10,-1)'
f'=IF(E{row}<1,-1E+12,'
f'H{row}*100000+J{row}*1000+K{row}-L{row}-N{row}/10)'
)
combo_rows.append(row)
combo_idx += 1
@@ -991,14 +1000,26 @@ def build_dashboard(wb: Workbook) -> None:
status_rng, CellIsRule(operator="equal", formula=['"CONT PIERDUT"'], fill=fail_fill)
)
ws.conditional_formatting.add(
status_rng, CellIsRule(operator="equal", formula=['"NU"'], fill=warn_fill)
status_rng, CellIsRule(operator="equal", formula=['"BREACH"'], fill=fail_fill)
)
ws.conditional_formatting.add(
status_rng, CellIsRule(operator="equal", formula=['"PRE-CANDIDAT"'], fill=warn_fill)
)
top_title_row = row + 2
ws[f"A{top_title_row}"] = "TOP CANDIDATE"
ws[f"A{top_title_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{top_title_row}:J{top_title_row}")
top_header_row = top_title_row + 1
top_note_row = top_title_row + 1
ws[f"A{top_note_row}"] = (
"Top 10 ferestre după scor compus. CANDIDAT = îndeplinește toate pragurile "
"(N≥40, WR≥55%, ExpR≥0.2, no breach). PRE-CANDIDAT = N≥1 fără breach dar sub praguri. "
"BREACH = ar fi pierdut contul prop."
)
ws[f"A{top_note_row}"].font = Font(name="Calibri", size=10, italic=True, color="595959")
ws[f"A{top_note_row}"].alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
ws.merge_cells(f"A{top_note_row}:J{top_note_row}")
top_header_row = top_note_row + 1
top_headers = [
"#", "Fereastra", "Strategie", "N", "WR", "Expectancy R",
"Profit Factor", "Cum P&L $", "Max DD Prop $", "Status Edge",
@@ -1021,7 +1042,7 @@ def build_dashboard(wb: Workbook) -> None:
["A", "D", "E", "G", "H", "J", "K", "N", "R"],
):
ws[f"{target}{r}"] = (
f'=IFERROR(IF({rank_formula}<0,"",'
f'=IFERROR(IF({rank_formula}<=-1E+11,"",'
f'INDEX(${source}${first_combo}:${source}${last_combo},{match_formula})),"")'
)
for c in range(1, len(top_headers) + 1):
@@ -1034,56 +1055,86 @@ def build_dashboard(wb: Workbook) -> None:
ws[f"H{r}"].number_format = '"$"#,##0.00'
ws[f"I{r}"].number_format = '"$"#,##0.00'
# Helper pentru a emite un block breakdown (per Sesiune / Strategie / etc.)
def _emit_breakdown(
# CF pe coloana Status Edge din TOP CANDIDATE
top_status_rng = f"J{top_header_row + 1}:J{top_header_row + 10}"
ws.conditional_formatting.add(
top_status_rng, CellIsRule(operator="equal", formula=['"CANDIDAT"'], fill=pass_fill)
)
ws.conditional_formatting.add(
top_status_rng, CellIsRule(operator="equal", formula=['"PRE-CANDIDAT"'], fill=warn_fill)
)
ws.conditional_formatting.add(
top_status_rng, CellIsRule(operator="equal", formula=['"BREACH"'], fill=fail_fill)
)
# Conditional formatting reutilizabil pentru celulele Cum $
bd_green = PatternFill("solid", fgColor="C6EFCE")
bd_red = PatternFill("solid", fgColor="FFC7CE")
# Helper pentru breakdown wide: rânduri = items, coloane = 5 strategii Cum $ + N total
def _emit_breakdown_strats(
start_row: int, title: str, first_col_label: str,
items: list[str], item_range: str, overlay_strat: str,
items: list[str], item_range: str,
) -> int:
# Layout: A=item, B..F=5 strategii (Cum $), G=N total
last_col_idx = 1 + len(STRAT_KEYS) + 1 # A + 5 strategii + N
last_letter = get_column_letter(last_col_idx)
ws[f"A{start_row}"] = title
ws[f"A{start_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{start_row}:F{start_row}")
headers = [first_col_label, "N", "Wins", "WR", "Expectancy R", "Cum $"]
ws.merge_cells(f"A{start_row}:{last_letter}{start_row}")
headers = [first_col_label] + [STRAT_LABELS[s] for s in STRAT_KEYS] + ["N total"]
for col_idx, h in enumerate(headers, start=1):
c = ws.cell(row=start_row + 1, column=col_idx, value=h)
c.font = HEADER_FONT
c.fill = HEADER_FILL
c.alignment = CENTER
c.border = BORDER
strat_letters = [get_column_letter(2 + i) for i in range(len(STRAT_KEYS))]
n_letter = get_column_letter(last_col_idx)
for i, item in enumerate(items):
r = start_row + 2 + i
ws[f"A{r}"] = item
ws[f"B{r}"] = f'=COUNTIF({item_range},"{item}")'
ws[f"C{r}"] = f'=COUNTIFS({item_range},"{item}",{W[overlay_strat]},1)'
ws[f"D{r}"] = f"=IFERROR(C{r}/B{r},0)"
ws[f"E{r}"] = (
f'=IFERROR(AVERAGEIFS({R[overlay_strat]},{item_range},"{item}"),0)'
for idx, strat in enumerate(STRAT_KEYS):
cl = strat_letters[idx]
ws[f"{cl}{r}"] = f'=SUMIFS({D[strat]},{item_range},"{item}")'
ws[f"{cl}{r}"].number_format = '"$"#,##0.00'
ws[f"{n_letter}{r}"] = f'=COUNTIF({item_range},"{item}")'
ws[f"{n_letter}{r}"].number_format = "0"
for col_idx in range(1, last_col_idx + 1):
cell = ws.cell(row=r, column=col_idx)
cell.border = BORDER
cell.alignment = LEFT if col_idx == 1 else RIGHT
if 2 <= col_idx <= 1 + len(STRAT_KEYS):
cell.fill = DERIVED_FILL
# CF pe coloanele 5 strategii: verde >0, roșu <0
if items:
first_data_row = start_row + 2
last_data_row = start_row + 1 + len(items)
cf_rng = (
f"{strat_letters[0]}{first_data_row}:"
f"{strat_letters[-1]}{last_data_row}"
)
ws[f"F{r}"] = f'=SUMIFS({D[overlay_strat]},{item_range},"{item}")'
ws[f"B{r}"].number_format = "0"
ws[f"C{r}"].number_format = "0"
ws[f"D{r}"].number_format = "0.0%"
ws[f"E{r}"].number_format = "+0.000;-0.000;0.000"
ws[f"F{r}"].number_format = '"$"#,##0.00'
for c in ("A", "B", "C", "D", "E", "F"):
ws[f"{c}{r}"].border = BORDER
ws[f"{c}{r}"].alignment = RIGHT if c != "A" else LEFT
return start_row + 2 + len(items)
ws.conditional_formatting.add(
cf_rng, CellIsRule(operator="greaterThan", formula=["0"], fill=bd_green)
)
ws.conditional_formatting.add(
cf_rng, CellIsRule(operator="lessThan", formula=["0"], fill=bd_red)
)
return start_row + 1 + len(items)
# Breakdowns — toate folosesc overlay-ul Hybrid+BE (recomandat de trader)
overlay = "hybrid_be"
# Breakdowns — toate cele 5 strategii vizibile, Cum P&L $ per strategie
start = top_header_row + 13
after_sess = start
after_strat = _emit_breakdown(
after_sess + 2, "PER STRATEGIE (overlay: Hybrid + BE)", "Strategie",
STRATEGIES, _range("Strategie"), overlay,
after_strat = _emit_breakdown_strats(
start + 2, "PER STRATEGIE — Cum P&L $ per strategie", "Strategie",
STRATEGIES, _range("Strategie"),
)
after_ind = _emit_breakdown(
after_strat + 2, "PER INDICATOR (overlay: Hybrid + BE)", "Indicator",
INDICATORS, _range("Indicator"), overlay,
after_ind = _emit_breakdown_strats(
after_strat + 2, "PER INDICATOR — Cum P&L $ per strategie", "Indicator",
INDICATORS, _range("Indicator"),
)
after_dir = _emit_breakdown(
after_ind + 2, "PER DIRECȚIE (overlay: Hybrid + BE)", "Direcție",
DIRECTIONS, _range("Direcție"), overlay,
after_dir = _emit_breakdown_strats(
after_ind + 2, "PER DIRECȚIE — Cum P&L $ per strategie", "Direcție",
DIRECTIONS, _range("Direcție"),
)
# ---- PROP FIRM COMPLIANCE ----
@@ -1235,9 +1286,72 @@ def build_dashboard(wb: Workbook) -> None:
for r in range(prop_header_row + 1, prop_header_row + 1 + len(prop_metrics)):
ws.row_dimensions[r].height = 60
# ---- PROP FIRM COMPLIANCE per FEREASTRĂ × STRATEGIE ----
# Reshape compliance: rânduri = combo (fereastră × strategie), coloane = metrici compliance.
# Datele sunt referențiate direct din FERESTRE CANDIDATE (cols A, D, M, N, O, P, Q).
if combo_rows:
win_prop_title_row = prop_header_row + 1 + len(prop_metrics) + 2
ws[f"A{win_prop_title_row}"] = "PROP FIRM COMPLIANCE — per FEREASTRĂ × STRATEGIE"
ws[f"A{win_prop_title_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{win_prop_title_row}:G{win_prop_title_row}")
win_prop_note_row = win_prop_title_row + 1
ws[f"A{win_prop_note_row}"] = (
"Defalcat pe fiecare combinație de fereastră tradabilă × strategie management. "
"CONFORM = ar fi supraviețuit pe contul de prop pe acel slot."
)
ws[f"A{win_prop_note_row}"].font = Font(name="Calibri", size=10, italic=True, color="595959")
ws[f"A{win_prop_note_row}"].alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
ws.merge_cells(f"A{win_prop_note_row}:G{win_prop_note_row}")
win_prop_header_row = win_prop_note_row + 1
win_prop_headers = [
"Fereastra", "Strategie", "Worst Daily Prop $", "Max DD Prop $",
"Daily Breach", "Max Breach", "Overall Prop",
]
for col_idx, h in enumerate(win_prop_headers, start=1):
c = ws.cell(row=win_prop_header_row, column=col_idx, value=h)
c.font = HEADER_FONT
c.fill = HEADER_FILL
c.alignment = CENTER
c.border = BORDER
# source cols din FERESTRE CANDIDATE: A=Fereastra, D=Strategie, M=Worst Daily,
# N=Max DD, O=Daily Breach, P=Max Breach, Q=Overall Prop
source_cols = ["A", "D", "M", "N", "O", "P", "Q"]
for offset, combo_row in enumerate(combo_rows, start=1):
r = win_prop_header_row + offset
for col_idx, source in enumerate(source_cols, start=1):
target = get_column_letter(col_idx)
ws[f"{target}{r}"] = f"={source}{combo_row}"
cell = ws[f"{target}{r}"]
cell.border = BORDER
cell.fill = DERIVED_FILL
cell.alignment = CENTER if col_idx in (1, 2, 5, 6, 7) else RIGHT
ws[f"C{r}"].number_format = '"$"#,##0.00'
ws[f"D{r}"].number_format = '"$"#,##0.00'
# CF pe Overall Prop (col G) și pe Daily/Max Breach (cols E, F)
win_prop_first = win_prop_header_row + 1
win_prop_last = win_prop_header_row + len(combo_rows)
overall_rng_win = f"G{win_prop_first}:G{win_prop_last}"
ws.conditional_formatting.add(
overall_rng_win, CellIsRule(operator="equal", formula=['"CONFORM"'], fill=pass_fill)
)
ws.conditional_formatting.add(
overall_rng_win, CellIsRule(operator="equal", formula=['"CONT PIERDUT"'], fill=fail_fill)
)
breach_rng_win = f"E{win_prop_first}:F{win_prop_last}"
ws.conditional_formatting.add(
breach_rng_win, CellIsRule(operator="equal", formula=['"DA"'], fill=fail_fill)
)
ws.conditional_formatting.add(
breach_rng_win, CellIsRule(operator="equal", formula=['"NU"'], fill=pass_fill)
)
# Column widths
widths = {
"A": 18, "B": 10, "C": 10, "D": 16, "E": 8, "F": 8, "G": 10,
"A": 18, "B": 10, "C": 18, "D": 16, "E": 13, "F": 13, "G": 16,
"H": 13, "I": 13, "J": 12, "K": 13, "L": 15, "M": 20,
"N": 18, "O": 13, "P": 14, "Q": 15, "R": 13, "S": 8,
}