Show equivalent contract leverage on options order panel.

Display notional-over-premium leverage at current index and at the user target price for quick sizing comparison.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 16:58:25 +08:00
parent 27921213b0
commit 25879dd007
4 changed files with 79 additions and 20 deletions
+53 -19
View File
@@ -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) {
+14
View File
@@ -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)
+4 -1
View File
@@ -46,6 +46,7 @@
<div><span class="k">张数</span><span id="opt-order-sheets" class="v"></span></div>
<div><span class="k">ETH/BTC 数量</span><span id="opt-order-eth" class="v"></span></div>
<div><span class="k">预估权利金</span><span id="opt-order-premium" class="v"></span></div>
<div><span class="k">合约杠杆</span><span id="opt-order-leverage" class="v" title="名义价值÷权利金,测算用"></span></div>
<div><span class="k">到期平衡</span><span id="opt-order-expiry-be" class="v"></span></div>
<div><span class="k">距平衡</span><span id="opt-order-dist-be" class="v"></span></div>
</div>
@@ -54,6 +55,8 @@
<input type="number" id="opt-target-idx" class="opt-target-idx" step="0.1" min="0" placeholder="到期时指数价">
<span class="k">预计盈利</span>
<span id="opt-est-profit" class="v"></span>
<span class="k">目标杠杆</span>
<span id="opt-est-leverage" class="v" title="目标位名义价值÷权利金"></span>
<span class="muted opt-est-note">到期测算,仅供参考</span>
</div>
<div class="form-row options-order-mode-row">
@@ -133,4 +136,4 @@
</div>
</div>
</div>
<script src="/static/options_panel.js?v=8"></script>
<script src="/static/options_panel.js?v=9"></script>
+8
View File
@@ -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