chore: restore codebase to state before 2026-08-11 changes
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+29
-124
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
@@ -26,14 +25,6 @@ _OKX_OPTION_ERR_ZH: dict[str, str] = {
|
||||
}
|
||||
|
||||
_OPTIONS_BALANCE_CACHE: dict[str, Any] = {"updated_at": 0.0, "data": None}
|
||||
# 期权合约列表变化慢;短缓存+限频退避,避免 50011 拖垮期权链
|
||||
_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 = float(os.getenv("OKX_OPTIONS_TICKERS_CACHE_SEC", "10") or "10")
|
||||
|
||||
|
||||
def invalidate_options_balance_cache() -> None:
|
||||
@@ -41,14 +32,6 @@ def invalidate_options_balance_cache() -> None:
|
||||
_OPTIONS_BALANCE_CACHE["data"] = None
|
||||
|
||||
|
||||
def invalidate_option_instruments_cache(inst_family: str | None = None) -> None:
|
||||
with _INSTRUMENTS_CACHE_LOCK:
|
||||
if inst_family:
|
||||
_INSTRUMENTS_CACHE.pop(str(inst_family), None)
|
||||
else:
|
||||
_INSTRUMENTS_CACHE.clear()
|
||||
|
||||
|
||||
def _okx_trade_error_message(exc: BaseException | None = None, resp: Any = None) -> str:
|
||||
row: dict[str, Any] | None = None
|
||||
if isinstance(resp, dict):
|
||||
@@ -662,105 +645,24 @@ def fetch_index_price(ex: ccxt.okx, uly: str) -> float | None:
|
||||
def fetch_option_instruments(
|
||||
ex: ccxt.okx,
|
||||
inst_family: str,
|
||||
*,
|
||||
force: bool = False,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""拉取 live 期权合约列表;短 TTL 缓存,遇 50011 退避重试并可回退过期缓存."""
|
||||
family = (inst_family or "").strip()
|
||||
if not family:
|
||||
return []
|
||||
now = time.time()
|
||||
with _INSTRUMENTS_CACHE_LOCK:
|
||||
cached = _INSTRUMENTS_CACHE.get(family)
|
||||
if (
|
||||
not force
|
||||
and cached
|
||||
and now - float(cached.get("updated_at") or 0) < _INSTRUMENTS_CACHE_TTL_SEC
|
||||
and isinstance(cached.get("rows"), list)
|
||||
and cached["rows"]
|
||||
):
|
||||
return list(cached["rows"])
|
||||
|
||||
last_err: BaseException | None = None
|
||||
rows: list[dict[str, Any]] = []
|
||||
for attempt in range(4):
|
||||
try:
|
||||
raw = ex.public_get_public_instruments(
|
||||
{"instType": "OPTION", "instFamily": family}
|
||||
).get("data") or []
|
||||
rows = [r for r in raw if isinstance(r, dict) and r.get("state") == "live"]
|
||||
last_err = None
|
||||
break
|
||||
except Exception as e:
|
||||
last_err = e
|
||||
if _is_okx_rate_limit(e) and attempt < 3:
|
||||
time.sleep(0.8 * (2**attempt))
|
||||
continue
|
||||
break
|
||||
|
||||
if rows:
|
||||
with _INSTRUMENTS_CACHE_LOCK:
|
||||
_INSTRUMENTS_CACHE[family] = {"updated_at": time.time(), "rows": list(rows)}
|
||||
return rows
|
||||
|
||||
# 限频/短暂失败:优先用未过期太久的缓存,避免整页「拉取失败」
|
||||
if cached and isinstance(cached.get("rows"), list) and cached["rows"]:
|
||||
age = now - float(cached.get("updated_at") or 0)
|
||||
if age < _INSTRUMENTS_STALE_SEC and (
|
||||
last_err is None or _is_okx_rate_limit(last_err) or not rows
|
||||
):
|
||||
return list(cached["rows"])
|
||||
|
||||
if last_err is not None:
|
||||
raise last_err
|
||||
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,
|
||||
*,
|
||||
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) < max(1.0, _TICKERS_CACHE_TTL_SEC)
|
||||
and isinstance(cached.get("rows"), dict)
|
||||
and cached["rows"]
|
||||
):
|
||||
return dict(cached["rows"])
|
||||
|
||||
def fetch_option_tickers(ex: ccxt.okx, inst_family: str) -> dict[str, dict[str, Any]]:
|
||||
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": 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
|
||||
if _is_okx_rate_limit(e) and attempt < 2:
|
||||
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
|
||||
try:
|
||||
rows = ex.public_get_market_tickers(
|
||||
{"instType": "OPTION", "instFamily": inst_family}
|
||||
).get("data") or []
|
||||
for r in rows:
|
||||
if isinstance(r, dict) and r.get("instId"):
|
||||
out[str(r["instId"])] = r
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
@@ -781,17 +683,22 @@ def build_option_chain(
|
||||
max_ms = now_ms + max_dte_days * 86400 * 1000
|
||||
instruments_err = ""
|
||||
instruments: list[dict[str, Any]] = []
|
||||
rate_limited = False
|
||||
try:
|
||||
instruments = fetch_option_instruments(ex, family)
|
||||
if not instruments:
|
||||
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__
|
||||
rate_limited = _is_okx_rate_limit(e)
|
||||
if rate_limited:
|
||||
instruments_err = "OKX 请求过于频繁(50011),请稍后点「刷新链」重试"
|
||||
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
|
||||
@@ -870,8 +777,6 @@ def build_option_chain(
|
||||
"expiries": exp_list,
|
||||
"instruments_count": len(instruments),
|
||||
}
|
||||
if rate_limited:
|
||||
out["rate_limited"] = True
|
||||
if not exp_list:
|
||||
if instruments_err:
|
||||
out["chain_error"] = f"拉取期权合约失败: {instruments_err}"
|
||||
|
||||
Reference in New Issue
Block a user