From b9544e53ee1ef020984055f4987d29b300aeca24 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 14 Aug 2026 21:04:00 +0800 Subject: [PATCH] Enrich sim option positions with public index and mark prices. Co-authored-by: Cursor --- lib/sim/broker_lib.py | 95 ++++++++++++++++++++++++++++++++++++------- lib/sim/hooks.py | 2 +- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/lib/sim/broker_lib.py b/lib/sim/broker_lib.py index 9e24afb..db3d2de 100644 --- a/lib/sim/broker_lib.py +++ b/lib/sim/broker_lib.py @@ -552,20 +552,85 @@ class SimBroker: ) return result - def option_positions_okx_rows(self) -> list[dict[str, Any]]: - """对齐 OKX positions 行字段, 供 format_position_row 使用.""" - rows = [] + def option_positions_okx_rows(self, exchange: Any = None) -> list[dict[str, Any]]: + """对齐 OKX positions 行字段, 供 format_position_row 使用. + + 模拟盘补充公开行情的 idxPx / markPx, 否则指数价与平掉回本均为空. + """ + from lib.exchange.okx_options_lib import ( + expiry_ms_from_inst_id, + fetch_index_price, + inst_family_from_inst_id, + option_fields_from_inst_id, + quote_option_contract, + ) + + rows: list[dict[str, Any]] = [] + idx_cache: dict[str, float | None] = {} for p in self.list_option_positions(): - rows.append( - { - "instId": p["inst_id"], - "pos": str(p["sheets"]), - "avgPx": str(p["entry_px"]), - "markPx": str(p["entry_px"]), - "upl": "0", - "uplRatio": "0", - "posSide": "long", - "mgnMode": "isolated", - } - ) + inst_id = str(p["inst_id"] or "") + sheets = float(p["sheets"]) + entry = float(p["entry_px"]) + ct_mult = float(p.get("ct_mult") or 0.01) + opt_type, strike = option_fields_from_inst_id(inst_id) + mark = entry + idx = None + exp_ms = expiry_ms_from_inst_id(inst_id) + + if exchange is not None and inst_id: + try: + q = quote_option_contract(exchange, inst_id) + if q.get("ok"): + raw_mark = q.get("mark") + if raw_mark is None: + raw_mark = q.get("bid") + if raw_mark is not None and float(raw_mark) > 0: + mark = float(raw_mark) + if q.get("index_px") is not None: + idx = float(q["index_px"]) + if q.get("opt_type"): + opt_type = str(q["opt_type"]) + if q.get("strike") is not None: + strike = float(q["strike"]) + if q.get("exp_time") is not None: + try: + exp_ms = int(float(q["exp_time"])) + except (TypeError, ValueError): + pass + except Exception: + pass + if idx is None: + family = inst_family_from_inst_id(inst_id) or "" + uly = family.replace("_UM", "") if family else "" + if uly and uly not in idx_cache: + try: + idx_cache[uly] = fetch_index_price(exchange, uly) + except Exception: + idx_cache[uly] = None + idx = idx_cache.get(uly) + + eth = abs(sheets) * ct_mult + upl = (mark - entry) * eth + prem = float(p.get("premium_paid_usdc") or 0) or (entry * eth) + upl_ratio = (upl / prem) if prem > 1e-12 else 0.0 + row: dict[str, Any] = { + "instId": inst_id, + "pos": str(sheets), + "availPos": str(sheets), + "avgPx": str(entry), + "markPx": str(mark), + "upl": str(round(upl, 4)), + "uplRatio": str(round(upl_ratio, 6)), + "posSide": "long", + "mgnMode": "isolated", + } + if idx is not None: + row["idxPx"] = str(idx) + if opt_type: + row["optType"] = opt_type + if strike is not None: + row["stk"] = str(strike) + if exp_ms: + row["expTime"] = str(exp_ms) + rows.append(row) return rows diff --git a/lib/sim/hooks.py b/lib/sim/hooks.py index f677093..3d755e6 100644 --- a/lib/sim/hooks.py +++ b/lib/sim/hooks.py @@ -340,7 +340,7 @@ def _patch_okx_options_lib(app_module: Any) -> None: def fetch_option_positions(ex): try: if _GET_DB is not None and is_sim_mode(_GET_DB): - return broker().option_positions_okx_rows() + return broker().option_positions_okx_rows(ex) except Exception: pass return _orig_fetch_pos(ex)