Swap 永期 layout to left option select / right order; default opt:perp ratio 2.

Call pairs with perp short and Put with perp long; sizing shows coin notional so 1 BTC option hedges 0.5 BTC perp.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-14 12:54:09 +08:00
parent 4c48c95562
commit 73a1d5f709
6 changed files with 263 additions and 162 deletions
+105 -42
View File
@@ -65,6 +65,18 @@
b.classList.toggle("is-selected", on);
b.classList.toggle("active", on);
});
const hint = $("hp-po-pair-hint");
if (hint) {
if (isOptionPrimary()) {
hint.textContent = dir === "short" ? "买 Put + 永续多" : "买 Call + 永续空";
} else {
hint.textContent = dir === "short" ? "永续空 + 保险 Call" : "永续多 + 保险 Put";
}
}
const typeLab = $("hp-opt-type-label");
if (typeLab && isOptionPrimary()) {
typeLab.textContent = dir === "short" ? "Put" : "Call";
}
}
function setDirection(dir, forceReload) {
@@ -282,7 +294,8 @@
levEl.value = kind === "otm" ? "200" : "100";
}
if (ratioEl && (force || !state.opRatioTouched)) {
ratioEl.value = kind === "otm" ? "4" : "2";
// 默认 2:期权名义÷比例=永续名义(例 1 BTC 期权 → 0.5 BTC 永续)
ratioEl.value = "2";
}
}
@@ -313,11 +326,6 @@
insMoney.classList.remove("hidden");
}
}
document.querySelectorAll(".hp-money-otm").forEach(function (otmBtn) {
otmBtn.classList.toggle("hidden", !on);
if (!on) otmBtn.setAttribute("hidden", "hidden");
else otmBtn.removeAttribute("hidden");
});
if (!on && state.moneyFilter === "otm") {
state.moneyFilter = "itm";
syncMoneyUI();
@@ -329,11 +337,26 @@
badge.classList.toggle("is-insurance", !on);
}
const title = $("hp-po-card-title");
if (title) title.textContent = "执行参数";
if (title) title.textContent = on ? "下单" : "执行参数";
const dirLong = document.querySelector('.hp-po-dir[data-dir="long"]');
const dirShort = document.querySelector('.hp-po-dir[data-dir="short"]');
if (dirLong) dirLong.title = on ? "做多=买Call+永续空" : "做多永续";
if (dirShort) dirShort.title = on ? "做空=买Put+永续多" : "做空永续";
if (dirLong) {
dirLong.title = on ? "买 Call → 永续空" : "做多永续";
if (on) {
dirLong.innerHTML = '<span class="hp-oo-check" aria-hidden="true">✓</span>Call';
} else {
dirLong.innerHTML = '<span class="hp-oo-check" aria-hidden="true">✓</span>做多';
}
}
if (dirShort) {
dirShort.title = on ? "买 Put → 永续多" : "做空永续";
if (on) {
dirShort.innerHTML = '<span class="hp-oo-check" aria-hidden="true">✓</span>Put';
} else {
dirShort.innerHTML = '<span class="hp-oo-check" aria-hidden="true">✓</span>做空';
}
}
syncPoDirUI();
syncPoActionBtn();
}
@@ -351,33 +374,75 @@
return n;
}
function computeOpSizing(ask, ctMult) {
function computeOpSizing(ask, ctMult, sheetsOverride) {
const budget = numInput("hp-premium-budget", 0);
const ratio = numInput("hp-opt-perp-ratio", 2);
const cs = Number((state.market && state.market.contract_size) || 0.01);
const usable = budget * 0.95;
const a = Number(ask || 0);
const ct = Number(ctMult || 0.01);
if (!(budget > 0) || !(a > 0) || !(ct > 0) || !(ratio > 0) || !(cs > 0)) {
if (!(ct > 0) || !(ratio > 0) || !(cs > 0)) {
return null;
}
let eth = Math.floor((usable / a) * 100 + 1e-12) / 100;
if (!(eth > 0)) return null;
let sheets = Math.floor(eth / ct + 1e-12);
if (!(sheets > 0)) return null;
eth = Math.round(sheets * ct * 100) / 100;
let sheets;
let eth;
if (sheetsOverride != null && Number(sheetsOverride) > 0) {
sheets = Math.floor(Number(sheetsOverride) + 1e-12);
if (!(sheets > 0)) return null;
eth = Math.round(sheets * ct * 100) / 100;
} else {
if (!(budget > 0) || !(a > 0)) return null;
eth = Math.floor((usable / a) * 100 + 1e-12) / 100;
if (!(eth > 0)) return null;
sheets = Math.floor(eth / ct + 1e-12);
if (!(sheets > 0)) return null;
eth = Math.round(sheets * ct * 100) / 100;
}
const perpEth = eth / ratio;
const contracts = perpEth / cs;
return {
usable: usable,
eth_qty: eth,
perp_qty: perpEth,
sheets: sheets,
contracts: contracts,
premium_est: a * sheets * ct,
premium_est: a > 0 ? a * sheets * ct : 0,
ratio: ratio,
};
}
function currentOpSizing() {
if (!state.selected) return null;
const sheets = numInput("hp-sheets", 0);
return computeOpSizing(
state.selected.ask,
state.selected.ct_mult || 0.01,
sheets > 0 ? sheets : null
);
}
function formatOpSizingHtml(sized, amtPrec) {
if (!sized) return "";
const coin = (state.underlying || "ETH").toUpperCase();
const prec = amtPrec != null ? amtPrec : coin === "BTC" ? 4 : 2;
return (
"期权 <strong>" +
fmt(sized.eth_qty, 2) +
"</strong> " +
coin +
" / " +
sized.sheets +
" 张 → 永续 <strong>" +
fmt(sized.perp_qty, 4) +
"</strong> " +
coin +
" (" +
fmt(sized.contracts, prec) +
" 张) · 比例 " +
fmt(sized.ratio, 2)
);
}
function defaultPerpLeverage(uly) {
const u = (uly || "ETH").toUpperCase();
const btc = Number(root.getAttribute("data-btc-leverage") || 10);
@@ -1041,22 +1106,11 @@
const sz = $("hp-sizing-line");
if (sz) {
if (isOptionPrimary()) {
const sized =
state.selected &&
computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
const sized = currentOpSizing();
if (sized) {
sz.innerHTML =
"执行预算 <strong>" +
fmt(sized.usable, 2) +
"</strong> · 期权 ETH <strong>" +
fmt(sized.eth_qty, 2) +
"</strong> / " +
sized.sheets +
" 张 · 永续 <strong>" +
fmt(sized.contracts, amtPrec) +
"</strong> 张";
sz.innerHTML = formatOpSizingHtml(sized, amtPrec);
} else {
sz.textContent = "填写权利金并选用期权后显示定仓(权利金×0.95,ETH两位小数)";
sz.textContent = "填写权利金或张数并选择期权后显示定仓(默认比例2:期权÷2=永续)";
}
} else if (d.full_margin_sizing) {
const s = d.full_margin_sizing;
@@ -1301,7 +1355,7 @@
(prevInst && c.inst_id === prevInst ? " active" : "") +
'" data-inst="' +
c.inst_id +
'">选用</button></td>';
'">选择</button></td>';
tbody.appendChild(tr);
});
tbody.querySelectorAll(".hp-pick").forEach(function (btn) {
@@ -1652,8 +1706,8 @@
} else {
if (!state.selected) throw new Error("请选用期权腿");
if (isOptionPrimary()) {
const sized = computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
if (!sized) throw new Error("请填写权利金并确认卖一有效");
const sized = currentOpSizing();
if (!sized) throw new Error("请填写权利金或张数并确认卖一有效");
const optPts = numInput("hp-opt-target-pts", NaN);
const perpPts = numInput("hp-perp-target-pts", NaN);
if (!(optPts > 0) || !(perpPts > 0)) throw new Error("请填写期权/永续目标位点数(须大于0)");
@@ -1896,10 +1950,9 @@
if ($("hp-opt-perp-ratio")) {
$("hp-opt-perp-ratio").addEventListener("input", function () {
state.opRatioTouched = true;
if (state.selected) {
const sized = computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
if (sized && $("hp-sheets")) $("hp-sheets").value = String(sized.sheets);
}
const sz = $("hp-sizing-line");
const sized = currentOpSizing();
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
void loadMarket();
});
}
@@ -2027,7 +2080,17 @@
}
if ($("hp-exp-select")) $("hp-exp-select").addEventListener("change", renderListStrikes);
if ($("hp-oo-exp-select")) $("hp-oo-exp-select").addEventListener("change", renderTStrikes);
if ($("hp-sheets")) $("hp-sheets").addEventListener("input", updatePremiumLine);
if ($("hp-sheets")) {
$("hp-sheets").addEventListener("input", function () {
updatePremiumLine();
if (isOptionPrimary() && state.selected) {
const sz = $("hp-sizing-line");
const sized = currentOpSizing();
if (sz && sized) sz.innerHTML = formatOpSizingHtml(sized);
void loadMarket();
}
});
}
["hp-oo-sheets-a", "hp-oo-sheets-b"].forEach(function (id) {
const el = $(id);
if (el) el.addEventListener("input", updateOoPremiumLine);
@@ -2631,7 +2694,7 @@
index_px: indexPx() || Number(state.market.mark || 0),
entry: indexPx() || Number(state.market.mark || 0),
premium_budget: prem,
option_perp_ratio: numInput("hp-opt-perp-ratio", 4),
option_perp_ratio: numInput("hp-opt-perp-ratio", 2),
option_target_points: optPts,
perp_target_points: perpPts,
strike_interval: numInput("hp-strike-interval", 15),
@@ -2680,8 +2743,8 @@
} else {
if (!state.selected) throw new Error("请选用期权腿");
if (isOptionPrimary()) {
const sized = computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
if (!sized) throw new Error("请填写权利金并确认卖一有效");
const sized = currentOpSizing();
if (!sized) throw new Error("请填写权利金或张数并确认卖一有效");
const optPts = numInput("hp-opt-target-pts", NaN);
const perpPts = numInput("hp-perp-target-pts", NaN);
if (!(optPts > 0) || !(perpPts > 0)) throw new Error("请填写期权/永续目标位点数(须大于0)");
+35
View File
@@ -3911,6 +3911,41 @@ html[data-theme="light"] .opt-be-dist-down {
.hedge-plan-page-wrap #hp-po-layout > .card {
align-self: stretch;
}
.hedge-plan-page-wrap .hp-po-left-stack,
.hedge-plan-page-wrap .hp-po-right-stack {
display: flex;
flex-direction: column;
gap: 10px;
flex: 1 1 auto;
min-height: 0;
}
.hedge-plan-page-wrap .hp-po-left-stack > .hp-opt-card,
.hedge-plan-page-wrap .hp-po-select-card {
flex: 1 1 auto;
display: flex;
flex-direction: column;
min-height: 0;
}
.hedge-plan-page-wrap .hp-po-left-stack > .hp-opt-card .hp-strike-table-wrap--6,
.hedge-plan-page-wrap .hp-po-select-card .hp-strike-table-wrap--6 {
flex: 1 1 auto;
min-height: 220px;
}
.hedge-plan-page-wrap .hp-po-pair-hint {
margin-left: 8px;
font-size: 0.82rem;
white-space: nowrap;
}
.hedge-plan-page-wrap .hp-po-chain-hint {
margin: 4px 0 8px;
font-size: 0.78rem;
line-height: 1.4;
}
.hedge-plan-page-wrap .hp-po-order-card .hp-po-perp-quote-card {
height: auto;
flex: 0 0 auto;
margin-bottom: 8px;
}
.hedge-plan-page-wrap .hp-po-right-stack > .hp-po-perp-quote-card {
height: auto;
flex: 0 0 auto;