"""CryptoCompare public/API-key optional fallback adapter.""" from __future__ import annotations import os from typing import Dict, List from .base import ProviderResult, base_asset, fail, fetch_json, normalize_spot_symbol, ok, to_float, to_int BASE = "https://min-api.cryptocompare.com/data" def _params(params: Dict[str, object]) -> Dict[str, object]: key = os.getenv("CRYPTOCOMPARE_KEY") or os.getenv("CRYPTOCOMPARE_API_KEY") if key: params = dict(params) params["api_key"] = key return params async def ticker(symbol: str) -> ProviderResult: base = base_asset(symbol) payload, error, status, latency = await fetch_json("cryptocompare", "ticker", f"{BASE}/pricemultifull", params=_params({"fsyms": base, "tsyms": "USD"}), timeout=8.0) if error or not isinstance(payload, dict): return fail("cryptocompare", "ticker", error or "price payload invalid", latency, status) raw = ((payload.get("RAW") or {}).get(base) or {}).get("USD") or {} data = { "symbol": normalize_spot_symbol(symbol), "price": to_float(raw.get("PRICE")), "lastPrice": to_float(raw.get("PRICE")), "volume24h": to_float(raw.get("VOLUME24HOUR")), "quoteVolume24h": to_float(raw.get("VOLUME24HOURTO")), "priceChangePercent": to_float(raw.get("CHANGEPCT24HOUR")), "high24h": to_float(raw.get("HIGH24HOUR")), "low24h": to_float(raw.get("LOW24HOUR")), "provider": "cryptocompare", } if data["price"] is None: return fail("cryptocompare", "ticker", f"price missing for {base}", latency, status) return ok("cryptocompare", "ticker", data, latency) async def ohlcv(symbol: str, interval: str = "1h", limit: int = 100) -> ProviderResult: base = base_asset(symbol) tf = (interval or "1h").lower() if tf.endswith("m"): endpoint = "histominute"; aggregate = max(1, to_int(tf[:-1], 1) or 1) elif tf.endswith("d"): endpoint = "histoday"; aggregate = max(1, to_int(tf[:-1], 1) or 1) else: endpoint = "histohour"; aggregate = max(1, to_int(tf[:-1].replace('h',''), 1) or 1) payload, error, status, latency = await fetch_json( "cryptocompare", "ohlcv", f"{BASE}/v2/{endpoint}", params=_params({"fsym": base, "tsym": "USD", "limit": min(max(int(limit), 1), 2000), "aggregate": aggregate}), timeout=10.0, ) if error or not isinstance(payload, dict): return fail("cryptocompare", "ohlcv", error or "ohlcv payload invalid", latency, status) rows = ((payload.get("Data") or {}).get("Data") or [])[-limit:] candles = [] for row in rows: if not isinstance(row, dict): continue candles.append({ "timestamp": to_int(row.get("time")) * 1000 if to_int(row.get("time")) else None, "open": to_float(row.get("open")), "high": to_float(row.get("high")), "low": to_float(row.get("low")), "close": to_float(row.get("close")), "volume": to_float(row.get("volumefrom")), "quoteVolume": to_float(row.get("volumeto")), "provider": "cryptocompare", }) candles = [c for c in candles if c.get("timestamp") is not None] if not candles: return fail("cryptocompare", "ohlcv", f"no candles for {base}", latency, status) return ok("cryptocompare", "ohlcv", candles, latency)