From aa1a2da2b7a3c5b52344b9bd51c1f3d3f8834fd5 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 23 Aug 2026 08:21:31 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=9D=E5=B9=B3=E8=A1=A1=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E8=A1=8C=E6=9D=83=E4=BB=B7=E5=88=B0=E5=B9=B3=E8=A1=A1=E4=BB=B7?= =?UTF-8?q?=E7=9A=84=E4=BB=B7=E5=B7=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call 为 BE−K、Put 为 K−BE;链/下单/持仓统一;顺带修复 format_position_row 中 row_mode 引用顺序。 Co-authored-by: Cursor --- lib/common/static/options_panel.js | 5 ++--- lib/exchange/okx_options_lib.py | 28 +++++++++++++----------- lib/options/options_pricing_lib.py | 19 ++++++++++++++++ lib/options/templates/options_panel.html | 2 +- tests/test_options_pricing.py | 9 +++++++- 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 4b05140..9b597d7 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -1434,15 +1434,14 @@ function fmtDist(v) { if (v === null || v === undefined || Number.isNaN(Number(v))) return "—"; const n = Number(v); - const sign = n > 0 ? "+" : ""; - return sign + n.toFixed(1); + if (n < 0) return n.toFixed(1); + return n.toFixed(1); } function distBeClass(v) { if (v === null || v === undefined || Number.isNaN(Number(v))) return ""; const n = Number(v); if (n > 0) return "opt-be-dist-up"; - if (n < 0) return "opt-be-dist-down"; return ""; } diff --git a/lib/exchange/okx_options_lib.py b/lib/exchange/okx_options_lib.py index 04edc80..36d9051 100644 --- a/lib/exchange/okx_options_lib.py +++ b/lib/exchange/okx_options_lib.py @@ -17,6 +17,7 @@ from lib.options.options_pricing_lib import ( is_shallow_itm, option_moneyness, option_moneyness_label, + strike_distance_to_be, ) _OKX_OPTION_ERR_ZH: dict[str, str] = { @@ -903,7 +904,7 @@ def build_option_chain( "mark_px": mark, "ask_estimated": q["ask_estimated"], "expiry_be_px": expiry_be, - "dist_expiry_be": idx_distance_to_be(idx, expiry_be), + "dist_expiry_be": strike_distance_to_be(strike, expiry_be, opt_type=opt_type), "moneyness": mny, "moneyness_label": option_moneyness_label(mny), "ct_mult": _safe_float(meta.get("ctMult")) or 0.01, @@ -1050,7 +1051,7 @@ def quote_option_contract(ex: ccxt.okx, inst_id: str) -> dict[str, Any]: "open_block_msg": "" if can_open else open_block_msg, "index_px": idx, "expiry_be_px": expiry_be, - "dist_expiry_be": idx_distance_to_be(idx, expiry_be), + "dist_expiry_be": strike_distance_to_be(strike, expiry_be, opt_type=str(opt_type or "")), "ct_mult": _safe_float(meta.get("ctMult")) or 0.01, "min_sz": int(_safe_float(meta.get("minSz")) or 1), "tick_sz": tick_sz, @@ -1792,6 +1793,7 @@ def format_position_row( close_breakeven_idx, expiry_breakeven_px, idx_distance_to_be, + strike_distance_to_be, total_premium, ) @@ -1809,6 +1811,16 @@ def format_position_row( opt_type = parsed_type if strike is None: strike = parsed_strike + try: + from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode + + row_mode = margin_mode_from_inst_id(inst_id) if inst_id else "usdc" + underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH" + premium_ccy = premium_ccy_for_mode(row_mode, underly) + except Exception: + row_mode = "usdc" + underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH" + premium_ccy = "USDC" eth_amount = round(abs(sheets) * ct_mult, 8) premium_paid = ( round(total_premium(avg, eth_amount), 8) if avg is not None and eth_amount > 0 else None @@ -1832,16 +1844,6 @@ def format_position_row( ct_mult=ct_mult, ) exp_time_ms = normalize_option_exp_ms(pos.get("expTime"), inst_id) - try: - from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode - - row_mode = margin_mode_from_inst_id(inst_id) if inst_id else "usdc" - underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH" - premium_ccy = premium_ccy_for_mode(row_mode, underly) - except Exception: - row_mode = "usdc" - underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH" - premium_ccy = "USDC" return { "inst_id": inst_id or pos.get("instId"), "pos": sheets, @@ -1867,7 +1869,7 @@ def format_position_row( "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_expiry_be": strike_distance_to_be(strike, expiry_be, opt_type=str(opt_type or "")), "dist_close_be": idx_distance_to_be(idx_px, close_be), "raw": pos, } diff --git a/lib/options/options_pricing_lib.py b/lib/options/options_pricing_lib.py index f679af7..e392b9a 100644 --- a/lib/options/options_pricing_lib.py +++ b/lib/options/options_pricing_lib.py @@ -531,6 +531,25 @@ def idx_distance_to_be(idx_px: float | None, be_px: float | None) -> float | Non return round(float(be_px) - float(idx_px), 2) +def strike_distance_to_be( + strike: float | None, + be_px: float | None, + *, + opt_type: str | None = None, +) -> float | None: + """行权价到到期平衡价的价差(Call:BE−K, Put:K−BE).""" + if strike is None or be_px is None: + return None + k = float(strike) + be = float(be_px) + o = (opt_type or "").upper() + if o == "C": + return round(be - k, 2) + if o == "P": + return round(k - be, 2) + return round(abs(be - k), 2) + + def format_options_breakeven_line( *, expiry_be_px: float | None, diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index 99fcc3c..076ae2f 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -380,4 +380,4 @@ - + diff --git a/tests/test_options_pricing.py b/tests/test_options_pricing.py index b0d6cd4..a6ee8e2 100644 --- a/tests/test_options_pricing.py +++ b/tests/test_options_pricing.py @@ -349,6 +349,13 @@ def test_expiry_breakeven_coin_margin(): ) == round(2425 / (1 - 0.0187), 2) +def test_strike_distance_to_be(): + from lib.options.options_pricing_lib import strike_distance_to_be + + assert strike_distance_to_be(2390, 2430, opt_type="C") == 40.0 + assert strike_distance_to_be(2450, 2411, opt_type="P") == 39.0 + + def test_straddle_breakeven_band_coin(): from lib.options.options_pricing_lib import straddle_breakeven_band @@ -454,4 +461,4 @@ def test_format_position_row_breakeven(): assert row["expiry_be_px"] == 3515.6 assert row["idx_px"] == 3480.0 assert row["close_be_px"] is not None - assert row["dist_expiry_be"] == 35.6 + assert row["dist_expiry_be"] == 15.6