fix(options): speed up chain refresh with fast path and non-blocking UI
Skip full REST tickers when WS is warm, seed subscriptions off-request, and keep the old chain visible while refreshing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -30,6 +30,9 @@ _INSTRUMENTS_CACHE: dict[str, dict[str, Any]] = {}
|
||||
_INSTRUMENTS_CACHE_LOCK = threading.Lock()
|
||||
_INSTRUMENTS_CACHE_TTL_SEC = 90.0
|
||||
_INSTRUMENTS_STALE_SEC = 600.0
|
||||
_TICKERS_CACHE: dict[str, dict[str, Any]] = {}
|
||||
_TICKERS_CACHE_LOCK = threading.Lock()
|
||||
_TICKERS_CACHE_TTL_SEC = 8.0
|
||||
|
||||
|
||||
def invalidate_options_balance_cache() -> None:
|
||||
@@ -712,17 +715,40 @@ def fetch_option_instruments(
|
||||
return []
|
||||
|
||||
|
||||
def fetch_option_tickers(ex: ccxt.okx, inst_family: str) -> dict[str, dict[str, Any]]:
|
||||
def fetch_option_tickers(
|
||||
ex: ccxt.okx,
|
||||
inst_family: str,
|
||||
*,
|
||||
force: bool = False,
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
family = (inst_family or "").strip()
|
||||
if not family:
|
||||
return {}
|
||||
now = time.time()
|
||||
with _TICKERS_CACHE_LOCK:
|
||||
cached = _TICKERS_CACHE.get(family)
|
||||
if (
|
||||
not force
|
||||
and cached
|
||||
and now - float(cached.get("updated_at") or 0) < _TICKERS_CACHE_TTL_SEC
|
||||
and isinstance(cached.get("rows"), dict)
|
||||
and cached["rows"]
|
||||
):
|
||||
return dict(cached["rows"])
|
||||
|
||||
out: dict[str, dict[str, Any]] = {}
|
||||
last_err: BaseException | None = None
|
||||
for attempt in range(3):
|
||||
try:
|
||||
rows = ex.public_get_market_tickers(
|
||||
{"instType": "OPTION", "instFamily": inst_family}
|
||||
{"instType": "OPTION", "instFamily": family}
|
||||
).get("data") or []
|
||||
for r in rows:
|
||||
if isinstance(r, dict) and r.get("instId"):
|
||||
out[str(r["instId"])] = r
|
||||
if out:
|
||||
with _TICKERS_CACHE_LOCK:
|
||||
_TICKERS_CACHE[family] = {"updated_at": time.time(), "rows": dict(out)}
|
||||
return out
|
||||
except Exception as e:
|
||||
last_err = e
|
||||
@@ -730,6 +756,8 @@ def fetch_option_tickers(ex: ccxt.okx, inst_family: str) -> dict[str, dict[str,
|
||||
time.sleep(0.6 * (attempt + 1))
|
||||
continue
|
||||
break
|
||||
if cached and isinstance(cached.get("rows"), dict) and cached["rows"]:
|
||||
return dict(cached["rows"])
|
||||
if last_err is not None and _is_okx_rate_limit(last_err):
|
||||
return out
|
||||
return out
|
||||
@@ -743,6 +771,9 @@ def build_option_chain(
|
||||
itm_only: bool = True,
|
||||
itm_max_dist_usd: float = 30.0,
|
||||
index_px: float | None = None,
|
||||
tickers_override: dict[str, dict[str, Any]] | None = None,
|
||||
fetch_tickers: bool = True,
|
||||
force_tickers: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
u = (underlying or "ETH").upper()
|
||||
family = f"{u}-USD_UM"
|
||||
@@ -763,7 +794,13 @@ def build_option_chain(
|
||||
rate_limited = _is_okx_rate_limit(e)
|
||||
if rate_limited:
|
||||
instruments_err = "OKX 请求过于频繁(50011),请稍后点「刷新链」重试"
|
||||
tickers = fetch_option_tickers(ex, family)
|
||||
tickers: dict[str, dict[str, Any]] = {}
|
||||
if fetch_tickers:
|
||||
tickers = fetch_option_tickers(ex, family, force=force_tickers)
|
||||
if tickers_override:
|
||||
for iid, row in tickers_override.items():
|
||||
if isinstance(row, dict) and iid:
|
||||
tickers[str(iid)] = {**(tickers.get(str(iid)) or {}), **row}
|
||||
expiries: dict[str, list[dict[str, Any]]] = {}
|
||||
skipped_no_index = 0
|
||||
for meta in instruments:
|
||||
|
||||
Reference in New Issue
Block a user