diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 4f4bbbe..2a2723d 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -184,33 +184,67 @@ return Math.round((intrinsic - entry) * amt * 10000) / 10000; } - function updateEstimatedProfit() { - const el = document.getElementById("opt-est-profit"); + function calcContractLeverage(indexPx, ethAmount, totalPremium) { + if (indexPx == null || ethAmount == null || totalPremium == null) return null; + const idx = Number(indexPx); + const amt = Number(ethAmount); + const prem = Number(totalPremium); + if (!Number.isFinite(idx) || !Number.isFinite(amt) || !Number.isFinite(prem) || amt <= 0 || prem <= 0) { + return null; + } + return Math.round((idx * amt) / prem * 10) / 10; + } + + function fmtLeverage(v) { + if (v === null || v === undefined || Number.isNaN(Number(v))) return "—"; + return "约 " + Number(v).toFixed(1) + "×"; + } + + function updateOrderEstimates() { + const levEl = document.getElementById("opt-order-leverage"); + const profitEl = document.getElementById("opt-est-profit"); + const targetLevEl = document.getElementById("opt-est-leverage"); const targetEl = document.getElementById("opt-target-idx"); - if (!el || !targetEl) return; const q = state.orderQuote; if (!q || !q.ok) { - el.textContent = "—"; - el.className = "v"; - return; - } - const targetRaw = targetEl.value; - if (targetRaw === "" || targetRaw == null) { - el.textContent = "—"; - el.className = "v"; + if (levEl) levEl.textContent = "—"; + if (profitEl) { + profitEl.textContent = "—"; + profitEl.className = "v"; + } + if (targetLevEl) targetLevEl.textContent = "—"; return; } const sz = q.sizing || {}; const ethAmount = sz.eth_amount; - const profit = estimateExpiryProfit(q.opt_type, q.strike, Number(targetRaw), q.ask, ethAmount); - if (profit == null || Number.isNaN(profit)) { - el.textContent = "—"; - el.className = "v"; - return; + const premium = sz.total_premium; + const lev = calcContractLeverage(q.index_px, ethAmount, premium); + if (levEl) levEl.textContent = fmtLeverage(lev); + + if (profitEl && targetEl) { + const targetRaw = targetEl.value; + if (targetRaw === "" || targetRaw == null) { + profitEl.textContent = "—"; + profitEl.className = "v"; + if (targetLevEl) targetLevEl.textContent = "—"; + } else { + const profit = estimateExpiryProfit(q.opt_type, q.strike, Number(targetRaw), q.ask, ethAmount); + if (profit == null || Number.isNaN(profit)) { + profitEl.textContent = "—"; + profitEl.className = "v"; + } else { + const sign = profit > 0 ? "+" : ""; + profitEl.textContent = sign + profit.toFixed(4) + " USDC"; + profitEl.className = "v " + pnlCls(profit); + } + const targetLev = calcContractLeverage(Number(targetRaw), ethAmount, premium); + if (targetLevEl) targetLevEl.textContent = fmtLeverage(targetLev); + } } - const sign = profit > 0 ? "+" : ""; - el.textContent = sign + profit.toFixed(4) + " USDC"; - el.className = "v " + pnlCls(profit); + } + + function updateEstimatedProfit() { + updateOrderEstimates(); } function fmtDist(v) { diff --git a/lib/options/options_pricing_lib.py b/lib/options/options_pricing_lib.py index 5be60da..f6ddef7 100644 --- a/lib/options/options_pricing_lib.py +++ b/lib/options/options_pricing_lib.py @@ -257,3 +257,17 @@ def estimate_expiry_profit_at_index( else: return None return round((intrinsic - float(entry_px)) * float(eth_amount), 4) + + +def equivalent_contract_leverage( + *, + index_px: float | None, + eth_amount: float | None, + total_premium: float | None, +) -> float | None: + """名义价值 / 权利金,近似相当于永续合约杠杆倍数(测算用)。""" + if index_px is None or eth_amount is None or total_premium is None: + return None + if eth_amount <= 0 or total_premium <= 0: + return None + return round(float(index_px) * float(eth_amount) / float(total_premium), 1) diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index e61ceaf..747e5b3 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -46,6 +46,7 @@
张数
ETH/BTC 数量
预估权利金
+
合约杠杆
到期平衡
距平衡
@@ -54,6 +55,8 @@ 预计盈利 + 目标杠杆 + 到期测算,仅供参考
@@ -133,4 +136,4 @@
- + diff --git a/tests/test_options_pricing.py b/tests/test_options_pricing.py index 54cd604..d6b3dfa 100644 --- a/tests/test_options_pricing.py +++ b/tests/test_options_pricing.py @@ -67,6 +67,14 @@ def test_option_moneyness(): assert option_moneyness_label("otm") == "虚值" +def test_equivalent_contract_leverage(): + from lib.options.options_pricing_lib import equivalent_contract_leverage + + # index 1768, 0.2 ETH, premium 2.44 -> ~144.9x + lev = equivalent_contract_leverage(index_px=1768, eth_amount=0.2, total_premium=2.44) + assert lev == 144.9 + + def test_estimate_expiry_profit_at_index(): from lib.options.options_pricing_lib import estimate_expiry_profit_at_index