#!/usr/bin/env python3 """Coinglass data connector — funding rates, open interest, long/short ratios. Uses the free fapi.coinglass.com internal API where available. Some endpoints may return empty data without authentication. Set COINGLASS_API_KEY env var for authenticated access to open-api.coinglass.com. """ import argparse import json import os import sys from typing import Any import requests FREE_BASE = "https://fapi.coinglass.com/api" AUTH_BASE = "https://open-api.coinglass.com/public/v2" TIMEOUT = 30 def _free_get(path: str, params: dict | None = None) -> Any: headers = { "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "Referer": "https://www.coinglass.com/", } r = requests.get(f"{FREE_BASE}/{path}", params=params, headers=headers, timeout=TIMEOUT) r.raise_for_status() data = r.json() if data.get("code") == "0" or data.get("success"): return data.get("data", []) raise ValueError(f"API error: {data.get('msg', 'unknown')}") def _auth_get(path: str, params: dict | None = None) -> Any: key = os.environ.get("COINGLASS_API_KEY") if not key: raise EnvironmentError("COINGLASS_API_KEY not set. Get one at https://www.coinglass.com/pricing") headers = {"coinglassSecret": key} r = requests.get(f"{AUTH_BASE}/{path}", params=params, headers=headers, timeout=TIMEOUT) r.raise_for_status() data = r.json() if data.get("success") or data.get("code") == "0": return data.get("data", []) raise ValueError(f"API error: {data.get('msg', 'unknown')}") # ── Data fetchers ─────────────────────────────────────────────────────────── def get_funding_rates() -> list[dict]: """Funding rates across exchanges.""" try: data = _free_get("fundingRate/v2/home") if data: return data except Exception: pass return _auth_get("funding") def get_open_interest() -> list[dict]: """Aggregated open interest data.""" try: data = _free_get("openInterest/v3/home") if data: return data except Exception: pass return _auth_get("open_interest") def get_long_short_ratio() -> list[dict]: """Global long/short account ratios.""" try: data = _free_get("futures/longShort/v2/home") if data: return data except Exception: pass return _auth_get("long_short") # ── Summary helpers ───────────────────────────────────────────────────────── def _no_data_msg(name: str) -> str: return (f"No {name} data available (free API may be restricted).\n" "Set COINGLASS_API_KEY for full access: https://www.coinglass.com/pricing") def summary_funding(data: list[dict]) -> str: if not data: return _no_data_msg("funding rate") lines = ["═══ Funding Rates ═══", ""] for item in data[:20]: symbol = item.get("symbol", item.get("coin", "?")) rate = None if "uMarginList" in item: for m in item["uMarginList"]: rate = m.get("rate") if rate is not None: break else: rate = item.get("rate") if rate is not None: lines.append(f" {symbol:<10} {float(rate)*100:>8.4f}%") else: lines.append(f" {symbol:<10} (rate unavailable)") return "\n".join(lines) def summary_oi(data: list[dict]) -> str: if not data: return _no_data_msg("open interest") lines = ["═══ Open Interest ═══", ""] for item in data[:20]: symbol = item.get("symbol", item.get("coin", "?")) oi = item.get("openInterest", item.get("oi", 0)) lines.append(f" {symbol:<10} OI: ${float(oi):>15,.0f}") return "\n".join(lines) def summary_ls(data: list[dict]) -> str: if not data: return _no_data_msg("long/short") lines = ["═══ Long/Short Ratios ═══", ""] for item in data[:20]: symbol = item.get("symbol", item.get("coin", "?")) long_rate = item.get("longRate", item.get("longRatio", "?")) short_rate = item.get("shortRate", item.get("shortRatio", "?")) lines.append(f" {symbol:<10} Long: {long_rate} Short: {short_rate}") return "\n".join(lines) # ── CLI ───────────────────────────────────────────────────────────────────── def main(): common = argparse.ArgumentParser(add_help=False) common.add_argument("--pretty", action="store_true", help="Pretty-print JSON output") common.add_argument("--summary", action="store_true", help="Human-readable summary") parser = argparse.ArgumentParser(description="Coinglass data connector", parents=[common]) sub = parser.add_subparsers(dest="command", required=True) sub.add_parser("funding", help="Funding rates across exchanges", parents=[common]) sub.add_parser("oi", help="Open interest overview", parents=[common]) sub.add_parser("long-short", help="Long/short ratios", parents=[common]) args = parser.parse_args() try: if args.command == "funding": data = get_funding_rates() if args.summary: print(summary_funding(data)); return result = data elif args.command == "oi": data = get_open_interest() if args.summary: print(summary_oi(data)); return result = data elif args.command == "long-short": data = get_long_short_ratio() if args.summary: print(summary_ls(data)); return result = data else: parser.print_help(); return json.dump(result, sys.stdout, indent=2 if args.pretty else None) print() except EnvironmentError as e: print(str(e), file=sys.stderr); sys.exit(1) except requests.HTTPError as e: print(json.dumps({"error": str(e)}), file=sys.stderr); sys.exit(1) except Exception as e: print(json.dumps({"error": f"{type(e).__name__}: {e}"}), file=sys.stderr); sys.exit(1) if __name__ == "__main__": main()