Add OKX options expiry and close breakeven to hub monitor and dashboard.

Expose bePx-based expiry balance and mark-to-close breakeven on positions so monitor and dashboard can show both labels per contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 16:10:03 +08:00
parent 7bbb5663c9
commit 3570a6900e
8 changed files with 269 additions and 9 deletions
+3
View File
@@ -324,6 +324,9 @@
'<div class="pos-grid">' +
'<div class="pos-cell"><span class="pos-label">开仓均价</span><span class="pos-value">' + fmt(p.avg_px, 4) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">' + fmt(p.mark_px, 4) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">指数价</span><span class="pos-value">' + fmt(p.idx_px, 0) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">到期平衡</span><span class="pos-value">' + fmt(p.expiry_be_px, 0) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">平掉回本</span><span class="pos-value">' + fmt(p.close_be_px, 0) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ' + uplCls + '">' + fmt(p.upl, 4) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
(p.upl_ratio_pct != null ? p.upl_ratio_pct + "%" : "—") + "</span></div>" +
+32 -2
View File
@@ -666,23 +666,53 @@ def transfer_main_sub_account(
def format_position_row(pos: dict[str, Any], ct_mult: float = 0.01) -> dict[str, Any]:
from lib.options.options_pricing_lib import (
close_breakeven_idx,
expiry_breakeven_px,
idx_distance_to_be,
)
sheets = _safe_float(pos.get("pos")) or 0.0
avg = _safe_float(pos.get("avgPx"))
mark = _safe_float(pos.get("markPx"))
upl = _safe_float(pos.get("upl"))
upl_ratio = _safe_float(pos.get("uplRatio"))
idx_px = _safe_float(pos.get("idxPx"))
opt_type = pos.get("optType")
strike = _safe_float(pos.get("stk"))
delta_pa = _safe_float(pos.get("deltaPA"))
expiry_be = expiry_breakeven_px(
opt_type=str(opt_type or ""),
strike=strike,
avg_px=avg,
be_px_api=_safe_float(pos.get("bePx")),
)
close_be = close_breakeven_idx(
opt_type=str(opt_type or ""),
idx_px=idx_px,
mark_px=mark,
avg_px=avg,
delta_pa=delta_pa,
pos=sheets,
ct_mult=ct_mult,
)
return {
"inst_id": pos.get("instId"),
"pos": sheets,
"eth_amount": round(abs(sheets) * ct_mult, 8),
"avg_px": avg,
"mark_px": mark,
"idx_px": idx_px,
"upl": upl,
"upl_ratio_pct": round(upl_ratio * 100, 2) if upl_ratio is not None else None,
"exp_time": pos.get("expTime"),
"opt_type": pos.get("optType"),
"strike": _safe_float(pos.get("stk")),
"opt_type": opt_type,
"strike": strike,
"avail_pos": _safe_float(pos.get("availPos")),
"expiry_be_px": expiry_be,
"close_be_px": close_be,
"dist_expiry_be": idx_distance_to_be(idx_px, expiry_be),
"dist_close_be": idx_distance_to_be(idx_px, close_be),
"raw": pos,
}
+72
View File
@@ -132,3 +132,75 @@ def option_moneyness(*, opt_type: str, strike: float, index_px: float) -> str:
def option_moneyness_label(moneyness: str) -> str:
return {"itm": "实值", "otm": "虚值", "atm": "平值"}.get((moneyness or "").lower(), "")
def expiry_breakeven_px(
*,
opt_type: str,
strike: float | None,
avg_px: float | None,
be_px_api: float | None = None,
) -> float | None:
"""到期平衡点:持有至到期时标的指数盈亏为 0 的价格。优先 OKX bePx。"""
if be_px_api is not None and be_px_api > 0:
return round(float(be_px_api), 2)
if strike is None or avg_px is None:
return None
o = (opt_type or "").upper()
if o == "C":
return round(strike + avg_px, 2)
if o == "P":
return round(strike - avg_px, 2)
return None
def close_breakeven_idx(
*,
opt_type: str,
idx_px: float | None,
mark_px: float | None,
avg_px: float | None,
delta_pa: float | None = None,
pos: float = 0,
ct_mult: float = 0.01,
) -> float | None:
"""
平掉回本:标的指数达到该价位时,按标记价平仓近似盈亏为 0。
优先用 deltaPA 线性外推,否则用时间价值近似(适合短期轻度实值)。
"""
if idx_px is None or mark_px is None or avg_px is None:
return None
eth_amt = abs(float(pos)) * float(ct_mult)
if eth_amt > 1e-12 and delta_pa is not None and abs(float(delta_pa)) > 1e-12:
slope = float(delta_pa) / eth_amt
return round(float(idx_px) + (float(avg_px) - float(mark_px)) / slope, 2)
o = (opt_type or "").upper()
if o == "C":
return round(float(idx_px) + float(avg_px) - float(mark_px), 2)
if o == "P":
return round(float(idx_px) + float(mark_px) - float(avg_px), 2)
return None
def idx_distance_to_be(idx_px: float | None, be_px: float | None) -> float | None:
"""指数距平衡点(正=指数需上涨才到平衡点)。"""
if idx_px is None or be_px is None:
return None
return round(float(be_px) - float(idx_px), 2)
def format_options_breakeven_line(
*,
expiry_be_px: float | None,
close_be_px: float | None,
idx_px: float | None = None,
) -> str:
"""持仓摘要行:到期平衡 / 平掉回本。"""
parts: list[str] = []
if expiry_be_px is not None:
parts.append(f"到期平衡{expiry_be_px:.0f}")
if close_be_px is not None:
parts.append(f"平掉回本{close_be_px:.0f}")
if idx_px is not None and parts:
return " ".join(parts) + f"(指数{idx_px:.0f}"
return " ".join(parts)