prop
This commit is contained in:
@@ -81,11 +81,16 @@ DERIVED_HEADERS = (
|
||||
[f"R_{s}" for s in STRAT_KEYS]
|
||||
+ [f"$_{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 = (
|
||||
[f"Win_{s}" for s in STRAT_KEYS]
|
||||
+ [f"Peak_{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
|
||||
|
||||
@@ -128,7 +133,7 @@ def build_config(wb: Workbook) -> None:
|
||||
|
||||
ws["A4"] = "Account Size Start ($)"
|
||||
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["B5"] = 1.0
|
||||
@@ -147,6 +152,54 @@ def build_config(wb: Workbook) -> None:
|
||||
ws["B5"].number_format = '0.0"%"'
|
||||
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 E–J (6 coloane)
|
||||
list_columns = [
|
||||
("Strategii", STRATEGIES),
|
||||
@@ -295,6 +348,29 @@ def _f_drawdown(r: int, peak_col: str, balance_col: str) -> str:
|
||||
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
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -330,6 +406,16 @@ def build_trades(wb: Workbook) -> None:
|
||||
ws[f'{COL[f"DD_{strat}"]}{r}'] = _f_drawdown(
|
||||
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
|
||||
ws["B2"] = date(2026, 5, 13)
|
||||
@@ -353,7 +439,10 @@ def build_trades(wb: Workbook) -> None:
|
||||
for strat in STRAT_KEYS:
|
||||
for r in range(2, MAX_ROWS + 2):
|
||||
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'
|
||||
|
||||
for r in range(2, MAX_ROWS + 2):
|
||||
@@ -370,12 +459,11 @@ def build_trades(wb: Workbook) -> None:
|
||||
}
|
||||
derived_letters = {COL["Zi"], COL["Sesiune"]}
|
||||
for strat in STRAT_KEYS:
|
||||
derived_letters.add(COL[f"R_{strat}"])
|
||||
derived_letters.add(COL[f"$_{strat}"])
|
||||
derived_letters.add(COL[f"Bal_{strat}"])
|
||||
for prefix in ("R_", "$_", "Bal_", "$Prop_", "BalProp_"):
|
||||
derived_letters.add(COL[f"{prefix}{strat}"])
|
||||
helper_letters = set()
|
||||
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}"])
|
||||
|
||||
for r in range(2, MAX_ROWS + 2):
|
||||
@@ -397,9 +485,19 @@ def build_trades(wb: Workbook) -> None:
|
||||
ws.column_dimensions[col].width = w
|
||||
# Derived + helper: width 11
|
||||
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
|
||||
|
||||
# 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
|
||||
def _add_dv(col_name: str, source: str) -> None:
|
||||
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"
|
||||
"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",
|
||||
INDICATORS, _range("Indicator"), overlay,
|
||||
)
|
||||
_emit_breakdown(
|
||||
after_dir = _emit_breakdown(
|
||||
after_ind + 2, "PER DIRECȚIE (overlay: Hybrid + BE)", "Direcție",
|
||||
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
|
||||
widths = {"A": 22, "B": 14, "C": 14, "D": 14, "E": 16, "F": 16, "G": 75}
|
||||
for col, w in widths.items():
|
||||
@@ -702,6 +989,26 @@ def build_dashboard(wb: Workbook) -> None:
|
||||
chart.set_categories(cats)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user