#!/usr/bin/env python3
"""
BTC Funding Rate Fetcher for Multiple Exchanges
Fetches current/predicted BTC perpetual funding rates from public APIs.
No API keys required (public endpoints only).
Run with: python3 btc_funding_rate_fetcher.py
"""

import requests
import json
from datetime import datetime, timezone
import sys

# Timeout for all requests (seconds)
TIMEOUT = 10

def fetch_json(url, params=None):
    """Helper to fetch JSON with error handling."""
    try:
        headers = {'User-Agent': 'Mozilla/5.0 (compatible; FundingRateBot/1.0)'}
        resp = requests.get(url, params=params, headers=headers, timeout=TIMEOUT)
        resp.raise_for_status()
        return resp.json()
    except Exception as e:
        return {"error": str(e)}

def format_rate(rate_decimal):
    """Convert decimal rate (e.g. 0.000123) to percentage string with 4 decimals."""
    try:
        pct = float(rate_decimal) * 100
        return f"{pct:+.4f}%"
    except (ValueError, TypeError):
        return "N/A"

def get_binance():
    url = "https://fapi.binance.com/fapi/v1/premiumIndex"
    params = {"symbol": "BTCUSDT"}
    data = fetch_json(url, params)
    if "error" in data:
        return f"Error: {data['error'][:60]}"
    try:
        # Response is a dict (not list) when symbol is specified
        if isinstance(data, list):
            rate = data[0]["lastFundingRate"]
        else:
            rate = data.get("lastFundingRate") or data.get("fundingRate")
        return format_rate(rate)
    except (KeyError, IndexError, TypeError):
        return "N/A"

def get_okx():
    url = "https://www.okx.com/api/v5/public/funding-rate"
    params = {"instId": "BTC-USDT-SWAP"}
    data = fetch_json(url, params)
    if "error" in data or data.get("code") != "0":
        return "Error" if "error" in data else f"API Error: {data.get('msg', 'Unknown')}"
    try:
        rate = data["data"][0]["fundingRate"]
        return format_rate(rate)
    except (KeyError, IndexError, TypeError):
        return "N/A"

def get_bybit():
    url = "https://api.bybit.com/v5/market/tickers"
    params = {"category": "linear", "symbol": "BTCUSDT"}
    data = fetch_json(url, params)
    if "error" in data or data.get("retCode") != 0:
        return "Error" if "error" in data else f"API Error: {data.get('retMsg', 'Unknown')}"
    try:
        rate = data["result"]["list"][0]["fundingRate"]
        return format_rate(rate)
    except (KeyError, IndexError, TypeError):
        return "N/A"

def get_kucoin():
    # Using the futures endpoint - takes predictedValue as next funding rate
    url = "https://api-futures.kucoin.com/api/v1/funding-rate/XBTUSDTM/current"
    data = fetch_json(url)
    if "error" in data or data.get("code") != "200000":
        return "Error" if "error" in data else f"API Error: {data.get('msg', 'Unknown')}"
    try:
        # Prefer predictedValue (next period), fallback to value
        rate = data["data"].get("predictedValue") or data["data"].get("value")
        return format_rate(rate)
    except (KeyError, TypeError):
        return "N/A"

def get_mexc():
    url = "https://api.mexc.com/api/v1/contract/funding_rate/BTC_USDT"
    data = fetch_json(url)
    if "error" in data or not data.get("success"):
        return "Error" if "error" in data else "API Error"
    try:
        rate = data["data"]["fundingRate"]
        return format_rate(rate)
    except (KeyError, TypeError):
        return "N/A"

def get_bingx():
    url = "https://open-api.bingx.com/openApi/swap/v2/quote/premiumIndex"
    params = {"symbol": "BTC-USDT"}
    data = fetch_json(url, params)
    if "error" in data or data.get("code") != 0:
        return "Error" if "error" in data else f"API Error: {data.get('msg', 'Unknown')}"
    try:
        d = data.get("data", {})
        # Try multiple possible keys (API sometimes varies)
        rate = (d.get("fundingRate") or 
                d.get("funding_rate") or 
                d.get("lastFundingRate") or 
                d.get("markPrice"))  # fallback unlikely
        return format_rate(rate)
    except (KeyError, TypeError):
        return "N/A"

def get_gate():
    url = "https://api.gateio.ws/api/v4/futures/usdt/contracts/BTC_USDT"
    data = fetch_json(url)
    if "error" in data:
        return f"Error: {data['error'][:60]}"
    try:
        # Single contract response is a dict; field uses underscore
        rate = data.get("funding_rate")
        if rate is None and isinstance(data, list):
            rate = data[0].get("funding_rate") if data else None
        return format_rate(rate) if rate is not None else "N/A"
    except (KeyError, TypeError, IndexError):
        return "N/A"

def get_bitget():
    url = "https://api.bitget.com/api/v2/mix/market/current-fund-rate"
    params = {"symbol": "BTCUSDT", "productType": "usdt-futures"}
    data = fetch_json(url, params)
    if "error" in data or data.get("code") != "00000":
        return "Error" if "error" in data else f"API Error: {data.get('msg', 'Unknown')}"
    try:
        # Response usually has data as list or dict
        item = data["data"][0] if isinstance(data.get("data"), list) else data.get("data", {})
        rate = item.get("fundingRate") or item.get("funding_rate")
        return format_rate(rate)
    except (KeyError, IndexError, TypeError):
        return "N/A"

def main():
    print("=" * 70)
    print(f"BTC Perpetual Funding Rates - Fetched at {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}")
    print("=" * 70)
    print(f"{'Exchange':<12} | {'Funding Rate (next period)'}")
    print("-" * 70)

    exchanges = [
        ("Binance", get_binance),
        ("OKX", get_okx),
        ("Bybit", get_bybit),
        ("KuCoin", get_kucoin),
        ("MEXC", get_mexc),
        ("BingX", get_bingx),
        ("Gate.io", get_gate),
        ("Bitget", get_bitget),
    ]

    for name, func in exchanges:
        rate_str = func()
        print(f"{name:<12} | {rate_str}")

    print("-" * 70)
    print("Note: Rates are predicted/current next funding rates (positive = longs pay shorts).")
    print("Data from public APIs - may vary slightly by timing. No API keys used.")
    print("=" * 70)

if __name__ == "__main__":
    main()