"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 || "—")}
Side held
${esc(pSide)}
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]) => `
${s[k] ?? 0}
${l}
`).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 `

${name}

${esc(v.error)}

`; 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} ${rows}
MarketSideSize Avg → CurCostValueP&L
`; } 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

${body}
PairLegs CostValueNet P&L
`; } 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} ${body}
MarketResultSideSize CostPayoutRealized
`; } 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) => `
${n}
${l}
`; const pstat = (n, l, big) => { if (n == null) return `
${l}
`; 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 `
${v}
${l}
`; }; 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 `
Total P&L
`; 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 `
${v}
${sub}
Total P&L
`; })()} ${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) => ``; 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);