ferestre
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -14,7 +14,8 @@ Rulare:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import date, time
|
import shutil
|
||||||
|
from datetime import date, datetime, time, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
@@ -71,6 +72,37 @@ STRAT_LABELS = {
|
|||||||
# Trades sheet — schema
|
# Trades sheet — schema
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _candidate_windows() -> list[tuple[str, time, time]]:
|
||||||
|
"""Ferestre suprapuse intre 16:30 si 23:00, evaluate pe ora Romaniei."""
|
||||||
|
base = datetime(2000, 1, 1, 16, 30)
|
||||||
|
last_start = datetime(2000, 1, 1, 22, 0)
|
||||||
|
hard_ends = [
|
||||||
|
datetime(2000, 1, 1, 22, 45),
|
||||||
|
datetime(2000, 1, 1, 23, 0),
|
||||||
|
]
|
||||||
|
durations = [timedelta(minutes=m) for m in (60, 90, 120, 180)]
|
||||||
|
seen: set[tuple[time, time]] = set()
|
||||||
|
windows: list[tuple[str, time, time]] = []
|
||||||
|
|
||||||
|
start = base
|
||||||
|
while start <= last_start:
|
||||||
|
ends = [start + d for d in durations]
|
||||||
|
ends += [end for end in hard_ends if end - start >= timedelta(minutes=60)]
|
||||||
|
for end in ends:
|
||||||
|
if end > hard_ends[-1]:
|
||||||
|
continue
|
||||||
|
key = (start.time(), end.time())
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
windows.append((f"{start:%H:%M}-{end:%H:%M}", start.time(), end.time()))
|
||||||
|
start += timedelta(minutes=30)
|
||||||
|
return windows
|
||||||
|
|
||||||
|
|
||||||
|
TRADABLE_WINDOWS = _candidate_windows()
|
||||||
|
|
||||||
|
|
||||||
INPUT_HEADERS = [
|
INPUT_HEADERS = [
|
||||||
"#", "Data", "Ora RO", "Zi", "Sesiune",
|
"#", "Data", "Ora RO", "Zi", "Sesiune",
|
||||||
"Strategie", "Indicator", "TF",
|
"Strategie", "Indicator", "TF",
|
||||||
@@ -78,6 +110,8 @@ INPUT_HEADERS = [
|
|||||||
"Outcome", "Notes",
|
"Outcome", "Notes",
|
||||||
]
|
]
|
||||||
DERIVED_HEADERS = (
|
DERIVED_HEADERS = (
|
||||||
|
["SL $", "SL $ Prop"]
|
||||||
|
+
|
||||||
[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]
|
||||||
@@ -323,6 +357,16 @@ def _f_dollar(r: int, r_col: str) -> str:
|
|||||||
return f'=IF({rc}="","",{rc}*{sl}/100*Config!$B$4)'
|
return f'=IF({rc}="","",{rc}*{sl}/100*Config!$B$4)'
|
||||||
|
|
||||||
|
|
||||||
|
def _f_sl_dollar(r: int) -> str:
|
||||||
|
sl = f"{COL['SL %']}{r}"
|
||||||
|
return f'=IF({sl}="","",{sl}/100*Config!$B$4)'
|
||||||
|
|
||||||
|
|
||||||
|
def _f_sl_dollar_prop(r: int) -> str:
|
||||||
|
sl = f"{COL['SL %']}{r}"
|
||||||
|
return f'=IF({sl}="","",{sl}/100*Config!$B$11)'
|
||||||
|
|
||||||
|
|
||||||
def _f_balance(r: int, dollar_col: str) -> str:
|
def _f_balance(r: int, dollar_col: str) -> str:
|
||||||
dc = COL[dollar_col]
|
dc = COL[dollar_col]
|
||||||
return f'=IF({dc}{r}="","",Config!$B$4 + SUM(${dc}$2:{dc}{r}))'
|
return f'=IF({dc}{r}="","",Config!$B$4 + SUM(${dc}$2:{dc}{r}))'
|
||||||
@@ -396,6 +440,8 @@ def build_trades(wb: Workbook) -> None:
|
|||||||
ws.cell(row=r, column=1, value="=ROW()-1")
|
ws.cell(row=r, column=1, value="=ROW()-1")
|
||||||
ws[f'{COL["Zi"]}{r}'] = _f_day(r)
|
ws[f'{COL["Zi"]}{r}'] = _f_day(r)
|
||||||
ws[f'{COL["Sesiune"]}{r}'] = _f_session(r)
|
ws[f'{COL["Sesiune"]}{r}'] = _f_session(r)
|
||||||
|
ws[f'{COL["SL $"]}{r}'] = _f_sl_dollar(r)
|
||||||
|
ws[f'{COL["SL $ Prop"]}{r}'] = _f_sl_dollar_prop(r)
|
||||||
|
|
||||||
for strat in STRAT_KEYS:
|
for strat in STRAT_KEYS:
|
||||||
ws[f'{COL[f"R_{strat}"]}{r}'] = R_FN[strat](r)
|
ws[f'{COL[f"R_{strat}"]}{r}'] = R_FN[strat](r)
|
||||||
@@ -438,6 +484,10 @@ def build_trades(wb: Workbook) -> None:
|
|||||||
for r in range(2, MAX_ROWS + 2):
|
for r in range(2, MAX_ROWS + 2):
|
||||||
ws[f"{COL[col_name]}{r}"].number_format = '0.000"%"'
|
ws[f"{COL[col_name]}{r}"].number_format = '0.000"%"'
|
||||||
|
|
||||||
|
for col_name in ("SL $", "SL $ Prop"):
|
||||||
|
for r in range(2, MAX_ROWS + 2):
|
||||||
|
ws[f"{COL[col_name]}{r}"].number_format = '"$"#,##0.00'
|
||||||
|
|
||||||
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"
|
||||||
@@ -459,7 +509,7 @@ def build_trades(wb: Workbook) -> None:
|
|||||||
"Outcome", "Notes",
|
"Outcome", "Notes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
derived_letters = {COL["Zi"], COL["Sesiune"]}
|
derived_letters = {COL["Zi"], COL["Sesiune"], COL["SL $"], COL["SL $ Prop"]}
|
||||||
for strat in STRAT_KEYS:
|
for strat in STRAT_KEYS:
|
||||||
for prefix in ("R_", "$_", "Bal_", "$Prop_", "BalProp_"):
|
for prefix in ("R_", "$_", "Bal_", "$Prop_", "BalProp_"):
|
||||||
derived_letters.add(COL[f"{prefix}{strat}"])
|
derived_letters.add(COL[f"{prefix}{strat}"])
|
||||||
@@ -485,6 +535,8 @@ def build_trades(wb: Workbook) -> None:
|
|||||||
}
|
}
|
||||||
for col, w in widths.items():
|
for col, w in widths.items():
|
||||||
ws.column_dimensions[col].width = w
|
ws.column_dimensions[col].width = w
|
||||||
|
for col_name in ("SL $", "SL $ Prop"):
|
||||||
|
ws.column_dimensions[COL[col_name]].width = 12
|
||||||
# Derived + helper: width 11
|
# Derived + helper: width 11
|
||||||
for strat in STRAT_KEYS:
|
for strat in STRAT_KEYS:
|
||||||
for prefix in (
|
for prefix in (
|
||||||
@@ -754,6 +806,234 @@ def build_dashboard(wb: Workbook) -> None:
|
|||||||
hint_cell.alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
|
hint_cell.alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
|
||||||
hint_cell.border = BORDER
|
hint_cell.border = BORDER
|
||||||
|
|
||||||
|
# ---- FERESTRE CANDIDATE x STRATEGIE ----
|
||||||
|
# Tabel principal pentru alegerea ferestrei tradabile. Drawdown-ul este
|
||||||
|
# calculat cu helper-e ascunse pe fereastra curenta, nu din DD global.
|
||||||
|
window_title_row = 5 + len(metrics) + 2
|
||||||
|
ws[f"A{window_title_row}"] = "FERESTRE CANDIDATE x STRATEGIE"
|
||||||
|
ws[f"A{window_title_row}"].font = SUBTITLE_FONT
|
||||||
|
ws.merge_cells(f"A{window_title_row}:R{window_title_row}")
|
||||||
|
|
||||||
|
window_header_row = window_title_row + 1
|
||||||
|
window_headers = [
|
||||||
|
"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.font = HEADER_FONT
|
||||||
|
c.fill = HEADER_FILL
|
||||||
|
c.alignment = CENTER
|
||||||
|
c.border = BORDER
|
||||||
|
|
||||||
|
TIME_RANGE = _range("Ora RO")
|
||||||
|
PROP_D = {s: _range(f"$Prop_{s}") for s in STRAT_KEYS}
|
||||||
|
helper_start_col = 27 # AA, ascuns.
|
||||||
|
|
||||||
|
def _emit_window_helpers(visible_row: int, strat: str, combo_idx: int) -> dict[str, str]:
|
||||||
|
base_col = helper_start_col + combo_idx * 7
|
||||||
|
helper_names = ["Cum", "Peak", "DD", "DailyProp", "CumProp", "PeakProp", "DDProp"]
|
||||||
|
cols = {name: get_column_letter(base_col + idx) for idx, name in enumerate(helper_names)}
|
||||||
|
for idx, name in enumerate(helper_names):
|
||||||
|
col = get_column_letter(base_col + idx)
|
||||||
|
ws[f"{col}1"] = f"{name}_{visible_row}"
|
||||||
|
ws.column_dimensions[col].hidden = True
|
||||||
|
ws.column_dimensions[col].width = 3
|
||||||
|
|
||||||
|
start_cell = f"$B${visible_row}"
|
||||||
|
end_cell = f"$C${visible_row}"
|
||||||
|
dollar_col = COL[f"$_{strat}"]
|
||||||
|
prop_col = COL[f"$Prop_{strat}"]
|
||||||
|
time_col = COL["Ora RO"]
|
||||||
|
date_col = COL["Data"]
|
||||||
|
outcome_col = COL["Outcome"]
|
||||||
|
|
||||||
|
for helper_row, trade_row in enumerate(range(2, MAX_ROWS + 2), start=2):
|
||||||
|
in_window = (
|
||||||
|
f'AND(Trades!${outcome_col}{trade_row}<>"",'
|
||||||
|
f"Trades!${time_col}{trade_row}>={start_cell},"
|
||||||
|
f"Trades!${time_col}{trade_row}<{end_cell})"
|
||||||
|
)
|
||||||
|
dollar = f"Trades!${dollar_col}{trade_row}"
|
||||||
|
prop = f"Trades!${prop_col}{trade_row}"
|
||||||
|
if helper_row == 2:
|
||||||
|
ws[f"{cols['Cum']}{helper_row}"] = f"=IF({in_window},{dollar},0)"
|
||||||
|
ws[f"{cols['Peak']}{helper_row}"] = f"=MAX(0,{cols['Cum']}{helper_row})"
|
||||||
|
ws[f"{cols['CumProp']}{helper_row}"] = f"=IF({in_window},{prop},0)"
|
||||||
|
ws[f"{cols['PeakProp']}{helper_row}"] = f"=MAX(0,{cols['CumProp']}{helper_row})"
|
||||||
|
else:
|
||||||
|
prev = helper_row - 1
|
||||||
|
ws[f"{cols['Cum']}{helper_row}"] = (
|
||||||
|
f"={cols['Cum']}{prev}+IF({in_window},{dollar},0)"
|
||||||
|
)
|
||||||
|
ws[f"{cols['Peak']}{helper_row}"] = (
|
||||||
|
f"=MAX({cols['Peak']}{prev},{cols['Cum']}{helper_row})"
|
||||||
|
)
|
||||||
|
ws[f"{cols['CumProp']}{helper_row}"] = (
|
||||||
|
f"={cols['CumProp']}{prev}+IF({in_window},{prop},0)"
|
||||||
|
)
|
||||||
|
ws[f"{cols['PeakProp']}{helper_row}"] = (
|
||||||
|
f"=MAX({cols['PeakProp']}{prev},{cols['CumProp']}{helper_row})"
|
||||||
|
)
|
||||||
|
ws[f"{cols['DD']}{helper_row}"] = (
|
||||||
|
f"={cols['Peak']}{helper_row}-{cols['Cum']}{helper_row}"
|
||||||
|
)
|
||||||
|
ws[f"{cols['DDProp']}{helper_row}"] = (
|
||||||
|
f"={cols['PeakProp']}{helper_row}-{cols['CumProp']}{helper_row}"
|
||||||
|
)
|
||||||
|
ws[f"{cols['DailyProp']}{helper_row}"] = (
|
||||||
|
f'=IF({in_window},'
|
||||||
|
f'SUMIFS(Trades!${prop_col}$2:Trades!${prop_col}{trade_row},'
|
||||||
|
f'Trades!${date_col}$2:Trades!${date_col}{trade_row},Trades!${date_col}{trade_row},'
|
||||||
|
f'Trades!${time_col}$2:Trades!${time_col}{trade_row},">="&{start_cell},'
|
||||||
|
f'Trades!${time_col}$2:Trades!${time_col}{trade_row},"<"&{end_cell}),'
|
||||||
|
f'"")'
|
||||||
|
)
|
||||||
|
return cols
|
||||||
|
|
||||||
|
pass_fill = PatternFill("solid", fgColor="C6EFCE")
|
||||||
|
fail_fill = PatternFill("solid", fgColor="FFC7CE")
|
||||||
|
warn_fill = PatternFill("solid", fgColor="FFEB9C")
|
||||||
|
combo_rows: list[int] = []
|
||||||
|
combo_idx = 0
|
||||||
|
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(AND(E{row}>=40,G{row}>=55%,H{row}>=0.2,'
|
||||||
|
f'O{row}="NU",P{row}="NU"),"CANDIDAT","NU")'
|
||||||
|
)
|
||||||
|
ws[f"S{row}"] = (
|
||||||
|
f'=IF(R{row}="CANDIDAT",H{row}*100000+J{row}*1000+K{row}-L{row}-N{row}/10,-1)'
|
||||||
|
)
|
||||||
|
combo_rows.append(row)
|
||||||
|
combo_idx += 1
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
for r in combo_rows:
|
||||||
|
for c in range(1, len(window_headers) + 1):
|
||||||
|
cell = ws.cell(row=r, column=c)
|
||||||
|
cell.border = BORDER
|
||||||
|
cell.alignment = RIGHT if c not in (1, 4, 15, 16, 17, 18) else CENTER
|
||||||
|
if c not in (1, 2, 3, 4):
|
||||||
|
cell.fill = DERIVED_FILL
|
||||||
|
for c in ("B", "C"):
|
||||||
|
ws[f"{c}{r}"].number_format = "hh:mm"
|
||||||
|
ws[f"E{r}"].number_format = "0"
|
||||||
|
ws[f"F{r}"].number_format = "0"
|
||||||
|
ws[f"G{r}"].number_format = "0.0%"
|
||||||
|
ws[f"H{r}"].number_format = "+0.000;-0.000;0.000"
|
||||||
|
for c in ("I", "K", "L", "M", "N"):
|
||||||
|
ws[f"{c}{r}"].number_format = '"$"#,##0.00'
|
||||||
|
ws[f"J{r}"].number_format = "0.00"
|
||||||
|
ws.column_dimensions["S"].hidden = True
|
||||||
|
|
||||||
|
if combo_rows:
|
||||||
|
first_combo = combo_rows[0]
|
||||||
|
last_combo = combo_rows[-1]
|
||||||
|
status_rng = f"Q{first_combo}:R{last_combo}"
|
||||||
|
ws.conditional_formatting.add(
|
||||||
|
status_rng, CellIsRule(operator="equal", formula=['"CONFORM"'], fill=pass_fill)
|
||||||
|
)
|
||||||
|
ws.conditional_formatting.add(
|
||||||
|
status_rng, CellIsRule(operator="equal", formula=['"CANDIDAT"'], fill=pass_fill)
|
||||||
|
)
|
||||||
|
ws.conditional_formatting.add(
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
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_headers = [
|
||||||
|
"#", "Fereastra", "Strategie", "N", "WR", "Expectancy R",
|
||||||
|
"Profit Factor", "Cum P&L $", "Max DD Prop $", "Status Edge",
|
||||||
|
]
|
||||||
|
for col_idx, header in enumerate(top_headers, start=1):
|
||||||
|
c = ws.cell(row=top_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, 11):
|
||||||
|
r = top_header_row + idx
|
||||||
|
ws[f"A{r}"] = idx
|
||||||
|
if combo_rows:
|
||||||
|
rank_formula = f"LARGE($S${first_combo}:$S${last_combo},{idx})"
|
||||||
|
match_formula = f"MATCH({rank_formula},$S${first_combo}:$S${last_combo},0)"
|
||||||
|
for target, source in zip(
|
||||||
|
["B", "C", "D", "E", "F", "G", "H", "I", "J"],
|
||||||
|
["A", "D", "E", "G", "H", "J", "K", "N", "R"],
|
||||||
|
):
|
||||||
|
ws[f"{target}{r}"] = (
|
||||||
|
f'=IFERROR(IF({rank_formula}<0,"",'
|
||||||
|
f'INDEX(${source}${first_combo}:${source}${last_combo},{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, 10) else CENTER
|
||||||
|
ws[f"E{r}"].number_format = "0.0%"
|
||||||
|
ws[f"F{r}"].number_format = "+0.000;-0.000;0.000"
|
||||||
|
ws[f"G{r}"].number_format = "0.00"
|
||||||
|
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.)
|
# Helper pentru a emite un block breakdown (per Sesiune / Strategie / etc.)
|
||||||
def _emit_breakdown(
|
def _emit_breakdown(
|
||||||
start_row: int, title: str, first_col_label: str,
|
start_row: int, title: str, first_col_label: str,
|
||||||
@@ -791,11 +1071,8 @@ def build_dashboard(wb: Workbook) -> None:
|
|||||||
|
|
||||||
# Breakdowns — toate folosesc overlay-ul Hybrid+BE (recomandat de trader)
|
# Breakdowns — toate folosesc overlay-ul Hybrid+BE (recomandat de trader)
|
||||||
overlay = "hybrid_be"
|
overlay = "hybrid_be"
|
||||||
start = 5 + len(metrics) + 2 # 2 rânduri spațiu după tabelul de metrici
|
start = top_header_row + 13
|
||||||
after_sess = _emit_breakdown(
|
after_sess = start
|
||||||
start, "PER SESIUNE (overlay: Hybrid + BE)", "Sesiune",
|
|
||||||
SESSIONS, _range("Sesiune"), overlay,
|
|
||||||
)
|
|
||||||
after_strat = _emit_breakdown(
|
after_strat = _emit_breakdown(
|
||||||
after_sess + 2, "PER STRATEGIE (overlay: Hybrid + BE)", "Strategie",
|
after_sess + 2, "PER STRATEGIE (overlay: Hybrid + BE)", "Strategie",
|
||||||
STRATEGIES, _range("Strategie"), overlay,
|
STRATEGIES, _range("Strategie"), overlay,
|
||||||
@@ -959,7 +1236,11 @@ def build_dashboard(wb: Workbook) -> None:
|
|||||||
ws.row_dimensions[r].height = 60
|
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": 18, "B": 10, "C": 10, "D": 16, "E": 8, "F": 8, "G": 10,
|
||||||
|
"H": 13, "I": 13, "J": 12, "K": 13, "L": 15, "M": 20,
|
||||||
|
"N": 18, "O": 13, "P": 14, "Q": 15, "R": 13, "S": 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
|
||||||
|
|
||||||
@@ -989,7 +1270,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, "H4")
|
ws.add_chart(chart, "U4")
|
||||||
|
|
||||||
# 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()
|
||||||
@@ -1009,7 +1290,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, "H30")
|
ws.add_chart(chart_prop, "U30")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -1030,6 +1311,11 @@ def build_workbook() -> Workbook:
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if OUTPUT.exists():
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||||
|
backup = OUTPUT.with_name(f"{OUTPUT.stem}.backup-{timestamp}{OUTPUT.suffix}")
|
||||||
|
shutil.copy2(OUTPUT, backup)
|
||||||
|
print(f"Backup {backup}")
|
||||||
wb = build_workbook()
|
wb = build_workbook()
|
||||||
wb.save(OUTPUT)
|
wb.save(OUTPUT)
|
||||||
print(f"Wrote {OUTPUT}")
|
print(f"Wrote {OUTPUT}")
|
||||||
|
|||||||
Reference in New Issue
Block a user