""" Binance data provider. Used primarily for high-quality OHLCV data (klines endpoint). No API key required for public market data. """ import httpx from config import BINANCE_BASE_URL, TIMEFRAME_TO_BINANCE from errors import SymbolNotFoundError, ProviderError, UnsupportedTimeframeError def _binance_symbol(symbol: str) -> str: sym = symbol.upper() if not sym.endswith("USDT"): sym = sym + "USDT" return sym async def fetch_price(symbol: str) -> dict: import time binance_sym = _binance_symbol(symbol) url = f"{BINANCE_BASE_URL}/ticker/price" params = {"symbol": binance_sym} async with httpx.AsyncClient(timeout=10.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 400: raise SymbolNotFoundError(f"Symbol not found on Binance: {symbol}") resp.raise_for_status() except httpx.HTTPStatusError as e: raise ProviderError(f"Binance HTTP error: {e.response.status_code}") from e except httpx.RequestError as e: raise ProviderError(f"Binance request failed: {e}") from e data = resp.json() return {"symbol": symbol.upper(), "price": float(data["price"]), "timestamp": int(time.time())} async def fetch_ohlcv(symbol: str, timeframe: str, limit: int = 100) -> dict: interval = TIMEFRAME_TO_BINANCE.get(timeframe) if not interval: raise UnsupportedTimeframeError(f"Unsupported timeframe: {timeframe}. Supported: {list(TIMEFRAME_TO_BINANCE.keys())}") binance_sym = _binance_symbol(symbol) url = f"{BINANCE_BASE_URL}/klines" params = {"symbol": binance_sym, "interval": interval, "limit": min(limit, 1000)} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.get(url, params=params) if resp.status_code == 400: raise SymbolNotFoundError(f"Symbol not found on Binance: {symbol}") resp.raise_for_status() except httpx.HTTPStatusError as e: raise ProviderError(f"Binance klines HTTP error: {e.response.status_code}") from e except httpx.RequestError as e: raise ProviderError(f"Binance klines request failed: {e}") from e raw = resp.json() if not raw: raise ProviderError(f"Binance returned empty klines for {symbol} {timeframe}") candles = [[int(row[0] / 1000), float(row[1]), float(row[2]), float(row[3]), float(row[4]), float(row[5])] for row in raw] return {"symbol": symbol.upper(), "timeframe": timeframe, "candles": candles}