diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index e842d25..8dd6e68 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -614,7 +614,13 @@ if (strike == null || per == null) return "—"; const k = Number(strike); const d = Number(per); - if (!Number.isFinite(k) || !Number.isFinite(d)) return "—"; + if (!Number.isFinite(k) || !Number.isFinite(d) || d <= 0) return "—"; + if (isCoinMarginMode()) { + if (d >= 1) return "—"; + const lo = Math.round((k / (1 + d)) * 10) / 10; + const hi = Math.round((k / (1 - d)) * 10) / 10; + return lo.toFixed(0) + " ~ " + hi.toFixed(0); + } const lo = Math.round((k - d) * 10) / 10; const hi = Math.round((k + d) * 10) / 10; return lo.toFixed(0) + " ~ " + hi.toFixed(0); @@ -623,6 +629,9 @@ function formatStraddlePremiumCell(callAsk, putAsk) { const per = straddleAskPerUnit(callAsk, putAsk); if (per == null) return '不可双买'; + if (isCoinMarginMode()) { + return fmt(per, 4) + " " + (state.underlying || "ETH"); + } return fmtUsdc(per) + " USDC"; } diff --git a/lib/exchange/okx_options_lib.py b/lib/exchange/okx_options_lib.py index 11998f2..bd809a4 100644 --- a/lib/exchange/okx_options_lib.py +++ b/lib/exchange/okx_options_lib.py @@ -889,6 +889,7 @@ def build_option_chain( strike=strike, ask_px=ask, mark_px=mark, + quote_in_coin="_UM" not in (family or "").upper(), ) mny = option_moneyness(opt_type=opt_type, strike=strike, index_px=idx) exp_key = str(exp_ms) @@ -1028,11 +1029,18 @@ def quote_option_contract(ex: ccxt.okx, inst_id: str) -> dict[str, Any]: idx = fetch_index_price(ex, uly) opt_type = meta.get("optType") strike = _safe_float(meta.get("stk")) + try: + from lib.options.options_margin_mode_lib import margin_mode_from_inst_id + + quote_in_coin = margin_mode_from_inst_id(inst_id) == "coin" + except Exception: + quote_in_coin = "USD_UM" not in inst_id.upper() and "-USD-" in inst_id.upper() expiry_be = expiry_breakeven_from_ask( opt_type=str(opt_type or ""), strike=strike, ask_px=book_ask if can_open else None, mark_px=mark, + quote_in_coin=quote_in_coin, ) return { "ok": True, @@ -1824,11 +1832,18 @@ def format_position_row( round(total_premium(avg, eth_amount), 8) if avg is not None and eth_amount > 0 else None ) delta_pa = _safe_float(pos.get("deltaPA")) + try: + from lib.options.options_margin_mode_lib import margin_mode_from_inst_id + + quote_in_coin = margin_mode_from_inst_id(inst_id) == "coin" + except Exception: + quote_in_coin = "USD_UM" not in inst_id.upper() and "-USD-" in inst_id.upper() expiry_be = expiry_breakeven_px( opt_type=str(opt_type or ""), strike=strike, avg_px=avg, be_px_api=_safe_float(pos.get("bePx")), + quote_in_coin=quote_in_coin, ) close_be = close_breakeven_idx( opt_type=str(opt_type or ""), diff --git a/lib/options/options_pricing_lib.py b/lib/options/options_pricing_lib.py index d591f16..5962086 100644 --- a/lib/options/options_pricing_lib.py +++ b/lib/options/options_pricing_lib.py @@ -438,10 +438,16 @@ def expiry_breakeven_from_ask( strike: float | None, ask_px: float | None, mark_px: float | None = None, + quote_in_coin: bool = False, ) -> float | None: """买入前预估到期平衡:权利金按卖一;无卖一时回退标记价.""" prem = ask_px if ask_px is not None and ask_px > 0 else mark_px - return expiry_breakeven_px(opt_type=opt_type, strike=strike, avg_px=prem) + return expiry_breakeven_px( + opt_type=opt_type, + strike=strike, + avg_px=prem, + quote_in_coin=quote_in_coin, + ) def expiry_breakeven_px( @@ -450,17 +456,37 @@ def expiry_breakeven_px( strike: float | None, avg_px: float | None, be_px_api: float | None = None, + quote_in_coin: bool = False, ) -> float | None: - """到期平衡点:持有至到期时标的指数盈亏为 0 的价格.优先 OKX bePx.""" + """到期平衡点:持有至到期时标的指数盈亏为 0 的价格.优先 OKX bePx. + + USDC: Call K+p / Put K-p (p 为美元报价). + 币本位: Call K/(1-p) / Put K/(1+p) (p 为币报价,与卖一同单位). + """ 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 + try: + k = float(strike) + p = float(avg_px) + except (TypeError, ValueError): + return None + if p <= 0: + return round(k, 2) o = (opt_type or "").upper() + if quote_in_coin: + if o == "C": + if p >= 1: + return None + return round(k / (1.0 - p), 2) + if o == "P": + return round(k / (1.0 + p), 2) + return None if o == "C": - return round(strike + avg_px, 2) + return round(k + p, 2) if o == "P": - return round(strike - avg_px, 2) + return round(k - p, 2) return None @@ -618,20 +644,30 @@ def straddle_premium_total( def straddle_breakeven_band( strike: float | None, combined_ask_per_unit: float | None, + *, + quote_in_coin: bool = False, ) -> tuple[float | None, float | None]: """跨式到期平衡带:下平衡 ~ 上平衡(按双卖一报价和).""" if strike is None or combined_ask_per_unit is None: return None, None k = float(strike) d = float(combined_ask_per_unit) + if d <= 0: + return None, None + if quote_in_coin: + if d >= 1: + return None, None + return round(k / (1.0 + d), 2), round(k / (1.0 - d), 2) return round(k - d, 2), round(k + d, 2) def format_straddle_band( strike: float | None, combined_ask_per_unit: float | None, + *, + quote_in_coin: bool = False, ) -> str: - lo, hi = straddle_breakeven_band(strike, combined_ask_per_unit) + lo, hi = straddle_breakeven_band(strike, combined_ask_per_unit, quote_in_coin=quote_in_coin) if lo is None or hi is None: return "" return f"{lo:.0f} ~ {hi:.0f}"