const $ = id => document.getElementById(id); let lastResult = null; function log(msg, cls = "") { const line = document.createElement("div"); line.textContent = msg; if (cls) line.className = cls; $("log").appendChild(line); $("log").scrollTop = $("log").scrollHeight; } function setStatus(online) { $("status").textContent = online ? "● ONLINE" : "● OFFLINE"; $("status").className = online ? "status online" : "status offline"; } function fmt(n) { return Number(n || 0).toLocaleString("en-IN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function fmtDate(s) { if (!s) return ""; return new Date(`${s}T00:00:00Z`).toLocaleDateString("en-IN", { day: "2-digit", month: "short", year: "numeric", timeZone: "UTC" }); } function initDates() { const today = new Date(); const end = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate())); const start = new Date(end); start.setUTCDate(start.getUTCDate() - 30); $("start").value = start.toISOString().slice(0,10); $("end").value = end.toISOString().slice(0,10); } async function refreshStatus() { try { const r = await fetch("/api/status"); const d = await r.json(); setStatus(Boolean(d.connected)); } catch { setStatus(false); } } $("loginLink").onclick = async () => { try { const r = await fetch("/api/login-url"); const d = await r.json(); if (!d.url) throw new Error(d.error || "Login URL unavailable"); window.open(d.url, "_blank", "noopener,noreferrer"); log("> ICICI Direct login opened", "ok"); } catch (e) { log("> " + e.message, "error"); } }; $("connect").onclick = async () => { const apiSession = $("session").value.trim(); if (!apiSession) { log("> API Session is required", "error"); return; } $("connect").disabled = true; $("log").textContent = ""; log("> POST /api/connect"); log("> generating Breeze session …"); try { const r = await fetch("/api/connect", { method: "POST", headers: {"Content-Type":"application/json"}, body: JSON.stringify({ apiSession }) }); const d = await r.json(); if (!r.ok) throw new Error(d.error || "Breeze connection failed"); log("> generate_session → OK", "ok"); log("> BREEZE SESSION ESTABLISHED", "ok"); setStatus(true); } catch (e) { setStatus(false); log("> " + e.message, "error"); } finally { $("connect").disabled = false; } }; $("logout").onclick = async () => { await fetch("/api/logout", {method:"POST"}); $("session").value = ""; $("log").textContent = "/ session cleared"; setStatus(false); }; function renderMetrics(m) { const cards = [ ["TOTAL LEGS", m.trades], ["WIN RATE", `${Number(m.winRate).toFixed(2)}%`], ["NET P&L", `₹ ${fmt(m.netPnl)}`], ["PROFIT FACTOR", m.profitFactor == null ? "∞" : Number(m.profitFactor).toFixed(2)], ["MAX DRAWDOWN", `₹ ${fmt(m.maxDrawdown)}`], ["EXPECTANCY / LEG", `₹ ${fmt(m.expectancy)}`], ["GROSS PROFIT", `₹ ${fmt(m.grossProfit)}`], ["GROSS LOSS", `₹ ${fmt(m.grossLoss)}`], ["BROKERAGE", `₹ ${fmt(m.brokerage)}`], ["WINS", m.wins], ["LOSSES", m.losses] ]; $("metrics").innerHTML = cards.map(([a,b]) => `
${a}
${b}
` ).join(""); } function renderTables(r) { $("dailyTable").innerHTML = ` ENTRYEXITP&LCUMULATIVERESULT ${r.daily.map(d => ` ${fmtDate(d.entryDate)} ${d.entryTime} ${fmtDate(d.exitDate)} ${d.exitTime} ₹ ${fmt(d.pnl)} ₹ ${fmt(d.cum)} ${d.result} `).join("")}`; $("tradesTable").innerHTML = ` ENTRYEXITRIGHTSTRIKEEXPIRY ENTRY ₹EXIT ₹GROSSNETREASON ${r.trades.map(t => ` ${fmtDate(t.entryDate)} ${t.entryTime} ${fmtDate(t.exitDate)} ${t.exitTime} ${t.right}${t.strike}${fmtDate(t.expiry)} ${fmt(t.entry)}${fmt(t.exit)} ₹ ${fmt(t.gross)} ₹ ${fmt(t.net)} ${t.reason} `).join("")}`; } function downloadCSV(filename, rows) { const csv = rows.map(row => row.map(v => { const s = String(v ?? ""); return /[",\n]/.test(s) ? `"${s.replaceAll('"','""')}"` : s; }).join(",")).join("\n"); const blob = new Blob([csv], {type:"text/csv;charset=utf-8"}); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); URL.revokeObjectURL(a.href); } $("run").onclick = async () => { const start = $("start").value; const end = $("end").value; if (!start || !end || start > end) { alert("Check the backtest date range."); return; } $("run").disabled = true; $("run").textContent = "⏳ FETCHING REAL DATA…"; $("progress").classList.remove("hidden"); $("bar").style.width = "10%"; $("progressText").textContent = "Requesting Breeze historical candles…"; try { const r = await fetch("/api/backtest", { method: "POST", headers: {"Content-Type":"application/json"}, body: JSON.stringify({ start, end, targetPts: Number($("target").value || 20), slPts: Number($("sl").value || 10), brokerage: Number($("brokerage").value || 40), sqOff: $("sqoff").value || "15:10" }) }); const d = await r.json(); if (!r.ok) throw new Error(d.error || "Backtest failed"); lastResult = d; $("bar").style.width = "100%"; $("progressText").textContent = `REAL DATA COMPLETE · ${d.trades.length} LEGS`; renderMetrics(d.metrics); renderTables(d); $("results").classList.remove("hidden"); } catch (e) { alert(e.message); $("progressText").textContent = "ERROR: " + e.message; } finally { setTimeout(() => $("progress").classList.add("hidden"), 700); $("run").disabled = false; $("run").textContent = "▶ RUN REAL BREEZE BACKTEST"; } }; $("downloadTrades").onclick = () => { if (!lastResult) return; const rows = [ ["Entry Date","Entry Time","Exit Date","Exit Time","Right","Strike","Expiry","Entry","Exit","Gross","Brokerage","Net","Reason"], ...lastResult.trades.map(t => [ t.entryDate,t.entryTime,t.exitDate,t.exitTime,t.right,t.strike,t.expiry, t.entry,t.exit,t.gross,t.brokerage,t.net,t.reason ]) ]; downloadCSV("nifty_real_breeze_trades.csv", rows); }; $("downloadDaily").onclick = () => { if (!lastResult) return; const rows = [ ["Entry Date","Entry Time","Exit Date","Exit Time","P&L","Cumulative","Result"], ...lastResult.daily.map(d => [ d.entryDate,d.entryTime,d.exitDate,d.exitTime,d.pnl,d.cum,d.result ]) ]; downloadCSV("nifty_real_breeze_daily.csv", rows); }; initDates(); refreshStatus();