增加K线

This commit is contained in:
dekun
2026-05-30 10:48:57 +08:00
parent e9f1a6a46f
commit 48df49b09c
3 changed files with 266 additions and 11 deletions
+93 -4
View File
@@ -15,12 +15,36 @@ logger = logging.getLogger(__name__)
_RATE_LIMIT_CODES = {418, 429}
_SYMBOLS_CACHE_FILE = ROOT_DIR / "data" / "symbols_cache.json"
_SYMBOL_META_CACHE_FILE = ROOT_DIR / "data" / "symbol_meta_cache.json"
def _precision_from_tick_size(tick_size: str) -> int:
tick = tick_size.strip()
if "." not in tick:
return 0
dec = tick.split(".", 1)[1]
trimmed = dec.rstrip("0")
return len(trimmed) if trimmed else len(dec)
def _parse_symbol_price_meta(symbol_info: dict[str, Any]) -> dict[str, Any]:
tick_size = "0.01"
for f in symbol_info.get("filters", []):
if f.get("filterType") == "PRICE_FILTER":
tick_size = str(f.get("tickSize", tick_size))
break
precision = _precision_from_tick_size(tick_size)
api_precision = symbol_info.get("pricePrecision")
if isinstance(api_precision, int) and api_precision > precision:
precision = api_precision
return {"tick_size": tick_size, "price_precision": precision}
class BinanceFuturesClient:
def __init__(self) -> None:
self.base = settings.binance_fapi_base.rstrip("/")
self._symbols_cache: list[str] | None = None
self._symbol_meta_cache: dict[str, dict[str, Any]] | None = None
self._client: httpx.AsyncClient | None = None
self._throttle_lock = asyncio.Lock()
self._last_request_at: float = 0.0
@@ -130,6 +154,69 @@ class BinanceFuturesClient:
except Exception as e:
logger.warning("Save symbols cache file failed: %s", e)
def _load_symbol_meta_file(self) -> dict[str, dict[str, Any]] | None:
try:
if _SYMBOL_META_CACHE_FILE.is_file():
data = json.loads(_SYMBOL_META_CACHE_FILE.read_text(encoding="utf-8"))
if isinstance(data, dict):
return data
except Exception as e:
logger.warning("Load symbol meta cache file failed: %s", e)
return None
def _save_symbol_meta_file(self, meta: dict[str, dict[str, Any]]) -> None:
try:
_SYMBOL_META_CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
_SYMBOL_META_CACHE_FILE.write_text(
json.dumps(meta, ensure_ascii=False),
encoding="utf-8",
)
except Exception as e:
logger.warning("Save symbol meta cache file failed: %s", e)
async def _ensure_symbol_meta(self) -> dict[str, dict[str, Any]]:
if self._symbol_meta_cache:
return self._symbol_meta_cache
if self.is_rate_limited():
cached = self._load_symbol_meta_file()
if cached:
self._symbol_meta_cache = cached
return cached
raise BinanceRateLimitedError(self.rate_limit_remaining_sec(), "symbol_meta")
try:
info = await self._get("/fapi/v1/exchangeInfo")
meta: dict[str, dict[str, Any]] = {}
for s in info.get("symbols", []):
if (
s.get("contractType") == "PERPETUAL"
and s.get("quoteAsset") == "USDT"
and s.get("status") == "TRADING"
):
sym = s["symbol"]
meta[sym] = _parse_symbol_price_meta(s)
self._symbol_meta_cache = meta
self._save_symbol_meta_file(meta)
return meta
except BinanceRateLimitedError:
cached = self._load_symbol_meta_file()
if cached:
self._symbol_meta_cache = cached
return cached
raise
async def get_symbol_price_meta(self, symbol: str) -> dict[str, Any]:
sym = symbol.upper().strip()
meta_map = await self._ensure_symbol_meta()
if sym in meta_map:
return meta_map[sym]
return {"tick_size": "0.01", "price_precision": 2}
def clear_symbol_cache(self) -> None:
self._symbols_cache = None
self._symbol_meta_cache = None
async def get_usdt_perpetual_symbols(self) -> list[str]:
if self._symbols_cache:
return self._symbols_cache
@@ -145,15 +232,20 @@ class BinanceFuturesClient:
try:
info = await self._get("/fapi/v1/exchangeInfo")
symbols = []
meta: dict[str, dict[str, Any]] = {}
for s in info.get("symbols", []):
if (
s.get("contractType") == "PERPETUAL"
and s.get("quoteAsset") == "USDT"
and s.get("status") == "TRADING"
):
symbols.append(s["symbol"])
sym = s["symbol"]
symbols.append(sym)
meta[sym] = _parse_symbol_price_meta(s)
self._symbols_cache = sorted(symbols)
self._symbol_meta_cache = meta
self._save_symbols_file(self._symbols_cache)
self._save_symbol_meta_file(meta)
logger.info("Loaded %d USDT perpetual symbols", len(self._symbols_cache))
return self._symbols_cache
except BinanceRateLimitedError:
@@ -164,9 +256,6 @@ class BinanceFuturesClient:
return cached
raise
def clear_symbol_cache(self) -> None:
self._symbols_cache = None
async def get_24hr_tickers(self) -> list[dict]:
data = await self._get("/fapi/v1/ticker/24hr")
return data if isinstance(data, list) else []