Add hub perpetual-options hedge calculator tab and sizing formula.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-28 12:37:51 +08:00
parent f360242188
commit 4bcf88b5cb
6 changed files with 496 additions and 5 deletions
+135 -1
View File
@@ -572,8 +572,135 @@
}
}
function renderPerpOptionsResult(data) {
const box = $("calc-po-result");
if (!box) return;
const a = data.case_a || {};
const b = data.case_b || {};
const capitalHint = data.capital_ok
? "资金充足(参考)"
: "保证金高于交易资金(仅提示)";
box.classList.remove("hidden");
box.innerHTML =
'<div class="calc-summary">' +
"<div><span>标的</span><strong>" +
esc(data.base || "—") +
" · 永续 " +
fmtTrim(data.perp_coins, 4) +
" 币</strong></div>" +
"<div><span>单币权利金</span><strong>" +
fmt(data.prem_per_coin, 4) +
"U</strong></div>" +
"<div><span>永续毛收益</span><strong class=\"" +
pnlClass(data.perp_gross_u) +
'">' +
fmtU(data.perp_gross_u) +
"</strong></div>" +
"<div><span>永续手续费</span><strong>" +
fmt(data.perp_fee_u, 4) +
"U</strong></div>" +
"<div><span>权利金预算</span><strong>" +
fmt(data.premium_budget_u, 4) +
"U</strong></div>" +
"<div><span>期权开仓</span><strong>" +
fmtTrim(data.opt_coins, 6) +
" 币 / " +
fmtTrim(data.opt_sheets, 4) +
" 张</strong></div>" +
"<div><span>永续保证金</span><strong>" +
fmt(data.perp_margin_u, 2) +
"U</strong></div>" +
"<div><span>开仓参考</span><strong>" +
esc(capitalHint) +
"</strong></div>" +
"</div>" +
'<div class="calc-po-cases">' +
'<section class="calc-po-case">' +
"<h4>情景 A · 永续方向对</h4>" +
'<div class="calc-summary">' +
"<div><span>永续盈亏</span><strong class=\"" +
pnlClass(a.perp_pnl_u) +
'">' +
fmtU(a.perp_pnl_u) +
"</strong></div>" +
"<div><span>权利金(全亏)</span><strong>" +
fmt(a.premium_u, 4) +
"U</strong></div>" +
"<div><span>手续费</span><strong>" +
fmt(a.fee_u, 4) +
"U</strong></div>" +
"<div><span>净利</span><strong class=\"" +
pnlClass(a.net_u) +
'">' +
fmtU(a.net_u) +
"</strong></div>" +
"</div></section>" +
'<section class="calc-po-case">' +
"<h4>情景 B · 期权方向对</h4>" +
'<div class="calc-summary">' +
"<div><span>期权内在</span><strong>" +
fmtU(b.opt_intrinsic_u) +
"</strong></div>" +
"<div><span>权利金</span><strong>" +
fmt(b.premium_u, 4) +
"U</strong></div>" +
"<div><span>期权净利</span><strong class=\"" +
pnlClass(b.opt_net_u) +
'">' +
fmtU(b.opt_net_u) +
"</strong></div>" +
"<div><span>永续盈亏</span><strong class=\"" +
pnlClass(b.perp_pnl_u) +
'">' +
fmtU(b.perp_pnl_u) +
"</strong></div>" +
"<div><span>组合净利</span><strong class=\"" +
pnlClass(b.portfolio_net_u) +
'">' +
fmtU(b.portfolio_net_u) +
"</strong></div>" +
"</div></section></div>";
}
async function submitPerpOptions(e) {
e.preventDefault();
const body = {
base: ($("calc-po-base") && $("calc-po-base").value) || "ETH",
spot: num("calc-po-spot"),
capital_usdt: num("calc-po-capital"),
target_profit_u: num("calc-po-target"),
move_mode: ($("calc-po-move-mode") && $("calc-po-move-mode").value) || "points",
move_value: num("calc-po-move"),
perp_leverage: num("calc-po-perp-lev"),
option_leverage: num("calc-po-opt-lev"),
ct_mult: num("calc-po-ct-mult") || 0.01,
};
try {
const r = await fetch("/api/calculator/perp-options", {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const j = await r.json();
if (!j.ok) {
showErr("calc-po-result", j.msg || "计算失败");
return;
}
renderPerpOptionsResult(j.data);
} catch (err) {
showErr("calc-po-result", String(err));
}
}
function syncPoMoveLabel() {
const mode = ($("calc-po-move-mode") && $("calc-po-move-mode").value) || "points";
const lab = $("calc-po-move-label");
if (lab) lab.textContent = mode === "pct" ? "波动率 %" : "波动点数";
}
function applyCalcTab(tab) {
const t = tab === "roll" ? "roll" : "trend";
const t = tab === "roll" || tab === "po" ? tab : "trend";
const layout = page.querySelector(".calc-layout");
if (layout) layout.setAttribute("data-calc-tab", t);
page.querySelectorAll(".calc-m-tab").forEach(function (btn) {
@@ -609,13 +736,20 @@
await loadCalculatorExchanges();
const trendForm = $("calc-trend-form");
const rollForm = $("calc-roll-form");
const poForm = $("calc-po-form");
const dirSel = $("calc-trend-direction");
const poMode = $("calc-po-move-mode");
if (trendForm) trendForm.addEventListener("submit", submitTrend);
if (rollForm) rollForm.addEventListener("submit", submitRoll);
if (poForm) poForm.addEventListener("submit", submitPerpOptions);
if (dirSel) {
dirSel.addEventListener("change", syncTrendAddLabel);
syncTrendAddLabel();
}
if (poMode) {
poMode.addEventListener("change", syncPoMoveLabel);
syncPoMoveLabel();
}
bindRollLegsUI();
bindMarket("calc-trend");
bindMarket("calc-roll");