From 73a1d5f709310e5323434fe8ed086721927c2b9f Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 14 Oct 2026 12:54:09 +0800 Subject: [PATCH] =?UTF-8?q?Swap=20=E6=B0=B8=E6=9C=9F=20layout=20to=20left?= =?UTF-8?q?=20option=20select=20/=20right=20order;=20default=20opt:perp=20?= =?UTF-8?q?ratio=202.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/对冲计划-以期权为主.md | 2 +- lib/common/static/hedge_plan.js | 147 +++++++---- lib/common/static/instance_theme.css | 35 +++ .../hedge_plan_option_primary_lib.py | 2 +- .../templates/hedge_plan_panel.html | 237 +++++++++--------- tests/test_hedge_plan_option_primary.py | 2 +- 6 files changed, 263 insertions(+), 162 deletions(-) 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 @@
-
-

- 以期权为主 - 执行参数 - · ETH - 合约账户 -

-
- 规则说明 -
-

账户:永续腿走合约账户(USDT);期权腿走币本位(交易账户 USDT 买币开期权)。

-

模式:在 env HEDGE_PLAN_OPTION_PRIMARY 切换(true=以期权为主 / false=保险模式);标题前标识当前模式。

-

保险模式:做多配 Put、做空配 Call;左填开仓/止盈止损;仅实值/平值;交易所 TP/SL 出场。

-

以期权为主:填参后点「策略启动」进入盯盘(非现场开仓);杠杆/间隔达标后自动先开期权再市价永续。右侧列表仅展示达标候选。

-
-
-
- - -
-
-
- - -
-
- -
-
-

资金与杠杆配置

-
- - - -
-
-
-

选约条件

-
- - - - -
-
-
-

出场条件

-
- - -
-
-
-
-
-
-
-
-
-
-
-

永续行情 合约账户

-
- 标记 — -
-

加载中…

- -
+ +
+
-

期权 · Call 期权账户

+

期权选择 · Call 币本位

+
+ + +
+
+
+ + +
+ 买 Call + 永续空 +
+

点「选择」填入右侧下单;比例默认 2(期权名义:永续名义),如期权 1 BTC → 永续约 0.5 BTC。

@@ -198,6 +97,110 @@
+ + +
+

+ 以期权为主 + 下单 + · ETH + 合约账户 +

+
+ 规则说明 +
+

组合:买 Call → 永续空;买 Put → 永续多。

+

比例:期权:永续默认 2(期权名义 ÷ 比例 = 永续名义)。例:期权 1 BTC → 永续 0.5 BTC。

+

账户:永续腿合约账户(USDT);期权腿币本位(交易账户 USDT 买币)。

+
+
+
+

永续行情

+
+ 标记 — +
+

加载中…

+ +
+ +
+
+

资金与杠杆

+
+ + + + +
+
+
+

选约过滤

+
+ + + +
+
+
+

出场条件

+
+ + +
+
+
+
+
+
+
diff --git a/tests/test_hedge_plan_option_primary.py b/tests/test_hedge_plan_option_primary.py index 7505dc5..efaba0b 100644 --- a/tests/test_hedge_plan_option_primary.py +++ b/tests/test_hedge_plan_option_primary.py @@ -104,7 +104,7 @@ class TestOptionPrimary(unittest.TestCase): "premium_budget": 100, "option_target_points": 50, "perp_target_points": 30, - "option_perp_ratio": 4, + "option_perp_ratio": 2, "option_leverage": 200, "moneyness": "otm", }