Sistemato salvataggio grafici e tolta visualizzazione dall'esecuzione

This commit is contained in:
fredmaloggia
2025-11-21 07:21:29 +01:00
parent f342c3aac3
commit 8716d80ecd
3 changed files with 89 additions and 63 deletions

View File

@@ -20,6 +20,7 @@ import numpy as np
import sqlalchemy as sa
from sqlalchemy import text
import matplotlib.pyplot as plt
from pathlib import Path
from shared_utils import (
build_pattern_library,
@@ -70,6 +71,10 @@ def savefig_safe(path, **kwargs):
# =========================================
# PARAMETRI GLOBALI
# =========================================
OUTPUT_DIR = Path("output")
PLOT_DIR = Path("plot")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
PLOT_DIR.mkdir(parents=True, exist_ok=True)
CONFIG = load_config()
DB_CONFIG = require_section(CONFIG, "db")
PATTERN_CONFIG = require_section(CONFIG, "pattern")
@@ -80,12 +85,18 @@ PATTERN_CONFIG = CONFIG.get("pattern", {})
TAGGING_CONFIG = CONFIG.get("tagging", {})
RANKING_CONFIG = CONFIG.get("ranking", {})
UNIVERSO_XLSX = "Universo per Trading System.xlsx"
UNIVERSO_XLSX = "Input/Universo per Trading System.xlsx"
# Export
OUTPUT_HURST_XLSX = "hurst_by_isin.xlsx"
OUTPUT_PATTERN_XLSX = "pattern_signals.xlsx"
ERROR_LOG_CSV = "errori_isin.csv"
OUTPUT_HURST_XLSX = OUTPUT_DIR / "hurst_by_isin.xlsx"
OUTPUT_PATTERN_XLSX = OUTPUT_DIR / "pattern_signals.xlsx"
ERROR_LOG_CSV = OUTPUT_DIR / "errori_isin.csv"
FORWARD_BT_SIGNALS_XLSX = OUTPUT_DIR / "forward_bt_signals.xlsx"
FORWARD_BT_SUMMARY_XLSX = OUTPUT_DIR / "forward_bt_summary.xlsx"
TRADES_REPORT_XLSX = OUTPUT_DIR / "trades_report.xlsx"
DAILY_FROM_TRADES_CSV = OUTPUT_DIR / "daily_from_trades.csv"
DAILY_FROM_TRADES_XLSX = OUTPUT_DIR / "daily_from_trades.xlsx"
FINAL_METRICS_XLSX = OUTPUT_DIR / "final_metrics.xlsx"
# Stored Procedure & parametri
STORED_PROC = str(require_value(DB_CONFIG, "stored_proc", "db"))
@@ -883,10 +894,10 @@ bt_summary_df = pd.DataFrame(bt_summary) if bt_summary else pd.DataFrame(
columns=["ISIN","Nome","Categoria","Asset Class","CAGR_%","AnnVol_%","Sharpe","MaxDD_%eq","Calmar","HitRate_%","AvgTradeRet_bps","Turnover_%/step","N_Steps"]
)
bt_signals_df.to_excel("forward_bt_signals.xlsx", index=False)
bt_summary_df.to_excel("forward_bt_summary.xlsx", index=False)
print(f"✅ Salvato: forward_bt_signals.xlsx ({len(bt_signals_df):,} righe)")
print(f"✅ Salvato: forward_bt_summary.xlsx ({len(bt_summary_df):,} righe)")
bt_signals_df.to_excel(FORWARD_BT_SIGNALS_XLSX, index=False)
bt_summary_df.to_excel(FORWARD_BT_SUMMARY_XLSX, index=False)
print(f"✅ Salvato: {FORWARD_BT_SIGNALS_XLSX} ({len(bt_signals_df):,} righe)")
print(f"✅ Salvato: {FORWARD_BT_SUMMARY_XLSX} ({len(bt_summary_df):,} righe)")
if errors:
pd.DataFrame(errors).to_csv(ERROR_LOG_CSV, index=False)
@@ -957,7 +968,7 @@ def plot_heatmap_monthly(r: pd.Series, title: str, save_path: str = None):
plt.tight_layout()
if save_path:
savefig_safe(save_path, dpi=150)
plt.show()
# Non mostrare il plot durante l'esecuzione
def inverse_vol_weights(df, window=60, max_weight=None):
vol = df.rolling(window).std()
@@ -1420,7 +1431,7 @@ def plot_portfolio_composition(weights: pd.DataFrame,
full = save_path
print(f"💾 Salvato: {full}")
plt.show()
# Plot salvato senza visualizzazione interattiva
def make_active_weights(w_base: pd.DataFrame,
sig: pd.DataFrame,
@@ -1469,12 +1480,11 @@ plt.plot(eq_eq, label="Equal Weight")
plt.plot(eq_rp, label="Risk Parity")
plt.legend(); plt.grid(); plt.title("Equity line - Selezione dinamica (Top N)")
plt.tight_layout()
savefig_safe("equity_line_portafogli.png", dpi=150)
plt.show()
savefig_safe(str(PLOT_DIR / "equity_line_portafogli.png"), dpi=150)
for name, r, path in [
("Equal Weight", ret_eq, "heatmap_equal_weight.png"),
("Risk Parity", ret_rp, "heatmap_risk_parity.png"),
("Equal Weight", ret_eq, PLOT_DIR / "heatmap_equal_weight.png"),
("Risk Parity", ret_rp, PLOT_DIR / "heatmap_risk_parity.png"),
]:
m = portfolio_metrics(r)
@@ -1491,7 +1501,7 @@ import matplotlib.pyplot as plt
def plot_portfolio_composition_fixed(weights: pd.DataFrame,
title: str,
save_path: str | None = None,
max_legend: int = 12):
max_legend: int = 20):
"""
Stacked area dei pesi nel tempo.
'weights' deve essere già quello ATTIVO (già mascherato con i Signal)
@@ -1527,10 +1537,17 @@ def plot_portfolio_composition_fixed(weights: pd.DataFrame,
# Raggruppa coda lunga in "Altri"
if len(ordered) > max_legend:
head, tail = ordered[:max_legend], ordered[max_legend:]
head = ordered[:max_legend]
# Garantisce che 'Cash' resti in legenda anche oltre il cap
if "Cash" not in head and "Cash" in ordered:
head = head[:-1] + ["Cash"]
tail = [c for c in ordered if c not in head]
W_show = W[head].copy()
W_show["Altri"] = W[tail].sum(1)
ordered = head + ["Altri"]
if tail:
W_show["Altri"] = W[tail].sum(1)
ordered = head + ["Altri"]
else:
ordered = head
else:
W_show = W[ordered].copy()
@@ -1563,7 +1580,7 @@ def plot_portfolio_composition_fixed(weights: pd.DataFrame,
fig.savefig(save_path, dpi=150, bbox_inches="tight")
print(f"💾 Salvato: {os.path.abspath(save_path)}")
plt.show()
# Plot salvato senza visualizzazione interattiva
# --- 1) Pesi teorici dei tre portafogli (già costruiti sopra) ---
# w_eq : equal weight su 'cols'
@@ -1607,9 +1624,8 @@ w_rp_act = make_active_weights(w_rp, wide_sig, renorm_to_1=False, add_cash=Tru
w_agg_act = make_active_weights(w_agg, wide_sig, renorm_to_1=False, add_cash=True, cash_label="Cash")
# --- 3) Plot + salvataggio ---
os.makedirs("plots", exist_ok=True)
plot_portfolio_composition_fixed(w_eq_act, "Equal Weight (attivi + Cash)", "plots/composition_equal_weight_active.png")
plot_portfolio_composition_fixed(w_rp_act, "Risk Parity (attivi + Cash)", "plots/composition_risk_parity_active.png")
plot_portfolio_composition_fixed(w_eq_act, "Equal Weight (attivi + Cash)", str(PLOT_DIR / "composition_equal_weight_active.png"))
plot_portfolio_composition_fixed(w_rp_act, "Risk Parity (attivi + Cash)", str(PLOT_DIR / "composition_risk_parity_active.png"))
# -----------------------------
@@ -1721,11 +1737,11 @@ rep_rp = make_trades_report(wide_sig[[c for c in cols if c in wide_sig.columns]
wide_pnl[[c for c in cols if c in wide_pnl.columns]],
w_rp, "Risk Parity")
with pd.ExcelWriter("trades_report.xlsx") as xw:
with pd.ExcelWriter(TRADES_REPORT_XLSX) as xw:
rep_eq.to_excel(xw, "Equal_Weight", index=False)
rep_rp.to_excel(xw, "Risk_Parity", index=False)
print("✅ Report trades salvato in trades_report.xlsx")
print(f"✅ Report trades salvato in {TRADES_REPORT_XLSX}")
# ============================================================
# 5.6 Rebuild DAILY PnL from trades_report (calendarized)
# → per rendere coerente il compounding dei trade con equity/heatmap
@@ -1808,17 +1824,15 @@ daily_from_trades = rebuild_daily_from_trades_dict(trades_dict)
# Salva su disco (CSV + XLSX) per ispezione
if not daily_from_trades.empty:
daily_from_trades.to_csv("daily_from_trades.csv", index_label="Date")
daily_from_trades.to_csv(DAILY_FROM_TRADES_CSV, index_label="Date")
try:
with pd.ExcelWriter("daily_from_trades.xlsx") as xw:
with pd.ExcelWriter(DAILY_FROM_TRADES_XLSX) as xw:
daily_from_trades.to_excel(xw, "Daily", index=True)
except Exception as e:
print(f"[WARN] Impossibile scrivere daily_from_trades.xlsx: {e}")
print(f"[WARN] Impossibile scrivere {DAILY_FROM_TRADES_XLSX}: {e}")
# Plot equity & heatmap basati sui DAILY da trade (coerenti col compounding)
import matplotlib.pyplot as plt
from pathlib import Path
Path("plots_by_topN").mkdir(exist_ok=True)
fig, ax = plt.subplots(figsize=(10,6))
for col, lab in [("Equal_Weight","Equal Weight"),
@@ -1829,9 +1843,9 @@ if not daily_from_trades.empty:
ax.legend(); ax.grid(True)
ax.set_title("Equity line ricostruita dai trades (calendarizzata)")
fig.tight_layout()
fig.savefig("plots_by_topN/equity_from_trades.png", dpi=150)
fig.savefig(PLOT_DIR / "equity_from_trades.png", dpi=150)
plt.close(fig)
print("💾 Salvato: plots_by_topN/equity_from_trades.png")
print(f"💾 Salvato: {PLOT_DIR / 'equity_from_trades.png'}")
# Heatmap per ciascuna strategia
for col, lab, fname in [
@@ -1841,7 +1855,7 @@ if not daily_from_trades.empty:
if col in daily_from_trades.columns:
try:
plot_heatmap_monthly(daily_from_trades[col], f"Heatmap mensile {lab} (da trades)",
save_path=f"plots_by_topN/{fname}")
save_path=PLOT_DIR / fname)
except Exception as e:
print(f"[WARN] Heatmap {lab} da trades: {e}")
else:
@@ -2093,13 +2107,13 @@ final_byN_df = pd.DataFrame(rows_byN)[[
# Salvataggio: aggiunge/riscrive i fogli in final_metrics.xlsx
# - mantiene (se vuoi) anche il foglio "Portfolio_Metrics" del caso corrente TOP_N
try:
with pd.ExcelWriter("final_metrics.xlsx", engine="openpyxl", mode="a", if_sheet_exists="replace") as xw:
with pd.ExcelWriter(FINAL_METRICS_XLSX, engine="openpyxl", mode="a", if_sheet_exists="replace") as xw:
final_byN_df.to_excel(xw, "Portfolio_Metrics_By_N", index=False)
except Exception:
with pd.ExcelWriter("final_metrics.xlsx") as xw:
with pd.ExcelWriter(FINAL_METRICS_XLSX) as xw:
final_byN_df.to_excel(xw, "Portfolio_Metrics_By_N", index=False)
print("✅ Salvato: final_metrics.xlsx (Portfolio_Metrics_By_N) per TopN = 8..15")
print(f"✅ Salvato: {FINAL_METRICS_XLSX} (Portfolio_Metrics_By_N) per TopN = 8..15")
# ======================================================================
# 6bis) Plot per ciascun TopN (8..15): Equity + Heatmap per strategia
@@ -2108,8 +2122,8 @@ import os
import numpy as np
import matplotlib.pyplot as plt
OUT_DIR = "plots_by_topN"
os.makedirs(OUT_DIR, exist_ok=True)
OUT_DIR = PLOT_DIR
OUT_DIR.mkdir(parents=True, exist_ok=True)
def _safe_series(r: pd.Series) -> pd.Series:
"""Forza tipo numerico e se tutto NaN, rimpiazza con 0.0 (linea piatta ma plot salvato)."""
@@ -2133,9 +2147,9 @@ def _save_equity_plot_byN(ret_eq, ret_rp, top_n: int):
eq_rp.plot(ax=ax, label="Risk Parity")
ax.legend()
ax.grid(True)
ax.set_title(f"Equity line TopN={top_n}")
ax.set_title(f"Equity line - TopN={top_n}")
fig.tight_layout()
savefig_safe(os.path.join(OUT_DIR, f"equity_topN_{top_n}.png"), dpi=150)
savefig_safe(str(OUT_DIR / f"equity_topN_{top_n}.png"), dpi=150)
plt.close(fig)
def _save_heatmaps_byN(ret_eq, ret_rp, top_n: int):
@@ -2144,13 +2158,13 @@ def _save_heatmaps_byN(ret_eq, ret_rp, top_n: int):
plot_heatmap_monthly(
ret_eq,
f"Heatmap mensile Equal Weight (TopN={top_n})",
save_path=os.path.join(OUT_DIR, f"heatmap_equal_topN_{top_n}.png")
f"Heatmap mensile - Equal Weight (TopN={top_n})",
save_path=OUT_DIR / f"heatmap_equal_topN_{top_n}.png"
)
plot_heatmap_monthly(
ret_rp,
f"Heatmap mensile Risk Parity (TopN={top_n})",
save_path=os.path.join(OUT_DIR, f"heatmap_rp_topN_{top_n}.png")
f"Heatmap mensile - Risk Parity (TopN={top_n})",
save_path=OUT_DIR / f"heatmap_rp_topN_{top_n}.png"
)
# Loop 8..15 replicando i plot per ciascuna combinazione
@@ -2170,8 +2184,8 @@ import os
import numpy as np
import matplotlib.pyplot as plt
OUT_DIR = "plots_by_topN"
os.makedirs(OUT_DIR, exist_ok=True)
OUT_DIR = PLOT_DIR
OUT_DIR.mkdir(parents=True, exist_ok=True)
# -- safety: helper per pesi attivi e plotting, se mancassero già nel file --
@@ -2203,7 +2217,7 @@ if 'plot_portfolio_composition_fixed' not in globals():
def plot_portfolio_composition_fixed(weights: pd.DataFrame,
title: str,
save_path: str | None = None,
max_legend: int = 12):
max_legend: int = 20):
if weights is None or getattr(weights, "empty", True):
print(f"[SKIP] Nessun peso per: {title}")
return
@@ -2221,10 +2235,16 @@ if 'plot_portfolio_composition_fixed' not in globals():
if "Cash" in ordered:
ordered = [c for c in ordered if c!="Cash"] + ["Cash"]
if len(ordered) > max_legend:
head, tail = ordered[:max_legend], ordered[max_legend:]
head = ordered[:max_legend]
if "Cash" not in head and "Cash" in ordered:
head = head[:-1] + ["Cash"]
tail = [c for c in ordered if c not in head]
W_show = W[head].copy()
W_show["Altri"] = W[tail].sum(1)
ordered = head + ["Altri"]
if tail:
W_show["Altri"] = W[tail].sum(1)
ordered = head + ["Altri"]
else:
ordered = head
else:
W_show = W[ordered].copy()
cmap = plt.colormaps.get_cmap("tab20")
@@ -2245,7 +2265,7 @@ if 'plot_portfolio_composition_fixed' not in globals():
os.makedirs(folder, exist_ok=True)
fig.savefig(save_path, dpi=150, bbox_inches="tight")
print(f"💾 Salvato: {os.path.abspath(save_path)}")
plt.show()
# Nessuna visualizzazione interattiva
def _build_weights_for_isins(base_isins_N, crypto_isin_N, wide_pnl):
"""Costruisce i pesi TEORICI per Equal / Risk Parity / Aggressiva su un dato insieme di ISIN."""
@@ -2295,8 +2315,8 @@ for top_n in range(8, 16):
w_rp_act_N = make_active_weights(w_rp_N, wide_sig, renorm_to_1=False, add_cash=True, cash_label="Cash")
# path di salvataggio
sp_eq = os.path.join(OUT_DIR, f"composition_equal_topN_{top_n}.png")
sp_rp = os.path.join(OUT_DIR, f"composition_rp_topN_{top_n}.png")
sp_eq = OUT_DIR / f"composition_equal_topN_{top_n}.png"
sp_rp = OUT_DIR / f"composition_rp_topN_{top_n}.png"
# plot + salvataggio (SOLO Equal e Risk Parity)
plot_portfolio_composition_fixed(w_eq_act_N, f"Equal Weight (attivi + Cash) TopN={top_n}", sp_eq)

View File

@@ -31,14 +31,16 @@ from shared_utils import (
# PATH & OUTPUT
# =============================================================================
BASE_DIR = Path(__file__).resolve().parent
AUDIT_LOG_CSV = BASE_DIR / "trades_audit_log.csv"
OUTPUT_DIR = BASE_DIR / "output"
PLOT_DIR = BASE_DIR / "plot"
AUDIT_LOG_CSV = OUTPUT_DIR / "trades_audit_log.csv"
CONNECTION_TXT = BASE_DIR / "connection.txt"
OUT_DAILY_CSV = BASE_DIR / "daily_returns_by_strategy.csv"
OUT_EQUITY_CSV = BASE_DIR / "equity_by_strategy.csv"
OUT_DEBUG_CSV = BASE_DIR / "debug_daily_by_strategy.csv"
PLOT_EQUITY = BASE_DIR / "equity_by_strategy.png"
PLOT_DD = BASE_DIR / "drawdown_by_strategy.png"
OUT_DAILY_CSV = OUTPUT_DIR / "daily_returns_by_strategy.csv"
OUT_EQUITY_CSV = OUTPUT_DIR / "equity_by_strategy.csv"
OUT_DEBUG_CSV = OUTPUT_DIR / "debug_daily_by_strategy.csv"
PLOT_EQUITY = PLOT_DIR / "equity_by_strategy.png"
PLOT_DD = PLOT_DIR / "drawdown_by_strategy.png"
# Stored procedure
SP_NAME_DEFAULT = "opt_RendimentoGiornaliero1_ALL"
@@ -243,6 +245,8 @@ def rebuild_daily_from_log(audit: pd.DataFrame, returns_wide: pd.DataFrame) -> p
# MAIN
# =============================================================================
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
PLOT_DIR.mkdir(parents=True, exist_ok=True)
if not AUDIT_LOG_CSV.exists():
raise FileNotFoundError("Missing trades_audit_log.csv")
@@ -295,7 +299,6 @@ def main():
plt.legend()
plt.tight_layout()
plt.savefig(str(PLOT_EQUITY), dpi=150)
plt.show()
plt.close()
# Drawdown
@@ -308,7 +311,6 @@ def main():
plt.legend()
plt.tight_layout()
plt.savefig(str(PLOT_DD), dpi=150)
plt.show()
plt.close()
print("Salvati:")

View File

@@ -69,15 +69,17 @@ RANKING_CONFIG = CONFIG.get("ranking", {})
SIGNALS_CONFIG = CONFIG.get("signals", {})
BASE_DIR = Path(".")
UNIVERSO_XLSX = BASE_DIR / "Universo per Trading System.xlsx"
OUTPUT_DIR = BASE_DIR / "output"
# Universe now expected inside Input folder
UNIVERSO_XLSX = BASE_DIR / "Input" / "Universo per Trading System.xlsx"
CONNECTION_TXT = BASE_DIR / "connection.txt"
AUDIT_LOG_CSV = BASE_DIR / "trades_audit_log.csv"
AUDIT_LOG_CSV = OUTPUT_DIR / "trades_audit_log.csv"
OPEN_TRADES_DIR = BASE_DIR / "open_trades"
DROPBOX_EXPORT_DIR = Path(r"C:\Users\Admin\Dropbox\Condivisa Lavoro\Segnali di trading su ETF")
def _dated_signals_filename() -> Path:
date_prefix = pd.Timestamp.today().strftime("%Y%m%d")
return BASE_DIR / f"{date_prefix}_signals.xlsx"
return OUTPUT_DIR / f"{date_prefix}_signals.xlsx"
# Stored procedure / parametri DB
SP_NAME_DEFAULT = str(require_value(DB_CONFIG, "stored_proc", "db"))
@@ -448,6 +450,7 @@ def save_open_trades(strategy: str, df: pd.DataFrame):
def append_audit_rows(rows: List[Dict]):
if not rows:
return
ensure_dir(AUDIT_LOG_CSV.parent)
log = pd.DataFrame(rows)
if AUDIT_LOG_CSV.exists():
old = pd.read_csv(AUDIT_LOG_CSV)
@@ -701,6 +704,7 @@ def update_positions_and_build_orders(universe: pd.DataFrame,
# =========================
def main_run(run_date: Optional[dt.date] = None):
today = run_date or dt.date.today()
ensure_dir(OUTPUT_DIR)
# 1) Universo
universe = load_universe(UNIVERSO_XLSX)