filtru Prima per Indicator pe grid FERESTRE CANDIDATE x STRATEGIE

- PrimaWin_<idx> helper columns in Trades (per fereastră)
- DASH_WIN_COL dict refactor (zero hardcoded litere)
- Coloana Filtru (D) în window grid: rânduri Toate/Prima paralele
- Score split T (Score_Toate) + U (Score_Prima), ambele hidden
- TOP CANDIDATE: 2 sub-secțiuni — TOP 5 Toate + TOP 5 Prima
- Config!B17 escape hatch (DA/NU) pentru performanță
- 5 sample rows care exercită Prima edge cases
- scripts/verify_template.py: 18 aserțiuni smoke test
- .gitignore: data/backtest.backup-*.xlsx
- sterse 2 backup-uri vechi din git

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 20:25:05 +03:00
parent 9a961f0df5
commit 85ca68b11e
6 changed files with 618 additions and 171 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ venv/
# Excel temp/lock files # Excel temp/lock files
~$*.xlsx ~$*.xlsx
*.xlsx.bak *.xlsx.bak
data/backtest.backup-*.xlsx
# OS / editor # OS / editor
.DS_Store .DS_Store

Binary file not shown.

View File

@@ -118,6 +118,8 @@ DERIVED_HEADERS = (
+ [f"$Prop_{s}" for s in STRAT_KEYS] + [f"$Prop_{s}" for s in STRAT_KEYS]
+ [f"BalProp_{s}" for s in STRAT_KEYS] + [f"BalProp_{s}" for s in STRAT_KEYS]
) )
PRIMA_HELPERS = [f"PrimaWin_{idx}" for idx in range(len(TRADABLE_WINDOWS))]
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]
@@ -125,6 +127,7 @@ HELPER_HEADERS = (
+ [f"DailyPL_{s}" for s in STRAT_KEYS] + [f"DailyPL_{s}" for s in STRAT_KEYS]
+ [f"PeakProp_{s}" for s in STRAT_KEYS] + [f"PeakProp_{s}" for s in STRAT_KEYS]
+ [f"DDProp_{s}" for s in STRAT_KEYS] + [f"DDProp_{s}" for s in STRAT_KEYS]
+ PRIMA_HELPERS
) )
TRADES_HEADERS = INPUT_HEADERS + DERIVED_HEADERS + HELPER_HEADERS TRADES_HEADERS = INPUT_HEADERS + DERIVED_HEADERS + HELPER_HEADERS
@@ -234,6 +237,22 @@ def build_config(wb: Workbook) -> None:
ws["B14"].number_format = '0.0"%"' ws["B14"].number_format = '0.0"%"'
ws["B15"].number_format = "$#,##0" ws["B15"].number_format = "$#,##0"
# Escape hatch performanță: activează/dezactivează filtrul Prima per Indicator
ws["A17"] = "Activează filtru Prima"
ws["B17"] = "DA"
ws["C17"] = (
"DA = adaugi rândurile Prima în window grid. "
"NU = doar Toate (workbook mai rapid)."
)
ws["B17"].fill = INPUT_FILL
ws["B17"].border = BORDER
ws["B17"].alignment = CENTER
dv_prima = DataValidation(
type="list", formula1='"DA,NU"', allow_blank=False,
)
dv_prima.add("B17")
ws.add_data_validation(dv_prima)
# Liste dropdown — coloanele EJ (6 coloane) # Liste dropdown — coloanele EJ (6 coloane)
list_columns = [ list_columns = [
("Strategii", STRATEGIES), ("Strategii", STRATEGIES),
@@ -291,6 +310,36 @@ def _f_session(r: int) -> str:
) )
def _f_prima_in_window(r: int, win_idx: int) -> str:
"""1 dacă trade-ul este prima cronologic pe (Data, Indicator) ÎN fereastra cu indexul win_idx.
Guard suplimentar pe Config!B17 (escape hatch performanță): dacă utilizatorul
setează "NU", toate PrimaWin_* devin 0 instant, fără recalcularea COUNTIFS.
Outcome inclus în COUNTIFS ca să nu blocheze rândurile parțial completate.
"""
_, start_t, end_t = TRADABLE_WINDOWS[win_idx]
start_s = f"TIME({start_t.hour},{start_t.minute},0)"
end_s = f"TIME({end_t.hour},{end_t.minute},0)"
d = f'{COL["Data"]}{r}'
ind = f'{COL["Indicator"]}{r}'
t = f'{COL["Ora RO"]}{r}'
o = f'{COL["Outcome"]}{r}'
data_rng = _range("Data")
ind_rng = _range("Indicator")
ora_rng = _range("Ora RO")
outcome_rng = _range("Outcome")
return (
f'=IF(Config!$B$17<>"DA",0,'
f'IF(OR({d}="",{t}="",{ind}="",{o}=""),0,'
f'IF(AND({t}>={start_s},{t}<{end_s}),'
f'IF(COUNTIFS({data_rng},{d},{ind_rng},{ind},'
f'{ora_rng},">="&{start_s},{ora_rng},"<"&{end_s},'
f'{ora_rng},"<"&{t},'
f'{outcome_rng},"<>"'
f')=0,1,0),0)))'
)
def _f_r_tp0only(r: int) -> str: def _f_r_tp0only(r: int) -> str:
o = f'{COL["Outcome"]}{r}' o = f'{COL["Outcome"]}{r}'
sl = f'{COL["SL %"]}{r}' sl = f'{COL["SL %"]}{r}'
@@ -471,19 +520,34 @@ def build_trades(wb: Workbook) -> None:
r, f"PeakProp_{strat}", f"BalProp_{strat}" r, f"PeakProp_{strat}", f"BalProp_{strat}"
) )
# Sample row 2 # Coloanele PrimaWin_<idx> — 1 dacă trade-ul e prima per (Data, Indicator) în fereastră
ws["B2"] = date(2026, 5, 13) for win_idx in range(len(TRADABLE_WINDOWS)):
ws["C2"] = time(17, 33) ws[f'{COL[f"PrimaWin_{win_idx}"]}{r}'] = _f_prima_in_window(r, win_idx)
ws[f'{COL["Strategie"]}2'] = "M2D"
ws[f'{COL["Indicator"]}2'] = "DIA" # Sample rows
ws[f'{COL["TF"]}2'] = "1min" SAMPLE_ROWS = [
ws[f'{COL["Direcție"]}2'] = "Sell" # (data, ora, strat, ind, tf, dir, sl, tp0, tp1, tp2, outcome, notes)
ws[f'{COL["SL %"]}2'] = 0.30 (date(2026, 5, 13), time(17, 33), "M2D", "DIA", "1min", "Sell", 0.30, 0.10, 0.15, 0.30, "TP1", "Prima DIA în 16:30-18:00"),
ws[f'{COL["TP0 %"]}2'] = 0.10 (date(2026, 5, 13), time(17, 50), "M2D", "DIA", "1min", "Buy", 0.25, 0.10, 0.15, 0.25, "SL", "DIA a doua oară — NU Prima în 16:30-18:00, dar Prima în 17:30-19:00"),
ws[f'{COL["TP1 %"]}2'] = 0.15 (date(2026, 5, 13), time(17, 34), "M2D", "SPY", "1min", "Sell", 0.20, 0.08, 0.12, 0.20, "TP1", "SPY — indicator diferit, Prima independent"),
ws[f'{COL["TP2 %"]}2'] = 0.30 (date(2026, 5, 13), time(17, 40), "M2D", "DIA", "1min", "Sell", 0.20, 0.08, 0.12, 0.20, "", "Outcome gol — test D1: NU blochează Prima pentru row 2/3"),
ws[f'{COL["Outcome"]}2'] = "TP1" (date(2026, 5, 14), time(22, 15), "M2D", "DIA", "1min", "Sell", 0.30, 0.10, 0.15, 0.30, "TP0", "Zi diferită — Prima reset per (Data, Indicator, Fereastră)"),
ws[f'{COL["Notes"]}2'] = "Exemplu — șterge când începi" ]
for offset, sample in enumerate(SAMPLE_ROWS):
r = 2 + offset
data_v, ora, strat_v, ind, tf, dirn, sl, tp0, tp1, tp2, outcome, notes = sample
ws[f"B{r}"] = data_v
ws[f"C{r}"] = ora
ws[f'{COL["Strategie"]}{r}'] = strat_v
ws[f'{COL["Indicator"]}{r}'] = ind
ws[f'{COL["TF"]}{r}'] = tf
ws[f'{COL["Direcție"]}{r}'] = dirn
ws[f'{COL["SL %"]}{r}'] = sl
ws[f'{COL["TP0 %"]}{r}'] = tp0
ws[f'{COL["TP1 %"]}{r}'] = tp1
ws[f'{COL["TP2 %"]}{r}'] = tp2
ws[f'{COL["Outcome"]}{r}'] = outcome
ws[f'{COL["Notes"]}{r}'] = notes
# Number formats # Number formats
for col_name in ("SL %", "TP0 %", "TP1 %", "TP2 %"): for col_name in ("SL %", "TP0 %", "TP1 %", "TP2 %"):
@@ -523,6 +587,8 @@ def build_trades(wb: Workbook) -> None:
for strat in STRAT_KEYS: for strat in STRAT_KEYS:
for prefix in ("Win_", "Peak_", "DD_", "DailyPL_", "PeakProp_", "DDProp_"): for prefix in ("Win_", "Peak_", "DD_", "DailyPL_", "PeakProp_", "DDProp_"):
helper_letters.add(COL[f"{prefix}{strat}"]) helper_letters.add(COL[f"{prefix}{strat}"])
for win_idx in range(len(TRADABLE_WINDOWS)):
helper_letters.add(COL[f"PrimaWin_{win_idx}"])
for r in range(2, MAX_ROWS + 2): for r in range(2, MAX_ROWS + 2):
for cl in input_letters: for cl in input_letters:
@@ -558,6 +624,13 @@ def build_trades(wb: Workbook) -> None:
ws.column_dimensions[cl].outlineLevel = 1 ws.column_dimensions[cl].outlineLevel = 1
ws.column_dimensions[cl].hidden = True ws.column_dimensions[cl].hidden = True
# Helper-ele PrimaWin_<idx> — ~40 coloane la sfârșit, ascunse în outline
for win_idx in range(len(TRADABLE_WINDOWS)):
cl = COL[f"PrimaWin_{win_idx}"]
ws.column_dimensions[cl].width = 3
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]
@@ -816,20 +889,26 @@ def build_dashboard(wb: Workbook) -> None:
# ---- FERESTRE CANDIDATE x STRATEGIE ---- # ---- FERESTRE CANDIDATE x STRATEGIE ----
# Tabel principal pentru alegerea ferestrei tradabile. Drawdown-ul este # Tabel principal pentru alegerea ferestrei tradabile. Drawdown-ul este
# calculat cu helper-e ascunse pe fereastra curenta, nu din DD global. # calculat cu helper-e ascunse pe fereastra curenta, nu din DD global.
# DASH_WIN_COL: mapă nume → literă, ca să eliminăm hardcoding-ul de litere.
DASH_WIN_HEADERS = [
"Fereastra", "Start", "End", "Filtru", "Strategie",
"N", "Wins", "WR", "Expectancy R", "Expectancy $", "Profit Factor",
"Cum P&L $", "Max Drawdown $", "Worst Daily Loss Prop $",
"Max Drawdown Prop $", "Daily Breach", "Max Loss Breach",
"Status Prop", "Status Edge", "Score_Toate", "Score_Prima",
]
DASH_WIN_COL = {
name: get_column_letter(i + 1) for i, name in enumerate(DASH_WIN_HEADERS)
}
last_dash_col = DASH_WIN_COL[DASH_WIN_HEADERS[-1]]
window_title_row = 5 + len(metrics) + 2 window_title_row = 5 + len(metrics) + 2
ws[f"A{window_title_row}"] = "FERESTRE CANDIDATE x STRATEGIE" ws[f"A{window_title_row}"] = "FERESTRE CANDIDATE x STRATEGIE"
ws[f"A{window_title_row}"].font = SUBTITLE_FONT ws[f"A{window_title_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{window_title_row}:R{window_title_row}") ws.merge_cells(f"A{window_title_row}:{last_dash_col}{window_title_row}")
window_header_row = window_title_row + 1 window_header_row = window_title_row + 1
window_headers = [ for col_idx, header in enumerate(DASH_WIN_HEADERS, start=1):
"Fereastra", "Start", "End", "Strategie", "N", "Wins", "WR",
"Expectancy R", "Expectancy $", "Profit Factor", "Cum P&L $",
"Max Drawdown $", "Worst Daily Loss Prop $", "Max Drawdown Prop $",
"Daily Breach", "Max Loss Breach", "Status Prop", "Status Edge",
"Score",
]
for col_idx, header in enumerate(window_headers, start=1):
c = ws.cell(row=window_header_row, column=col_idx, value=header) c = ws.cell(row=window_header_row, column=col_idx, value=header)
c.font = HEADER_FONT c.font = HEADER_FONT
c.fill = HEADER_FILL c.fill = HEADER_FILL
@@ -840,7 +919,10 @@ def build_dashboard(wb: Workbook) -> None:
PROP_D = {s: _range(f"$Prop_{s}") for s in STRAT_KEYS} PROP_D = {s: _range(f"$Prop_{s}") for s in STRAT_KEYS}
helper_start_col = 27 # AA, ascuns. helper_start_col = 27 # AA, ascuns.
def _emit_window_helpers(visible_row: int, strat: str, combo_idx: int) -> dict[str, str]: def _emit_window_helpers(
visible_row: int, strat: str, combo_idx: int,
win_idx: int, use_prima: bool = False,
) -> dict[str, str]:
base_col = helper_start_col + combo_idx * 7 base_col = helper_start_col + combo_idx * 7
helper_names = ["Cum", "Peak", "DD", "DailyProp", "CumProp", "PeakProp", "DDProp"] helper_names = ["Cum", "Peak", "DD", "DailyProp", "CumProp", "PeakProp", "DDProp"]
cols = {name: get_column_letter(base_col + idx) for idx, name in enumerate(helper_names)} cols = {name: get_column_letter(base_col + idx) for idx, name in enumerate(helper_names)}
@@ -857,13 +939,21 @@ def build_dashboard(wb: Workbook) -> None:
time_col = COL["Ora RO"] time_col = COL["Ora RO"]
date_col = COL["Data"] date_col = COL["Data"]
outcome_col = COL["Outcome"] outcome_col = COL["Outcome"]
prima_col = COL[f"PrimaWin_{win_idx}"] if use_prima else None
for helper_row, trade_row in enumerate(range(2, MAX_ROWS + 2), start=2): for helper_row, trade_row in enumerate(range(2, MAX_ROWS + 2), start=2):
in_window = ( in_window_base = (
f'AND(Trades!${outcome_col}{trade_row}<>"",' f'AND(Trades!${outcome_col}{trade_row}<>"",'
f"Trades!${time_col}{trade_row}>={start_cell}," f"Trades!${time_col}{trade_row}>={start_cell},"
f"Trades!${time_col}{trade_row}<{end_cell})" f"Trades!${time_col}{trade_row}<{end_cell})"
) )
if use_prima:
in_window = (
f"AND({in_window_base},"
f"Trades!${prima_col}{trade_row}=1)"
)
else:
in_window = in_window_base
dollar = f"Trades!${dollar_col}{trade_row}" dollar = f"Trades!${dollar_col}{trade_row}"
prop = f"Trades!${prop_col}{trade_row}" prop = f"Trades!${prop_col}{trade_row}"
if helper_row == 2: if helper_row == 2:
@@ -907,89 +997,143 @@ def build_dashboard(wb: Workbook) -> None:
combo_rows: list[int] = [] combo_rows: list[int] = []
combo_idx = 0 combo_idx = 0
row = window_header_row + 1 row = window_header_row + 1
for label, start_time, end_time in TRADABLE_WINDOWS:
for strat in STRAT_KEYS:
helper_cols = _emit_window_helpers(row, strat, combo_idx)
ws[f"A{row}"] = label
ws[f"B{row}"] = start_time
ws[f"C{row}"] = end_time
ws[f"D{row}"] = STRAT_LABELS[strat]
ws[f"E{row}"] = (
f'=COUNTIFS({OUTCOME_RANGE},"<>",{TIME_RANGE},">="&B{row},'
f'{TIME_RANGE},"<"&C{row})'
)
ws[f"F{row}"] = (
f'=COUNTIFS({W[strat]},1,{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&B{row},{TIME_RANGE},"<"&C{row})'
)
ws[f"G{row}"] = f"=IFERROR(F{row}/E{row},0)"
ws[f"H{row}"] = (
f'=IFERROR(AVERAGEIFS({R[strat]},{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&B{row},{TIME_RANGE},"<"&C{row}),0)'
)
ws[f"I{row}"] = (
f'=IFERROR(AVERAGEIFS({D[strat]},{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&B{row},{TIME_RANGE},"<"&C{row}),0)'
)
ws[f"J{row}"] = (
f'=IFERROR(SUMIFS({D[strat]},{D[strat]},">0",{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&B{row},{TIME_RANGE},"<"&C{row})/'
f'ABS(SUMIFS({D[strat]},{D[strat]},"<0",{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&B{row},{TIME_RANGE},"<"&C{row})),0)'
)
ws[f"K{row}"] = (
f'=SUMIFS({D[strat]},{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&B{row},{TIME_RANGE},"<"&C{row})'
)
ws[f"L{row}"] = (
f'=IFERROR(MAX({helper_cols["DD"]}2:{helper_cols["DD"]}{MAX_ROWS + 1}),0)'
)
ws[f"M{row}"] = (
f'=IFERROR(MIN({helper_cols["DailyProp"]}2:'
f'{helper_cols["DailyProp"]}{MAX_ROWS + 1}),0)'
)
ws[f"N{row}"] = (
f'=IFERROR(MAX({helper_cols["DDProp"]}2:'
f'{helper_cols["DDProp"]}{MAX_ROWS + 1}),0)'
)
ws[f"O{row}"] = f'=IF(M{row}<-Config!$B$13,"DA","NU")'
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(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(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
row += 1
# Pre-compute column letters from DASH_WIN_COL for legibility
A_ = DASH_WIN_COL["Fereastra"]
B_ = DASH_WIN_COL["Start"]
C_ = DASH_WIN_COL["End"]
D_ = DASH_WIN_COL["Filtru"]
E_ = DASH_WIN_COL["Strategie"]
F_ = DASH_WIN_COL["N"]
G_ = DASH_WIN_COL["Wins"]
H_ = DASH_WIN_COL["WR"]
I_ = DASH_WIN_COL["Expectancy R"]
J_ = DASH_WIN_COL["Expectancy $"]
K_ = DASH_WIN_COL["Profit Factor"]
L_ = DASH_WIN_COL["Cum P&L $"]
M_ = DASH_WIN_COL["Max Drawdown $"]
N_ = DASH_WIN_COL["Worst Daily Loss Prop $"]
O_ = DASH_WIN_COL["Max Drawdown Prop $"]
P_ = DASH_WIN_COL["Daily Breach"]
Q_ = DASH_WIN_COL["Max Loss Breach"]
R_LET = DASH_WIN_COL["Status Prop"]
S_LET = DASH_WIN_COL["Status Edge"]
T_LET = DASH_WIN_COL["Score_Toate"]
U_LET = DASH_WIN_COL["Score_Prima"]
FILTERS = [("Toate", False), ("Prima", True)]
for win_idx, (label, start_time, end_time) in enumerate(TRADABLE_WINDOWS):
for strat in STRAT_KEYS:
for filter_label, use_prima in FILTERS:
helper_cols = _emit_window_helpers(
row, strat, combo_idx, win_idx=win_idx, use_prima=use_prima,
)
prima_range = (
_range(f"PrimaWin_{win_idx}") if use_prima else None
)
extra = f",{prima_range},1" if use_prima else ""
ws[f"{A_}{row}"] = label
ws[f"{B_}{row}"] = start_time
ws[f"{C_}{row}"] = end_time
ws[f"{D_}{row}"] = filter_label
ws[f"{E_}{row}"] = STRAT_LABELS[strat]
ws[f"{F_}{row}"] = (
f'=COUNTIFS({OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&{B_}{row},{TIME_RANGE},"<"&{C_}{row}{extra})'
)
ws[f"{G_}{row}"] = (
f'=COUNTIFS({W[strat]},1,{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&{B_}{row},{TIME_RANGE},"<"&{C_}{row}{extra})'
)
ws[f"{H_}{row}"] = f"=IFERROR({G_}{row}/{F_}{row},0)"
ws[f"{I_}{row}"] = (
f'=IFERROR(AVERAGEIFS({R[strat]},{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&{B_}{row},{TIME_RANGE},"<"&{C_}{row}{extra}),0)'
)
ws[f"{J_}{row}"] = (
f'=IFERROR(AVERAGEIFS({D[strat]},{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&{B_}{row},{TIME_RANGE},"<"&{C_}{row}{extra}),0)'
)
ws[f"{K_}{row}"] = (
f'=IFERROR(SUMIFS({D[strat]},{D[strat]},">0",{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&{B_}{row},{TIME_RANGE},"<"&{C_}{row}{extra})/'
f'ABS(SUMIFS({D[strat]},{D[strat]},"<0",{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&{B_}{row},{TIME_RANGE},"<"&{C_}{row}{extra})),0)'
)
ws[f"{L_}{row}"] = (
f'=SUMIFS({D[strat]},{OUTCOME_RANGE},"<>",'
f'{TIME_RANGE},">="&{B_}{row},{TIME_RANGE},"<"&{C_}{row}{extra})'
)
ws[f"{M_}{row}"] = (
f'=IFERROR(MAX({helper_cols["DD"]}2:{helper_cols["DD"]}{MAX_ROWS + 1}),0)'
)
ws[f"{N_}{row}"] = (
f'=IFERROR(MIN({helper_cols["DailyProp"]}2:'
f'{helper_cols["DailyProp"]}{MAX_ROWS + 1}),0)'
)
ws[f"{O_}{row}"] = (
f'=IFERROR(MAX({helper_cols["DDProp"]}2:'
f'{helper_cols["DDProp"]}{MAX_ROWS + 1}),0)'
)
ws[f"{P_}{row}"] = f'=IF({N_}{row}<-Config!$B$13,"DA","NU")'
ws[f"{Q_}{row}"] = f'=IF({O_}{row}>Config!$B$15,"DA","NU")'
ws[f"{R_LET}{row}"] = (
f'=IF(OR({P_}{row}="DA",{Q_}{row}="DA"),'
f'"CONT PIERDUT","CONFORM")'
)
ws[f"{S_LET}{row}"] = (
f'=IF({F_}{row}<1,"",'
f'IF(OR({P_}{row}="DA",{Q_}{row}="DA"),"BREACH",'
f'IF(AND({F_}{row}>=40,{H_}{row}>=55%,{I_}{row}>=0.2),'
f'"CANDIDAT","PRE-CANDIDAT")))'
)
ws[f"{T_LET}{row}"] = (
f'=IF(OR({F_}{row}<1,{D_}{row}<>"Toate"),-1E+12,'
f'{I_}{row}*100000+{K_}{row}*1000+{L_}{row}-{M_}{row}-{O_}{row}/10)'
)
ws[f"{U_LET}{row}"] = (
f'=IF(OR({F_}{row}<1,{D_}{row}<>"Prima"),-1E+12,'
f'{I_}{row}*100000+{K_}{row}*1000+{L_}{row}-{M_}{row}-{O_}{row}/10)'
)
combo_rows.append(row)
combo_idx += 1
row += 1
# Indici 1-based ai coloanelor centrate
center_idx = {
DASH_WIN_HEADERS.index(name) + 1
for name in ("Fereastra", "Filtru", "Strategie",
"Daily Breach", "Max Loss Breach",
"Status Prop", "Status Edge")
}
# Primele 5 coloane (Fereastra, Start, End, Filtru, Strategie) nu primesc fill derivat
no_fill_idx = set(range(1, 6))
for r in combo_rows: for r in combo_rows:
for c in range(1, len(window_headers) + 1): for c in range(1, len(DASH_WIN_HEADERS) + 1):
cell = ws.cell(row=r, column=c) cell = ws.cell(row=r, column=c)
cell.border = BORDER cell.border = BORDER
cell.alignment = RIGHT if c not in (1, 4, 15, 16, 17, 18) else CENTER cell.alignment = CENTER if c in center_idx else RIGHT
if c not in (1, 2, 3, 4): if c not in no_fill_idx:
cell.fill = DERIVED_FILL cell.fill = DERIVED_FILL
for c in ("B", "C"): ws[f"{B_}{r}"].number_format = "hh:mm"
ws[f"{c}{r}"].number_format = "hh:mm" ws[f"{C_}{r}"].number_format = "hh:mm"
ws[f"E{r}"].number_format = "0" ws[f"{F_}{r}"].number_format = "0"
ws[f"F{r}"].number_format = "0" ws[f"{G_}{r}"].number_format = "0"
ws[f"G{r}"].number_format = "0.0%" ws[f"{H_}{r}"].number_format = "0.0%"
ws[f"H{r}"].number_format = "+0.000;-0.000;0.000" ws[f"{I_}{r}"].number_format = "+0.000;-0.000;0.000"
for c in ("I", "K", "L", "M", "N"): for c_letter in (J_, L_, M_, N_, O_):
ws[f"{c}{r}"].number_format = '"$"#,##0.00' ws[f"{c_letter}{r}"].number_format = '"$"#,##0.00'
ws[f"J{r}"].number_format = "0.00" ws[f"{K_}{r}"].number_format = "0.00"
ws.column_dimensions["S"].hidden = True # Score_Toate și Score_Prima ascunse
ws.column_dimensions[T_LET].hidden = True
ws.column_dimensions[U_LET].hidden = True
if combo_rows: if combo_rows:
first_combo = combo_rows[0] first_combo = combo_rows[0]
last_combo = combo_rows[-1] last_combo = combo_rows[-1]
status_rng = f"Q{first_combo}:R{last_combo}" status_rng = f"{R_LET}{first_combo}:{S_LET}{last_combo}"
ws.conditional_formatting.add( ws.conditional_formatting.add(
status_rng, CellIsRule(operator="equal", formula=['"CONFORM"'], fill=pass_fill) status_rng, CellIsRule(operator="equal", formula=['"CONFORM"'], fill=pass_fill)
) )
@@ -1006,65 +1150,108 @@ def build_dashboard(wb: Workbook) -> None:
status_rng, CellIsRule(operator="equal", formula=['"PRE-CANDIDAT"'], fill=warn_fill) status_rng, CellIsRule(operator="equal", formula=['"PRE-CANDIDAT"'], fill=warn_fill)
) )
top_title_row = row + 2 # ---- TOP CANDIDATE — două sub-secțiuni: Toate + Prima ----
ws[f"A{top_title_row}"] = "TOP CANDIDATE" # Score_Toate (col T) și Score_Prima (col U) sunt populate condițional pe Filtru;
ws[f"A{top_title_row}"].font = SUBTITLE_FONT # LARGE pe coloana corespunzătoare extrage doar rândurile relevante.
ws.merge_cells(f"A{top_title_row}:J{top_title_row}")
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 = [ top_headers = [
"#", "Fereastra", "Strategie", "N", "WR", "Expectancy R", "#", "Fereastra", "Filtru", "Strategie", "N", "WR", "Expectancy R",
"Profit Factor", "Cum P&L $", "Max DD Prop $", "Status Edge", "Profit Factor", "Cum P&L $", "Max DD Prop $", "Status Edge",
] ]
for col_idx, header in enumerate(top_headers, start=1): # Mapă coloană target din TOP → header din DASH_WIN_COL
c = ws.cell(row=top_header_row, column=col_idx, value=header) top_source_names = [
c.font = HEADER_FONT "Fereastra", "Filtru", "Strategie", "N", "WR", "Expectancy R",
c.fill = HEADER_FILL "Profit Factor", "Cum P&L $", "Max Drawdown Prop $", "Status Edge",
c.alignment = CENTER ]
c.border = BORDER top_target_letters = ["B", "C", "D", "E", "F", "G", "H", "I", "J", "K"]
for idx in range(1, 11): def _emit_top_subsection(start_row: int, title: str, note: str,
r = top_header_row + idx score_col: str, count: int = 5) -> int:
ws[f"A{r}"] = idx ws[f"A{start_row}"] = title
if combo_rows: ws[f"A{start_row}"].font = SUBTITLE_FONT
rank_formula = f"LARGE($S${first_combo}:$S${last_combo},{idx})" ws.merge_cells(f"A{start_row}:K{start_row}")
match_formula = f"MATCH({rank_formula},$S${first_combo}:$S${last_combo},0)" note_row = start_row + 1
for target, source in zip( ws[f"A{note_row}"] = note
["B", "C", "D", "E", "F", "G", "H", "I", "J"], ws[f"A{note_row}"].font = Font(
["A", "D", "E", "G", "H", "J", "K", "N", "R"], name="Calibri", size=10, italic=True, color="595959"
): )
ws[f"{target}{r}"] = ( ws[f"A{note_row}"].alignment = Alignment(
f'=IFERROR(IF({rank_formula}<=-1E+11,"",' horizontal="left", vertical="center", wrap_text=True
f'INDEX(${source}${first_combo}:${source}${last_combo},{match_formula})),"")' )
ws.merge_cells(f"A{note_row}:K{note_row}")
header_row = note_row + 1
for col_idx, header in enumerate(top_headers, start=1):
c = ws.cell(row=header_row, column=col_idx, value=header)
c.font = HEADER_FONT
c.fill = HEADER_FILL
c.alignment = CENTER
c.border = BORDER
for idx in range(1, count + 1):
r = header_row + idx
ws[f"A{r}"] = idx
if combo_rows:
rank_formula = (
f"LARGE(${score_col}${first_combo}:${score_col}${last_combo},{idx})"
) )
for c in range(1, len(top_headers) + 1): match_formula = (
cell = ws.cell(row=r, column=c) f"MATCH({rank_formula},"
cell.border = BORDER f"${score_col}${first_combo}:${score_col}${last_combo},0)"
cell.alignment = RIGHT if c not in (2, 3, 10) else CENTER )
ws[f"E{r}"].number_format = "0.0%" for target, source_name in zip(top_target_letters, top_source_names):
ws[f"F{r}"].number_format = "+0.000;-0.000;0.000" source = DASH_WIN_COL[source_name]
ws[f"G{r}"].number_format = "0.00" ws[f"{target}{r}"] = (
ws[f"H{r}"].number_format = '"$"#,##0.00' f'=IFERROR(IF({rank_formula}<=-1E+11,"",'
ws[f"I{r}"].number_format = '"$"#,##0.00' f'INDEX(${source}${first_combo}:${source}${last_combo},'
f'{match_formula})),"")'
)
for c in range(1, len(top_headers) + 1):
cell = ws.cell(row=r, column=c)
cell.border = BORDER
cell.alignment = RIGHT if c not in (2, 3, 4, 11) else CENTER
# Number formats — coloanele după shift cu +1 (Filtru e nou D):
# E=N, F=WR, G=ExpR, H=PF, I=CumPL, J=MaxDDProp, K=StatusEdge
ws[f"F{r}"].number_format = "0.0%"
ws[f"G{r}"].number_format = "+0.000;-0.000;0.000"
ws[f"H{r}"].number_format = "0.00"
ws[f"I{r}"].number_format = '"$"#,##0.00'
ws[f"J{r}"].number_format = '"$"#,##0.00'
# CF pe coloana Status Edge din TOP CANDIDATE # CF pe Status Edge (col K)
top_status_rng = f"J{top_header_row + 1}:J{top_header_row + 10}" top_status_rng = f"K{header_row + 1}:K{header_row + count}"
ws.conditional_formatting.add( ws.conditional_formatting.add(
top_status_rng, CellIsRule(operator="equal", formula=['"CANDIDAT"'], fill=pass_fill) 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),
)
return header_row + count
top_title_row = row + 2
after_top_toate = _emit_top_subsection(
top_title_row,
"TOP 5 FERESTRE — Toate trade-urile",
(
"Top 5 după scor compus, calculat pe rândurile cu Filtru=Toate. "
"CANDIDAT = îndeplinește pragurile (N≥40, WR≥55%, ExpR≥0.2, no breach). "
"PRE-CANDIDAT = N≥1 fără breach dar sub praguri. BREACH = ar fi pierdut prop."
),
score_col=T_LET,
) )
ws.conditional_formatting.add( after_top_prima = _emit_top_subsection(
top_status_rng, CellIsRule(operator="equal", formula=['"PRE-CANDIDAT"'], fill=warn_fill) after_top_toate + 2,
) "TOP 5 FERESTRE — Prima per Indicator",
ws.conditional_formatting.add( (
top_status_rng, CellIsRule(operator="equal", formula=['"BREACH"'], fill=fail_fill) "Top 5 după scor compus, calculat pe rândurile cu Filtru=Prima (doar primul "
"trade pe (Data, Indicator) în fiecare fereastră). Util pentru a vedea dacă "
"filtrul Prima identifică ferestre mai eficiente decât Toate."
),
score_col=U_LET,
) )
# Conditional formatting reutilizabil pentru celulele Cum $ # Conditional formatting reutilizabil pentru celulele Cum $
@@ -1123,7 +1310,7 @@ def build_dashboard(wb: Workbook) -> None:
return start_row + 1 + len(items) return start_row + 1 + len(items)
# Breakdowns — toate cele 5 strategii vizibile, Cum P&L $ per strategie # Breakdowns — toate cele 5 strategii vizibile, Cum P&L $ per strategie
start = top_header_row + 13 start = after_top_prima + 2
after_strat = _emit_breakdown_strats( after_strat = _emit_breakdown_strats(
start + 2, "PER STRATEGIE — Cum P&L $ per strategie", "Strategie", start + 2, "PER STRATEGIE — Cum P&L $ per strategie", "Strategie",
STRATEGIES, _range("Strategie"), STRATEGIES, _range("Strategie"),
@@ -1287,26 +1474,26 @@ def build_dashboard(wb: Workbook) -> None:
ws.row_dimensions[r].height = 60 ws.row_dimensions[r].height = 60
# ---- PROP FIRM COMPLIANCE per FEREASTRĂ × STRATEGIE ---- # ---- PROP FIRM COMPLIANCE per FEREASTRĂ × STRATEGIE ----
# Reshape compliance: rânduri = combo (fereastră × strategie), coloane = metrici compliance. # Reshape compliance: rânduri = combo (fereastră × strategie × filtru),
# Datele sunt referențiate direct din FERESTRE CANDIDATE (cols A, D, M, N, O, P, Q). # coloane = metrici compliance. Datele referențiate prin DASH_WIN_COL.
if combo_rows: if combo_rows:
win_prop_title_row = prop_header_row + 1 + len(prop_metrics) + 2 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}"] = "PROP FIRM COMPLIANCE — per FEREASTRĂ × STRATEGIE"
ws[f"A{win_prop_title_row}"].font = SUBTITLE_FONT ws[f"A{win_prop_title_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{win_prop_title_row}:G{win_prop_title_row}") ws.merge_cells(f"A{win_prop_title_row}:H{win_prop_title_row}")
win_prop_note_row = win_prop_title_row + 1 win_prop_note_row = win_prop_title_row + 1
ws[f"A{win_prop_note_row}"] = ( ws[f"A{win_prop_note_row}"] = (
"Defalcat pe fiecare combinație de fereastră tradabilă × strategie management. " "Defalcat pe fiecare combinație de fereastră tradabilă × strategie management × filtru. "
"CONFORM = ar fi supraviețuit pe contul de prop pe acel slot." "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}"].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[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}") ws.merge_cells(f"A{win_prop_note_row}:H{win_prop_note_row}")
win_prop_header_row = win_prop_note_row + 1 win_prop_header_row = win_prop_note_row + 1
win_prop_headers = [ win_prop_headers = [
"Fereastra", "Strategie", "Worst Daily Prop $", "Max DD Prop $", "Fereastra", "Filtru", "Strategie", "Worst Daily Prop $", "Max DD Prop $",
"Daily Breach", "Max Breach", "Overall Prop", "Daily Breach", "Max Breach", "Overall Prop",
] ]
for col_idx, h in enumerate(win_prop_headers, start=1): for col_idx, h in enumerate(win_prop_headers, start=1):
@@ -1316,9 +1503,13 @@ def build_dashboard(wb: Workbook) -> None:
c.alignment = CENTER c.alignment = CENTER
c.border = BORDER c.border = BORDER
# source cols din FERESTRE CANDIDATE: A=Fereastra, D=Strategie, M=Worst Daily, # source cols din FERESTRE CANDIDATE (via DASH_WIN_COL)
# N=Max DD, O=Daily Breach, P=Max Breach, Q=Overall Prop source_names = [
source_cols = ["A", "D", "M", "N", "O", "P", "Q"] "Fereastra", "Filtru", "Strategie",
"Worst Daily Loss Prop $", "Max Drawdown Prop $",
"Daily Breach", "Max Loss Breach", "Status Prop",
]
source_cols = [DASH_WIN_COL[name] for name in source_names]
for offset, combo_row in enumerate(combo_rows, start=1): for offset, combo_row in enumerate(combo_rows, start=1):
r = win_prop_header_row + offset r = win_prop_header_row + offset
for col_idx, source in enumerate(source_cols, start=1): for col_idx, source in enumerate(source_cols, start=1):
@@ -1327,21 +1518,21 @@ def build_dashboard(wb: Workbook) -> None:
cell = ws[f"{target}{r}"] cell = ws[f"{target}{r}"]
cell.border = BORDER cell.border = BORDER
cell.fill = DERIVED_FILL cell.fill = DERIVED_FILL
cell.alignment = CENTER if col_idx in (1, 2, 5, 6, 7) else RIGHT cell.alignment = CENTER if col_idx in (1, 2, 3, 6, 7, 8) else RIGHT
ws[f"C{r}"].number_format = '"$"#,##0.00'
ws[f"D{r}"].number_format = '"$"#,##0.00' ws[f"D{r}"].number_format = '"$"#,##0.00'
ws[f"E{r}"].number_format = '"$"#,##0.00'
# CF pe Overall Prop (col G) și pe Daily/Max Breach (cols E, F) # CF pe Overall Prop (col H) și pe Daily/Max Breach (cols F, G)
win_prop_first = win_prop_header_row + 1 win_prop_first = win_prop_header_row + 1
win_prop_last = win_prop_header_row + len(combo_rows) win_prop_last = win_prop_header_row + len(combo_rows)
overall_rng_win = f"G{win_prop_first}:G{win_prop_last}" overall_rng_win = f"H{win_prop_first}:H{win_prop_last}"
ws.conditional_formatting.add( ws.conditional_formatting.add(
overall_rng_win, CellIsRule(operator="equal", formula=['"CONFORM"'], fill=pass_fill) overall_rng_win, CellIsRule(operator="equal", formula=['"CONFORM"'], fill=pass_fill)
) )
ws.conditional_formatting.add( ws.conditional_formatting.add(
overall_rng_win, CellIsRule(operator="equal", formula=['"CONT PIERDUT"'], fill=fail_fill) overall_rng_win, CellIsRule(operator="equal", formula=['"CONT PIERDUT"'], fill=fail_fill)
) )
breach_rng_win = f"E{win_prop_first}:F{win_prop_last}" breach_rng_win = f"F{win_prop_first}:G{win_prop_last}"
ws.conditional_formatting.add( ws.conditional_formatting.add(
breach_rng_win, CellIsRule(operator="equal", formula=['"DA"'], fill=fail_fill) breach_rng_win, CellIsRule(operator="equal", formula=['"DA"'], fill=fail_fill)
) )
@@ -1349,11 +1540,29 @@ def build_dashboard(wb: Workbook) -> None:
breach_rng_win, CellIsRule(operator="equal", formula=['"NU"'], fill=pass_fill) breach_rng_win, CellIsRule(operator="equal", formula=['"NU"'], fill=pass_fill)
) )
# Column widths # Column widths — aliniate cu DASH_WIN_COL (A=Fereastra ... U=Score_Prima)
widths = { widths = {
"A": 18, "B": 10, "C": 18, "D": 16, "E": 13, "F": 13, "G": 16, DASH_WIN_COL["Fereastra"]: 18,
"H": 13, "I": 13, "J": 12, "K": 13, "L": 15, "M": 20, DASH_WIN_COL["Start"]: 10,
"N": 18, "O": 13, "P": 14, "Q": 15, "R": 13, "S": 8, DASH_WIN_COL["End"]: 18,
DASH_WIN_COL["Filtru"]: 10,
DASH_WIN_COL["Strategie"]: 16,
DASH_WIN_COL["N"]: 8,
DASH_WIN_COL["Wins"]: 8,
DASH_WIN_COL["WR"]: 10,
DASH_WIN_COL["Expectancy R"]: 13,
DASH_WIN_COL["Expectancy $"]: 13,
DASH_WIN_COL["Profit Factor"]: 12,
DASH_WIN_COL["Cum P&L $"]: 13,
DASH_WIN_COL["Max Drawdown $"]: 15,
DASH_WIN_COL["Worst Daily Loss Prop $"]: 20,
DASH_WIN_COL["Max Drawdown Prop $"]: 18,
DASH_WIN_COL["Daily Breach"]: 13,
DASH_WIN_COL["Max Loss Breach"]: 14,
DASH_WIN_COL["Status Prop"]: 15,
DASH_WIN_COL["Status Edge"]: 13,
DASH_WIN_COL["Score_Toate"]: 8,
DASH_WIN_COL["Score_Prima"]: 8,
} }
for col, w in widths.items(): for col, w in widths.items():
ws.column_dimensions[col].width = w ws.column_dimensions[col].width = w
@@ -1384,7 +1593,7 @@ def build_dashboard(wb: Workbook) -> None:
min_row=2, max_row=MAX_ROWS + 1, min_row=2, max_row=MAX_ROWS + 1,
) )
chart.set_categories(cats) chart.set_categories(cats)
ws.add_chart(chart, "U4") ws.add_chart(chart, "V4")
# Equity curve prop — al doilea chart, separat de modelul abstract # Equity curve prop — al doilea chart, separat de modelul abstract
chart_prop = LineChart() chart_prop = LineChart()
@@ -1404,7 +1613,7 @@ def build_dashboard(wb: Workbook) -> None:
) )
chart_prop.add_data(data_prop, titles_from_data=True) chart_prop.add_data(data_prop, titles_from_data=True)
chart_prop.set_categories(cats) chart_prop.set_categories(cats)
ws.add_chart(chart_prop, "U30") ws.add_chart(chart_prop, "V30")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

