Enrich sim option positions with public index and mark prices.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+80
-15
@@ -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
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user