import asyncio import logging from typing import Any import httpx from .config import settings from .http_client import httpx_client_kwargs logger = logging.getLogger(__name__) class BinanceFuturesClient: def __init__(self) -> None: self.base = settings.binance_fapi_base.rstrip("/") self._symbols_cache: list[str] | None = None async def _get(self, path: str, params: dict | None = None) -> Any: url = f"{self.base}{path}" async with httpx.AsyncClient( timeout=30.0, **httpx_client_kwargs("binance") ) as client: resp = await client.get(url, params=params or {}) resp.raise_for_status() return resp.json() async def get_usdt_perpetual_symbols(self) -> list[str]: if self._symbols_cache: return self._symbols_cache info = await self._get("/fapi/v1/exchangeInfo") symbols = [] 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"]) self._symbols_cache = sorted(symbols) logger.info("Loaded %d USDT perpetual symbols", len(self._symbols_cache)) return self._symbols_cache def clear_symbol_cache(self) -> None: self._symbols_cache = None async def get_klines( self, symbol: str, start_ms: int, end_ms: int, interval: str = "1h", ) -> list[list]: all_klines: list[list] = [] cursor = start_ms while cursor < end_ms: batch = await self._get( "/fapi/v1/klines", { "symbol": symbol, "interval": interval, "startTime": cursor, "endTime": end_ms, "limit": 1500, }, ) if not batch: break all_klines.extend(batch) last_open = int(batch[-1][0]) next_cursor = last_open + 3600_000 if next_cursor <= cursor: break cursor = next_cursor if len(batch) < 1500: break return all_klines async def get_price(self, symbol: str) -> float: data = await self._get("/fapi/v1/ticker/price", {"symbol": symbol}) return float(data["price"]) async def get_prices_batch(self, symbols: list[str]) -> dict[str, float]: tickers = await self._get("/fapi/v1/ticker/price") sym_set = set(symbols) return {t["symbol"]: float(t["price"]) for t in tickers if t["symbol"] in sym_set} binance_client = BinanceFuturesClient()