fix: OKX option quote requires instFamily with instId
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -90,6 +90,35 @@ def _pos_side_from_position(pos: dict[str, Any] | None) -> str | None:
|
||||
return "net"
|
||||
|
||||
|
||||
def inst_family_from_inst_id(inst_id: str) -> str | None:
|
||||
"""从 instId 解析 instFamily,如 ETH-USD_UM-260707-1790-C → ETH-USD_UM。"""
|
||||
parts = (inst_id or "").strip().split("-")
|
||||
if len(parts) < 4:
|
||||
return None
|
||||
return "-".join(parts[:-3])
|
||||
|
||||
|
||||
def fetch_option_instrument_meta(ex: ccxt.okx, inst_id: str) -> dict[str, Any] | None:
|
||||
family = inst_family_from_inst_id(inst_id)
|
||||
if not family:
|
||||
return None
|
||||
try:
|
||||
rows = ex.public_get_public_instruments(
|
||||
{"instType": "OPTION", "instFamily": family, "instId": inst_id}
|
||||
).get("data") or []
|
||||
if rows and isinstance(rows[0], dict):
|
||||
return rows[0]
|
||||
rows = ex.public_get_public_instruments(
|
||||
{"instType": "OPTION", "instFamily": family}
|
||||
).get("data") or []
|
||||
for r in rows:
|
||||
if isinstance(r, dict) and str(r.get("instId")) == inst_id:
|
||||
return r
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _extract_ccy_balance(balance: dict[str, Any], ccy: str) -> float | None:
|
||||
ccy = (ccy or "").upper()
|
||||
if not isinstance(balance, dict):
|
||||
@@ -244,45 +273,48 @@ def build_option_chain(
|
||||
|
||||
|
||||
def quote_option_contract(ex: ccxt.okx, inst_id: str) -> dict[str, Any]:
|
||||
meta_rows = ex.public_get_public_instruments(
|
||||
{"instType": "OPTION", "instId": inst_id}
|
||||
).get("data") or []
|
||||
if not meta_rows:
|
||||
return {"ok": False, "msg": "合约不存在"}
|
||||
meta = meta_rows[0]
|
||||
t_rows = ex.public_get_market_ticker({"instId": inst_id}).get("data") or []
|
||||
t = t_rows[0] if t_rows else {}
|
||||
ask = _safe_float(t.get("askPx"))
|
||||
bid = _safe_float(t.get("bidPx"))
|
||||
if ask is None or bid is None:
|
||||
book_bid, book_ask = _fetch_book_bid_ask(ex, inst_id)
|
||||
if ask is None:
|
||||
ask = book_ask
|
||||
if bid is None:
|
||||
bid = book_bid
|
||||
mark = _safe_float(t.get("markPx"))
|
||||
tick_sz = meta.get("tickSz")
|
||||
if ask is None and mark is not None:
|
||||
ask = round_option_px(mark, tick_sz, "buy")
|
||||
if bid is None and mark is not None:
|
||||
bid = round_option_px(mark, tick_sz, "sell")
|
||||
uly = str(meta.get("uly") or "")
|
||||
idx = fetch_index_price(ex, uly)
|
||||
return {
|
||||
"ok": True,
|
||||
"inst_id": inst_id,
|
||||
"meta": meta,
|
||||
"ask": ask,
|
||||
"bid": bid,
|
||||
"mark": mark,
|
||||
"index_px": idx,
|
||||
"ct_mult": _safe_float(meta.get("ctMult")) or 0.01,
|
||||
"min_sz": int(_safe_float(meta.get("minSz")) or 1),
|
||||
"tick_sz": meta.get("tickSz"),
|
||||
"strike": _safe_float(meta.get("stk")),
|
||||
"opt_type": meta.get("optType"),
|
||||
"exp_time": meta.get("expTime"),
|
||||
}
|
||||
inst_id = (inst_id or "").strip()
|
||||
if not inst_id:
|
||||
return {"ok": False, "msg": "缺少 inst_id"}
|
||||
try:
|
||||
meta = fetch_option_instrument_meta(ex, inst_id)
|
||||
if not meta:
|
||||
return {"ok": False, "msg": "合约不存在"}
|
||||
t_rows = ex.public_get_market_ticker({"instId": inst_id}).get("data") or []
|
||||
t = t_rows[0] if t_rows else {}
|
||||
ask = _safe_float(t.get("askPx"))
|
||||
bid = _safe_float(t.get("bidPx"))
|
||||
if ask is None or bid is None:
|
||||
book_bid, book_ask = _fetch_book_bid_ask(ex, inst_id)
|
||||
if ask is None:
|
||||
ask = book_ask
|
||||
if bid is None:
|
||||
bid = book_bid
|
||||
mark = _safe_float(t.get("markPx"))
|
||||
tick_sz = meta.get("tickSz")
|
||||
if ask is None and mark is not None:
|
||||
ask = round_option_px(mark, tick_sz, "buy")
|
||||
if bid is None and mark is not None:
|
||||
bid = round_option_px(mark, tick_sz, "sell")
|
||||
uly = str(meta.get("uly") or "")
|
||||
idx = fetch_index_price(ex, uly)
|
||||
return {
|
||||
"ok": True,
|
||||
"inst_id": inst_id,
|
||||
"meta": meta,
|
||||
"ask": ask,
|
||||
"bid": bid,
|
||||
"mark": mark,
|
||||
"index_px": idx,
|
||||
"ct_mult": _safe_float(meta.get("ctMult")) or 0.01,
|
||||
"min_sz": int(_safe_float(meta.get("minSz")) or 1),
|
||||
"tick_sz": tick_sz,
|
||||
"strike": _safe_float(meta.get("stk")),
|
||||
"opt_type": meta.get("optType"),
|
||||
"exp_time": meta.get("expTime"),
|
||||
}
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
def place_option_limit_order(
|
||||
|
||||
@@ -5,7 +5,12 @@ from lib.options.options_pricing_lib import (
|
||||
sheets_from_eth_amount,
|
||||
total_premium,
|
||||
)
|
||||
from lib.exchange.okx_options_lib import format_option_px, round_option_px
|
||||
from lib.exchange.okx_options_lib import format_option_px, inst_family_from_inst_id, round_option_px
|
||||
|
||||
|
||||
def test_inst_family_from_inst_id():
|
||||
assert inst_family_from_inst_id("ETH-USD_UM-260707-1790-C") == "ETH-USD_UM"
|
||||
assert inst_family_from_inst_id("BTC-USD-260925-60000-C") == "BTC-USD"
|
||||
|
||||
|
||||
def test_round_option_px():
|
||||
|
||||
Reference in New Issue
Block a user