Update status for using KubuntuPC
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user