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

@@ -728,6 +728,179 @@ async function pollStatus(jobId) {
}, 1000);
}
// =================================================
// DIFFERENT RENDER PLOTS
// =================================================
function renderEquityAndReturns(strategyId, s, data) {
console.log("Plotly object:", Plotly); // Esto debería mostrarte el objeto Plotly completo
if (Plotly && Plotly.subplots) {
console.log("make_subplots is available");
} else {
console.error("make_subplots is NOT available");
}
// Crea el subplot con dos filas y una columna (1x2)
var fig = Plotly.subplots.make_subplots({
rows: 2, // 2 filas
cols: 1, // 1 columna
shared_xaxes: true, // Compartir el eje X entre ambos gráficos
vertical_spacing: 0.1, // Espacio entre los gráficos
subplot_titles: [`Equity — ${strategyId}`, `Returns & Trades — ${strategyId}`], // Títulos para cada subgráfico
column_widths: [0.7] // Ajustar el ancho de las columnas si es necesario
});
// Datos de Equity
const equityTrace = {
y: s.window_equity, // Datos de la equity
type: "scatter", // Tipo de gráfico: línea
mode: "lines", // Modo: línea
name: "Equity"
};
// Datos de Return %
const returnsTrace = {
y: s.window_returns_pct || [], // Datos de returns
type: "bar", // Tipo de gráfico: barra
name: "Return %",
marker: { color: "#3b82f6" }, // Color de la barra
yaxis: "y1", // Asociar al primer eje Y
};
// Datos de Trades
const tradesTrace = {
y: s.window_trades || [], // Datos de trades
type: "bar", // Tipo de gráfico: barra
name: "Trades",
marker: { color: "#f59e0b" }, // Color de la barra
yaxis: "y2", // Asociar al segundo eje Y
};
// Agregar la traza de "Equity" al subplot (fila 1, columna 1)
fig.addTrace(equityTrace, 1, 1);
// Agregar las trazas de "Returns" y "Trades" al subplot (fila 2, columna 1)
fig.addTrace(returnsTrace, 2, 1);
fig.addTrace(tradesTrace, 2, 1);
// Configurar los ejes y los márgenes
fig.update_layout({
title: `Strategy Overview — ${strategyId}`,
yaxis: {
title: "Equity",
showgrid: true
},
yaxis2: {
title: "Return % / Trades",
overlaying: "y",
side: "right",
showgrid: true
},
xaxis: {
title: "Windows",
showgrid: true
},
showlegend: true
});
// Renderizar en el contenedor del gráfico
Plotly.newPlot("plot_strategy", fig);
}
function renderRollingSharpe(strategyId, s, data) {
const roll = s.diagnostics?.rolling?.rolling_sharpe_like || [];
const x = roll.map((_, i) => i + 1);
Plotly.newPlot("plot_strategy", [{
x,
y: roll,
type: "scatter",
mode: "lines+markers",
name: `Rolling Sharpe-like (k=${s.diagnostics?.rolling?.rolling_window ?? "?"})`
}], {
margin: { t: 40 },
title: `Rolling Sharpe-like — ${strategyId}`,
xaxis: { title: "Window" },
yaxis: { title: "Sharpe-like" }
});
}
function renderOOSReturnsDistribution(strategyId, s, data) {
const edges = s.diagnostics?.distribution?.hist_bin_edges || [];
const counts = s.diagnostics?.distribution?.hist_counts || [];
const centers = edges.map((_, i) => (edges[i] + edges[i + 1]) / 2);
Plotly.newPlot("plot_strategy", [{
x: centers,
y: counts,
type: "bar",
name: "OOS Return% (bins)"
}], {
margin: { t: 40 },
title: `OOS Returns Distribution — ${strategyId}`,
xaxis: { title: "Return % (window)" },
yaxis: { title: "Count" }
});
}
function renderDrawdownEvolution(strategyId, s, data) {
const dd = s.diagnostics?.drawdown?.drawdown_pct || [];
const x = dd.map((_, i) => i);
Plotly.newPlot("plot_strategy", [{
x,
y: dd,
type: "scatter",
mode: "lines",
name: "Drawdown %"
}], {
margin: { t: 40 },
title: `Drawdown Evolution — ${strategyId}`,
xaxis: { title: "Point (initial + windows)" },
yaxis: { title: "Drawdown %", zeroline: true }
});
}
function renderTradeDensity(strategyId, s, data) {
const tpw = s.diagnostics?.trades?.trades_per_window || [];
const tpd = s.diagnostics?.trades?.trades_per_day || [];
const x = tpw.map((_, i) => i + 1);
Plotly.newPlot("plot_strategy", [
{
x,
y: tpw,
type: "bar",
name: "Trades / window",
yaxis: "y1"
},
{
x,
y: tpd,
type: "scatter",
mode: "lines+markers",
name: "Trades / day",
yaxis: "y2"
}
], {
margin: { t: 40 },
title: `Trade Density — ${strategyId}`,
barmode: "group",
xaxis: { title: "Window" },
yaxis: { title: "Trades / window" },
yaxis2: {
title: "Trades / day",
overlaying: "y",
side: "right",
zeroline: false
}
});
}
// =================================================
// RENDER RESULTS
// =================================================
@@ -854,6 +1027,7 @@ function populatePlotSelector(data) {
function selectStrategy(strategyId, data) {
if (!strategyId || !data) return;
// Actualiza selectedStrategyId
selectedStrategyId = strategyId;
const row = (data.results || []).find(r => r.strategy_id === strategyId);
@@ -880,35 +1054,26 @@ function selectStrategy(strategyId, data) {
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)
// 3) Verificar si los datos de la estrategia están disponibles
const strategyData = data?.series?.strategies?.[selectedStrategyId];
if (!strategyData) {
showPlotAlert(
row?.status === "fail" ? "danger" : "warning",
`${(row?.status || "warning").toUpperCase()}${strategyId}`,
row?.message || "No chart series available for this strategy.",
row?.warnings
"warning",
`No data available${strategyId}`,
"Strategy data not available for rendering.",
[]
);
clearPlots();
highlightSelectedRow(strategyId);
return;
}
renderStrategyCharts(strategyId, s, data);
highlightSelectedRow(strategyId);
// 4) Mantén el gráfico previamente seleccionado en el dropdown
const chartType = document.getElementById("plot_strategy_select").value;
renderChart(chartType, selectedStrategyId, strategyData, data); // Renderiza el gráfico correctamente
// 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
);
}
highlightSelectedRow(strategyId);
}
function renderValidateResponse(data) {
@@ -1011,115 +1176,27 @@ function renderValidateResponse(data) {
}
}
function renderStrategyCharts(strategyId, s, data) {
if (!s) return
// ============================
// 1⃣ EQUITY
// ============================
Plotly.newPlot("plot_equity", [{
y: s.window_equity,
type: "scatter",
mode: "lines",
name: "Equity"
}], {
margin: { t: 20 },
title: `Equity — ${strategyId}`
});
// ============================
// 2⃣ RETURNS + TRADES
// ============================
const ret = s.window_returns_pct || [];
const trd = s.window_trades || [];
const retMax = Math.max(0, ...ret);
const retMin = Math.min(0, ...ret);
const minTrades = data.config?.wf?.min_trades_test ?? 10;
const trdMaxRaw = Math.max(0, ...trd);
const trdMax = Math.max(trdMaxRaw, minTrades);
// Evitar divisiones raras
const retPosSpan = Math.max(1e-9, retMax);
const retNegSpan = Math.abs(retMin);
// Alinear el 0 visualmente
const trdNegSpan = (retNegSpan / retPosSpan) * trdMax;
const y1Range = [retMin, retMax];
const y2Range = [-trdNegSpan, trdMax];
Plotly.newPlot("plot_returns", [
{
y: ret,
type: "bar",
name: "Return %",
marker: { color: "#3b82f6" },
yaxis: "y1",
offsetgroup: "returns",
alignmentgroup: "group1"
},
{
y: trd,
type: "bar",
name: "Trades",
marker: { color: "#f59e0b" },
yaxis: "y2",
offsetgroup: "trades",
alignmentgroup: "group1"
}
], {
margin: { t: 20 },
barmode: "group",
yaxis: {
title: "Return %",
range: y1Range,
zeroline: true,
zerolinewidth: 2
},
yaxis2: {
title: "Trades",
overlaying: "y",
side: "right",
range: y2Range,
zeroline: true,
zerolinewidth: 2
},
shapes: [
{
type: "line",
x0: -0.5,
x1: trd.length - 0.5,
y0: minTrades,
y1: minTrades,
yref: "y2",
line: {
color: "red",
width: 2,
dash: "dash"
}
}
],
legend: {
orientation: "h"
},
title: `Returns & Trades — ${strategyId}`
});
function renderChart(chartType, strategyId, s, data) {
switch (chartType) {
case "equity":
renderEquityAndReturns(strategyId, s, data);
break;
case "rolling_sharpe":
renderRollingSharpe(strategyId, s, data);
break;
case "hist_oos_returns":
renderOOSReturnsDistribution(strategyId, s, data);
break;
case "drawdown":
renderDrawdownEvolution(strategyId, s, data);
break;
case "trade_density":
renderTradeDensity(strategyId, s, data);
break;
default:
renderEquityAndReturns(strategyId, s, data);
break;
}
}
function highlightSelectedRow(strategyId) {
@@ -1309,6 +1386,17 @@ async function init() {
.addEventListener("change", updateStopUI);
wireButtons();
document.getElementById("plot_strategy_select").addEventListener("change", function() {
const chartType = this.value;
const strategyData = lastValidationResult.series.strategies[selectedStrategyId];
// Verifica que selectedStrategyId tenga el valor correcto
console.log("selectedStrategyId:", selectedStrategyId);
console.log("Strategy Data:", strategyData);
renderChart(chartType, selectedStrategyId, strategyData, lastValidationResult);
});
const strategies = await fetchAvailableStrategies();
renderStrategiesList(strategies);
@@ -1329,7 +1417,7 @@ function ensurePlotAlertContainer() {
let el = document.getElementById("plot_alert");
if (el) return el;
const anchor = document.getElementById("plot_equity");
const anchor = document.getElementById("plot_strategy");
if (!anchor || !anchor.parentElement) return null;
el = document.createElement("div");
@@ -1373,10 +1461,8 @@ function clearPlotAlert() {
}
function clearPlots() {
const eq = document.getElementById("plot_equity");
const ret = document.getElementById("plot_returns");
const eq = document.getElementById("plot_strategy");
if (eq) eq.innerHTML = "";
if (ret) ret.innerHTML = "";
}
document.getElementById("lock_inherited")