Update status for using KubuntuPC
This commit is contained in:
@@ -135,8 +135,6 @@ def inspect_strategies_config(
|
|||||||
|
|
||||||
overall_status = "ok"
|
overall_status = "ok"
|
||||||
|
|
||||||
log.info(f"🔥 Strategies received: {len(payload.strategies)}")
|
|
||||||
|
|
||||||
results: List[Dict[str, Any]] = []
|
results: List[Dict[str, Any]] = []
|
||||||
series: Dict[str, Any] = {"strategies": {}} if include_series else {}
|
series: Dict[str, Any] = {"strategies": {}} if include_series else {}
|
||||||
|
|
||||||
@@ -154,6 +152,9 @@ def inspect_strategies_config(
|
|||||||
"strategy_id": sid,
|
"strategy_id": sid,
|
||||||
"status": "fail",
|
"status": "fail",
|
||||||
"message": f"Unknown strategy_id: {sid}",
|
"message": f"Unknown strategy_id: {sid}",
|
||||||
|
"warnings": [],
|
||||||
|
"series_available": False,
|
||||||
|
"series_error": "Unknown strategy_id (not in registry)",
|
||||||
"n_windows": 0,
|
"n_windows": 0,
|
||||||
"oos_final_equity": payload.account_equity,
|
"oos_final_equity": payload.account_equity,
|
||||||
"oos_total_return_pct": 0.0,
|
"oos_total_return_pct": 0.0,
|
||||||
@@ -180,6 +181,9 @@ def inspect_strategies_config(
|
|||||||
"strategy_id": sid,
|
"strategy_id": sid,
|
||||||
"status": "fail",
|
"status": "fail",
|
||||||
"message": msg,
|
"message": msg,
|
||||||
|
"warnings": [],
|
||||||
|
"series_available": False,
|
||||||
|
"series_error": "Unknown strategy_id (not in registry)",
|
||||||
"n_windows": 0,
|
"n_windows": 0,
|
||||||
"oos_final_equity": payload.account_equity,
|
"oos_final_equity": payload.account_equity,
|
||||||
"oos_total_return_pct": 0.0,
|
"oos_total_return_pct": 0.0,
|
||||||
@@ -224,6 +228,9 @@ def inspect_strategies_config(
|
|||||||
log.info(f"🧠 Step3 | WF run | strategy={sid}")
|
log.info(f"🧠 Step3 | WF run | strategy={sid}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
series_available = False
|
||||||
|
series_error = None
|
||||||
|
|
||||||
wf = WalkForwardValidator(
|
wf = WalkForwardValidator(
|
||||||
strategy_class=strategy_class,
|
strategy_class=strategy_class,
|
||||||
param_grid=None,
|
param_grid=None,
|
||||||
@@ -252,11 +259,54 @@ def inspect_strategies_config(
|
|||||||
status = "warning"
|
status = "warning"
|
||||||
msg = "No closed trades in OOS"
|
msg = "No closed trades in OOS"
|
||||||
warnings_list.append("Walk-forward produced no closed trades.")
|
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:
|
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_returns = win_df["return_pct"].tolist()
|
||||||
oos_dd = win_df["max_dd_pct"].tolist()
|
oos_dd = win_df["max_dd_pct"].tolist()
|
||||||
n_windows = len(win_df)
|
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()
|
trades = win_df["trades"].astype(int).tolist()
|
||||||
too_few = sum(t < int(payload.wf.min_trades_test) for t in trades)
|
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,
|
"strategy_id": sid,
|
||||||
"status": status,
|
"status": status,
|
||||||
"message": msg,
|
"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)),
|
"n_windows": int(len(windows_out)),
|
||||||
"oos_final_equity": oos_final,
|
"oos_final_equity": oos_final,
|
||||||
"oos_total_return_pct": float(oos_total_return),
|
"oos_total_return_pct": float(oos_total_return),
|
||||||
@@ -323,6 +375,9 @@ def inspect_strategies_config(
|
|||||||
"strategy_id": sid,
|
"strategy_id": sid,
|
||||||
"status": "fail",
|
"status": "fail",
|
||||||
"message": f"Exception: {e}",
|
"message": f"Exception: {e}",
|
||||||
|
"warnings": [],
|
||||||
|
"series_available": False,
|
||||||
|
"series_error": f"{type(e).__name__}: {e}",
|
||||||
"n_windows": 0,
|
"n_windows": 0,
|
||||||
"oos_final_equity": float(payload.account_equity),
|
"oos_final_equity": float(payload.account_equity),
|
||||||
"oos_total_return_pct": 0.0,
|
"oos_total_return_pct": 0.0,
|
||||||
|
|||||||
@@ -10,31 +10,31 @@ class RSIStrategy(Strategy):
|
|||||||
Estrategia basada en RSI (Relative Strength Index)
|
Estrategia basada en RSI (Relative Strength Index)
|
||||||
|
|
||||||
Señales:
|
Señales:
|
||||||
- BUY: Cuando RSI < oversold_threshold (mercado sobrevendido)
|
- BUY: Cuando RSI < oversold (mercado sobrevendido)
|
||||||
- SELL: Cuando RSI > overbought_threshold (mercado sobrecomprado)
|
- SELL: Cuando RSI > overbought (mercado sobrecomprado)
|
||||||
- HOLD: RSI en zona neutral
|
- HOLD: RSI en zona neutral
|
||||||
|
|
||||||
Parámetros:
|
Parámetros:
|
||||||
rsi_period: Periodo del RSI (default: 14)
|
rsi_period: Periodo del RSI (default: 14)
|
||||||
oversold_threshold: Umbral de sobreventa (default: 30)
|
oversold: Umbral de sobreventa (default: 30)
|
||||||
overbought_threshold: Umbral de sobrecompra (default: 70)
|
overbought: Umbral de sobrecompra (default: 70)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
strategy_id = "rsi"
|
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 = {
|
params = {
|
||||||
'rsi_period': rsi_period,
|
'rsi_period': rsi_period,
|
||||||
'oversold': oversold_threshold,
|
'oversold': oversold,
|
||||||
'overbought': overbought_threshold
|
'overbought': overbought
|
||||||
}
|
}
|
||||||
|
|
||||||
super().__init__(name="RSI Strategy", params=params)
|
super().__init__(name="RSI Strategy", params=params)
|
||||||
|
|
||||||
self.rsi_period = rsi_period
|
self.rsi_period = rsi_period
|
||||||
self.oversold = oversold_threshold
|
self.oversold = oversold
|
||||||
self.overbought = overbought_threshold
|
self.overbought = overbought
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parameters_schema(cls) -> dict:
|
def parameters_schema(cls) -> dict:
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ class StrategyRunResultSchema(BaseModel):
|
|||||||
status: Literal["ok", "warning", "fail"]
|
status: Literal["ok", "warning", "fail"]
|
||||||
message: str
|
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
|
n_windows: int
|
||||||
oos_final_equity: float
|
oos_final_equity: float
|
||||||
oos_total_return_pct: 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_total_return_pct).toFixed(2)}%</td>
|
||||||
<td>${Number(r.oos_max_dd_worst_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>${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>
|
</tr>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -827,7 +830,10 @@ function populatePlotSelector(data) {
|
|||||||
if (!sel) return;
|
if (!sel) return;
|
||||||
|
|
||||||
sel.innerHTML = "";
|
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) => {
|
ids.forEach((sid) => {
|
||||||
const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
opt.value = sid;
|
opt.value = sid;
|
||||||
@@ -835,43 +841,74 @@ function populatePlotSelector(data) {
|
|||||||
sel.appendChild(opt);
|
sel.appendChild(opt);
|
||||||
});
|
});
|
||||||
|
|
||||||
sel.onchange = () => renderPlotsForSelected(data);
|
sel.onchange = () => {
|
||||||
|
const sid = sel.value;
|
||||||
|
selectStrategy(sid, data);
|
||||||
|
};
|
||||||
|
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
sel.value = ids[0];
|
sel.value = selectedStrategyId || ids[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPlotsForSelected(data) {
|
function selectStrategy(strategyId, data) {
|
||||||
const sel = document.getElementById("plot_strategy_select");
|
if (!strategyId || !data) return;
|
||||||
const sid = sel ? sel.value : null;
|
|
||||||
if (!sid) return;
|
|
||||||
|
|
||||||
const s = data.series?.strategies?.[sid];
|
selectedStrategyId = strategyId;
|
||||||
if (!s) return;
|
|
||||||
|
|
||||||
const equity = s.window_equity || [];
|
const row = (data.results || []).find(r => r.strategy_id === strategyId);
|
||||||
const returns = s.window_returns_pct || [];
|
|
||||||
const xEq = [...Array(equity.length).keys()];
|
|
||||||
const xRet = [...Array(returns.length).keys()].map((i) => i + 1);
|
|
||||||
|
|
||||||
Plotly.newPlot("plot_equity", [
|
// 1) Alerts por status (siempre explícito)
|
||||||
{ x: xEq, y: equity, type: "scatter", mode: "lines", name: "Equity (OOS)" },
|
if (row?.status === "warning") {
|
||||||
], {
|
showPlotAlert("warning", `WARNING — ${strategyId}`, row.message || "Strategy warning.", row.warnings);
|
||||||
title: `WF OOS equity · ${sid}`,
|
} else if (row?.status === "fail") {
|
||||||
margin: { t: 40, l: 50, r: 20, b: 40 },
|
showPlotAlert("danger", `FAIL — ${strategyId}`, row.message || "Strategy failed.", row.warnings);
|
||||||
xaxis: { title: "Window index" },
|
} else {
|
||||||
yaxis: { title: "Equity" },
|
clearPlotAlert();
|
||||||
}, { displayModeBar: false });
|
}
|
||||||
|
|
||||||
Plotly.newPlot("plot_returns", [
|
// 2) Si el backend indica que NO hay serie, no intentamos renderizar
|
||||||
{ x: xRet, y: returns, type: "bar", name: "Return % (per window)" },
|
if (row && row.series_available === false) {
|
||||||
], {
|
showPlotAlert(
|
||||||
title: `WF returns per window · ${sid}`,
|
row.status === "fail" ? "danger" : "warning",
|
||||||
margin: { t: 40, l: 50, r: 20, b: 40 },
|
`${(row.status || "warning").toUpperCase()} — ${strategyId}`,
|
||||||
xaxis: { title: "Window" },
|
row.series_error || row.message || "No chart series available for this strategy.",
|
||||||
yaxis: { title: "Return (%)" },
|
row.warnings
|
||||||
}, { displayModeBar: false });
|
);
|
||||||
|
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) {
|
function renderValidateResponse(data) {
|
||||||
@@ -902,26 +939,13 @@ function renderValidateResponse(data) {
|
|||||||
// -------------------------------
|
// -------------------------------
|
||||||
// 3️⃣ Plots (primera estrategia por ahora)
|
// 3️⃣ Plots (primera estrategia por ahora)
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
if (data.series && data.series.strategies) {
|
if (data.results && data.results.length > 0) {
|
||||||
|
if (!selectedStrategyId) {
|
||||||
const strategies = data.series.strategies;
|
selectedStrategyId = data.results[0].strategy_id;
|
||||||
const keys = Object.keys(strategies);
|
|
||||||
|
|
||||||
if (!selectedStrategyId && keys.length > 0) {
|
|
||||||
selectedStrategyId = keys[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedStrategyId && strategies[selectedStrategyId]) {
|
|
||||||
renderStrategyCharts(
|
|
||||||
selectedStrategyId,
|
|
||||||
strategies[selectedStrategyId],
|
|
||||||
data
|
|
||||||
);
|
|
||||||
highlightSelectedRow(selectedStrategyId);
|
|
||||||
}
|
}
|
||||||
|
selectStrategy(selectedStrategyId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
// 4️⃣ Table
|
// 4️⃣ Table
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
@@ -981,18 +1005,7 @@ function renderValidateResponse(data) {
|
|||||||
console.log("Clicked:", selectedStrategyId);
|
console.log("Clicked:", selectedStrategyId);
|
||||||
|
|
||||||
selectedStrategyId = this.dataset.strategy;
|
selectedStrategyId = this.dataset.strategy;
|
||||||
|
selectStrategy(selectedStrategyId, lastValidationResult);
|
||||||
if (!lastValidationResult?.series?.strategies[selectedStrategyId]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderStrategyCharts(
|
|
||||||
selectedStrategyId,
|
|
||||||
lastValidationResult.series.strategies[selectedStrategyId],
|
|
||||||
lastValidationResult
|
|
||||||
);
|
|
||||||
|
|
||||||
highlightSelectedRow(selectedStrategyId);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1307,6 +1320,65 @@ async function init() {
|
|||||||
}, 0);
|
}, 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")
|
document.getElementById("lock_inherited")
|
||||||
.addEventListener("change", applyInheritedLock);
|
.addEventListener("change", applyInheritedLock);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user