Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
37-tool crypto derivatives data suite: funding rates, open interest, liquidations, Hyperliquid whale tracking, and ETF flows.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
tools/_api.py
1"""2Shared Coinglass API helper — eliminates return-None error pattern.34Every tools/*.py file repeated the same boilerplate:51. _get_api_key() → return None if missing62. proxied_get(url, headers, timeout) → return None on any error73. check response code → return None if not "0"89This module centralizes that into one `cg_request()` function that:10- Raises typed exceptions instead of returning None11- Provides actionable error messages for each failure mode12- Distinguishes API key errors, rate limits, server errors, parse errors13"""1415import os16import json17import requests1819try:20from core.http_client import proxied_get21except ImportError:22# Fallback for testing outside platform — use plain requests23def proxied_get(url, params=None, headers=None, timeout=30,24**kwargs):25return requests.get(26url, params=params, headers=headers, timeout=timeout,27**kwargs28)293031# ── Coinglass-specific exceptions ───────────────────────────3233class CoinglassError(Exception):34"""Base exception for all Coinglass API errors."""3536def __init__(self, message, code="UNKNOWN", suggestion=""):37self.code = code38self.suggestion = suggestion39super().__init__(message)404142class CoinglassAPIKeyError(CoinglassError):43"""API key missing or invalid."""44pass454647class CoinglassRateLimitError(CoinglassError):48"""Rate limited by Coinglass."""49pass505152class CoinglassServerError(CoinglassError):53"""Coinglass server returned 5xx."""54pass555657# ── API configuration ──────────────────────────────────────5859BASE_URL_V2 = "https://open-api.coinglass.com/public/v2"60BASE_URL_V4 = "https://open-api-v4.coinglass.com"6162HEADER_KEY_V2 = "coinglassSecret"63HEADER_KEY_V4 = "CG-API-KEY"646566def _get_api_key():67"""Get API key from environment."""68return os.getenv("COINGLASS_API_KEY")697071def _suggestion_for_status(status):72"""Map HTTP status to actionable suggestion."""73suggestions = {74401: "API key invalid or expired. Check COINGLASS_API_KEY.",75403: "Access denied. This endpoint may require a paid plan.",76404: "Endpoint not found. The API version may have changed.",77429: "Rate limited. Wait 60 seconds before retrying.",78500: "Coinglass server error. Retry in 1-2 minutes.",79502: "Coinglass gateway error. Retry in 1-2 minutes.",80503: "Coinglass service unavailable. Retry in 1-2 minutes.",81}82return suggestions.get(status, f"HTTP {status} error.")838485def cg_request(endpoint, params=None, version="v4", timeout=30):86"""87Make a Coinglass API request with structured error handling.8889Args:90endpoint: API path (e.g. "api/futures/supported-coins"91or "funding" for v2)92params: Query parameters dict93version: "v2" or "v4" (default "v4")94timeout: Request timeout in seconds9596Returns:97Parsed response data (the "data" field from Coinglass response).9899Raises:100CoinglassAPIKeyError: API key missing or rejected101CoinglassRateLimitError: 429 rate limit102CoinglassServerError: 5xx server error103CoinglassError: Any other API error104"""105api_key = _get_api_key()106if not api_key:107raise CoinglassAPIKeyError(108"COINGLASS_API_KEY not set in environment",109code="NO_API_KEY",110suggestion="Set COINGLASS_API_KEY in your .env file."111)112113if version == "v2":114base_url = BASE_URL_V2115headers = {"accept": "application/json", HEADER_KEY_V2: api_key}116else:117base_url = BASE_URL_V4118headers = {"accept": "application/json", HEADER_KEY_V4: api_key}119120url = f"{base_url}/{endpoint}"121122try:123response = proxied_get(124url, params=params, headers=headers, timeout=timeout125)126response.raise_for_status()127except requests.exceptions.HTTPError as e:128status = getattr(e.response, "status_code", None)129suggestion = _suggestion_for_status(status)130if status == 401:131raise CoinglassAPIKeyError(132f"HTTP 401 from Coinglass: {e}", code="HTTP_401",133suggestion=suggestion134) from e135if status == 429:136raise CoinglassRateLimitError(137f"Rate limited by Coinglass: {e}", code="HTTP_429",138suggestion=suggestion139) from e140if status and status >= 500:141raise CoinglassServerError(142f"Coinglass server error: {e}", code=f"HTTP_{status}",143suggestion=suggestion144) from e145raise CoinglassError(146f"HTTP {status} from Coinglass: {e}",147code=f"HTTP_{status}", suggestion=suggestion148) from e149except requests.exceptions.ConnectionError as e:150raise CoinglassError(151f"Cannot connect to Coinglass: {e}",152code="CONNECTION_ERROR",153suggestion="Check network. Retry in 30 seconds."154) from e155except requests.exceptions.Timeout as e:156raise CoinglassError(157f"Coinglass request timed out after {timeout}s",158code="TIMEOUT",159suggestion="Retry with a longer timeout or simpler query."160) from e161except requests.exceptions.RequestException as e:162raise CoinglassError(163f"Request failed: {type(e).__name__}: {e}",164code="REQUEST_ERROR",165suggestion="Unexpected network error. Retry."166) from e167168# Parse JSON169try:170data = response.json()171except (json.JSONDecodeError, ValueError) as e:172raise CoinglassError(173f"Invalid JSON from Coinglass: {e}",174code="PARSE_ERROR",175suggestion="API may be returning an error page. Try again later."176) from e177178# Check Coinglass response code179if isinstance(data, dict):180code = data.get("code")181if code is not None and str(code) != "0":182msg = data.get("msg", "Unknown API error")183raise CoinglassError(184f"Coinglass API error [{code}]: {msg}",185code=f"API_{code}",186suggestion="Check parameters. Some endpoints "187"require specific symbols or exchanges."188)189# Unwrap standard response envelope190if "data" in data:191return data["data"]192193return data194