Fix intermittent empty options expiry dropdown after coin select.
Retry instrument fetch, reject empty chains instead of caching them, and ignore stale loadChain races. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -596,13 +596,10 @@ def fetch_option_instruments(
|
||||
ex: ccxt.okx,
|
||||
inst_family: str,
|
||||
) -> list[dict[str, Any]]:
|
||||
try:
|
||||
rows = ex.public_get_public_instruments(
|
||||
{"instType": "OPTION", "instFamily": inst_family}
|
||||
).get("data") or []
|
||||
return [r for r in rows if isinstance(r, dict) and r.get("state") == "live"]
|
||||
except Exception:
|
||||
return []
|
||||
rows = ex.public_get_public_instruments(
|
||||
{"instType": "OPTION", "instFamily": inst_family}
|
||||
).get("data") or []
|
||||
return [r for r in rows if isinstance(r, dict) and r.get("state") == "live"]
|
||||
|
||||
|
||||
def fetch_option_tickers(ex: ccxt.okx, inst_family: str) -> dict[str, dict[str, Any]]:
|
||||
@@ -634,9 +631,27 @@ def build_option_chain(
|
||||
idx = index_px if index_px is not None else fetch_index_price(ex, uly)
|
||||
now_ms = time.time() * 1000
|
||||
max_ms = now_ms + max_dte_days * 86400 * 1000
|
||||
instruments = fetch_option_instruments(ex, family)
|
||||
instruments_err = ""
|
||||
instruments: list[dict[str, Any]] = []
|
||||
for attempt in range(2):
|
||||
try:
|
||||
instruments = fetch_option_instruments(ex, family)
|
||||
instruments_err = ""
|
||||
if instruments:
|
||||
break
|
||||
instruments_err = "期权合约列表为空"
|
||||
except Exception as e:
|
||||
instruments = []
|
||||
instruments_err = str(e) or e.__class__.__name__
|
||||
if attempt == 0:
|
||||
time.sleep(0.35)
|
||||
continue
|
||||
break
|
||||
if attempt == 0 and not instruments:
|
||||
time.sleep(0.35)
|
||||
tickers = fetch_option_tickers(ex, family)
|
||||
expiries: dict[str, list[dict[str, Any]]] = {}
|
||||
skipped_no_index = 0
|
||||
for meta in instruments:
|
||||
try:
|
||||
exp_ms = int(meta.get("expTime") or 0)
|
||||
@@ -646,7 +661,10 @@ def build_option_chain(
|
||||
continue
|
||||
opt_type = str(meta.get("optType") or "")
|
||||
strike = _safe_float(meta.get("stk"))
|
||||
if strike is None or idx is None:
|
||||
if strike is None:
|
||||
continue
|
||||
if idx is None:
|
||||
skipped_no_index += 1
|
||||
continue
|
||||
if itm_only and not is_shallow_itm(
|
||||
opt_type=opt_type,
|
||||
@@ -702,7 +720,25 @@ def build_option_chain(
|
||||
for exp_ms_str, contracts in sorted(expiries.items(), key=lambda x: int(x[0])):
|
||||
contracts.sort(key=lambda c: (c["opt_type"], c["strike"]))
|
||||
exp_list.append({"exp_time": int(exp_ms_str), "contracts": contracts})
|
||||
return {"underlying": u, "index_px": idx, "inst_family": family, "expiries": exp_list}
|
||||
out: dict[str, Any] = {
|
||||
"underlying": u,
|
||||
"index_px": idx,
|
||||
"inst_family": family,
|
||||
"expiries": exp_list,
|
||||
"instruments_count": len(instruments),
|
||||
}
|
||||
if not exp_list:
|
||||
if instruments_err:
|
||||
out["chain_error"] = f"拉取期权合约失败: {instruments_err}"
|
||||
elif idx is None:
|
||||
out["chain_error"] = "指数价获取失败,无法构建期权链"
|
||||
elif skipped_no_index:
|
||||
out["chain_error"] = "指数价缺失,合约已跳过"
|
||||
elif instruments:
|
||||
out["chain_error"] = f"近 {max_dte_days:g} 日内无可用到期(已过滤 {len(instruments)} 个合约)"
|
||||
else:
|
||||
out["chain_error"] = "期权合约列表为空,请稍后刷新"
|
||||
return out
|
||||
|
||||
|
||||
def quote_option_contract(ex: ccxt.okx, inst_id: str) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user