Estimate missing option ask from mark or intrinsic on chain list.
Deep ITM contracts often have no ask on the book; show mark-based estimates with a tilde and restore breakeven calculations. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -150,9 +150,10 @@
|
||||
"指数 " + state.underlying + " ≈ " + fmt(idx, 2) + " · 实值=价内 · 虚值=价外";
|
||||
}
|
||||
|
||||
function fmtPxSz(px, sz) {
|
||||
function fmtPxSz(px, sz, estimated) {
|
||||
if (px === null || px === undefined || Number.isNaN(Number(px))) return "—";
|
||||
const price = Number(px).toFixed(4).replace(/\.?0+$/, "");
|
||||
let price = Number(px).toFixed(4).replace(/\.?0+$/, "");
|
||||
if (estimated) price += "~";
|
||||
if (sz === null || sz === undefined || sz === "" || Number.isNaN(Number(sz))) return price;
|
||||
const s = Number(sz);
|
||||
const size = Math.abs(s - Math.round(s)) < 1e-9 ? String(Math.round(s)) : String(s);
|
||||
@@ -295,7 +296,7 @@
|
||||
"<td>" + c.strike + "</td>" +
|
||||
"<td>" + moneynessBadge(c) + "</td>" +
|
||||
"<td><code>" + c.inst_id + "</code></td>" +
|
||||
"<td class=\"opt-px-sz\">" + fmtPxSz(c.ask, c.ask_sz) + "</td>" +
|
||||
"<td class=\"opt-px-sz\">" + fmtPxSz(c.ask, c.ask_sz, c.ask_estimated) + "</td>" +
|
||||
"<td class=\"opt-px-sz\">" + fmtPxSz(c.bid, c.bid_sz) + "</td>" +
|
||||
"<td>" + (c.expiry_be_px != null ? fmt(c.expiry_be_px, 0) : "—") + "</td>" +
|
||||
'<td class="' + distBeClass(c.dist_expiry_be) + '">' + fmtDist(c.dist_expiry_be) + "</td>" +
|
||||
|
||||
@@ -115,6 +115,61 @@ def format_option_px(px: float, tick_sz: Any) -> str:
|
||||
return f"{px:.{decimals}f}".rstrip("0").rstrip(".") or "0"
|
||||
|
||||
|
||||
def _intrinsic_px_per_unit(opt_type: str, strike: float, index_px: float) -> float | None:
|
||||
o = (opt_type or "").upper()
|
||||
if o == "C" and index_px > strike:
|
||||
return float(index_px) - float(strike)
|
||||
if o == "P" and index_px < strike:
|
||||
return float(strike) - float(index_px)
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_chain_quote(
|
||||
*,
|
||||
ticker: dict[str, Any],
|
||||
meta: dict[str, Any],
|
||||
opt_type: str,
|
||||
strike: float,
|
||||
index_px: float,
|
||||
) -> dict[str, Any]:
|
||||
"""链列表报价:卖一缺失时用标记价/内在价值估算(深度实值常见无卖一)。"""
|
||||
tick_sz = meta.get("tickSz")
|
||||
ask = _safe_float(ticker.get("askPx"))
|
||||
bid = _safe_float(ticker.get("bidPx"))
|
||||
mark = _safe_float(ticker.get("markPx"))
|
||||
ask_sz = _safe_float(ticker.get("askSz"))
|
||||
bid_sz = _safe_float(ticker.get("bidSz"))
|
||||
ask_estimated = False
|
||||
|
||||
if ask is None and mark is not None and mark > 0:
|
||||
ask = round_option_px(mark, tick_sz, "buy")
|
||||
ask_estimated = True
|
||||
if ask is None:
|
||||
intrinsic = _intrinsic_px_per_unit(opt_type, strike, index_px)
|
||||
if intrinsic is not None and intrinsic > 0:
|
||||
ask = round_option_px(intrinsic, tick_sz, "buy")
|
||||
ask_estimated = True
|
||||
|
||||
if bid is None and mark is not None and mark > 0:
|
||||
bid = round_option_px(mark, tick_sz, "sell")
|
||||
if bid is None:
|
||||
intrinsic = _intrinsic_px_per_unit(opt_type, strike, index_px)
|
||||
if intrinsic is not None and intrinsic > 0:
|
||||
bid = round_option_px(intrinsic, tick_sz, "sell")
|
||||
|
||||
if ask_estimated:
|
||||
ask_sz = None
|
||||
|
||||
return {
|
||||
"ask": ask,
|
||||
"bid": bid,
|
||||
"ask_sz": ask_sz,
|
||||
"bid_sz": bid_sz,
|
||||
"mark_px": mark,
|
||||
"ask_estimated": ask_estimated,
|
||||
}
|
||||
|
||||
|
||||
def _fetch_book_bid_ask(ex: ccxt.okx, inst_id: str) -> tuple[float | None, float | None]:
|
||||
bid, ask, _, _ = _fetch_book_top(ex, inst_id)
|
||||
return bid, ask
|
||||
@@ -344,11 +399,18 @@ def build_option_chain(
|
||||
continue
|
||||
inst_id = str(meta.get("instId") or "")
|
||||
t = tickers.get(inst_id) or {}
|
||||
ask = _safe_float(t.get("askPx"))
|
||||
bid = _safe_float(t.get("bidPx"))
|
||||
mark = _safe_float(t.get("markPx"))
|
||||
ask_sz = _safe_float(t.get("askSz"))
|
||||
bid_sz = _safe_float(t.get("bidSz"))
|
||||
q = _resolve_chain_quote(
|
||||
ticker=t,
|
||||
meta=meta,
|
||||
opt_type=opt_type,
|
||||
strike=strike,
|
||||
index_px=idx,
|
||||
)
|
||||
ask = q["ask"]
|
||||
bid = q["bid"]
|
||||
mark = q["mark_px"]
|
||||
ask_sz = q["ask_sz"]
|
||||
bid_sz = q["bid_sz"]
|
||||
if ask is None and bid is None and mark is None:
|
||||
continue
|
||||
expiry_be = expiry_breakeven_from_ask(
|
||||
@@ -370,6 +432,7 @@ def build_option_chain(
|
||||
"ask_sz": ask_sz,
|
||||
"bid_sz": bid_sz,
|
||||
"mark_px": mark,
|
||||
"ask_estimated": q["ask_estimated"],
|
||||
"expiry_be_px": expiry_be,
|
||||
"dist_expiry_be": idx_distance_to_be(idx, expiry_be),
|
||||
"moneyness": mny,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="options-dual-grid">
|
||||
<div class="card options-order-card">
|
||||
<h2>期权下单</h2>
|
||||
<p class="muted options-hint">报价单位为每 1 ETH/BTC;1 张 = 0.01。卖一/买一列为 <strong>价格/张数</strong>。链展示近 <span id="opt-chain-dte">14</span> 日到期,标注实值/虚值;<strong>到期平衡</strong>按卖一预估(无卖一按标记价)。资金划转与 USDT/USDC 兑换见「系统设置 → 期权设置」。</p>
|
||||
<p class="muted options-hint">报价单位为每 1 ETH/BTC;1 张 = 0.01。卖一/买一列为 <strong>价格/张数</strong>;卖一无挂单时以标记价估算并标 <strong>~</strong>。链展示近 <span id="opt-chain-dte">14</span> 日到期,标注实值/虚值;<strong>到期平衡</strong>按卖一预估(无卖一按标记价)。资金划转与 USDT/USDC 兑换见「系统设置 → 期权设置」。</p>
|
||||
<div class="form-row options-chain-toolbar">
|
||||
<button type="button" class="btn-secondary opt-uly-btn active" data-uly="ETH">ETH</button>
|
||||
<button type="button" class="btn-secondary opt-uly-btn" data-uly="BTC">BTC</button>
|
||||
@@ -136,4 +136,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/options_panel.js?v=9"></script>
|
||||
<script src="/static/options_panel.js?v=10"></script>
|
||||
|
||||
@@ -90,6 +90,21 @@ def test_estimate_expiry_profit_at_index():
|
||||
assert p2 == round(-12.2 * 0.01, 4)
|
||||
|
||||
|
||||
def test_resolve_chain_quote_estimated_ask():
|
||||
from lib.exchange.okx_options_lib import _resolve_chain_quote
|
||||
|
||||
q = _resolve_chain_quote(
|
||||
ticker={"bidPx": "0.2", "bidSz": "3500"},
|
||||
meta={"tickSz": "0.2"},
|
||||
opt_type="C",
|
||||
strike=1650,
|
||||
index_px=1776,
|
||||
)
|
||||
assert q["ask_estimated"] is True
|
||||
assert q["ask"] is not None
|
||||
assert q["ask"] >= 120
|
||||
|
||||
|
||||
def test_format_quote_liquidity():
|
||||
from lib.options.options_pricing_lib import format_quote_liquidity
|
||||
|
||||
|
||||
Reference in New Issue
Block a user