Add hub perpetual-options hedge calculator tab and sizing formula.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user