preparando step 3 para dejarlo fino

This commit is contained in:
dam
2026-03-03 11:49:08 +01:00
parent 9a59879988
commit 35efec8dd6
44 changed files with 267296 additions and 1479 deletions

View File

@@ -87,6 +87,103 @@ def _accumulate_equity(initial: float, returns_pct: List[float]) -> List[float]:
return eq
def _compute_wf_diagnostics(
*,
window_returns_pct: List[float],
window_trades: List[int],
window_equity: List[float],
test_days: int,
rolling_window: int = 3,
hist_bins: int = 10,
) -> Dict[str, Any]:
"""Compute backend-ready diagnostics for Step 3 visualization.
Notes:
- Operates at *WF window* granularity (not daily).
- All series are aligned to windows (len == n_windows) except equity/drawdown
which include initial point (len == n_windows + 1).
"""
returns = np.asarray(window_returns_pct, dtype=float)
n = int(len(returns))
mean_r = float(np.mean(returns)) if n else 0.0
std_r = float(np.std(returns, ddof=0)) if n else 0.0
pos_rate = float(np.mean(returns > 0.0)) if n else 0.0
# Linear trend of window returns over time (per window index)
if n >= 2:
x = np.arange(n, dtype=float)
slope = float(np.polyfit(x, returns, 1)[0])
else:
slope = 0.0
# Rolling Sharpe-like over windows
k = int(max(1, rolling_window))
roll = [None] * n
if n and k > 1:
scale = float(np.sqrt(k))
for i in range(k - 1, n):
seg = returns[i - k + 1 : i + 1]
m = float(np.mean(seg))
s = float(np.std(seg, ddof=0))
roll[i] = float((m / s) * scale) if s > 0 else 0.0
elif n and k == 1:
roll = [float(r) for r in returns.tolist()]
# Histogram of window returns
if n >= 2:
bins = int(max(3, min(hist_bins, n)))
counts, edges = np.histogram(returns, bins=bins)
hist_counts = [int(c) for c in counts.tolist()]
hist_edges = [float(e) for e in edges.tolist()]
elif n == 1:
hist_counts = [1]
hist_edges = [float(returns[0] - 1.0), float(returns[0] + 1.0)]
else:
hist_counts = []
hist_edges = []
# Drawdown series from equity
eq = [float(x) for x in (window_equity or [])]
dd = []
peak = None
for v in eq:
if peak is None or v > peak:
peak = v
dd.append(float((v / peak - 1.0) * 100.0) if peak and peak > 0 else 0.0)
# Trades density
td = int(test_days) if int(test_days) > 0 else 1
t_int = [int(t) for t in (window_trades or [])]
trades_per_day = [float(t) / float(td) for t in t_int]
return {
"stability": {
"n_windows": n,
"mean_return_pct": mean_r,
"std_return_pct": std_r,
"positive_window_rate": pos_rate,
"return_slope_per_window": slope,
},
"rolling": {
"rolling_window": k,
"rolling_sharpe_like": roll,
},
"distribution": {
"hist_bin_edges": hist_edges,
"hist_counts": hist_counts,
},
"drawdown": {
"equity": eq,
"drawdown_pct": dd,
},
"trades": {
"trades_per_window": t_int,
"trades_per_day": trades_per_day,
},
}
# --------------------------------------------------
# Main
# --------------------------------------------------
@@ -289,6 +386,7 @@ def inspect_strategies_config(
"oos_total_return_pct": 0.0,
"oos_max_dd_worst_pct": 0.0,
"degradation_sharpe": None,
"diagnostics": _compute_wf_diagnostics(window_returns_pct=[], window_trades=[], window_equity=[float(payload.account_equity)], test_days=int(payload.wf.test_days)),
"windows": [],
})
@@ -298,6 +396,7 @@ def inspect_strategies_config(
"window_returns_pct": [],
"window_equity": [float(payload.account_equity)],
"window_trades": [],
"diagnostics": _compute_wf_diagnostics(window_returns_pct=[], window_trades=[], window_equity=[float(payload.account_equity)], test_days=int(payload.wf.test_days)),
}
if overall_status == "ok":
@@ -348,13 +447,24 @@ def inspect_strategies_config(
"params": dict(r["params"]) if isinstance(r["params"], dict) else r["params"],
})
oos_returns = win_df["return_pct"].astype(float).tolist()
oos_returns = win_df["return_pct"].astype(float).tolist()
eq_curve = _accumulate_equity(float(payload.account_equity), oos_returns)
oos_final = float(eq_curve[-1]) if eq_curve else float(payload.account_equity)
oos_total_return = (oos_final / float(payload.account_equity) - 1.0) * 100.0
oos_max_dd = float(np.min(win_df["max_dd_pct"])) if (win_df is not None and not win_df.empty) else 0.0
diagnostics = _compute_wf_diagnostics(
window_returns_pct=oos_returns,
window_trades=win_df["trades"].astype(int).tolist(),
window_equity=eq_curve,
test_days=int(payload.wf.test_days),
rolling_window=3,
hist_bins=10,
)
# keep worst-window DD also at top-level for backwards compatibility
diagnostics["drawdown"]["worst_window_dd_pct"] = float(oos_max_dd)
results.append({
"strategy_id": sid,
"status": status,
@@ -367,6 +477,7 @@ def inspect_strategies_config(
"oos_total_return_pct": float(oos_total_return),
"oos_max_dd_worst_pct": float(oos_max_dd),
"degradation_sharpe": None,
"diagnostics": diagnostics,
"windows": windows_out,
})
@@ -375,6 +486,7 @@ def inspect_strategies_config(
"window_returns_pct": oos_returns,
"window_equity": eq_curve,
"window_trades": win_df["trades"].tolist(),
"diagnostics": diagnostics,
}
except Exception as e:
@@ -422,4 +534,4 @@ def inspect_strategies_config(
if include_series:
out["series"] = series
return out
return out