Night shift: tweet analyzer, data connectors, feed monitor, market watch portal
This commit is contained in:
181
tools/data_sources/coinglass.py
Executable file
181
tools/data_sources/coinglass.py
Executable file
@ -0,0 +1,181 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user