preparando step 3 para dejarlo fino
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user