237
scripts/verify_template.py Normal file
View File

@@ -0,0 +1,237 @@
"""Smoke test for data/backtest.xlsx after regeneration.
Asserts column positions, sample row formula structure, and TOP CANDIDATE
formula sources. Run AFTER `python scripts/generate_template.py`.
Exit codes: 0 = all PASS, 1 = at least one FAIL, 2 = workbook missing.
"""
from __future__ import annotations
import sys
from pathlib import Path
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
# Romanian diacritics in test names — force UTF-8 stdout on Windows cp1252.
try:
sys.stdout.reconfigure(encoding="utf-8")
except (AttributeError, OSError):
pass
OUTPUT = Path(__file__).resolve().parent.parent / "data" / "backtest.xlsx"
FAILURES: list[str] = []
def _report(name: str, ok: bool, detail: str = "") -> None:
tag = "[PASS]" if ok else "[FAIL]"
msg = f"{tag} {name}"
if detail:
msg += f" -- {detail}"
print(msg)
if not ok:
FAILURES.append(name)
def _find_row_with(ws, col_letter: str, needle: str) -> int | None:
"""Return 1-based row where ws[col_letter+row].value == needle, else None."""
for row in range(1, ws.max_row + 1):
val = ws[f"{col_letter}{row}"].value
if val == needle:
return row
return None
def _find_col_for_header(ws, row: int, needle: str) -> str | None:
for col_idx in range(1, ws.max_column + 1):
if ws.cell(row=row, column=col_idx).value == needle:
return get_column_letter(col_idx)
return None
def check_window_grid_headers(wb) -> None:
ws = wb["Dashboard"]
header_row = _find_row_with(ws, "A", "Fereastra")
if header_row is None:
_report("Dashboard window grid header row", False, "no row with A='Fereastra'")
return
_report("Dashboard window grid header row", True, f"row {header_row}")
expected = {
"A": "Fereastra",
"D": "Filtru",
"E": "Strategie",
"F": "N",
"S": "Status Edge",
"T": "Score_Toate",
"U": "Score_Prima",
}
for col, want in expected.items():
got = ws[f"{col}{header_row}"].value
if got == want:
_report(f"Dashboard header {col}={want!r}", True)
else:
actual_col = _find_col_for_header(ws, header_row, want)
detail = (
f"got {got!r} at {col}; expected {want!r}. "
f"Header {want!r} actually at column {actual_col}"
)
_report(f"Dashboard header {col}={want!r}", False, detail)
def check_hidden_columns(wb) -> None:
ws = wb["Dashboard"]
for col in ("T", "U"):
hidden = ws.column_dimensions[col].hidden is True
_report(
f"Dashboard column {col} hidden",
hidden,
"" if hidden else f"hidden={ws.column_dimensions[col].hidden!r}",
)
def check_primawin_headers(wb) -> tuple[str | None, int]:
"""Return (column letter of PrimaWin_0, count of PrimaWin_* headers)."""
ws = wb["Trades"]
win0_col: str | None = None
count = 0
for col_idx in range(1, ws.max_column + 1):
v = ws.cell(row=1, column=col_idx).value
if isinstance(v, str) and v.startswith("PrimaWin_"):
count += 1
if v == "PrimaWin_0":
win0_col = get_column_letter(col_idx)
if count == 0:
_report("Trades has PrimaWin_* headers", False, "none found")
else:
_report(
"Trades has PrimaWin_* headers",
True,
f"{count} columns; PrimaWin_0 at column {win0_col}",
)
return win0_col, count
def check_primawin_formula(wb, win0_col: str | None) -> None:
if win0_col is None:
_report("PrimaWin_0 sample row formula", False, "no PrimaWin_0 column")
return
ws = wb["Trades"]
formula = ws[f"{win0_col}2"].value
if not isinstance(formula, str):
_report(
"PrimaWin_0 sample row formula",
False,
f"row 2 value is {formula!r}, not a formula",
)
return
must_have = ["TIME(16,30,0)", "Config!$B$17", "COUNTIFS"]
missing = [token for token in must_have if token not in formula]
if missing:
_report(
"PrimaWin_0 sample row formula",
False,
f"missing tokens: {missing}; formula starts: {formula[:120]!r}",
)
else:
_report("PrimaWin_0 sample row formula", True, "TIME(16,30,0) + Config!$B$17 present")
def check_top_candidate_sources(wb) -> None:
ws = wb["Dashboard"]
title_toate = "TOP 5 FERESTRE — Toate trade-urile"
title_prima = "TOP 5 FERESTRE — Prima per Indicator"
# Fallback: scan loosely if the em-dash doesn't match.
def _find_title(prefix: str) -> int | None:
for row in range(1, ws.max_row + 1):
v = ws.cell(row=row, column=1).value
if isinstance(v, str) and v.startswith(prefix):
return row
return None
cases = [
("TOP 5 Toate", title_toate, "Toate", "LARGE($T$"),
("TOP 5 Prima", title_prima, "Prima per Indicator", "LARGE($U$"),
]
for name, exact_title, fallback_prefix, expected_token in cases:
title_row = _find_row_with(ws, "A", exact_title)
if title_row is None:
title_row = _find_title(f"TOP 5 FERESTRE")
# Narrow: must mention the discriminator
title_row = None
for row in range(1, ws.max_row + 1):
v = ws.cell(row=row, column=1).value
if isinstance(v, str) and "TOP 5 FERESTRE" in v and fallback_prefix in v:
title_row = row
break
if title_row is None:
_report(f"{name} title found", False, f"no row containing 'TOP 5 FERESTRE' + {fallback_prefix!r}")
continue
_report(f"{name} title found", True, f"row {title_row}")
# Look at the next few rows for a LARGE() formula in any column.
found = False
for offset in (1, 2, 3):
scan_row = title_row + offset
for col_idx in range(1, ws.max_column + 1):
cell = ws.cell(row=scan_row, column=col_idx).value
if isinstance(cell, str) and expected_token in cell:
_report(
f"{name} uses {expected_token}",
True,
f"row {scan_row} col {get_column_letter(col_idx)}",
)
found = True
break
if found:
break
if not found:
# Sample a formula for diagnostics.
sample = ws.cell(row=title_row + 1, column=1).value
_report(
f"{name} uses {expected_token}",
False,
f"token not found in rows {title_row+1}..{title_row+3}; sample A: {sample!r}",
)
def check_config_escape_hatch(wb) -> None:
ws = wb["Config"]
a17 = ws["A17"].value
b17 = ws["B17"].value
_report(
"Config!A17 = 'Activează filtru Prima'",
a17 == "Activează filtru Prima",
f"got {a17!r}",
)
_report("Config!B17 = 'DA'", b17 == "DA", f"got {b17!r}")
def main() -> int:
if not OUTPUT.exists():
print(f"Workbook not found: {OUTPUT}")
print("RUN python scripts/generate_template.py FIRST")
return 2
wb = load_workbook(OUTPUT, data_only=False)
print(f"Verifying {OUTPUT}")
print("-" * 60)
check_window_grid_headers(wb)
check_hidden_columns(wb)
win0_col, _ = check_primawin_headers(wb)
check_primawin_formula(wb, win0_col)
check_top_candidate_sources(wb)
check_config_escape_hatch(wb)
print("-" * 60)
if FAILURES:
print(f"{len(FAILURES)} FAIL: {FAILURES}")
return 1
print("ALL CHECKS PASSED")
return 0
if __name__ == "__main__":
sys.exit(main())