Update status for using KubuntuPC

This commit is contained in:
DaM
2026-03-02 20:04:42 +01:00
parent 2ec51d0daa
commit 8259e85b68
4 changed files with 203 additions and 71 deletions

View File

@@ -135,8 +135,6 @@ def inspect_strategies_config(
overall_status = "ok"
log.info(f"🔥 Strategies received: {len(payload.strategies)}")
results: List[Dict[str, Any]] = []
series: Dict[str, Any] = {"strategies": {}} if include_series else {}
@@ -154,6 +152,9 @@ def inspect_strategies_config(
"strategy_id": sid,
"status": "fail",
"message": f"Unknown strategy_id: {sid}",
"warnings": [],
"series_available": False,
"series_error": "Unknown strategy_id (not in registry)",
"n_windows": 0,
"oos_final_equity": payload.account_equity,
"oos_total_return_pct": 0.0,
@@ -180,6 +181,9 @@ def inspect_strategies_config(
"strategy_id": sid,
"status": "fail",
"message": msg,
"warnings": [],
"series_available": False,
"series_error": "Unknown strategy_id (not in registry)",
"n_windows": 0,
"oos_final_equity": payload.account_equity,
"oos_total_return_pct": 0.0,
@@ -224,6 +228,9 @@ def inspect_strategies_config(
log.info(f"🧠 Step3 | WF run | strategy={sid}")
try:
series_available = False
series_error = None
wf = WalkForwardValidator(
strategy_class=strategy_class,
param_grid=None,
@@ -252,11 +259,54 @@ def inspect_strategies_config(
status = "warning"
msg = "No closed trades in OOS"
warnings_list.append("Walk-forward produced no closed trades.")
# ✅ Registrar resultado SIEMPRE (no continue silencioso)
results.append({
"strategy_id": sid,
"status": status,
"message": msg,
"warnings": warnings_list,
# ✅ OPCIÓN B: serie disponible si include_series (aunque sea baseline/empty)
"series_available": bool(include_series),
"series_error": None if include_series else "WF produced no closed trades / empty windows",
"n_windows": 0,
"oos_final_equity": float(payload.account_equity),
"oos_total_return_pct": 0.0,
"oos_max_dd_worst_pct": 0.0,
"degradation_sharpe": None,
"windows": [],
})
# ✅ Serie mínima para poder renderizar algo (equity baseline)
if include_series:
series["strategies"][sid] = {
"window_returns_pct": [],
"window_equity": [float(payload.account_equity)],
"window_trades": [],
}
if overall_status == "ok":
overall_status = "warning"
continue
else:
# 🔒 Validación explícita de columnas WF (no fallbacks silenciosos)
required_cols = {"return_pct", "max_dd_pct", "trades", "window", "train_start", "train_end", "test_start", "test_end", "sharpe", "params"}
missing = required_cols - set(win_df.columns)
if missing:
raise ValueError(f"WF windows missing required columns: {sorted(missing)}")
oos_returns = win_df["return_pct"].tolist()
oos_dd = win_df["max_dd_pct"].tolist()
n_windows = len(win_df)
required_cols = {"return_pct", "max_dd_pct", "trades", "window", "train_start", "train_end", "test_start", "test_end", "sharpe", "params"}
missing = required_cols - set(win_df.columns)
if missing:
raise ValueError(f"WF windows missing required columns: {sorted(missing)}")
trades = win_df["trades"].astype(int).tolist()
too_few = sum(t < int(payload.wf.min_trades_test) for t in trades)
@@ -301,7 +351,9 @@ def inspect_strategies_config(
"strategy_id": sid,
"status": status,
"message": msg,
"warnings": warnings_list if status == "warning" else [],
"warnings": warnings_list,
"series_available": bool(include_series),
"series_error": None,
"n_windows": int(len(windows_out)),
"oos_final_equity": oos_final,
"oos_total_return_pct": float(oos_total_return),
@@ -323,6 +375,9 @@ def inspect_strategies_config(
"strategy_id": sid,
"status": "fail",
"message": f"Exception: {e}",
"warnings": [],
"series_available": False,
"series_error": f"{type(e).__name__}: {e}",
"n_windows": 0,
"oos_final_equity": float(payload.account_equity),
"oos_total_return_pct": 0.0,

View File

@@ -10,31 +10,31 @@ class RSIStrategy(Strategy):
Estrategia basada en RSI (Relative Strength Index)
Señales:
- BUY: Cuando RSI < oversold_threshold (mercado sobrevendido)
- SELL: Cuando RSI > overbought_threshold (mercado sobrecomprado)
- BUY: Cuando RSI < oversold (mercado sobrevendido)
- SELL: Cuando RSI > overbought (mercado sobrecomprado)
- HOLD: RSI en zona neutral
Parámetros:
rsi_period: Periodo del RSI (default: 14)
oversold_threshold: Umbral de sobreventa (default: 30)
overbought_threshold: Umbral de sobrecompra (default: 70)
oversold: Umbral de sobreventa (default: 30)
overbought: Umbral de sobrecompra (default: 70)
"""
strategy_id = "rsi"
def __init__(self, rsi_period: int = 14, oversold_threshold: float = 30, overbought_threshold: float = 70):
def __init__(self, rsi_period: int = 14, oversold: float = 30, overbought: float = 70):
params = {
'rsi_period': rsi_period,
'oversold': oversold_threshold,
'overbought': overbought_threshold
'oversold': oversold,
'overbought': overbought
}
super().__init__(name="RSI Strategy", params=params)
self.rsi_period = rsi_period
self.oversold = oversold_threshold
self.overbought = overbought_threshold
self.oversold = oversold
self.overbought = overbought
@classmethod
def parameters_schema(cls) -> dict:

View File

@@ -57,6 +57,11 @@ class StrategyRunResultSchema(BaseModel):
status: Literal["ok", "warning", "fail"]
message: str
# ✅ explicitar warnings y disponibilidad de serie
warnings: List[str] = Field(default_factory=list)
series_available: bool = False
series_error: Optional[str] = None
n_windows: int
oos_final_equity: float
oos_total_return_pct: float

View File

@@ -795,7 +795,10 @@ function renderResultsTable(data) {
<td>${Number(r.oos_total_return_pct).toFixed(2)}%</td>
<td>${Number(r.oos_max_dd_worst_pct).toFixed(2)}%</td>
<td>${Number(r.oos_final_equity).toFixed(2)}</td>
<td class="text-secondary">${r.message || ""}</td>
<td class="text-secondary">
${r.message || ""}
${Array.isArray(r.warnings) && r.warnings.length ? `<div class="mt-1"><small>${r.warnings.map(escapeHtml).join(" · ")}</small></div>` : ""}
</td>
</tr>
`);
});
@@ -827,7 +830,10 @@ function populatePlotSelector(data) {
if (!sel) return;
sel.innerHTML = "";
const ids = Object.keys((data.series && data.series.strategies) ? data.series.strategies : {});
// ✅ usar results, no series (para que aparezcan también warning/fail)
const ids = (data.results || []).map(r => r.strategy_id);
ids.forEach((sid) => {
const opt = document.createElement("option");
opt.value = sid;
@@ -835,43 +841,74 @@ function populatePlotSelector(data) {
sel.appendChild(opt);
});
sel.onchange = () => renderPlotsForSelected(data);
sel.onchange = () => {
const sid = sel.value;
selectStrategy(sid, data);
};
if (ids.length > 0) {
sel.value = ids[0];
sel.value = selectedStrategyId || ids[0];
}
}
function renderPlotsForSelected(data) {
const sel = document.getElementById("plot_strategy_select");
const sid = sel ? sel.value : null;
if (!sid) return;
function selectStrategy(strategyId, data) {
if (!strategyId || !data) return;
const s = data.series?.strategies?.[sid];
if (!s) return;
selectedStrategyId = strategyId;
const equity = s.window_equity || [];
const returns = s.window_returns_pct || [];
const xEq = [...Array(equity.length).keys()];
const xRet = [...Array(returns.length).keys()].map((i) => i + 1);
const row = (data.results || []).find(r => r.strategy_id === strategyId);
Plotly.newPlot("plot_equity", [
{ x: xEq, y: equity, type: "scatter", mode: "lines", name: "Equity (OOS)" },
], {
title: `WF OOS equity · ${sid}`,
margin: { t: 40, l: 50, r: 20, b: 40 },
xaxis: { title: "Window index" },
yaxis: { title: "Equity" },
}, { displayModeBar: false });
// 1) Alerts por status (siempre explícito)
if (row?.status === "warning") {
showPlotAlert("warning", `WARNING — ${strategyId}`, row.message || "Strategy warning.", row.warnings);
} else if (row?.status === "fail") {
showPlotAlert("danger", `FAIL — ${strategyId}`, row.message || "Strategy failed.", row.warnings);
} else {
clearPlotAlert();
}
Plotly.newPlot("plot_returns", [
{ x: xRet, y: returns, type: "bar", name: "Return % (per window)" },
], {
title: `WF returns per window · ${sid}`,
margin: { t: 40, l: 50, r: 20, b: 40 },
xaxis: { title: "Window" },
yaxis: { title: "Return (%)" },
}, { displayModeBar: false });
// 2) Si el backend indica que NO hay serie, no intentamos renderizar
if (row && row.series_available === false) {
showPlotAlert(
row.status === "fail" ? "danger" : "warning",
`${(row.status || "warning").toUpperCase()}${strategyId}`,
row.series_error || row.message || "No chart series available for this strategy.",
row.warnings
);
clearPlots();
highlightSelectedRow(strategyId);
return;
}
// 3) Renderizar serie si existe
const s = data?.series?.strategies?.[strategyId];
if (!s) {
// fallback explícito (por si backend antiguo no manda series_available)
showPlotAlert(
row?.status === "fail" ? "danger" : "warning",
`${(row?.status || "warning").toUpperCase()}${strategyId}`,
row?.message || "No chart series available for this strategy.",
row?.warnings
);
clearPlots();
highlightSelectedRow(strategyId);
return;
}
renderStrategyCharts(strategyId, s, data);
highlightSelectedRow(strategyId);
// 4) Caso “serie vacía” (opción B) -> warning explícito (aunque ya lo tengas)
const trd = s.window_trades || [];
const hasTrades = Array.isArray(trd) && trd.some(v => (v ?? 0) > 0);
if (!hasTrades && row?.status !== "fail") {
showPlotAlert(
"warning",
`NO TRADES — ${strategyId}`,
"Walk-forward produced no closed trades in OOS. Charts may be flat/empty.",
row?.warnings
);
}
}
function renderValidateResponse(data) {
@@ -902,25 +939,12 @@ function renderValidateResponse(data) {
// -------------------------------
// 3⃣ Plots (primera estrategia por ahora)
// -------------------------------
if (data.series && data.series.strategies) {
const strategies = data.series.strategies;
const keys = Object.keys(strategies);
if (!selectedStrategyId && keys.length > 0) {
selectedStrategyId = keys[0];
if (data.results && data.results.length > 0) {
if (!selectedStrategyId) {
selectedStrategyId = data.results[0].strategy_id;
}
if (selectedStrategyId && strategies[selectedStrategyId]) {
renderStrategyCharts(
selectedStrategyId,
strategies[selectedStrategyId],
data
);
highlightSelectedRow(selectedStrategyId);
selectStrategy(selectedStrategyId, data);
}
}
// -------------------------------
// 4⃣ Table
@@ -981,18 +1005,7 @@ function renderValidateResponse(data) {
console.log("Clicked:", selectedStrategyId);
selectedStrategyId = this.dataset.strategy;
if (!lastValidationResult?.series?.strategies[selectedStrategyId]) {
return;
}
renderStrategyCharts(
selectedStrategyId,
lastValidationResult.series.strategies[selectedStrategyId],
lastValidationResult
);
highlightSelectedRow(selectedStrategyId);
selectStrategy(selectedStrategyId, lastValidationResult);
});
});
}
@@ -1307,6 +1320,65 @@ async function init() {
}, 0);
}
// =================================================
// PLOT ALERTS (Tabler) + SAFE PLOT CLEAR
// =================================================
function ensurePlotAlertContainer() {
// Lo colocamos antes del primer plot si existe
let el = document.getElementById("plot_alert");
if (el) return el;
const anchor = document.getElementById("plot_equity");
if (!anchor || !anchor.parentElement) return null;
el = document.createElement("div");
el.id = "plot_alert";
el.className = "mb-3";
anchor.parentElement.insertBefore(el, anchor);
return el;
}
function escapeHtml(str) {
return String(str ?? "")
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function showPlotAlert(type, title, message, warnings) {
const el = ensurePlotAlertContainer();
if (!el) return;
const warnHtml = Array.isArray(warnings) && warnings.length
? `<ul class="mb-0">${warnings.map(w => `<li>${escapeHtml(w)}</li>`).join("")}</ul>`
: "";
el.innerHTML = `
<div class="alert alert-${type}" role="alert">
<div>
<h4 class="alert-title">${escapeHtml(title)}</h4>
<div class="text-secondary">${escapeHtml(message || "")}</div>
${warnHtml}
</div>
</div>
`;
}
function clearPlotAlert() {
const el = document.getElementById("plot_alert");
if (el) el.innerHTML = "";
}
function clearPlots() {
const eq = document.getElementById("plot_equity");
const ret = document.getElementById("plot_returns");
if (eq) eq.innerHTML = "";
if (ret) ret.innerHTML = "";
}
document.getElementById("lock_inherited")
.addEventListener("change", applyInheritedLock);