Show bid/ask liquidity as price per sheet in options chain.
Display OKX askSz and bidSz beside top-of-book prices in the chain table and order panel using price/sheets format. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2588,6 +2588,10 @@ html[data-theme="light"] .settings-side-export-label {
|
|||||||
.options-page-wrap .options-strike-table code {
|
.options-page-wrap .options-strike-table code {
|
||||||
font-size: 0.66rem;
|
font-size: 0.66rem;
|
||||||
}
|
}
|
||||||
|
.opt-px-sz {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.opt-be-dist-up {
|
.opt-be-dist-up {
|
||||||
color: #5ee89a;
|
color: #5ee89a;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,15 @@
|
|||||||
"指数 " + state.underlying + " ≈ " + fmt(idx, 2) + " · 实值=价内 · 虚值=价外";
|
"指数 " + state.underlying + " ≈ " + fmt(idx, 2) + " · 实值=价内 · 虚值=价外";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fmtPxSz(px, sz) {
|
||||||
|
if (px === null || px === undefined || Number.isNaN(Number(px))) return "—";
|
||||||
|
const price = Number(px).toFixed(4).replace(/\.?0+$/, "");
|
||||||
|
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);
|
||||||
|
return price + "/" + size;
|
||||||
|
}
|
||||||
|
|
||||||
function fmtDist(v) {
|
function fmtDist(v) {
|
||||||
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
||||||
const n = Number(v);
|
const n = Number(v);
|
||||||
@@ -197,8 +206,8 @@
|
|||||||
"<td>" + c.strike + "</td>" +
|
"<td>" + c.strike + "</td>" +
|
||||||
"<td>" + moneynessBadge(c) + "</td>" +
|
"<td>" + moneynessBadge(c) + "</td>" +
|
||||||
"<td><code>" + c.inst_id + "</code></td>" +
|
"<td><code>" + c.inst_id + "</code></td>" +
|
||||||
"<td>" + fmt(c.ask, 4) + "</td>" +
|
"<td class=\"opt-px-sz\">" + fmtPxSz(c.ask, c.ask_sz) + "</td>" +
|
||||||
"<td>" + fmt(c.bid, 4) + "</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>" + (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>" +
|
'<td class="' + distBeClass(c.dist_expiry_be) + '">' + fmtDist(c.dist_expiry_be) + "</td>" +
|
||||||
'<td class="opt-row-actions">' +
|
'<td class="opt-row-actions">' +
|
||||||
@@ -229,7 +238,9 @@
|
|||||||
function fillOrderPanel(d) {
|
function fillOrderPanel(d) {
|
||||||
const sz = d.sizing || {};
|
const sz = d.sizing || {};
|
||||||
document.getElementById("opt-order-inst").textContent = d.inst_id || state.selectedInst || "";
|
document.getElementById("opt-order-inst").textContent = d.inst_id || state.selectedInst || "";
|
||||||
document.getElementById("opt-order-ask").textContent = fmt(d.ask, 4);
|
document.getElementById("opt-order-ask").textContent = fmtPxSz(d.ask, d.ask_sz);
|
||||||
|
const bidEl = document.getElementById("opt-order-bid");
|
||||||
|
if (bidEl) bidEl.textContent = fmtPxSz(d.bid, d.bid_sz);
|
||||||
document.getElementById("opt-order-sheets").textContent = sz.sheets != null ? sz.sheets : "—";
|
document.getElementById("opt-order-sheets").textContent = sz.sheets != null ? sz.sheets : "—";
|
||||||
document.getElementById("opt-order-eth").textContent = sz.eth_amount != null ? sz.eth_amount : "—";
|
document.getElementById("opt-order-eth").textContent = sz.eth_amount != null ? sz.eth_amount : "—";
|
||||||
document.getElementById("opt-order-premium").textContent = sz.total_premium != null ? fmt(sz.total_premium, 4) + " USDC" : "—";
|
document.getElementById("opt-order-premium").textContent = sz.total_premium != null ? fmt(sz.total_premium, 4) + " USDC" : "—";
|
||||||
|
|||||||
@@ -116,18 +116,27 @@ def format_option_px(px: float, tick_sz: Any) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _fetch_book_bid_ask(ex: ccxt.okx, inst_id: str) -> tuple[float | None, float | None]:
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_book_top(
|
||||||
|
ex: ccxt.okx, inst_id: str
|
||||||
|
) -> tuple[float | None, float | None, float | None, float | None]:
|
||||||
try:
|
try:
|
||||||
rows = ex.public_get_market_books({"instId": inst_id, "sz": "1"}).get("data") or []
|
rows = ex.public_get_market_books({"instId": inst_id, "sz": "1"}).get("data") or []
|
||||||
if not rows:
|
if not rows:
|
||||||
return None, None
|
return None, None, None, None
|
||||||
row = rows[0]
|
row = rows[0]
|
||||||
asks = row.get("asks") or []
|
asks = row.get("asks") or []
|
||||||
bids = row.get("bids") or []
|
bids = row.get("bids") or []
|
||||||
ask = _safe_float(asks[0][0]) if asks else None
|
ask = _safe_float(asks[0][0]) if asks else None
|
||||||
bid = _safe_float(bids[0][0]) if bids else None
|
bid = _safe_float(bids[0][0]) if bids else None
|
||||||
return bid, ask
|
ask_sz = _safe_float(asks[0][1]) if asks and len(asks[0]) > 1 else None
|
||||||
|
bid_sz = _safe_float(bids[0][1]) if bids and len(bids[0]) > 1 else None
|
||||||
|
return bid, ask, bid_sz, ask_sz
|
||||||
except Exception:
|
except Exception:
|
||||||
return None, None
|
return None, None, None, None
|
||||||
|
|
||||||
|
|
||||||
def _pos_side_from_position(pos: dict[str, Any] | None) -> str | None:
|
def _pos_side_from_position(pos: dict[str, Any] | None) -> str | None:
|
||||||
@@ -338,6 +347,8 @@ def build_option_chain(
|
|||||||
ask = _safe_float(t.get("askPx"))
|
ask = _safe_float(t.get("askPx"))
|
||||||
bid = _safe_float(t.get("bidPx"))
|
bid = _safe_float(t.get("bidPx"))
|
||||||
mark = _safe_float(t.get("markPx"))
|
mark = _safe_float(t.get("markPx"))
|
||||||
|
ask_sz = _safe_float(t.get("askSz"))
|
||||||
|
bid_sz = _safe_float(t.get("bidSz"))
|
||||||
if ask is None and bid is None and mark is None:
|
if ask is None and bid is None and mark is None:
|
||||||
continue
|
continue
|
||||||
expiry_be = expiry_breakeven_from_ask(
|
expiry_be = expiry_breakeven_from_ask(
|
||||||
@@ -356,6 +367,8 @@ def build_option_chain(
|
|||||||
"exp_time": exp_ms,
|
"exp_time": exp_ms,
|
||||||
"ask": ask,
|
"ask": ask,
|
||||||
"bid": bid,
|
"bid": bid,
|
||||||
|
"ask_sz": ask_sz,
|
||||||
|
"bid_sz": bid_sz,
|
||||||
"mark_px": mark,
|
"mark_px": mark,
|
||||||
"expiry_be_px": expiry_be,
|
"expiry_be_px": expiry_be,
|
||||||
"dist_expiry_be": idx_distance_to_be(idx, expiry_be),
|
"dist_expiry_be": idx_distance_to_be(idx, expiry_be),
|
||||||
@@ -385,12 +398,18 @@ def quote_option_contract(ex: ccxt.okx, inst_id: str) -> dict[str, Any]:
|
|||||||
t = t_rows[0] if t_rows else {}
|
t = t_rows[0] if t_rows else {}
|
||||||
ask = _safe_float(t.get("askPx"))
|
ask = _safe_float(t.get("askPx"))
|
||||||
bid = _safe_float(t.get("bidPx"))
|
bid = _safe_float(t.get("bidPx"))
|
||||||
if ask is None or bid is None:
|
ask_sz = _safe_float(t.get("askSz"))
|
||||||
book_bid, book_ask = _fetch_book_bid_ask(ex, inst_id)
|
bid_sz = _safe_float(t.get("bidSz"))
|
||||||
|
if ask is None or bid is None or ask_sz is None or bid_sz is None:
|
||||||
|
book_bid, book_ask, book_bid_sz, book_ask_sz = _fetch_book_top(ex, inst_id)
|
||||||
if ask is None:
|
if ask is None:
|
||||||
ask = book_ask
|
ask = book_ask
|
||||||
if bid is None:
|
if bid is None:
|
||||||
bid = book_bid
|
bid = book_bid
|
||||||
|
if ask_sz is None:
|
||||||
|
ask_sz = book_ask_sz
|
||||||
|
if bid_sz is None:
|
||||||
|
bid_sz = book_bid_sz
|
||||||
mark = _safe_float(t.get("markPx"))
|
mark = _safe_float(t.get("markPx"))
|
||||||
tick_sz = meta.get("tickSz")
|
tick_sz = meta.get("tickSz")
|
||||||
if ask is None and mark is not None:
|
if ask is None and mark is not None:
|
||||||
@@ -413,6 +432,8 @@ def quote_option_contract(ex: ccxt.okx, inst_id: str) -> dict[str, Any]:
|
|||||||
"meta": meta,
|
"meta": meta,
|
||||||
"ask": ask,
|
"ask": ask,
|
||||||
"bid": bid,
|
"bid": bid,
|
||||||
|
"ask_sz": ask_sz,
|
||||||
|
"bid_sz": bid_sz,
|
||||||
"mark": mark,
|
"mark": mark,
|
||||||
"index_px": idx,
|
"index_px": idx,
|
||||||
"expiry_be_px": expiry_be,
|
"expiry_be_px": expiry_be,
|
||||||
|
|||||||
@@ -28,6 +28,24 @@ def premium_per_sheet(quote_per_unit: float, ct_mult: float = 0.01) -> float:
|
|||||||
return float(quote_per_unit) * float(ct_mult)
|
return float(quote_per_unit) * float(ct_mult)
|
||||||
|
|
||||||
|
|
||||||
|
def format_quote_liquidity(px: float | None, sz: float | None, *, px_decimals: int = 4) -> str | None:
|
||||||
|
"""盘口展示:价格/张数,如 17.2/150。"""
|
||||||
|
if px is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
price = f"{float(px):.{px_decimals}f}".rstrip("0").rstrip(".")
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
if sz is None:
|
||||||
|
return price
|
||||||
|
try:
|
||||||
|
s = float(sz)
|
||||||
|
size = str(int(s)) if abs(s - int(s)) < 1e-9 else str(s).rstrip("0").rstrip(".")
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return price
|
||||||
|
return f"{price}/{size}"
|
||||||
|
|
||||||
|
|
||||||
def total_premium(quote_per_unit: float, eth_amount: float, ct_mult: float = 0.01) -> float:
|
def total_premium(quote_per_unit: float, eth_amount: float, ct_mult: float = 0.01) -> float:
|
||||||
return float(quote_per_unit) * float(eth_amount)
|
return float(quote_per_unit) * float(eth_amount)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div class="options-dual-grid">
|
<div class="options-dual-grid">
|
||||||
<div class="card options-order-card">
|
<div class="card options-order-card">
|
||||||
<h2>期权下单</h2>
|
<h2>期权下单</h2>
|
||||||
<p class="muted options-hint">报价单位为每 1 ETH/BTC;1 张 = 0.01。链展示近 <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>。链展示近 <span id="opt-chain-dte">14</span> 日到期,标注实值/虚值;<strong>到期平衡</strong>按卖一预估(无卖一按标记价)。资金划转与 USDT/USDC 兑换见「系统设置 → 期权设置」。</p>
|
||||||
<div class="form-row options-chain-toolbar">
|
<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 active" data-uly="ETH">ETH</button>
|
||||||
<button type="button" class="btn-secondary opt-uly-btn" data-uly="BTC">BTC</button>
|
<button type="button" class="btn-secondary opt-uly-btn" data-uly="BTC">BTC</button>
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
<th>行权价</th>
|
<th>行权价</th>
|
||||||
<th>类型</th>
|
<th>类型</th>
|
||||||
<th>合约</th>
|
<th>合约</th>
|
||||||
<th>卖一</th>
|
<th>卖一/张</th>
|
||||||
<th>买一</th>
|
<th>买一/张</th>
|
||||||
<th>到期平衡</th>
|
<th>到期平衡</th>
|
||||||
<th>距平衡</th>
|
<th>距平衡</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
@@ -41,7 +41,8 @@
|
|||||||
<h3 class="opt-order-title">下单</h3>
|
<h3 class="opt-order-title">下单</h3>
|
||||||
<div id="opt-order-inst" class="options-order-inst"></div>
|
<div id="opt-order-inst" class="options-order-inst"></div>
|
||||||
<div class="options-order-grid">
|
<div class="options-order-grid">
|
||||||
<div><span class="k">卖一(每1币)</span><span id="opt-order-ask" class="v">—</span></div>
|
<div><span class="k">卖一/张</span><span id="opt-order-ask" class="v">—</span></div>
|
||||||
|
<div><span class="k">买一/张</span><span id="opt-order-bid" class="v">—</span></div>
|
||||||
<div><span class="k">张数</span><span id="opt-order-sheets" class="v">—</span></div>
|
<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">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-premium" class="v">—</span></div>
|
||||||
|
|||||||
@@ -67,6 +67,15 @@ def test_option_moneyness():
|
|||||||
assert option_moneyness_label("otm") == "虚值"
|
assert option_moneyness_label("otm") == "虚值"
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_quote_liquidity():
|
||||||
|
from lib.options.options_pricing_lib import format_quote_liquidity
|
||||||
|
|
||||||
|
assert format_quote_liquidity(17.2, 150) == "17.2/150"
|
||||||
|
assert format_quote_liquidity(817.6, 11) == "817.6/11"
|
||||||
|
assert format_quote_liquidity(15.6, None) == "15.6"
|
||||||
|
assert format_quote_liquidity(None, 10) is None
|
||||||
|
|
||||||
|
|
||||||
def test_expiry_breakeven_from_ask():
|
def test_expiry_breakeven_from_ask():
|
||||||
from lib.options.options_pricing_lib import expiry_breakeven_from_ask
|
from lib.options.options_pricing_lib import expiry_breakeven_from_ask
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user