"use strict";
const ASSET = {
BTC: "#f7931a", ETH: "#7b87ff", SOL: "#19fb9b", XRP: "#3fb6e8",
BNB: "#f3ba2f", DOGE: "#c2a633", HYPE: "#22d3a6", ZEC: "#f4b728",
};
const ac = (a) => ASSET[a] || "#7c8cff";
const COLS_MONTHLY = [
{ k: "_mkt", t: "Market", align: "l", sort: "kalshi_ticker" },
{ k: "best_side", t: "Side", align: "l" },
{ k: "_px", t: "K / P px", align: "l", sort: "combined_cost" },
{ k: "combined_cost", t: "Comb $", f: "px" },
{ k: "worst_pnl", t: "Min $/ct", f: "s4" },
{ k: "net_return", t: "Net Ret", f: "pctBig" },
{ k: "annualized", t: "Annual", f: "pct" },
{ k: "max_contracts", t: "Max Ct", f: "sz" },
{ k: "total_gain", t: "Tot $", f: "s2" },
{ k: "poly_volume", t: "P Vol", f: "money" },
{ k: "days_to_expiry",t: "Days", f: "int" },
{ k: "status", t: "Status", align: "l", f: "status" },
];
const COLS_HOURLY = [
{ k: "_mkt", t: "Market", align: "l", sort: "asset" },
{ k: "_window", t: "Window", align: "l", sort: "minutes_to_resolve" },
{ k: "kalshi_strike", t: "K Strike", f: "strike" },
{ k: "implied_strike",t: "Binance open", f: "strike" },
{ k: "best_side", t: "Side", align: "l" },
{ k: "_px", t: "K / P px", align: "l", sort: "combined_cost" },
{ k: "combined_cost", t: "Comb $", f: "px" },
{ k: "worst_pnl", t: "Min $/ct", f: "s4" },
{ k: "net_return", t: "Net Ret", f: "pctBig" },
{ k: "divergence", t: "Feed Δ", f: "div" },
{ k: "poly_volume", t: "P Vol", f: "money" },
{ k: "status", t: "Status", align: "l", f: "status" },
];
let MODE = "monthly", POS_VIEW = "venue";
const COLS = () => (MODE === "hourly" ? COLS_HOURLY : COLS_MONTHLY);
// Static snapshot URLs — no backend; cache-bust on each load so returning
// users see fresh data after a GHA cron commit.
const endpoint = () => {
const f = MODE === "hourly" ? "hourly" : "scan";
return `./data/${f}.json?t=${Date.now()}`;
};
const STATIC_MODE = true;
let RAW = [], sortKey = "net_return", sortAsc = false, openKey = null;
const $ = (id) => document.getElementById(id);
const esc = (s) => String(s == null ? "" : s).replace(/[&<>"]/g,
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
const rid = (r) => (r.asset || "") + "|" + (r.kalshi_ticker || "") +
"|" + (r.poly_slug || "");
// "2026-05-21 16:15:07" (UTC, from GHA runner) -> "3 min ago" etc.
function ago(stamp) {
if (!stamp) return "—";
const t = Date.parse(stamp.replace(" ", "T") + "Z");
if (isNaN(t)) return stamp;
const s = Math.max(0, Math.round((Date.now() - t) / 1000));
if (s < 45) return "just now";
const m = Math.round(s / 60);
if (m < 60) return `${m} min ago`;
const h = Math.round(m / 60);
return `${h}h ago`;
}
function fmt(v, kind) {
if (v === null || v === undefined || v === "")
return '— ';
const sign = (n, body, big) => {
const c = n > 0 ? "pos" : n < 0 ? "neg" : "dimv";
return `${body} `;
};
switch (kind) {
case "int": return String(Math.round(v));
case "sz": return (+v).toLocaleString(undefined, { maximumFractionDigits: 0 });
case "px": return `${(+v).toFixed(3)} `;
case "money": return "$" + (+v).toLocaleString(undefined, { notation: v >= 1e6 ? "compact" : "standard", maximumFractionDigits: 1 });
case "pct": return sign(v, (v * 100).toFixed(1) + "%");
case "pctBig":return sign(v, (v > 0 ? "+" : "") + (v * 100).toFixed(2) + "%", true);
case "s4": return sign(v, (v > 0 ? "+" : "") + (+v).toFixed(4));
case "s2": return sign(v, (v > 0 ? "+" : "") + "$" + (+v).toFixed(2));
case "strike":return `${(+v).toLocaleString(undefined, { maximumFractionDigits: v < 10 ? 4 : 0 })} `;
case "div": { const p = (v * 100), hot = Math.abs(p) >= 0.3;
return `${p >= 0 ? "+" : ""}${p.toFixed(3)}% `; }
case "status":{ const s = String(v).replace(/\s+/g, "");
return `${v} `; }
default: return esc(v);
}
}
function thumb(r, sz) {
const s = sz || 34;
if (r.image)
return ` `;
return `
${esc((r.asset || "?").slice(0, 4))}
`;
}
function cellMarket(r) {
const title = r.kalshi_title || r.poly_question ||
`${r.asset} ${r.direction} ${r.kalshi_strike ?? ""}`;
const strikes = `K $${r.kalshi_strike ?? "—"} · P $${r.poly_strike ?? "—"}`;
return `${thumb(r)}
${esc(r.asset || "?")}
${esc(title)}
${esc(r.direction || "")} ${esc(strikes)}
`;
}
function cellPx(r) {
const k = r.kalshi_price, p = r.poly_price, c = r.combined_cost;
if (k == null || p == null) return '— ';
const pct = Math.min(100, (c / 1) * 100);
const col = c < 1 ? "var(--good)" : "var(--bad)";
return `
K ${k.toFixed(3)} P ${p.toFixed(3)}
cost vs $1 ${c.toFixed(3)}
`;
}
function cellWindow(r) {
if (!r.window_start) return '— ';
const m = r.minutes_to_resolve;
const mc = m != null && m <= 10 ? "neg" : m != null && m <= 25 ? "pos" : "dimv";
return `
${esc(r.window_start)} → ${esc(r.window_close)}
${m != null ? "resolves in " + m + "m" : ""}
`;
}
function renderHead() {
$("head").innerHTML = COLS().map((c) => {
const sk = c.sort || c.k;
let cls = c.align === "l" ? "l" : "";
if (sk === sortKey) cls += sortAsc ? " asc" : " sorted";
return `${c.t} `;
}).join("");
document.querySelectorAll("#head th").forEach((th) => {
th.onclick = () => {
const k = th.dataset.sk;
if (k === sortKey) sortAsc = !sortAsc; else { sortKey = k; sortAsc = false; }
renderHead(); renderBody();
};
});
}
function activeStatus() {
const b = document.querySelector("#statusSeg button.on");
return b ? b.dataset.s : "";
}
function activeAssets() {
return [...document.querySelectorAll("#assetChips .ac.on")].map((e) => e.dataset.a);
}
function filtered() {
const q = $("search").value.trim().toLowerCase();
const s = activeStatus(), as = activeAssets(), fav = $("favOnly").checked;
return RAW.filter((r) => {
if (s && r.status !== s) return false;
if (as.length && !as.includes(r.asset)) return false;
if (fav && !r.basis_favorable) return false;
if (q) {
const h = `${r.asset} ${r.kalshi_ticker} ${r.poly_slug || ""} ${r.kalshi_title || ""} ${r.poly_question || ""}`.toLowerCase();
if (!h.includes(q)) return false;
}
return true;
});
}
function sortRows(rows) {
return rows.sort((x, y) => {
let a = x[sortKey], b = y[sortKey];
a = a == null ? -Infinity : a; b = b == null ? -Infinity : b;
if (typeof a === "string" || typeof b === "string")
return sortAsc ? String(a).localeCompare(b) : String(b).localeCompare(a);
return sortAsc ? a - b : b - a;
});
}
function drawer(r) {
const kv = (k, v) => `${k}
${v}
`;
const money = (n, d = 4) => n == null ? "—"
: `${n > 0 ? "+" : ""}${(+n).toFixed(d)} `;
const kCard = `
Kalshi ${esc(r.kalshi_ticker)}
${esc(r.kalshi_title || "—")}
Yes ask
${r.kalshi_price != null && r.best_side === "YES+NO" ? r.kalshi_price.toFixed(3) : "—"}
No ask
${r.kalshi_price != null && r.best_side === "NO+YES" ? r.kalshi_price.toFixed(3) : "—"}
Open int
${r.kalshi_size != null ? Math.round(r.kalshi_size).toLocaleString() : "—"}
${esc(r.kalshi_rules || "Resolution rules unavailable.")}
`;
const pImg = r.image ? ` ` : "";
const pSideRaw = (r.best_side || "—").split("+")[1] || "—";
const pSide = MODE === "hourly"
? (pSideRaw === "NO" ? "Down" : pSideRaw === "YES" ? "Up" : pSideRaw)
: pSideRaw;
const pCard = `
Polymarket ${esc(r.poly_slug || "no paired market")}
${pImg}
${esc(r.poly_question || "—")}
Poly px
${r.poly_price != null ? r.poly_price.toFixed(3) : "—"}
Volume
$${(r.poly_volume || 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
${esc(r.poly_description || "Market description unavailable.")}
`;
const hourly = MODE === "hourly";
const scen = `
${hourly ? "Worst case" : "Min / guaranteed"}
${money(r.worst_pnl)}
Strike gap
${r.mid_pnl == null ? 'none ' : money(r.mid_pnl)}
Best case
${money(r.best_pnl)}
`;
const hRows = hourly ? `
${kv("Window", esc((r.window_start || "—") + " → " + (r.window_close || "—")))}
${kv("Resolves in", r.minutes_to_resolve != null ? r.minutes_to_resolve + " min" : "—")}
${kv("Implied strike (Binance open)", r.implied_strike != null ? r.implied_strike.toLocaleString() : "—")}
${kv("Binance spot / CF spot", `${r.binance_spot != null ? r.binance_spot.toLocaleString() : "—"} / ${r.cf_spot != null ? r.cf_spot.toLocaleString() : "n/a"}`)}
${kv("Feed divergence", fmt(r.divergence, "div"))}` : `
${kv("Annualized", fmt(r.annualized, "pct"))}
${kv("Total guaranteed $", money(r.total_gain, 2))}
${kv("Days to expiry", r.days_to_expiry ?? "—")}`;
const bd = `
${hourly ? "Speculative breakdown" : "Arb breakdown"}
${scen}
${hourly ? '
Different settlement feeds — legs are not a locked hedge.
' : ""}
${kv("Best side", esc(r.best_side || "—"))}
${kv("Combined cost", r.combined_cost != null ? "$" + r.combined_cost.toFixed(4) : "—")}
${kv("Total fee / ct", r.total_fee != null ? "$" + r.total_fee.toFixed(4) : "—")}
${kv("Net return", fmt(r.net_return, "pctBig"))}
${kv("Basis %", fmt(r.basis_pct, "pct") + (r.basis_favorable ? ' ✓ favorable ' : ' ✗ risk '))}
${kv("Max contracts", r.max_contracts != null ? Math.round(r.max_contracts).toLocaleString() : "—")}
${hRows}
`;
return `
${kCard}${pCard}${bd}
`;
}
function renderBody() {
const rows = sortRows(filtered());
$("rowCount").textContent = `${rows.length} of ${RAW.length} markets`;
$("empty").hidden = rows.length > 0;
const html = [];
for (const r of rows) {
const id = rid(r), isOpen = id === openKey;
html.push(`` +
COLS().map((c) => {
const cls = c.align === "l" ? "l" : "";
if (c.k === "_mkt") return `${cellMarket(r)} `;
if (c.k === "_px") return `${cellPx(r)} `;
if (c.k === "_window") return `${cellWindow(r)} `;
if (c.k === "best_side") {
let bs = r.best_side || "—";
if (MODE === "hourly" && bs !== "—")
bs = bs === "YES+NO" ? "K-Yes · P-Down" : "K-No · P-Up";
return `${esc(bs)} `;
}
return `${fmt(r[c.k], c.f)} `;
}).join("") + " ");
if (isOpen) html.push(drawer(r));
}
$("body").innerHTML = html.join("");
document.querySelectorAll("#body tr.r").forEach((tr) => {
tr.onclick = () => {
openKey = openKey === tr.dataset.id ? null : tr.dataset.id;
renderBody();
};
});
}
function renderSummary(s) {
const cards = MODE === "hourly" ? [
["ARB", "Edges", "s-arb"], ["NO ARB", "No edge", ""],
["BAD BASIS", "Bad basis", "s-bad"], ["NO DATA", "No data", ""],
["total", "Assets", ""],
] : [
["ARB", "Live arbs", "s-arb"], ["NO ARB", "No edge", ""],
["BAD BASIS", "Bad basis", "s-bad"], ["LOW SIZE", "Low size", "s-warn"],
["NO DATA", "No data", ""], ["total", "Scanned", ""],
];
$("summary").innerHTML = cards.map(([k, l, cls]) =>
``).join("") +
`${s.fetch_ms ?? "—"}ms
Fetch time
`;
}
function renderHero(rows) {
const hourly = MODE === "hourly";
const top = rows.filter((r) => r.status === "ARB" && r.net_return > 0)
.sort((a, b) => b.net_return - a.net_return).slice(0, 4);
const h = $("hero");
h.querySelector("h2").textContent =
hourly ? "Top dislocations" : "Top opportunities";
h.querySelector(".muted").textContent = hourly
? "combined cost < $1 — speculative, basis risk applies"
: "positive guaranteed return, ranked";
if (!top.length) { h.hidden = true; return; }
h.hidden = false;
$("heroCards").innerHTML = top.map((r) => `
${thumb(r, 38)}
${esc(r.kalshi_title || r.poly_question || r.asset)}
+${(r.net_return * 100).toFixed(2)}%
${hourly
? `${r.minutes_to_resolve ?? "—"}m left
${r.divergence != null ? (r.divergence * 100).toFixed(3) + "%" : "—"} feed Δ `
: `${r.annualized ? (r.annualized * 100).toFixed(0) + "%" : "—"} annual
$${(r.total_gain || 0).toFixed(2)} locked `}
${Math.round(r.max_contracts || 0).toLocaleString()} ct
${esc(r.best_side)}
`).join("");
document.querySelectorAll(".hcard").forEach((c) => c.onclick = () => {
openKey = c.dataset.id;
document.querySelector("#statusSeg button.on")?.classList.remove("on");
document.querySelector('#statusSeg button[data-s=""]').classList.add("on");
renderBody();
document.querySelector(`#body tr.r[data-id="${CSS.escape(c.dataset.id)}"]`)
?.scrollIntoView({ behavior: "smooth", block: "center" });
});
}
let POS_DATA = null;
const usd = (n, d = 2) => n == null ? "—"
: "$" + (+n).toLocaleString(undefined, { minimumFractionDigits: d, maximumFractionDigits: d });
function pnl(n, pct) {
if (n == null) return '— ';
const c = n > 0 ? "pos" : n < 0 ? "neg" : "dimv";
const p = pct != null ? ` (${(pct * 100).toFixed(1)}%) ` : "";
return `${n > 0 ? "+" : ""}${usd(n)}${p} `;
}
function posThumb(p) {
if (p.icon) return ` `;
const a = p.asset ? p.asset.slice(0, 4) : "?";
return `${esc(a)}
`;
}
function venuePanel(name, tag, v) {
const setup = (body) => `
${name} not connected ${body}`;
if (!v || v.configured === false) {
if (name === "Polymarket")
return setup(`Add your wallet to data/secrets.json:
{ "polymarket_wallet": "0xYourProxyWallet" }
Public address only — no private key. Copy
data/secrets.example.json to start.
`);
return setup(`Create a read-only API key in Kalshi → Settings → API Keys,
save the private-key file locally, then add to
data/secrets.json:
{ "kalshi_key_id": "…",
"kalshi_private_key_path": "/abs/path/key.pem" }
Signed locally via openssl — the key never leaves your
machine. ${v && v.reason === "key_file_not_found"
? 'Key file not found at that path. ' : ""}
`);
}
if (v.error)
return ``;
const ps = v.positions || [];
const head = `
${name} ${ps.length} open
${usd(v.total_value)} `;
if (!ps.length)
return `${head}
No open positions.
`;
const rows = ps.sort((a, b) => (b.value || 0) - (a.value || 0)).map((p) => `
${posThumb(p)}
${esc(p.market)}
${esc(p.ref || "")}
${esc(p.side)}
${(p.size || 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
${p.avg_price != null ? (+p.avg_price).toFixed(3) : "—"} → ${p.cur_price != null ? (+p.cur_price).toFixed(3) : "—"}
${usd(p.cost)}
${usd(p.value)}
${pnl(p.pnl, p.pnl_pct)}
`).join("");
return `${head}
Market Side Size
Avg → Cur Cost Value P&L
${rows}
`;
}
function pairedView(rows) {
if (!rows || !rows.length)
return `Paired exposure
No held positions match a known Kalshi↔Polymarket arb
pair (pairs come from the Monthly map). Positions still show under
By venue .
`;
const body = rows.map((r) => {
const legs = r.legs.map((l) => `
${l.venue[0]}
${esc(l.side)}
${esc(l.ref || "")}
${(l.size || 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
${pnl(l.pnl)}
`).join("");
return `
${esc(r.asset || "?")}
${r.complete ? 'paired ' : 'one leg '}
${legs}
${usd(r.cost)}
${usd(r.value)}
${pnl(r.pnl, r.pnl_pct)}
`;
}).join("");
return `Paired / netted exposure
Pair Legs
Cost Value Net P&L
${body}
`;
}
function historyCard(name, tag, rows, note) {
const head = `
${name} ${rows.length} settled${note ? ` · ${note} ` : ""}
${pnl(rows.reduce((s, r) => s + (r.realized || 0), 0))} `;
if (!rows.length)
return `${head}
No settled markets found.
`;
const body = rows.slice().sort((a, b) =>
(b.settled_date || "").localeCompare(a.settled_date || "")).map((r) => `
${r.icon ? `
` : ""}
${esc(r.market)}
${esc(r.settled_date || "")}
${esc(r.result)}
${esc(r.side || "—")}
${(r.size || 0).toLocaleString(undefined, { maximumFractionDigits: 0 })}
${usd(r.cost)}
${usd(r.payout)}
${pnl(r.realized)}
`).join("");
return `${head}
Market Result Side Size
Cost Payout Realized
${body}
`;
}
function historyView(h) {
h = h || {};
return `
${historyCard("Kalshi", "k", h.kalshi || [], "exact (settlements)")}
${historyCard("Polymarket", "p", h.polymarket || [], "reconstructed · payout derived")}
`;
}
function renderPositions(d) {
if (d) POS_DATA = d;
d = POS_DATA;
const t = d.totals || {};
const stat = (n, l, cls) =>
``;
const pstat = (n, l, big) => {
if (n == null)
return ``;
const cls = n > 0 ? "s-arb" : n < 0 ? "s-bad" : "";
const v = (n > 0 ? "+" : n < 0 ? "-" : "") +
"$" + Math.abs(n).toLocaleString(undefined,
{ minimumFractionDigits: 2, maximumFractionDigits: 2 });
return ``;
};
const strip = `
${stat(usd(t.poly_value), "Polymarket value")}
${stat(t.kalshi_value == null ? "—" : usd(t.kalshi_value), "Kalshi value")}
${pstat(t.kalshi_pnl, "Kalshi P&L")}
${pstat(t.poly_pnl, "Polymarket P&L")}
${(() => {
const n = t.total_pnl, r = t.total_return;
if (n == null)
return `
`;
const cls = n > 0 ? "s-arb" : n < 0 ? "s-bad" : "";
const v = (n > 0 ? "+" : n < 0 ? "-" : "") + "$" +
Math.abs(n).toLocaleString(undefined,
{ minimumFractionDigits: 2, maximumFractionDigits: 2 });
const sub = r == null ? "" :
`
${r > 0 ? "+" : ""}${(r * 100).toFixed(2)}% return
`;
return `
`;
})()}
${pstat(t.total_realized, "Realized P&L")}
${stat(t.open_positions ?? 0, "Open positions")}
${stat(t.settled_count ?? 0, "Settled")}
${stat(t.paired_count ?? 0, "Complete pairs", "s-arb")}
`;
const tab = (v, l) =>
`${l} `;
const seg = `
${tab("venue", "By venue")}${tab("paired", "Paired")}${tab("history", "History")}
`;
const content = POS_VIEW === "paired" ? pairedView(d.paired)
: POS_VIEW === "history" ? historyView(d.history)
: `
${venuePanel("Polymarket", "p", d.polymarket)}
${venuePanel("Kalshi", "k", d.kalshi)}
`;
$("positions").innerHTML = strip +
`${seg}
Read-only · credentials stay in local
data/secrets.json
` + content;
$("positions").querySelectorAll(".posseg button").forEach((b) =>
b.onclick = () => {
POS_VIEW = b.dataset.v; renderPositions();
});
}
function toast(msg, kind) {
const t = $("toast");
t.textContent = msg; t.className = "toast" + (kind ? " " + kind : "");
t.hidden = false; clearTimeout(toast._t);
toast._t = setTimeout(() => (t.hidden = true), kind === "err" ? 7000 : 2800);
}
function applyChrome() {
const scanner = MODE !== "positions";
$("summary").hidden = !scanner;
document.querySelector(".toolbar").hidden = !scanner;
document.querySelector("main").hidden = !scanner;
if ($("positions")) $("positions").hidden = scanner;
if (!scanner) { $("hero").hidden = true; $("hbanner").hidden = true; }
}
async function load(force) {
const b = $("refresh");
b.disabled = true; b.classList.add("loading");
b.querySelector(".blabel").textContent =
MODE === "positions" ? "Loading…" : "Scanning…";
applyChrome();
try {
const res = await fetch(endpoint() + (force ? "?force=1" : ""));
const d = await res.json();
if (!res.ok) throw new Error(d.error || res.status);
if (MODE === "positions") {
renderPositions(d);
$("meta").textContent =
`${d.generated_at} · ${d.totals.open_positions} open`;
} else {
RAW = d.rows;
renderSummary(d.summary);
buildAssetChips();
renderHero(RAW);
renderBody();
updateBanner(d.summary);
const unit = MODE === "hourly" ? "assets" : "pairs";
$("meta").textContent = `${ago(d.generated_at)} · ${d.summary.total} ${unit}`;
}
} catch (e) {
toast((MODE === "positions" ? "Load" : "Scan") + " failed: " + e.message, "err");
$("meta").textContent = "load failed";
} finally {
b.disabled = false; b.classList.remove("loading");
b.querySelector(".blabel").textContent = "Refresh";
}
}
function updateBanner(summary) {
const b = $("hbanner");
if (MODE !== "hourly") { b.hidden = true; return; }
b.hidden = false;
const d = summary && summary.max_divergence;
const el = $("hbDiv");
if (d == null) { el.textContent = "n/a"; el.classList.remove("hot"); return; }
const pct = (d * 100);
el.textContent = "max " + pct.toFixed(3) + "%";
el.classList.toggle("hot", pct >= 0.3);
}
function buildAssetChips() {
const el = $("assetChips");
if (el.dataset.built) return;
const assets = [...new Set(RAW.map((r) => r.asset))].filter(Boolean).sort();
el.innerHTML = assets.map((a) =>
` ${a} `).join("");
el.dataset.built = "1";
el.querySelectorAll(".ac").forEach((c) => c.onclick = () => {
c.classList.toggle("on"); renderBody();
});
}
async function loadSettings() {
const s = await (await fetch("/api/settings")).json();
s_kfee.value = s.kalshi_fee_rate; s_pfee.value = s.poly_fee_rate;
s_minret.value = s.min_net_return; s_minvol.value = s.min_poly_volume;
s_minct.value = s.min_contracts;
}
function wire() {
document.querySelectorAll("#tabs button").forEach((b) => b.onclick = () => {
if (b.classList.contains("on")) return;
document.querySelector("#tabs button.on")?.classList.remove("on");
b.classList.add("on");
MODE = b.dataset.m;
openKey = null;
sortKey = "net_return"; sortAsc = false;
const ac = $("assetChips"); ac.dataset.built = ""; ac.innerHTML = "";
document.querySelector("#statusSeg button.on")?.classList.remove("on");
document.querySelector('#statusSeg button[data-s=""]').classList.add("on");
$("search").value = "";
renderHead();
load(true);
});
$("refresh").onclick = () => load(true);
$("search").addEventListener("input", renderBody);
$("favOnly").addEventListener("change", renderBody);
document.querySelectorAll("#statusSeg button").forEach((b) => b.onclick = () => {
document.querySelector("#statusSeg button.on")?.classList.remove("on");
b.classList.add("on"); renderBody();
});
// STATIC_MODE: no auto-refresh checkbox, no settings panel (DOM absent).
if ($("auto")) {
let timer = null;
$("auto").onchange = (e) => {
clearInterval(timer);
if (e.target.checked) { load(true); timer = setInterval(() => load(true), 8000); }
};
}
if ($("settingsBtn")) {
$("settingsBtn").onclick = async () => {
await loadSettings(); $("settingsModal").hidden = false;
};
$("closeSettings").onclick = () => ($("settingsModal").hidden = true);
$("saveSettings").onclick = async () => {
const body = {
kalshi_fee_rate: +s_kfee.value, poly_fee_rate: +s_pfee.value,
min_net_return: +s_minret.value, min_poly_volume: +s_minvol.value,
min_contracts: +s_minct.value,
};
const r = await fetch("/api/settings", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (r.ok) { $("settingsModal").hidden = true; toast("Saved — rescanning", "ok"); load(true); }
else toast("Save failed", "err");
};
}
}
renderHead();
wire();
load(false);