import logging from typing import List import httpx from fastapi import APIRouter, HTTPException, Query from app.config import settings from app.schemas import Candle from app.services.price_store import price_store router = APIRouter() logger = logging.getLogger(__name__) VALID_ASSETS = {"BTC", "ETH"} VALID_TIMEFRAMES = {"5m", "15m", "1H", "4H", "1D", "1W"} BINANCE_INTERVAL = { "5m": "5m", "15m": "15m", "1H": "1h", "4H": "4h", "1D": "1d", "1W": "1w", } SYMBOL_MAP = {"BTC": "BTCUSDT", "ETH": "ETHUSDT"} async def fetch_binance_candles(asset: str, tf: str, limit: int) -> List[Candle]: symbol = SYMBOL_MAP[asset] interval = BINANCE_INTERVAL[tf] url = f"{settings.binance_rest_url}/api/v3/klines?symbol={symbol}&interval={interval}&limit={limit}" async with httpx.AsyncClient(timeout=15) as client: resp = await client.get(url) resp.raise_for_status() rows = resp.json() return [ Candle( time=row[0] // 1000, # ms → seconds open=float(row[1]), high=float(row[2]), low=float(row[3]), close=float(row[4]), volume=float(row[5]), ) for row in rows ] @router.get("/prices/{asset}", response_model=List[Candle]) async def get_prices( asset: str, tf: str = Query(default="4H"), limit: int = Query(default=200, ge=1, le=1000), ): asset = asset.upper() if asset not in VALID_ASSETS: raise HTTPException(status_code=400, detail=f"Asset must be one of {VALID_ASSETS}") if tf not in VALID_TIMEFRAMES: raise HTTPException(status_code=400, detail=f"Timeframe must be one of {VALID_TIMEFRAMES}") # 1m: use in-memory store (updated in real-time) if tf == "1m": candles = price_store.get_candles(asset, "1m", limit) return [Candle(**c) for c in candles] # All other timeframes: fetch directly from Binance try: return await fetch_binance_candles(asset, tf, limit) except Exception as exc: logger.error("Binance REST fetch failed for %s %s: %s", asset, tf, exc) raise HTTPException(status_code=502, detail="Failed to fetch price data")