backtest.xlsx ramane doar Config + Trades (editat zilnic, rapid la salvat). Dashboard-ul devine fisier separat data/Dashboard.xlsx, generat la comanda: - scripts/generate_dashboard.py: citeste backtest.xlsx read-only/data_only, reutilizeaza build_dashboard() pe un sheet Trades static, scrie Dashboard.xlsx - scripts/strip_dashboard.py: migrare unica prin chirurgie pe zip (pastreaza dropdown-urile x14 din Trades; openpyxl le-ar fi sters) - refresh_dashboard.bat: wrapper dublu-click (regenereaza + deschide) - build_workbook() nu mai include Dashboard; graficele de echitate eliminate - data/Dashboard.xlsx ignorat (output regenerabil) Sincronizare la comanda (nu live): ruleaza refresh_dashboard.bat dupa ce salvezi backtest.xlsx in Excel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
121 lines
4.3 KiB
Python
121 lines
4.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Generează data/Dashboard.xlsx dintr-un snapshot al data/backtest.xlsx.
|
|
|
|
CITEȘTE backtest.xlsx (read-only, data_only=True) și SCRIE un fișier SEPARAT
|
|
data/Dashboard.xlsx. NU atinge backtest.xlsx. Refolosește build_config() și
|
|
build_dashboard() din generate_template.py — aceeași logică de Dashboard, dar
|
|
pe un sheet Trades static (valori, fără formule), ca să țină backtest.xlsx mic.
|
|
|
|
Reruleaza prin refresh_dashboard.bat (sau direct):
|
|
python scripts/generate_dashboard.py
|
|
|
|
IMPORTANT: deschide și SALVEAZĂ backtest.xlsx în Excel cel puțin o dată după
|
|
ultima editare înainte de refresh. Scriptul citește valorile DEJA calculate de
|
|
Excel (R_/$_/Bal_/helpere). Dacă nu ai salvat în Excel, cache-ul de valori
|
|
lipsește și Dashboard-ul iese gol. (Aceeași constrângere ca Ferestre v2.)
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
import openpyxl
|
|
from openpyxl import Workbook
|
|
|
|
from generate_template import (
|
|
build_config,
|
|
build_dashboard,
|
|
TRADES_HEADERS,
|
|
MAX_ROWS,
|
|
)
|
|
|
|
SRC = Path(__file__).resolve().parent.parent / "data" / "backtest.xlsx"
|
|
OUT = Path(__file__).resolve().parent.parent / "data" / "Dashboard.xlsx"
|
|
|
|
# Rândurile de input (galbene) din sheet-ul Config — singurele pe care le purtăm
|
|
# din workbook-ul real (Account, Lot, limite prop, calibrare $/punct). Restul
|
|
# celulelor Config sunt formule recreate de build_config().
|
|
CONFIG_INPUT_ROWS = [4, 5, 9, 10, 12, 14, 17, 19, 20, 21, 22, 25]
|
|
|
|
|
|
def read_config_inputs(ws_cfg) -> dict[int, object]:
|
|
"""Citește valorile din coloana B a sheet-ului Config (read-only)."""
|
|
vals: dict[int, object] = {}
|
|
for r, row in enumerate(
|
|
ws_cfg.iter_rows(min_row=1, max_row=40, min_col=2, max_col=2), start=1
|
|
):
|
|
# read_only poate întoarce EmptyCell (fără .value) pentru celule goale
|
|
vals[r] = getattr(row[0], "value", None)
|
|
return vals
|
|
|
|
|
|
def apply_config_inputs(wb: Workbook, cfg_inputs: dict[int, object]) -> None:
|
|
"""Suprascrie inputurile Config cu valorile reale ale lui Marius."""
|
|
ws = wb["Config"]
|
|
for r in CONFIG_INPUT_ROWS:
|
|
v = cfg_inputs.get(r)
|
|
if v is not None:
|
|
ws.cell(row=r, column=2, value=v)
|
|
|
|
|
|
def copy_trades_values(wb: Workbook, ws_src) -> None:
|
|
"""Creează un sheet Trades static (valori) în ordinea exactă TRADES_HEADERS.
|
|
|
|
Mapează după NUMELE coloanei din sursă, ca literele din COL să corespundă cu
|
|
ce așteaptă formulele/charturile din build_dashboard, indiferent de ordinea
|
|
fizică din backtest.xlsx.
|
|
"""
|
|
ws = wb.create_sheet("Trades", 1)
|
|
|
|
src_rows = ws_src.iter_rows(min_row=1, values_only=True)
|
|
src_hdr = next(src_rows)
|
|
src_idx = {name: i for i, name in enumerate(src_hdr) if name is not None}
|
|
|
|
# Header (necesar pentru titles_from_data al charturilor Bal_*/BalProp_*)
|
|
for col_idx, name in enumerate(TRADES_HEADERS, start=1):
|
|
ws.cell(row=1, column=col_idx, value=name)
|
|
|
|
# Date — rândurile 2..MAX_ROWS+1, ca rangurile Trades!$X$2:$X$501 să se alinieze
|
|
r_out = 2
|
|
for src_row in src_rows:
|
|
if r_out > MAX_ROWS + 1:
|
|
break
|
|
for col_idx, name in enumerate(TRADES_HEADERS, start=1):
|
|
si = src_idx.get(name)
|
|
if si is None or si >= len(src_row):
|
|
continue
|
|
val = src_row[si]
|
|
if val is not None:
|
|
ws.cell(row=r_out, column=col_idx, value=val)
|
|
r_out += 1
|
|
|
|
ws.sheet_state = "hidden" # snapshot intern; Dashboard e singurul vizibil util
|
|
|
|
|
|
def main() -> int:
|
|
if not SRC.exists():
|
|
print(f"EROARE: nu găsesc {SRC}")
|
|
return 1
|
|
|
|
wb_src = openpyxl.load_workbook(SRC, read_only=True, data_only=True)
|
|
if "Trades" not in wb_src.sheetnames or "Config" not in wb_src.sheetnames:
|
|
print("EROARE: backtest.xlsx nu are sheet-urile Trades + Config.")
|
|
return 1
|
|
|
|
cfg_inputs = read_config_inputs(wb_src["Config"])
|
|
|
|
wb = Workbook()
|
|
wb.remove(wb.active)
|
|
build_config(wb) # Config la index 0 (cu formule)
|
|
apply_config_inputs(wb, cfg_inputs)
|
|
copy_trades_values(wb, wb_src["Trades"]) # Trades static la index 1 (ascuns)
|
|
build_dashboard(wb) # Dashboard la index 2 — formule + charturi
|
|
wb.active = wb.sheetnames.index("Dashboard")
|
|
|
|
wb_src.close()
|
|
wb.save(OUT)
|
|
print(f"Scris {OUT}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|