Ahora el Step 3 esta parcialmente acabado, y vamos a pasar a realizar el Step 3.5 donde añadimos el regimen de mercado

This commit is contained in:
dam
2026-03-06 11:19:38 +01:00
parent 35efec8dd6
commit 365304f396
4 changed files with 332 additions and 88 deletions

View File

View File

@@ -734,98 +734,342 @@ async function pollStatus(jobId) {
// =================================================
function renderEquityAndReturns(strategyId, s, data) {
const equity = s.window_equity || [];
const ret = s.window_returns_pct || [];
const trd = s.window_trades || [];
console.log("Plotly object:", Plotly); // Esto debería mostrarte el objeto Plotly completo
// X común (windows)
const n = Math.max(equity.length, ret.length, trd.length);
const x = Array.from({ length: n }, (_, i) => i);
if (Plotly && Plotly.subplots) {
console.log("make_subplots is available");
} else {
console.error("make_subplots is NOT available");
}
// ---- Escalado para alinear visualmente el 0 (returns) con el 0 (trades) ----
const retMax = Math.max(0, ...ret);
const retMin = Math.min(0, ...ret);
// 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
});
const minTrades = data.config?.wf?.min_trades_test ?? 10;
// Datos de Equity
const trdMaxRaw = Math.max(0, ...trd);
const trdMax = Math.max(trdMaxRaw, minTrades);
const retPosSpan = Math.max(1e-9, retMax);
const retNegSpan = Math.abs(retMin);
const trdNegSpan = (retNegSpan / retPosSpan) * trdMax;
const y1Range = [retMin, retMax];
const y2Range = [-trdNegSpan, trdMax];
// ---- Trazas ----
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"
x,
y: equity,
type: "scatter",
mode: "lines",
name: "Equity",
xaxis: "x",
yaxis: "y"
};
// Datos de Return %
const returnsTrace = {
y: s.window_returns_pct || [], // Datos de returns
type: "bar", // Tipo de gráfico: barra
x,
y: ret,
type: "bar",
name: "Return %",
marker: { color: "#3b82f6" }, // Color de la barra
yaxis: "y1", // Asociar al primer eje Y
marker: { color: "#3b82f6" },
xaxis: "x2",
yaxis: "y2",
offsetgroup: "returns",
alignmentgroup: "bottom"
};
// Datos de Trades
const tradesTrace = {
y: s.window_trades || [], // Datos de trades
type: "bar", // Tipo de gráfico: barra
x,
y: trd,
type: "bar",
name: "Trades",
marker: { color: "#f59e0b" }, // Color de la barra
yaxis: "y2", // Asociar al segundo eje Y
marker: { color: "#f59e0b" },
xaxis: "x2",
yaxis: "y3",
offsetgroup: "trades",
alignmentgroup: "bottom"
};
// Agregar la traza de "Equity" al subplot (fila 1, columna 1)
fig.addTrace(equityTrace, 1, 1);
// ---- Layout ----
const layout = {
grid: { rows: 2, columns: 1, pattern: "independent" },
// Agregar las trazas de "Returns" y "Trades" al subplot (fila 2, columna 1)
fig.addTrace(returnsTrace, 2, 1);
fig.addTrace(tradesTrace, 2, 1);
// IMPORTANTE: share X de verdad (pan/zoom sincronizado)
xaxis: { matches: "x2", showgrid: true },
xaxis2: { matches: "x", showgrid: true },
// Configurar los ejes y los márgenes
fig.update_layout({
title: `Strategy Overview — ${strategyId}`,
// Dominios (más altura para ver mejor)
// Ajusta estos números a gusto:
yaxis: {
domain: [0.60, 1.00],
title: "Equity",
showgrid: true
},
yaxis2: {
title: "Return % / Trades",
overlaying: "y",
side: "right",
showgrid: true
showgrid: true,
rangemode: "tozero"
},
xaxis: {
title: "Windows",
domain: [0.00, 1.00],
anchor: "y"
},
yaxis2: {
domain: [0.00, 0.25],
title: "Return %",
range: y1Range,
zeroline: true,
zerolinewidth: 2,
showgrid: true
},
showlegend: true
xaxis2: {
domain: [0.00, 1.00],
anchor: "y2",
title: "Windows"
},
yaxis3: {
title: "Trades",
overlaying: "y2",
side: "right",
range: y2Range,
zeroline: true,
zerolinewidth: 2,
showgrid: false
},
// Barras lado a lado
barmode: "group",
bargap: 0.2,
shapes: [
{
type: "line",
x0: -0.5,
x1: n - 0.5,
y0: minTrades,
y1: minTrades,
xref: "x2",
yref: "y3",
line: { color: "red", width: 2, dash: "dash" }
}
],
legend: { orientation: "h" },
margin: { t: 50, r: 70, l: 70, b: 50 }
};
const topDomain = layout.yaxis.domain; // [start,end]
const botDomain = layout.yaxis2.domain; // [start,end]
layout.annotations = [
{
text: `Equity — ${strategyId}`,
x: 0.5, xref: "paper",
y: topDomain[1] + 0.05, yref: "paper",
showarrow: false,
font: { size: 16 }
},
{
text: `Returns & Trades — ${strategyId}`,
x: 0.5, xref: "paper",
y: botDomain[1] + 0.05, yref: "paper",
showarrow: false,
font: { size: 16 }
}
];
Plotly.newPlot("plot_strategy", [equityTrace, returnsTrace, tradesTrace], layout, {
displayModeBar: true,
responsive: true
});
// Renderizar en el contenedor del gráfico
Plotly.newPlot("plot_strategy", fig);
const gd = document.getElementById("plot_strategy");
// Limpia listeners anteriores (si re-renderizas)
gd.removeAllListeners?.("plotly_relayout");
// Flag para evitar bucles cuando hacemos relayout desde el listener
gd.__syncing = false;
function clamp01(v) {
return Math.max(0, Math.min(1, v));
}
function zeroFrac(min, max) {
const span = max - min;
if (!isFinite(span) || span <= 1e-12) return 0;
return clamp01((0 - min) / span);
}
// Dado un max fijo y una fracción f (posición del 0), calcula el min
function minFromMaxAndFrac(max, f) {
const denom = Math.max(1e-9, (1 - f));
return -(f / denom) * max;
}
gd.on("plotly_relayout", (ev) => {
if (gd.__syncing) return;
const update = {};
// ===== 0) DETECTAR AUTOSCALE / RESET (prioridad máxima) =====
// Plotly puede indicar autoscale de varias maneras.
const autoscaleTriggered =
ev["yaxis2.autorange"] === true ||
ev["yaxis3.autorange"] === true ||
ev["yaxis.autorange"] === true ||
ev["xaxis.autorange"] === true ||
ev["xaxis2.autorange"] === true ||
ev["autosize"] === true ||
ev["resetScale2d"] === true ||
ev["xaxis.range[0]"] === undefined && ev["xaxis.range[1]"] === undefined && ev["yaxis.range[0]"] === undefined && ev["yaxis.range[1]"] === undefined
? false
: false;
// Si el usuario pulsa Autoscale/Reset, queremos SIEMPRE:
// - Reforzar yaxis2/yaxis3 con tus rangos calculados (0 alineado)
// - Volver a dibujar la línea minTrades
// - (Opcional) dejar X en autorange en ambos
if (
ev["yaxis2.autorange"] === true ||
ev["yaxis3.autorange"] === true ||
ev["yaxis.autorange"] === true ||
ev["xaxis.autorange"] === true ||
ev["xaxis2.autorange"] === true ||
ev["resetScale2d"] === true
) {
// update["yaxis2.autorange"] = false;
// update["yaxis3.autorange"] = false;
update["yaxis2.range"] = y1Range;
update["yaxis3.range"] = y2Range;
// Asegura que la línea de Min Trades se vea SIEMPRE
update["shapes[0].type"] = "line";
update["shapes[0].xref"] = "x2";
update["shapes[0].yref"] = "y3";
update["shapes[0].x0"] = -0.5;
update["shapes[0].x1"] = n - 0.5;
update["shapes[0].y0"] = minTrades;
update["shapes[0].y1"] = minTrades;
update["shapes[0].line.color"] = "red";
update["shapes[0].line.width"] = 2;
update["shapes[0].line.dash"] = "dash";
// Mantén X sincronizado también tras autoscale
// if (ev["xaxis.autorange"] === true || ev["xaxis2.autorange"] === true || ev["resetScale2d"] === true) {
// update["xaxis.autorange"] = true;
// update["xaxis2.autorange"] = true;
// }
gd.__syncing = true;
Plotly.relayout(gd, update).finally(() => {
gd.__syncing = false;
});
return; // IMPORTANTÍSIMO: no seguimos con el resto de sincronizaciones
}
// ===== 1) Sync X arriba/abajo (pan/zoom normal) =====
if (ev["xaxis2.range[0]"] !== undefined && ev["xaxis2.range[1]"] !== undefined) {
update["xaxis.range"] = [ev["xaxis2.range[0]"], ev["xaxis2.range[1]"]];
update["xaxis.autorange"] = false;
}
if (ev["xaxis.range[0]"] !== undefined && ev["xaxis.range[1]"] !== undefined) {
update["xaxis2.range"] = [ev["xaxis.range[0]"], ev["xaxis.range[1]"]];
update["xaxis2.autorange"] = false;
}
// ===== 2) Mantener 0 alineado cuando pan/zoom en Y2 o Y3 =====
const y2Changed = ev["yaxis2.range[0]"] !== undefined && ev["yaxis2.range[1]"] !== undefined;
const y3Changed = ev["yaxis3.range[0]"] !== undefined && ev["yaxis3.range[1]"] !== undefined;
const full = gd._fullLayout || {};
const curY2 = full.yaxis2?.range || y1Range;
const curY3 = full.yaxis3?.range || y2Range;
if (y2Changed && !y3Changed) {
const newY2 = [ev["yaxis2.range[0]"], ev["yaxis2.range[1]"]];
const f = zeroFrac(newY2[0], newY2[1]);
const y3MaxKeep = (curY3 && curY3.length === 2) ? curY3[1] : y2Range[1];
const y3Min = minFromMaxAndFrac(y3MaxKeep, f);
update["yaxis2.autorange"] = false;
update["yaxis3.autorange"] = false;
update["yaxis2.range"] = newY2;
update["yaxis3.range"] = [y3Min, y3MaxKeep];
}
if (y3Changed && !y2Changed) {
const newY3 = [ev["yaxis3.range[0]"], ev["yaxis3.range[1]"]];
const f = zeroFrac(newY3[0], newY3[1]);
const y2MaxKeep = (curY2 && curY2.length === 2) ? curY2[1] : y1Range[1];
const y2Min = minFromMaxAndFrac(y2MaxKeep, f);
update["yaxis2.autorange"] = false;
update["yaxis3.autorange"] = false;
update["yaxis3.range"] = newY3;
update["yaxis2.range"] = [y2Min, y2MaxKeep];
}
// Si ambos cambian (zoom box), prioriza el zeroFrac de y2
if (y2Changed && y3Changed) {
const newY2 = [ev["yaxis2.range[0]"], ev["yaxis2.range[1]"]];
const f = zeroFrac(newY2[0], newY2[1]);
const newY3 = [ev["yaxis3.range[0]"], ev["yaxis3.range[1]"]];
const y3MaxKeep = newY3[1];
const y3Min = minFromMaxAndFrac(y3MaxKeep, f);
update["yaxis2.autorange"] = false;
update["yaxis3.autorange"] = false;
update["yaxis2.range"] = newY2;
update["yaxis3.range"] = [y3Min, y3MaxKeep];
}
// ===== 3) Re-asegurar la línea minTrades (por si Plotly la toca en relayouts) =====
// (No molesta y evita que desaparezca en algunos reset)
update["shapes[0].y0"] = minTrades;
update["shapes[0].y1"] = minTrades;
if (Object.keys(update).length === 0) return;
gd.__syncing = true;
Plotly.relayout(gd, update).finally(() => {
gd.__syncing = false;
});
});
}
function renderRollingSharpe(strategyId, s, data) {
const roll = s.diagnostics?.rolling?.rolling_sharpe_like || [];
const x = roll.map((_, i) => i + 1);
Plotly.newPlot("plot_strategy", [{
const k = s.diagnostics?.rolling?.rolling_window ?? "?";
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" }
});
name: `Rolling Sharpe-like (k=${k})`
}
],
{
margin: { t: 60, r: 40, l: 60, b: 50 },
title: { text: `Rolling Sharpe-like — ${strategyId}`, x: 0.5 }, // ✅ centrado + dinámico
xaxis: { title: "Window", showgrid: true, zeroline: false },
yaxis: { title: "Sharpe-like", showgrid: true, zeroline: true, zerolinewidth: 2 },
legend: { orientation: "h" }
},
{ responsive: true, displayModeBar: true }
);
}
function renderOOSReturnsDistribution(strategyId, s, data) {
@@ -841,7 +1085,7 @@ function renderOOSReturnsDistribution(strategyId, s, data) {
name: "OOS Return% (bins)"
}], {
margin: { t: 40 },
title: `OOS Returns Distribution — ${strategyId}`,
title: { text: `OOS Returns Distribution — ${strategyId}`, x: 0.5 }, // ✅ centrado + dinámico
xaxis: { title: "Return % (window)" },
yaxis: { title: "Count" }
});
@@ -859,7 +1103,7 @@ function renderDrawdownEvolution(strategyId, s, data) {
name: "Drawdown %"
}], {
margin: { t: 40 },
title: `Drawdown Evolution — ${strategyId}`,
title: { text: `Drawdown Evolution — ${strategyId}`, x: 0.5 }, // ✅ centrado + dinámico
xaxis: { title: "Point (initial + windows)" },
yaxis: { title: "Drawdown %", zeroline: true }
});
@@ -888,7 +1132,7 @@ function renderTradeDensity(strategyId, s, data) {
}
], {
margin: { t: 40 },
title: `Trade Density — ${strategyId}`,
title: { text: `Trade Density — ${strategyId}`, x: 0.5 }, // ✅ centrado + dinámico,
barmode: "group",
xaxis: { title: "Window" },
yaxis: { title: "Trades / window" },

View File

@@ -27,9 +27,9 @@
<script src="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta19/dist/js/tabler.min.js"></script>
<!-- UI v2 core JS -->
<script src="/static/js/app.js"></script>
<!-- <script src="/static/js/app.js"></script>
<script src="/static/js/api.js"></script>
<script src="/static/js/router.js"></script>
<script src="/static/js/router.js"></script> -->
{% block extra_js %}{% endblock %}
</body>

View File

@@ -317,7 +317,7 @@
</div>
<div class="mt-3">
<div id="plot_strategy" style="height: 320px;"></div>
<div id="plot_strategy" style="height: 700px; width: 100%;"></div>
</div>
<hr class="my-4">
@@ -348,6 +348,6 @@
</div>
<script src="/static/js/plotly.js"></script>
<script src="https://cdn.plot.ly/plotly-3.3.1.min.js"></script>
<script src="/static/js/pages/calibration_strategies.js"></script>
{% endblock %}