ferestre
This commit is contained in:
@@ -14,7 +14,8 @@ Rulare:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, time
|
||||
import shutil
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from openpyxl import Workbook
|
||||
@@ -71,6 +72,37 @@ STRAT_LABELS = {
|
||||
# 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 = [
|
||||
"#", "Data", "Ora RO", "Zi", "Sesiune",
|
||||
"Strategie", "Indicator", "TF",
|
||||
@@ -78,6 +110,8 @@ INPUT_HEADERS = [
|
||||
"Outcome", "Notes",
|
||||
]
|
||||
DERIVED_HEADERS = (
|
||||
["SL $", "SL $ Prop"]
|
||||
+
|
||||
[f"R_{s}" for s in STRAT_KEYS]
|
||||
+ [f"$_{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)'
|
||||
|
||||
|
||||
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:
|
||||
dc = COL[dollar_col]
|
||||
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[f'{COL["Zi"]}{r}'] = _f_day(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:
|
||||
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):
|
||||
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 r in range(2, MAX_ROWS + 2):
|
||||
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",
|
||||
)
|
||||
}
|
||||
derived_letters = {COL["Zi"], COL["Sesiune"]}
|
||||
derived_letters = {COL["Zi"], COL["Sesiune"], COL["SL $"], COL["SL $ Prop"]}
|
||||
for strat in STRAT_KEYS:
|
||||
for prefix in ("R_", "$_", "Bal_", "$Prop_", "BalProp_"):
|
||||
derived_letters.add(COL[f"{prefix}{strat}"])
|
||||
@@ -485,6 +535,8 @@ def build_trades(wb: Workbook) -> None:
|
||||
}
|
||||
for col, w in widths.items():
|
||||
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
|
||||
for strat in STRAT_KEYS:
|
||||
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.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.)
|
||||
def _emit_breakdown(
|
||||
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)
|
||||
overlay = "hybrid_be"
|
||||
start = 5 + len(metrics) + 2 # 2 rânduri spațiu după tabelul de metrici
|
||||
after_sess = _emit_breakdown(
|
||||
start, "PER SESIUNE (overlay: Hybrid + BE)", "Sesiune",
|
||||
SESSIONS, _range("Sesiune"), overlay,
|
||||
)
|
||||
start = top_header_row + 13
|
||||
after_sess = start
|
||||
after_strat = _emit_breakdown(
|
||||
after_sess + 2, "PER STRATEGIE (overlay: Hybrid + BE)", "Strategie",
|
||||
STRATEGIES, _range("Strategie"), overlay,
|
||||
@@ -959,7 +1236,11 @@ def build_dashboard(wb: Workbook) -> None:
|
||||
ws.row_dimensions[r].height = 60
|
||||
|
||||
# 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():
|
||||
ws.column_dimensions[col].width = w
|
||||
|
||||
@@ -989,7 +1270,7 @@ def build_dashboard(wb: Workbook) -> None:
|
||||
min_row=2, max_row=MAX_ROWS + 1,
|
||||
)
|
||||
chart.set_categories(cats)
|
||||
ws.add_chart(chart, "H4")
|
||||
ws.add_chart(chart, "U4")
|
||||
|
||||
# Equity curve prop — al doilea chart, separat de modelul abstract
|
||||
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.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:
|
||||
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.save(OUTPUT)
|
||||
print(f"Wrote {OUTPUT}")
|
||||
|
||||
Reference in New Issue
Block a user