This commit is contained in:
Marius
2026-05-14 00:05:56 +03:00
parent 0e03d32004
commit 804e3a30eb
3 changed files with 315 additions and 8 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -81,11 +81,16 @@ DERIVED_HEADERS = (
[f"R_{s}" for s in STRAT_KEYS] [f"R_{s}" for s in STRAT_KEYS]
+ [f"$_{s}" for s in STRAT_KEYS] + [f"$_{s}" for s in STRAT_KEYS]
+ [f"Bal_{s}" for s in STRAT_KEYS] + [f"Bal_{s}" for s in STRAT_KEYS]
+ [f"$Prop_{s}" for s in STRAT_KEYS]
+ [f"BalProp_{s}" for s in STRAT_KEYS]
) )
HELPER_HEADERS = ( HELPER_HEADERS = (
[f"Win_{s}" for s in STRAT_KEYS] [f"Win_{s}" for s in STRAT_KEYS]
+ [f"Peak_{s}" for s in STRAT_KEYS] + [f"Peak_{s}" for s in STRAT_KEYS]
+ [f"DD_{s}" for s in STRAT_KEYS] + [f"DD_{s}" for s in STRAT_KEYS]
+ [f"DailyPL_{s}" for s in STRAT_KEYS]
+ [f"PeakProp_{s}" for s in STRAT_KEYS]
+ [f"DDProp_{s}" for s in STRAT_KEYS]
) )
TRADES_HEADERS = INPUT_HEADERS + DERIVED_HEADERS + HELPER_HEADERS TRADES_HEADERS = INPUT_HEADERS + DERIVED_HEADERS + HELPER_HEADERS
@@ -128,7 +133,7 @@ def build_config(wb: Workbook) -> None:
ws["A4"] = "Account Size Start ($)" ws["A4"] = "Account Size Start ($)"
ws["B4"] = 10000 ws["B4"] = 10000
ws["C4"] = "Balanța inițială pentru calcule $ și HWM" ws["C4"] = "Balanța inițială pentru calcule $ și HWM (model abstract)"
ws["A5"] = "Risk per Trade (%)" ws["A5"] = "Risk per Trade (%)"
ws["B5"] = 1.0 ws["B5"] = 1.0
@@ -147,6 +152,54 @@ def build_config(wb: Workbook) -> None:
ws["B5"].number_format = '0.0"%"' ws["B5"].number_format = '0.0"%"'
ws["B6"].number_format = "$#,##0.00" ws["B6"].number_format = "$#,##0.00"
# ---- Bloc Cont Prop Firm (separat de modelul abstract de mai sus) ----
ws["A8"] = "Cont Prop Firm"
ws["A8"].font = SUBTITLE_FONT
ws.merge_cells("A8:C8")
ws["A9"] = "Account Prop Start ($)"
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["A11"] = "Position Size ($)"
ws["B11"] = "=B9*B10/100"
ws["C11"] = "Auto — notional efectiv pe trade"
ws["A12"] = "Daily Loss Limit (%)"
ws["B12"] = 4.0
ws["C12"] = "Limită zilnică prop firm; depășire = cont mort"
ws["A13"] = "Daily Loss Limit ($)"
ws["B13"] = "=B9*B12/100"
ws["C13"] = "Auto — derivat din B9 și B12"
ws["A14"] = "Max Loss Limit (%)"
ws["B14"] = 7.0
ws["C14"] = "Limită totală pe cont; depășire = cont mort"
ws["A15"] = "Max Loss Limit ($)"
ws["B15"] = "=B9*B14/100"
ws["C15"] = "Auto — derivat din B9 și B14"
for r in (9, 10, 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
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["B11"].number_format = "$#,##0"
ws["B12"].number_format = '0.0"%"'
ws["B13"].number_format = "$#,##0"
ws["B14"].number_format = '0.0"%"'
ws["B15"].number_format = "$#,##0"
# Liste dropdown — coloanele EJ (6 coloane) # Liste dropdown — coloanele EJ (6 coloane)
list_columns = [ list_columns = [
("Strategii", STRATEGIES), ("Strategii", STRATEGIES),
@@ -295,6 +348,29 @@ def _f_drawdown(r: int, peak_col: str, balance_col: str) -> str:
return f'=IF({bc}="","",{pc}-{bc})' return f'=IF({bc}="","",{pc}-{bc})'
def _f_dollar_prop(r: int, r_col: str) -> str:
"""$ P&L pe contul de prop. Variabil per trade = R × SL%/100 × Position Size."""
rc = f"{COL[r_col]}{r}"
sl = f"{COL['SL %']}{r}"
return f'=IF({rc}="","",{rc}*{sl}/100*Config!$B$11)'
def _f_balance_prop(r: int, dollar_col: str) -> str:
dc = COL[dollar_col]
return f'=IF({dc}{r}="","",Config!$B$9 + SUM(${dc}$2:{dc}{r}))'
def _f_daily_pl(r: int, dollar_col: str) -> str:
"""Cumul P&L pe ziua curentă (până la rândul r inclusiv)."""
dc = COL[dollar_col]
d_col = COL["Data"]
d = f"{d_col}{r}"
return (
f'=IF(OR({dc}{r}="",{d}=""),"",'
f'SUMIFS(${dc}$2:{dc}{r},${d_col}$2:{d_col}{r},{d}))'
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Trades sheet # Trades sheet
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -330,6 +406,16 @@ def build_trades(wb: Workbook) -> None:
ws[f'{COL[f"DD_{strat}"]}{r}'] = _f_drawdown( ws[f'{COL[f"DD_{strat}"]}{r}'] = _f_drawdown(
r, f"Peak_{strat}", f"Bal_{strat}" r, f"Peak_{strat}", f"Bal_{strat}"
) )
# Prop firm tracking — paralel cu modelul abstract
ws[f'{COL[f"$Prop_{strat}"]}{r}'] = _f_dollar_prop(r, f"R_{strat}")
ws[f'{COL[f"BalProp_{strat}"]}{r}'] = _f_balance_prop(r, f"$Prop_{strat}")
ws[f'{COL[f"DailyPL_{strat}"]}{r}'] = _f_daily_pl(r, f"$Prop_{strat}")
ws[f'{COL[f"PeakProp_{strat}"]}{r}'] = _f_peak(
r, f"BalProp_{strat}", f"PeakProp_{strat}"
)
ws[f'{COL[f"DDProp_{strat}"]}{r}'] = _f_drawdown(
r, f"PeakProp_{strat}", f"BalProp_{strat}"
)
# Sample row 2 # Sample row 2
ws["B2"] = date(2026, 5, 13) ws["B2"] = date(2026, 5, 13)
@@ -353,7 +439,10 @@ def build_trades(wb: Workbook) -> None:
for strat in STRAT_KEYS: for strat in STRAT_KEYS:
for r in range(2, MAX_ROWS + 2): for r in range(2, MAX_ROWS + 2):
ws[f"{COL[f'R_{strat}']}{r}"].number_format = "+0.000;-0.000;0.000" ws[f"{COL[f'R_{strat}']}{r}"].number_format = "+0.000;-0.000;0.000"
for prefix in ("$_", "Bal_", "Peak_", "DD_"): for prefix in (
"$_", "Bal_", "Peak_", "DD_",
"$Prop_", "BalProp_", "DailyPL_", "PeakProp_", "DDProp_",
):
ws[f"{COL[f'{prefix}{strat}']}{r}"].number_format = '"$"#,##0.00' ws[f"{COL[f'{prefix}{strat}']}{r}"].number_format = '"$"#,##0.00'
for r in range(2, MAX_ROWS + 2): for r in range(2, MAX_ROWS + 2):
@@ -370,12 +459,11 @@ def build_trades(wb: Workbook) -> None:
} }
derived_letters = {COL["Zi"], COL["Sesiune"]} derived_letters = {COL["Zi"], COL["Sesiune"]}
for strat in STRAT_KEYS: for strat in STRAT_KEYS:
derived_letters.add(COL[f"R_{strat}"]) for prefix in ("R_", "$_", "Bal_", "$Prop_", "BalProp_"):
derived_letters.add(COL[f"$_{strat}"]) derived_letters.add(COL[f"{prefix}{strat}"])
derived_letters.add(COL[f"Bal_{strat}"])
helper_letters = set() helper_letters = set()
for strat in STRAT_KEYS: for strat in STRAT_KEYS:
for prefix in ("Win_", "Peak_", "DD_"): for prefix in ("Win_", "Peak_", "DD_", "DailyPL_", "PeakProp_", "DDProp_"):
helper_letters.add(COL[f"{prefix}{strat}"]) helper_letters.add(COL[f"{prefix}{strat}"])
for r in range(2, MAX_ROWS + 2): for r in range(2, MAX_ROWS + 2):
@@ -397,9 +485,19 @@ def build_trades(wb: Workbook) -> None:
ws.column_dimensions[col].width = w ws.column_dimensions[col].width = w
# Derived + helper: width 11 # Derived + helper: width 11
for strat in STRAT_KEYS: for strat in STRAT_KEYS:
for prefix in ("R_", "$_", "Bal_", "Win_", "Peak_", "DD_"): for prefix in (
"R_", "$_", "Bal_", "Win_", "Peak_", "DD_",
"$Prop_", "BalProp_", "DailyPL_", "PeakProp_", "DDProp_",
):
ws.column_dimensions[COL[f"{prefix}{strat}"]].width = 11 ws.column_dimensions[COL[f"{prefix}{strat}"]].width = 11
# Ascund helper-ele prop firm într-un outline collapsible
for strat in STRAT_KEYS:
for prefix in ("DailyPL_", "PeakProp_", "DDProp_"):
cl = COL[f"{prefix}{strat}"]
ws.column_dimensions[cl].outlineLevel = 1
ws.column_dimensions[cl].hidden = True
# Data validation dropdowns # Data validation dropdowns
def _add_dv(col_name: str, source: str) -> None: def _add_dv(col_name: str, source: str) -> None:
cl = COL[col_name] cl = COL[col_name]
@@ -514,6 +612,46 @@ METRIC_HINTS: dict[str, str] = {
"Exemplu: ai urcat la $11,500, ai coborât la $9,800 — DD = $1,700, adică 17% din peak.\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." "Un drawdown mare la backtest e foarte greu de tolerat în live cu bani reali — așteaptă-te să renunți."
), ),
# ---- Prop firm metrics ----
"Account Prop Start ($)": (
"Capitalul de start al contului de prop firm (default $50,000).\n"
"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)."
),
"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"
"Adunat peste $50,000 dă balanța finală reală."
),
"Final Balance Prop ($)": (
"Balanța finală a contului de prop = $50,000 + Cumulative P&L Prop.\n"
"Compar-o cu pragul de stop-out al firmei de prop: $50,000 $3,500 = $46,500."
),
"Worst Daily Loss ($)": (
"Cea mai proastă pierdere cumulativă într-o zi calendaristică.\n"
"Dacă e mai mică decât $2,000, ai depășit Daily Loss Limit într-o zi — cont mort.\n"
"Atenție: un singur breach = pierdere cont, indiferent dacă ai recuperat ulterior."
),
"Daily Limit Status": (
"PASS dacă nicio zi nu a depășit Daily Loss Limit ($2,000 default).\n"
"FAIL = strategia ar fi pierdut contul prin daily breach pe traseul logat."
),
"Max Account Drawdown ($)": (
"Cea mai mare cădere de la peak pe contul de prop.\n"
"Dacă > $3,500 (7% din $50k), ai depășit Max Loss Limit — cont mort."
),
"Max Loss Status": (
"PASS dacă Max Account Drawdown ≤ $3,500.\n"
"FAIL = strategia ar fi pierdut contul prin drawdown cumulativ."
),
"Overall Prop Status": (
"CONFORM = strategia ar fi supraviețuit pe contul de prop pe traseul logat.\n"
"CONT PIERDUT = cel puțin o breach (daily sau max) — strategia nu e viabilă pe acest cont prop."
),
} }
@@ -664,11 +802,160 @@ def build_dashboard(wb: Workbook) -> None:
after_strat + 2, "PER INDICATOR (overlay: Hybrid + BE)", "Indicator", after_strat + 2, "PER INDICATOR (overlay: Hybrid + BE)", "Indicator",
INDICATORS, _range("Indicator"), overlay, INDICATORS, _range("Indicator"), overlay,
) )
_emit_breakdown( after_dir = _emit_breakdown(
after_ind + 2, "PER DIRECȚIE (overlay: Hybrid + BE)", "Direcție", after_ind + 2, "PER DIRECȚIE (overlay: Hybrid + BE)", "Direcție",
DIRECTIONS, _range("Direcție"), overlay, DIRECTIONS, _range("Direcție"), overlay,
) )
# ---- PROP FIRM COMPLIANCE ----
PROP_RANGES = {
"dollar": {s: _range(f"$Prop_{s}") for s in STRAT_KEYS},
"balance": {s: _range(f"BalProp_{s}") for s in STRAT_KEYS},
"daily": {s: _range(f"DailyPL_{s}") for s in STRAT_KEYS},
"dd": {s: _range(f"DDProp_{s}") for s in STRAT_KEYS},
}
prop_title_row = after_dir + 2
ws[f"A{prop_title_row}"] = "PROP FIRM COMPLIANCE"
ws[f"A{prop_title_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{prop_title_row}:G{prop_title_row}")
# Header pentru tabel
prop_header_row = prop_title_row + 1
ws[f"A{prop_header_row}"] = "Metric"
for strat in STRAT_KEYS:
ws[f"{strat_cols[strat]}{prop_header_row}"] = STRAT_LABELS[strat]
ws[f"G{prop_header_row}"] = "Cum citesc"
for letter in ["A"] + list(strat_cols.values()) + ["G"]:
c = ws[f"{letter}{prop_header_row}"]
c.font = HEADER_FONT
c.fill = HEADER_FILL
c.alignment = CENTER
c.border = BORDER
# Definițiile rândurilor — (label, formula_fn(strat), number_format)
fail_pass_fmt = "@"
prop_metrics: list[tuple[str, callable, str]] = [
(
"Account Prop Start ($)",
lambda s: "=Config!$B$9",
'"$"#,##0',
),
(
"Position Size ($)",
lambda s: "=Config!$B$11",
'"$"#,##0',
),
(
"Cumulative P&L Prop ($)",
lambda s: f"=SUM({PROP_RANGES['dollar'][s]})",
'"$"#,##0.00',
),
(
"Final Balance Prop ($)",
lambda s: f"=Config!$B$9+SUM({PROP_RANGES['dollar'][s]})",
'"$"#,##0.00',
),
(
"Worst Daily Loss ($)",
lambda s: f"=IFERROR(MIN({PROP_RANGES['daily'][s]}),0)",
'"$"#,##0.00',
),
# placeholder pentru Daily Status — depinde de Worst Daily de mai sus
("Daily Limit Status", lambda s: None, fail_pass_fmt),
(
"Max Account Drawdown ($)",
lambda s: f"=IFERROR(MAX({PROP_RANGES['dd'][s]}),0)",
'"$"#,##0.00',
),
# placeholder pentru Max Status — depinde de Max DD de mai sus
("Max Loss Status", lambda s: None, fail_pass_fmt),
# placeholder pentru Overall — depinde de cele două statuses
("Overall Prop Status", lambda s: None, fail_pass_fmt),
]
prop_label_to_row = {
label: prop_header_row + 1 + idx
for idx, (label, _, _) in enumerate(prop_metrics)
}
worst_daily_row = prop_label_to_row["Worst Daily Loss ($)"]
daily_status_row = prop_label_to_row["Daily Limit Status"]
max_dd_row = prop_label_to_row["Max Account Drawdown ($)"]
max_status_row = prop_label_to_row["Max Loss Status"]
for idx, (label, fn, fmt) in enumerate(prop_metrics):
r = prop_header_row + 1 + idx
ws[f"A{r}"] = label
ws[f"A{r}"].font = Font(name="Calibri", size=11, bold=True)
ws[f"A{r}"].border = BORDER
ws[f"A{r}"].alignment = LEFT
for strat in STRAT_KEYS:
letter = strat_cols[strat]
if label == "Daily Limit Status":
formula = (
f'=IF({letter}{worst_daily_row}<-Config!$B$13,"FAIL","PASS")'
)
elif label == "Max Loss Status":
formula = (
f'=IF({letter}{max_dd_row}>Config!$B$15,"FAIL","PASS")'
)
elif label == "Overall Prop Status":
formula = (
f'=IF(OR({letter}{daily_status_row}="FAIL",'
f'{letter}{max_status_row}="FAIL"),'
f'"CONT PIERDUT","CONFORM")'
)
else:
formula = fn(strat)
cell = ws[f"{letter}{r}"]
cell.value = formula
cell.number_format = fmt
cell.fill = DERIVED_FILL
cell.border = BORDER
cell.alignment = RIGHT if fmt != fail_pass_fmt else CENTER
# Hint în coloana G
hint_cell = ws[f"G{r}"]
hint_cell.value = METRIC_HINTS.get(label, "")
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
# Conditional formatting pe status rows — verde PASS/CONFORM, roșu FAIL/CONT PIERDUT
pass_fill = PatternFill("solid", fgColor="C6EFCE")
fail_fill = PatternFill("solid", fgColor="FFC7CE")
for status_row in (daily_status_row, max_status_row):
rng = (
f"{strat_cols[STRAT_KEYS[0]]}{status_row}:"
f"{strat_cols[STRAT_KEYS[-1]]}{status_row}"
)
ws.conditional_formatting.add(
rng, CellIsRule(operator="equal", formula=['"PASS"'], fill=pass_fill)
)
ws.conditional_formatting.add(
rng, CellIsRule(operator="equal", formula=['"FAIL"'], fill=fail_fill)
)
overall_row = prop_label_to_row["Overall Prop Status"]
overall_rng = (
f"{strat_cols[STRAT_KEYS[0]]}{overall_row}:"
f"{strat_cols[STRAT_KEYS[-1]]}{overall_row}"
)
ws.conditional_formatting.add(
overall_rng,
CellIsRule(operator="equal", formula=['"CONFORM"'], fill=pass_fill),
)
ws.conditional_formatting.add(
overall_rng,
CellIsRule(
operator="equal", formula=['"CONT PIERDUT"'], fill=fail_fill
),
)
# Înălțime rânduri prop (cu hint multi-line)
for r in range(prop_header_row + 1, prop_header_row + 1 + len(prop_metrics)):
ws.row_dimensions[r].height = 60
# Column widths # Column widths
widths = {"A": 22, "B": 14, "C": 14, "D": 14, "E": 16, "F": 16, "G": 75} widths = {"A": 22, "B": 14, "C": 14, "D": 14, "E": 16, "F": 16, "G": 75}
for col, w in widths.items(): for col, w in widths.items():
@@ -702,6 +989,26 @@ def build_dashboard(wb: Workbook) -> None:
chart.set_categories(cats) chart.set_categories(cats)
ws.add_chart(chart, "H4") ws.add_chart(chart, "H4")
# Equity curve prop — al doilea chart, separat de modelul abstract
chart_prop = LineChart()
chart_prop.title = "Equity Curve — Prop ($50k start)"
chart_prop.style = 12
chart_prop.y_axis.title = "Balance Prop ($)"
chart_prop.x_axis.title = "Trade #"
chart_prop.height = 12
chart_prop.width = 24
data_prop = Reference(
wb["Trades"],
min_col=_col_to_int(COL[f"BalProp_{STRAT_KEYS[0]}"]),
max_col=_col_to_int(COL[f"BalProp_{STRAT_KEYS[-1]}"]),
min_row=1,
max_row=MAX_ROWS + 1,
)
chart_prop.add_data(data_prop, titles_from_data=True)
chart_prop.set_categories(cats)
ws.add_chart(chart_prop, "H30")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Main # Main