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/funding_rate.py
1#!/usr/bin/env python32"""3Coinglass Funding Rate Module45Fetch funding rates across major cryptocurrency exchanges including6Binance, OKX, Bybit, KuCoin, MEXC, Bitfinex, Kraken, and more.78Funding rates are fees set by cryptocurrency exchanges to maintain balance9between contract price and underlying asset price in perpetual futures contracts.10Positive rates mean longs pay shorts; negative rates mean shorts pay longs.1112Dependencies:13- requests: For HTTP API calls14- python-dotenv: For environment variable management1516Environment Variables Required:17- COINGLASS_API_KEY: Your Coinglass API key1819Usage Example:20from tools.coinglass.funding_rate import get_funding_rates, get_symbol_funding_rate2122# Get all funding rates for BTC23rates = get_funding_rates(symbol="BTC")2425# Get funding rate for specific exchange26binance_rate = get_symbol_funding_rate(symbol="BTC", exchange="Binance")2728CLI Usage:29python funding_rate.py --symbol BTC30python funding_rate.py --symbol ETH --exchange Binance31python funding_rate.py --all32"""3334import os35import sys36import json37import argparse38from typing import Dict, Any, Optional, List3940try:41from dotenv import load_dotenv42project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))43load_dotenv(os.path.join(project_root, '.env'))44except ImportError:45pass4647import requests48from core.http_client import proxied_get495051def _fmt_rate(val):52"""Format a funding rate value as a display string with % sign.53Coinglass returns values already in percent (0.01 means 0.01%), so NO multiplication."""54if val is None:55return None56sign = "+" if val >= 0 else ""57return f"{sign}{val:.4f}%"585960def _with_rate_unit(obj: dict, field: str):61"""Normalize any funding-rate field to a display-safe percent string."""62if field not in obj or obj.get(field) is None:63return64obj[field] = _fmt_rate(_rate_num(obj, field, 0.0))656667def _rate_num(obj: dict, field: str, default: float = 0.0) -> float:68"""Read numeric funding rate safely from number or percent-string values."""69v = obj.get(field)70if isinstance(v, (int, float)):71return float(v)72if isinstance(v, str):73s = v.strip()74if s.endswith('%'):75s = s[:-1]76try:77return float(s)78except ValueError:79return default80return default8182# Coinglass Configuration83BASE_URL = "https://open-api.coinglass.com/public/v2"84HEADER_KEY = "coinglassSecret"8586# Supported exchanges87EXCHANGES = [88"Binance", "OKX", "Bybit", "KuCoin", "MEXC", "CoinEx",89"Bitfinex", "Kraken", "dYdX", "Gate", "Bitmex"90]9192# Common symbols93SYMBOLS = ["BTC", "ETH", "SOL", "BNB", "XRP", "DOGE", "ADA", "AVAX", "LINK", "MATIC"]949596def _get_api_key() -> Optional[str]:97"""Get Coinglass API key from environment."""98return os.getenv("COINGLASS_API_KEY")99100101def get_funding_rates(symbol: Optional[str] = None) -> Optional[Dict[str, Any]]:102"""103Fetch funding rates across all exchanges.104105Args:106symbol: Optional symbol filter (BTC, ETH, etc.). If None, returns all.107108Returns:109Dictionary with funding rate data:110{111"symbol": "BTC",112"uMarginList": [113{114"exchangeName": "Binance",115"rate": 0.0058, # Funding rate already in % (i.e. 0.0058%)116"nextFundingTime": 1765497600000, # Unix ms117"fundingIntervalHours": 8118},119...120]121}122Returns None if request fails.123124Example:125rates = get_funding_rates("BTC")126for exchange in rates["data"]:127if exchange["symbol"] == "BTC":128for rate_info in exchange["uMarginList"]:129print(f"{rate_info['exchangeName']}: {rate_info['rate']:.4f}%")130"""131api_key = _get_api_key()132if not api_key:133print("Error: COINGLASS_API_KEY not found in environment", file=sys.stderr)134return None135136url = f"{BASE_URL}/funding"137headers = {138"accept": "application/json",139HEADER_KEY: api_key140}141142try:143response = proxied_get(url, headers=headers, timeout=30)144response.raise_for_status()145data = response.json()146147if data.get("code") != "0":148print(f"API Error: {data.get('msg', 'Unknown error')}", file=sys.stderr)149return None150151# Normalize all funding-rate fields to strings with `%` suffix.152for symbol_entry in data.get("data", []):153for margin_list_key in ("uMarginList", "cMarginList"):154for ex in symbol_entry.get(margin_list_key, []):155_with_rate_unit(ex, "rate")156_with_rate_unit(ex, "predictedRate")157158if symbol:159# Filter for specific symbol160filtered = [d for d in data.get("data", []) if d.get("symbol", "").upper() == symbol.upper()]161return {"code": "0", "msg": "success", "data": filtered}162163return data164165except requests.exceptions.RequestException as e:166print(f"Request failed: {e}", file=sys.stderr)167return None168except json.JSONDecodeError as e:169print(f"Failed to parse response: {e}", file=sys.stderr)170return None171172173def get_symbol_funding_rate(174symbol: str,175exchange: Optional[str] = None176) -> Optional[Dict[str, Any]]:177"""178Get funding rate for a specific symbol and optionally a specific exchange.179180Args:181symbol: Symbol to query (BTC, ETH, etc.)182exchange: Optional exchange name (Binance, OKX, Bybit, etc.)183184Returns:185Dictionary with funding rate info:186{187"symbol": "BTC",188"exchange": "Binance", # or "average" if no exchange specified189"rate": "+0.0058%",190"next_funding_time": 1765497600000,191"funding_interval_hours": 8,192"predicted_rate": "+0.0059%" # if available193}194Returns None if not found.195196Example:197rate = get_symbol_funding_rate("BTC", "Binance")198if rate:199print(f"BTC funding rate on Binance: {rate['rate']}")200"""201data = get_funding_rates(symbol)202if not data or not data.get("data"):203return None204205symbol_data = data["data"][0] if data["data"] else None206if not symbol_data:207return None208209if exchange:210# Find specific exchange211for rate_info in symbol_data.get("uMarginList", []):212if rate_info.get("exchangeName", "").lower() == exchange.lower():213rate_num = _rate_num(rate_info, "rate", 0.0)214predicted_num = _rate_num(rate_info, "predictedRate", None) if rate_info.get("predictedRate") is not None else None215return {216"symbol": symbol.upper(),217"exchange": rate_info.get("exchangeName"),218"rate": _fmt_rate(rate_num),219"next_funding_time": rate_info.get("nextFundingTime"),220"funding_interval_hours": rate_info.get("fundingIntervalHours"),221"predicted_rate": _fmt_rate(predicted_num) if predicted_num is not None else None222}223return None224else:225# Return average across all exchanges226rates = [_rate_num(r, "rate", 0.0) for r in symbol_data.get("uMarginList", []) if (r.get("rate") is not None)]227if not rates:228return None229avg_rate = sum(rates) / len(rates)230return {231"symbol": symbol.upper(),232"exchange": "average",233"rate": _fmt_rate(avg_rate),234"num_exchanges": len(rates),235"exchanges_data": symbol_data.get("uMarginList", [])236}237238239def get_funding_rate_by_exchange(exchange: str) -> Optional[List[Dict[str, Any]]]:240"""241Get funding rates for all symbols on a specific exchange.242243Args:244exchange: Exchange name (Binance, OKX, Bybit, etc.)245246Returns:247List of funding rate dicts for each symbol on the exchange:248[249{"symbol": "BTC", "rate": "+0.0058%"},250{"symbol": "ETH", "rate": "+0.0042%"},251...252]253Returns None if request fails.254"""255data = get_funding_rates()256if not data or not data.get("data"):257return None258259results = []260for symbol_data in data["data"]:261for rate_info in symbol_data.get("uMarginList", []):262if rate_info.get("exchangeName", "").lower() == exchange.lower():263rate_num = _rate_num(rate_info, "rate", 0.0)264predicted_num = _rate_num(rate_info, "predictedRate", None) if rate_info.get("predictedRate") is not None else None265results.append({266"symbol": symbol_data.get("symbol"),267"rate": _fmt_rate(rate_num),268"next_funding_time": rate_info.get("nextFundingTime"),269"funding_interval_hours": rate_info.get("fundingIntervalHours"),270"predicted_rate": _fmt_rate(predicted_num) if predicted_num is not None else None271})272273return sorted(results, key=lambda x: abs(_rate_num(x, "rate", 0.0)), reverse=True) if results else None274275276def analyze_funding_opportunity(symbol: str, threshold: float = 0.01) -> Optional[Dict[str, Any]]:277"""278Analyze funding rate arbitrage opportunities across exchanges.279280Args:281symbol: Symbol to analyze (BTC, ETH, etc.)282threshold: Minimum rate difference to flag (default 0.01 = 1%)283284Returns:285Dictionary with arbitrage analysis:286{287"symbol": "BTC",288"highest": {"exchange": "CoinEx", "rate": "+0.0200%"},289"lowest": {"exchange": "Kraken", "rate": "-0.0010%"},290"spread": "+0.0210%",291"opportunity": True/False,292"all_rates": [...]293}294"""295data = get_funding_rates(symbol)296if not data or not data.get("data") or not data["data"]:297return None298299symbol_data = data["data"][0]300rates_list = symbol_data.get("uMarginList", [])301302if not rates_list:303return None304305# Extract rates with exchange names306rates = [307{308"exchange": r.get("exchangeName"),309"rate": _fmt_rate(_rate_num(r, "rate", 0.0))310}311for r in rates_list312if (r.get("rate") is not None)313]314315if not rates:316return None317318sorted_rates = sorted(rates, key=lambda x: _rate_num(x, "rate", 0.0))319lowest = sorted_rates[0]320highest = sorted_rates[-1]321spread = _rate_num(highest, "rate", 0.0) - _rate_num(lowest, "rate", 0.0)322323return {324"symbol": symbol.upper(),325"highest": highest,326"lowest": lowest,327"spread": _fmt_rate(spread),328"opportunity": spread >= threshold,329"all_rates": sorted_rates330}331332333def main():334"""CLI entry point."""335parser = argparse.ArgumentParser(description="Fetch Coinglass funding rates")336parser.add_argument("--symbol", "-s", help="Symbol to query (BTC, ETH, etc.)")337parser.add_argument("--exchange", "-e", help="Specific exchange (Binance, OKX, etc.)")338parser.add_argument("--all", "-a", action="store_true", help="Get all funding rates")339parser.add_argument("--analyze", action="store_true", help="Analyze arbitrage opportunities")340parser.add_argument("--by-exchange", help="Get all rates for an exchange")341parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")342343args = parser.parse_args()344345if args.analyze and args.symbol:346result = analyze_funding_opportunity(args.symbol)347if result:348if args.json:349print(json.dumps(result, indent=2))350else:351print(f"\n{result['symbol']} Funding Rate Analysis")352print("=" * 50)353print(f"Highest: {result['highest']['exchange']}: {result['highest']['rate']}")354print(f"Lowest: {result['lowest']['exchange']}: {result['lowest']['rate']}")355print(f"Spread: {result['spread']}")356print(f"Opportunity: {'YES' if result['opportunity'] else 'NO'}")357else:358print(f"No data found for {args.symbol}")359return360361if args.by_exchange:362result = get_funding_rate_by_exchange(args.by_exchange)363if result:364if args.json:365print(json.dumps(result, indent=2))366else:367print(f"\n{args.by_exchange} Funding Rates")368print("=" * 50)369for r in result[:20]: # Top 20370print(f"{r['symbol']:10s} {r['rate']:>10s}")371else:372print(f"No data found for exchange {args.by_exchange}")373return374375if args.symbol:376result = get_symbol_funding_rate(args.symbol, args.exchange)377if result:378if args.json:379print(json.dumps(result, indent=2))380else:381print(f"\n{result['symbol']} Funding Rate")382print("=" * 50)383if args.exchange:384print(f"Exchange: {result['exchange']}")385print(f"Rate: {result['rate']}")386if result.get('predicted_rate'):387print(f"Predicted: {result['predicted_rate']}")388else:389print(f"Average Rate: {result['rate']}")390print(f"Exchanges: {result['num_exchanges']}")391else:392print(f"No funding rate found for {args.symbol}" + (f" on {args.exchange}" if args.exchange else ""))393return394395if args.all:396result = get_funding_rates()397if result and result.get("data"):398if args.json:399print(json.dumps(result, indent=2))400else:401print("\nAll Funding Rates")402print("=" * 50)403for symbol_data in result["data"][:20]: # First 20 symbols404symbol = symbol_data.get("symbol", "???")405rates = symbol_data.get("uMarginList", [])406if rates:407numeric_rates = [_rate_num(r, "rate", 0.0) for r in rates if (r.get("rate") is not None)]408if numeric_rates:409avg = sum(numeric_rates) / len(numeric_rates)410print(f"{symbol:10s} avg: {_fmt_rate(avg)}")411else:412print("Failed to fetch funding rates")413return414415# Default: show help416parser.print_help()417418419if __name__ == "__main__":420main()421