// src/web/ui/v2/static/js/pages/calibration_risk.js console.log( "[calibration_risk] script loaded ✅", new Date().toISOString() ); // ================================================= // INSPECT RISK & STOPS // ================================================= async function inspectCalibrationRisk() { console.log("[calibration_risk] inspectCalibrationRisk() START ✅"); // -------------------------------------------------- // Load calibration context from Step 1 // -------------------------------------------------- const symbol = localStorage.getItem("calibration.symbol"); const timeframe = localStorage.getItem("calibration.timeframe"); if (!symbol || !timeframe) { alert("Calibration context not found. Please complete Step 1 (Data) first."); return; } // -------------------------------------------------- // Read form inputs // -------------------------------------------------- const accountEquityEl = document.getElementById("account_equity"); const riskFractionEl = document.getElementById("risk_fraction"); const maxPositionEl = document.getElementById("max_position_fraction"); const stopTypeEl = document.getElementById("stop_type"); const stopFractionEl = document.getElementById("stop_fraction"); const atrPeriodEl = document.getElementById("atr_period"); const atrMultiplierEl = document.getElementById("atr_multiplier"); const maxDrawdownEl = document.getElementById("max_drawdown_pct"); if (!accountEquityEl || !riskFractionEl || !maxPositionEl || !stopTypeEl || !maxDrawdownEl) { console.error("[calibration_risk] Missing required form elements"); alert("Internal error: risk form is incomplete."); return; } // -------------------------------------------------- // Build stop config // -------------------------------------------------- const stopType = stopTypeEl.value; let stopConfig = { type: stopType }; if (stopType === "fixed" || stopType === "trailing") { if (!stopFractionEl) { alert("Stop fraction input missing"); return; } stopConfig.stop_fraction = parseFloat(stopFractionEl.value) / 100; } if (stopType === "atr") { if (!atrPeriodEl || !atrMultiplierEl) { alert("ATR parameters missing"); return; } stopConfig.atr_period = parseInt(atrPeriodEl.value); stopConfig.atr_multiplier = parseFloat(atrMultiplierEl.value); } // -------------------------------------------------- // Build payload // -------------------------------------------------- const payload = buildRiskPayload(); console.log("[calibration_risk] inspect payload:", payload); // -------------------------------------------------- // Disable next step while inspecting // -------------------------------------------------- disableNextStep(); // -------------------------------------------------- // Call API // -------------------------------------------------- try { const res = await fetch("/api/v2/calibration/risk/inspect", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const data = await res.json(); console.log("[calibration_risk] inspect response:", data); renderRiskResult(payload, data); // -------------------------------------------------- // Enable next step if OK or WARNING // -------------------------------------------------- if (data.valid && data.status !== "fail") { enableNextStep(); } } catch (err) { console.error("[calibration_risk] inspect FAILED", err); alert("Error inspecting risk configuration"); disableNextStep(); } } // ================================================= // VALIDATE RISK & STOPS // ================================================= async function validateCalibrationRisk() { console.log("[calibration_risk] inspectCalibrationRisk() START ✅"); // -------------------------------------------------- // Load calibration context from Step 1 // -------------------------------------------------- const symbol = localStorage.getItem("calibration.symbol"); const timeframe = localStorage.getItem("calibration.timeframe"); if (!symbol || !timeframe) { alert("Calibration context not found. Please complete Step 1 (Data) first."); return; } // -------------------------------------------------- // Read form inputs // -------------------------------------------------- const accountEquityEl = document.getElementById("account_equity"); const riskFractionEl = document.getElementById("risk_fraction"); const maxPositionEl = document.getElementById("max_position_fraction"); const stopTypeEl = document.getElementById("stop_type"); const stopFractionEl = document.getElementById("stop_fraction"); const atrPeriodEl = document.getElementById("atr_period"); const atrMultiplierEl = document.getElementById("atr_multiplier"); const maxDrawdownEl = document.getElementById("max_drawdown_pct"); if (!accountEquityEl || !riskFractionEl || !maxPositionEl || !stopTypeEl || !maxDrawdownEl) { console.error("[calibration_risk] Missing required form elements"); alert("Internal error: risk form is incomplete."); return; } // -------------------------------------------------- // Build stop config // -------------------------------------------------- const stopType = stopTypeEl.value; let stopConfig = { type: stopType }; if (stopType === "fixed" || stopType === "trailing") { if (!stopFractionEl) { alert("Stop fraction input missing"); return; } stopConfig.stop_fraction = parseFloat(stopFractionEl.value) / 100; } if (stopType === "atr") { if (!atrPeriodEl || !atrMultiplierEl) { alert("ATR parameters missing"); return; } stopConfig.atr_period = parseInt(atrPeriodEl.value); stopConfig.atr_multiplier = parseFloat(atrMultiplierEl.value); } // -------------------------------------------------- // Build payload // -------------------------------------------------- const payload = buildRiskPayload(); console.log("[calibration_risk] inspect payload:", payload); // -------------------------------------------------- // Disable next step while inspecting // -------------------------------------------------- disableNextStep(); // -------------------------------------------------- // Call API // -------------------------------------------------- try { const res = await fetch("/api/v2/calibration/risk/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const data = await res.json(); console.log("[calibration_risk] inspect response:", data); renderRiskResult(payload, data); // -------------------------------------------------- // Enable next step if OK or WARNING // -------------------------------------------------- if (data.valid && data.status !== "fail") { enableNextStep(); } } catch (err) { console.error("[calibration_risk] inspect FAILED", err); alert("Error inspecting risk configuration"); disableNextStep(); } } // ================================================= // PDF REPORT RISK & STOPS // ================================================= async function generateRiskReport() { console.log("[calibration_risk] generateRiskReport() START"); const payload = buildRiskPayload(); // reutiliza la misma función que validate const res = await fetch("/api/v2/calibration/risk/report", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!res.ok) { console.error("Failed to generate report"); return; } const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "risk_report.pdf"; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); } // ================================================= // WIZARD NAVIGATION // ================================================= function enableNextStep() { const btn = document.getElementById("next-step-btn"); if (!btn) return; btn.classList.remove("btn-outline-secondary"); btn.classList.add("btn-outline-primary"); btn.setAttribute("aria-disabled", "false"); } function disableNextStep() { const btn = document.getElementById("next-step-btn"); if (!btn) return; btn.classList.remove("btn-outline-primary"); btn.classList.add("btn-outline-secondary"); btn.setAttribute("aria-disabled", "true"); } // ================================================= // RENDER RESULT // ================================================= function renderRiskResult(payload, data) { const container = document.getElementById("risk_result"); const badge = document.getElementById("risk_status_badge"); const message = document.getElementById("risk_message"); const debug = document.getElementById("risk_debug"); if (!container || !badge || !message || !debug) { console.warn("[calibration_risk] Result elements missing"); return; } container.classList.remove("d-none"); badge.className = "badge"; if (data.status === "ok") { badge.classList.add("bg-success"); } else if (data.status === "warning") { badge.classList.add("bg-warning"); } else { badge.classList.add("bg-danger"); } badge.textContent = data.status.toUpperCase(); message.textContent = data.message; debug.textContent = JSON.stringify(data.checks, null, 2); renderRiskChecks(data.checks); if (data.checks?.stop_sanity?.metrics) { renderStopQuantiles(data.checks.stop_sanity.metrics); } renderRiskFormulas(payload, data) if (!data.series) { console.warn("[calibration_risk] No series returned, skipping plots"); return; } if (data.series?.timestamps && data.series?.position_size_pct) { renderPositionSizePlot({ timestamps: data.series.timestamps, positionSizePct: data.series.position_size_pct, maxPositionFraction: payload.risk.max_position_fraction }); renderEffectiveRiskPlot({ timestamps: data.series.timestamps, effectiveRiskPct: data.series.effective_risk_pct, targetRiskFraction: payload.risk.risk_fraction }); renderStopDistanceDistribution({ stopDistances: data.series.stop_distances, quantiles: data.checks?.stop_sanity?.metrics }); } } // ================================================= // RENDER RISK CHECKS // ================================================= function renderRiskChecks(checks) { const list = document.getElementById("risk_checks_list"); if (!list || !checks) return; list.innerHTML = ""; Object.entries(checks).forEach(([key, check]) => { const li = document.createElement("li"); li.classList.add("mb-1"); let icon = "❌"; let color = "text-danger"; if (check.status === "ok") { icon = "✅"; color = "text-success"; } else if (check.status === "warning") { icon = "⚠️"; color = "text-warning"; } const title = key.replace(/_/g, " "); li.innerHTML = ` ${icon} ${title} ${check.message ? ` — ${check.message}` : ""} `; list.appendChild(li); }); } function renderStopQuantiles(metrics) { const el = document.getElementById("stop_quantiles"); if (!el || !metrics) return; el.innerHTML = `