diff --git a/docs/对冲计划-以期权为主.md b/docs/对冲计划-以期权为主.md index 562e13f..cf88d02 100644 --- a/docs/对冲计划-以期权为主.md +++ b/docs/对冲计划-以期权为主.md @@ -22,7 +22,7 @@ | 权利金 | 用户填(USDC 预算) | | 永续杠杆 | 100 | | 期权杠杆 | 实/平 100;虚 200 | -| 期权:永续比例 | 实/平 2;虚 4 | +| 期权:永续比例 | 默认 2(期权名义÷比例=永续名义;例 1 BTC→0.5 BTC) | | 到期时间(最短 h) | 36 | | 期权间隔(点) | 15 | | 期权/永续目标位 | 相对 K 点数,须 **>0** | diff --git a/lib/common/static/hedge_plan.js b/lib/common/static/hedge_plan.js index 61644b2..9a61e86 100644 --- a/lib/common/static/hedge_plan.js +++ b/lib/common/static/hedge_plan.js @@ -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 = 'Call'; + } else { + dirLong.innerHTML = '做多'; + } + } + if (dirShort) { + dirShort.title = on ? "买 Put → 永续多" : "做空永续"; + if (on) { + dirShort.innerHTML = 'Put'; + } else { + dirShort.innerHTML = '做空'; + } + } + 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 ( + "期权 " + + fmt(sized.eth_qty, 2) + + " " + + coin + + " / " + + sized.sheets + + " 张 → 永续 " + + fmt(sized.perp_qty, 4) + + " " + + 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 = - "执行预算 " + - fmt(sized.usable, 2) + - " · 期权 ETH " + - fmt(sized.eth_qty, 2) + - " / " + - sized.sheets + - " 张 · 永续 " + - fmt(sized.contracts, amtPrec) + - " 张"; + 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 + - '">选用'; + '">选择'; 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)"); diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index 5a35936..f2e5ea3 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -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; diff --git a/lib/hedge_plan/hedge_plan_option_primary_lib.py b/lib/hedge_plan/hedge_plan_option_primary_lib.py index ec82129..efeaa27 100644 --- a/lib/hedge_plan/hedge_plan_option_primary_lib.py +++ b/lib/hedge_plan/hedge_plan_option_primary_lib.py @@ -12,7 +12,7 @@ DEFAULT_PERP_LEVERAGE = 100 DEFAULT_OPT_LEVERAGE_ITM_ATM = 100.0 DEFAULT_OPT_LEVERAGE_OTM = 200.0 DEFAULT_RATIO_ITM_ATM = 2.0 -DEFAULT_RATIO_OTM = 4.0 +DEFAULT_RATIO_OTM = 2.0 OTM_LEV_FLOOR = 180.0 diff --git a/lib/hedge_plan/templates/hedge_plan_panel.html b/lib/hedge_plan/templates/hedge_plan_panel.html index 9bc7a10..dbdeca9 100644 --- a/lib/hedge_plan/templates/hedge_plan_panel.html +++ b/lib/hedge_plan/templates/hedge_plan_panel.html @@ -45,124 +45,22 @@