/** * 中控策略对比:同风险额 R 下 合约 / 单期权 / 期期7:3 */ (function () { const page = document.getElementById("page-compare"); if (!page) return; let inited = false; let calcTimer = null; function $(id) { return document.getElementById(id); } function esc(s) { return String(s == null ? "" : s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function num(id) { const el = $(id); if (!el) return null; const n = Number(el.value); return Number.isFinite(n) ? n : null; } function text(id) { const el = $(id); return el ? String(el.value || "").trim() : ""; } function fmtU(v) { if (v == null || !Number.isFinite(Number(v))) return "—"; const n = Number(v); const abs = Math.abs(n).toFixed(2); if (Math.abs(n) < 1e-9) return "0.00U"; return (n > 0 ? "+" : "-") + abs + "U"; } function pnlClass(v) { const n = Number(v); if (!Number.isFinite(n) || Math.abs(n) < 1e-9) return ""; return n > 0 ? "cmp-pnl-pos" : "cmp-pnl-neg"; } function setStatus(msg, isErr) { const el = $("cmp-status"); if (!el) return; el.textContent = msg || ""; el.className = "toolbar-meta" + (isErr ? " err" : ""); } function syncDirectionDefaults() { const dir = text("cmp-direction") || "long"; const isLong = dir === "long"; const optType = $("cmp-opt-type"); const mainType = $("cmp-hedge-main-type"); const sideType = $("cmp-hedge-side-type"); if (optType && !optType.dataset.touched) optType.value = isLong ? "C" : "P"; if (mainType && !mainType.dataset.touched) mainType.value = isLong ? "C" : "P"; if (sideType && !sideType.dataset.touched) sideType.value = isLong ? "P" : "C"; } function collectPayload() { const tp = num("cmp-tp"); return { base: text("cmp-base") || "ETH", direction: text("cmp-direction") || "long", entry: num("cmp-entry"), sl: num("cmp-sl"), tp: tp, risk_u: num("cmp-risk"), tp_opt: num("cmp-tp-opt") != null ? num("cmp-tp-opt") : tp, tp_hedge: num("cmp-tp-hedge") != null ? num("cmp-tp-hedge") : tp, option: { opt_type: text("cmp-opt-type") || "C", strike: num("cmp-opt-strike"), ask: num("cmp-opt-ask"), }, hedge: { main: { opt_type: text("cmp-hedge-main-type") || "C", strike: num("cmp-hedge-main-strike"), ask: num("cmp-hedge-main-ask"), }, side: { opt_type: text("cmp-hedge-side-type") || "P", strike: num("cmp-hedge-side-strike"), ask: num("cmp-hedge-side-ask"), }, }, }; } function renderSummaryCards(data) { const box = $("cmp-summary"); if (!box) return; const perp = data.perp || {}; const opt = data.option || {}; const hedge = data.hedge || {}; const cards = []; cards.push(`

单独合约

张数${esc(perp.sheets)}
止损占用${fmtU(perp.risk_used_u)}
面值${esc(perp.contract_size)} 币/张
`); if (opt.ok) { cards.push(`

单独期权 · ${esc(opt.opt_type)} ${esc(opt.strike)}

张数${esc(opt.sheets)}
权利金${fmtU(opt.premium_u)}
单张成本${fmtU(opt.unit_cost_u)}
`); } else { cards.push(`

单独期权

${esc(opt.msg || "输入不完整")}

`); } if (hedge.ok) { const m = hedge.main || {}; const s = hedge.side || {}; cards.push(`

期期对冲 7:3

主腿 ${esc(m.opt_type)} ${esc(m.strike)}${esc(m.sheets)} 张 · ${fmtU(m.premium_u)}
次腿 ${esc(s.opt_type)} ${esc(s.strike)}${esc(s.sheets)} 张 · ${fmtU(s.premium_u)}
总权利金${fmtU(hedge.premium_u)}
`); } else { cards.push(`

期期对冲

${esc(hedge.msg || "输入不完整")}

`); } box.innerHTML = cards.join(""); } function cell(v, note) { const main = `${fmtU(v)}`; if (!note) return main; return `${main}
${esc(note)}
`; } function renderTable(data) { const box = $("cmp-table-wrap"); if (!box) return; const perp = data.perp || {}; const opt = data.option && data.option.ok ? data.option : null; const hedge = data.hedge && data.hedge.ok ? data.hedge : null; const dash = "—"; box.innerHTML = `
路径 单独合约 单独期权 期期对冲
A 干净止盈
盈利能力主对比
${cell(perp.path_a_tp)} ${opt ? cell(opt.path_a_tp) : dash} ${hedge ? cell(hedge.path_a_tp) : dash}
B 打止损
合约实现亏损;期权另注最坏
${cell(perp.path_b_sl)} ${ opt ? cell(opt.path_b_sl, "最坏到期亏满权利金 " + fmtU(opt.path_b_worst)) : dash } ${ hedge ? cell(hedge.path_b_sl, "最坏双腿归零 " + fmtU(hedge.path_b_worst)) : dash }
C 先止损再去止盈
合约踏空对照
${cell( perp.path_c_realized, "踏空未拿到 " + fmtU(perp.path_c_missed) )} ${opt ? cell(opt.path_c_hold_to_tp, opt.path_c_note || "") : dash} ${hedge ? cell(hedge.path_c_hold_to_tp, hedge.path_c_note || "") : dash}
`; } function renderRecommend(data) { const box = $("cmp-recommend"); if (!box) return; const rec = data.recommend || {}; const bullets = Array.isArray(rec.bullets) ? rec.bullets : []; const warns = Array.isArray(data.warnings) ? data.warnings : []; box.innerHTML = `
推荐:${esc(rec.choice || "—")}

${esc(rec.reason || "")}

${ warns.length ? `
${warns.map((w) => esc(w)).join(" · ")}
` : "" }

${(data.notes || []).map(esc).join(" · ")}

`; } async function runCalc() { const payload = collectPayload(); if ( payload.entry == null || payload.sl == null || payload.tp == null || payload.risk_u == null ) { setStatus("请填写入场 / 止损 / 止盈 / 风险额", true); return; } setStatus("计算中…"); try { const r = await fetch("/api/compare/calc", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const data = await r.json(); if (!data.ok) { setStatus(data.msg || "计算失败", true); return; } renderSummaryCards(data); renderTable(data); renderRecommend(data); setStatus("已更新"); } catch (e) { setStatus(String(e.message || e), true); } } function scheduleCalc() { if (calcTimer) clearTimeout(calcTimer); calcTimer = setTimeout(() => { void runCalc(); }, 280); } function bind() { const form = $("cmp-form"); if (!form || form.dataset.bound === "1") return; form.dataset.bound = "1"; form.addEventListener("submit", (ev) => { ev.preventDefault(); void runCalc(); }); form.querySelectorAll("input, select").forEach((el) => { el.addEventListener("change", () => { if (el.id === "cmp-direction") syncDirectionDefaults(); if ( el.id === "cmp-opt-type" || el.id === "cmp-hedge-main-type" || el.id === "cmp-hedge-side-type" ) { el.dataset.touched = "1"; } scheduleCalc(); }); el.addEventListener("input", scheduleCalc); }); const btn = $("cmp-btn-run"); if (btn) btn.addEventListener("click", () => void runCalc()); } window.hubComparePage = { init() { if (!inited) { bind(); syncDirectionDefaults(); inited = true; } scheduleCalc(); }, destroy() { /* keep form state */ }, }; })();