Tags: python cryptocurrency api quantitative-finance tutorial Estimated reading time: 25 minutes | Difficulty: Intermediate The Problem Nobody Talks About It's 3 AM. Bitcoin just dropped 18% in 45 minutes. Your long position got liquidated while you were asleep. You wake up to a notification that your account balance is zero. This isn't a hypothetical. On August 5, 2024, over 1billioninliquidationshitthemarketinasinglehour.Thesignalsweretherebeforehandfundingratesatextremehighs,openinterestsurging,long/shortratioatdangerouslevels.Butnobodywaswatching.Thecryptomarketruns24/7.Youdont.Inthistutorial,IllshowyouhowtobuildaproductionreadycryptoderivativesmonitorusingtheCoinGlassAPIV4andPython.Bytheend,youllhaveasystemthat:Fetchesrealtimefundingrates,openinterest,liquidationdata,andlong/shortratiosDetectsanomalousmarketconditionsautomaticallySendsTelegramalertswhenrisksignalsfireRuns24/7ona1 billion in liquidations hit the market in a single hour. The signals were there beforehand — funding rates at extreme highs, open interest surging, long/short ratio at dangerous levels. But nobody was watching. The crypto market runs 24/7. You don't. In this tutorial, I'll show you how to build a production-ready crypto derivatives monitor using the CoinGlass API V4 and Python. By the end, you'll have a system that: Fetches real-time funding rates, open interest, liquidation data, and long/short ratios Detects anomalous market conditions automatically Sends Telegram alerts when risk signals fire Runs 24/7 on a 5/month cloud server Let's build it. Table of Contents Why Derivatives Data Matters Understanding the Four Key Metrics Setting Up CoinGlass API V4 Building the Data Layer Building the Signal Engine Building the Alert Layer Putting It All Together Deploying to Production What to Watch For Next Steps Why Derivatives Data Matters Most crypto traders watch price charts. The traders who consistently outperform watch something else entirely: the derivatives market . Here's why: the perpetual futures market in crypto is enormous. On any given day, BTC perpetual swaps alone trade over $50 billion in volume — dwarfing the spot market. This market is driven by leveraged positions, and leveraged positions create predictable pressure points. When too many traders are positioned on the same side, the market has a structural incentive to move against them. Smart money knows where the liquidations are clustered. Liquidation cascades don't happen randomly — they happen at predictable price levels, triggered by identifiable conditions. The four metrics we'll monitor are: ┌─────────────────────────────────────────────────────────┐ │ The Derivatives Intelligence Stack │ ├─────────────────────┬───────────────────────────────────┤ │ Metric │ What It Tells You │ ├─────────────────────┼───────────────────────────────────┤ │ Funding Rate │ Cost of holding leveraged │ │ │ positions; extreme values = │ │ │ sentiment at limits │ ├─────────────────────┼───────────────────────────────────┤ │ Open Interest │ Total capital in open positions; │ │ │ surges signal incoming moves │ ├─────────────────────┼───────────────────────────────────┤ │ Liquidation Data │ Forced position closures; │ │ │ cascades amplify price moves │ ├─────────────────────┼───────────────────────────────────┤ │ Long/Short Ratio │ Directional bias of market │ │ │ participants; extremes = │ │ │ contrarian signals │ └─────────────────────┴───────────────────────────────────┘ None of these metrics are available on a standard price chart. You need a derivatives data API to access them — and that's where CoinGlass comes in. Understanding the Four Key Metrics Before writing a single line of code, let's make sure we understand what we're actually measuring. Funding Rate Perpetual futures contracts don't expire, which means exchanges need a mechanism to keep their price anchored to the spot market. That mechanism is the funding rate — a periodic payment exchanged between long and short holders. Positive funding rate : Longs pay shorts. The market is leaning bullish, and longs are paying a premium to maintain their positions. Negative funding rate : Shorts pay longs. The market is leaning bearish. The signal isn't the direction — it's the magnitude . When the average funding rate across exchanges exceeds +0.1% per 8 hours, the cost of holding longs becomes unsustainable. History shows that when funding rates reach these extremes, mean reversion usually follows within 24–72 hours. Funding Rate Interpretation Guide ─────────────────────────────────────────────────────────── Rate Interpretation Risk Level ───────── ────────────────── ──────────

+0.10% → Extreme greed 🔴 HIGH (longs at risk) +0.05 to Elevated bullish 🟡 MEDIUM +0.10% sentiment -0.05 to Neutral / balanced 🟢 LOW +0.05% -0.05 to Elevated bearish 🟡 MEDIUM -0.10% sentiment < -0.10% → Extreme fear 🔴 HIGH (shorts at risk) ─────────────────────────────────────────────────────────── Open Interest (OI) Open interest measures the total value of all open derivative positions — every long and short that hasn't been closed yet. Think of it as the total amount of money currently "at stake" in the market. OI alone tells you about the size of the market. But OI changes tell you something far more important: OI surging + price rising : New money is entering long positions. Bullish conviction is building. OI surging + price falling : New money is entering short positions. Bearish conviction is building. OI falling + price rising : Shorts are covering (short squeeze). Rally may lack sustainability. OI falling + price falling : Longs are capitulating. Potential exhaustion of selling pressure. Liquidation Data When a leveraged position's losses exceed its margin, the exchange forcibly closes it — this is a liquidation . Large-scale liquidations are both a symptom and a cause of price moves. The dangerous scenario: liquidation cascades . Price falls → triggers long liquidations → liquidation selling pushes price lower → triggers more liquidations → price accelerates downward. We saw this loop play out multiple times in 2024. CoinGlass tracks liquidation data across 30+ exchanges in real time. Monitoring hourly liquidation volumes gives you an early warning system for cascade events. Long/Short Ratio The long/short ratio tells you what percentage of accounts are positioned long vs. short. Critically, CoinGlass provides two versions of this ratio: Global L/S ratio : All accounts — dominated by retail traders Top trader L/S ratio : Top 5–20% of accounts by position size — closer to "smart money" The most powerful signal: when these two diverge. When retail is overwhelmingly long while top traders are quietly building short positions, that's an institutional-grade warning sign. Setting Up CoinGlass API V4 Getting Your API Key Go to coinglass.com/pricing Create an account and choose a plan (free tier available for testing) Navigate to your dashboard and generate an API key ⚠️ Important : CoinGlass has fully migrated to API V4 . All previous API versions are deprecated. Make sure you're using the V4 base URL: https://open-api-v4.coinglass.com Project Setup # Create project directory mkdir crypto-derivatives-monitor cd crypto-derivatives-monitor # Create virtual environment python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate # Install dependencies pip install requests schedule python-dotenv anthropic Create a .env file for your credentials: COINGLASS_API_KEY = your_coinglass_api_key_here TELEGRAM_BOT_TOKEN = your_telegram_bot_token TELEGRAM_CHAT_ID = your_telegram_chat_id Project structure: crypto-derivatives-monitor/ ├── .env ├── config.py ├── data_layer.py ├── signal_engine.py ├── alert_layer.py ├── monitor.py └── requirements.txt Config File # config.py import os from dotenv import load_dotenv load_dotenv () # CoinGlass API V4 BASE_URL = " https://open-api-v4.coinglass.com " API_KEY = os . getenv ( " COINGLASS_API_KEY " ) HEADERS = { " CG-API-KEY " : API_KEY , " Accept " : " application/json " } # Telegram TELEGRAM_TOKEN = os . getenv ( " TELEGRAM_BOT_TOKEN " ) TELEGRAM_CHAT_ID = os . getenv ( " TELEGRAM_CHAT_ID " ) # Monitoring settings SYMBOLS = [ " BTC " , " ETH " ] POLL_INTERVAL = 5 # minutes DEFAULT_INTERVAL = " h1 " # candle size for history endpoints DEFAULT_LIMIT = 2 # enough to calculate 1-period change # Signal thresholds FUNDING_RATE_HIGH = 0.10 # % — extreme greed FUNDING_RATE_LOW = - 0.05 # % — extreme fear OI_CHANGE_THRESHOLD = 5.0 # % change in 1 hour LIQ_THRESHOLD_USD = 50_000_000 # $50M single-side liquidation LONG_RATIO_HIGH = 65.0 # % — too many longs SHORT_RATIO_HIGH = 60.0 # % — too many shorts TOP_DIVERGENCE_GAP = 10.0 # % gap between retail and top traders Building the Data Layer The data layer is responsible for one thing: fetching clean, structured data from CoinGlass API V4 . Each function maps directly to an official V4 endpoint. # data_layer.py import requests import logging from datetime import datetime from config import BASE_URL , HEADERS logger = logging . getLogger ( name ) def _get ( endpoint : str , params : dict ) -> list : """ Generic GET helper with error handling. Returns the ' data ' array from CoinGlass API response. """ url = f " { BASE_URL }{ endpoint } " try : resp = requests . get ( url , headers = HEADERS , params = params , timeout = 10 ) resp . raise_for_status () return resp . json (). get ( " data " , []) except requests . exceptions . Timeout : logger . error ( f " Timeout on { endpoint } " ) return [] except requests . exceptions . HTTPError as e : logger . error ( f " HTTP error on { endpoint } : { e . response . status_code } " ) return [] except Exception as e : logger . error ( f " Unexpected error on { endpoint } : { e } " ) return [] # ── Funding Rate ────────────────────────────────────────────────────────────── def get_funding_rate_by_exchange ( symbol : str = " BTC " ) -> dict : """ Fetch current funding rates across all exchanges.

Endpoint: GET /api/futures/fundingRate/exchange-list
Params:
    symbol (str): Asset symbol e.g. " BTC " , " ETH " Response fields per item:
    exchangeName  — Exchange identifier
    fundingRate   — Current rate (decimal, e.g. 0.0001 = 0.01%)
    nextFundingTime — Unix timestamp (ms) of next settlement """ data = _get ( " /api/futures/fundingRate/exchange-list " , { " symbol " : symbol }) if not data : return {} rates = [ float ( x [ " fundingRate " ]) * 100 for x in data if x . get ( " fundingRate " ) is not None ] avg = sum ( rates ) / len ( rates ) if rates else 0.0 return { " avg_funding_rate_pct " : round ( avg , 6 ), " exchange_count " : len ( rates ), " max_rate_pct " : round ( max ( rates ), 6 ) if rates else 0 , " min_rate_pct " : round ( min ( rates ), 6 ) if rates else 0 , " exchanges " : [ { " name " : x . get ( " exchangeName " , "" ), " rate_pct " : round ( float ( x . get ( " fundingRate " , 0 )) * 100 , 6 ), " next_funding " : x . get ( " nextFundingTime " ) } for x in data [: 8 ] ] } def get_funding_rate_history ( symbol : str = " BTC " , interval : str = " h1 " , limit : int = 24 ) -> list : """ Fetch OHLC history of funding rates.

Endpoint: GET /api/futures/fundingRate/ohlc-history
Params:
    symbol   (str): Asset symbol
    interval (str): Candle size — m1 m5 m15 m30 h1 h4 h8 h24
    limit    (int): Number of candles to return (max 200)
Response fields per item:
    t — timestamp (ms)
    o, h, l, c — open/high/low/close funding rate (decimal) """ return _get ( " /api/futures/fundingRate/ohlc-history " , { " symbol " : symbol , " interval " : interval , " limit " : limit } ) # ── Open Interest ───────────────────────────────────────────────────────────── def get_open_interest_history ( symbol : str = " BTC " , interval : str = " h1 " , limit : int = 2 ) -> dict : """ Fetch open interest OHLC history and compute period change.

Endpoint: GET /api/futures/openInterest/ohlc-history
Params:
    symbol   (str): Asset symbol
    interval (str): Candle size — m1 m5 m15 m30 h1 h4 h8 h24
    limit    (int): Number of candles (min 2 to compute change)
Response fields per item:
    t       — timestamp (ms)
    o, h, l, c — open interest in USD """ data = _get ( " /api/futures/openInterest/ohlc-history " , { " symbol " : symbol , " interval " : interval , " limit " : limit } ) if len ( data ) < 2 : return {} latest = data [ - 1 ] previous = data [ - 2 ] change = ( latest [ " c " ] - previous [ " c " ]) / previous [ " c " ] * 100 if previous [ " c " ] else 0 return { " current_oi_usd " : latest [ " c " ], " previous_oi_usd " : previous [ " c " ], " change_pct " : round ( change , 3 ), " period_high_usd " : latest [ " h " ], " period_low_usd " : latest [ " l " ], " timestamp_ms " : latest [ " t " ] } # ── Liquidations ────────────────────────────────────────────────────────────── def get_liquidation_history ( symbol : str = " BTC " , interval : str = " h1 " , limit : int = 1 ) -> dict : """ Fetch aggregated liquidation history (longs + shorts).

Endpoint: GET /api/futures/liquidation/history
Params:
    symbol   (str): Asset symbol
    interval (str): Candle size — m5 m15 m30 h1 h4 h8 h24
    limit    (int): Number of candles
Response fields per item:
    t           — timestamp (ms)
    longLiqUsd  — Long liquidations in USD
    shortLiqUsd — Short liquidations in USD """ data = _get ( " /api/futures/liquidation/history " , { " symbol " : symbol , " interval " : interval , " limit " : limit } ) if not data : return {} latest = data [ - 1 ] long_liq = float ( latest . get ( " longLiqUsd " , 0 )) short_liq = float ( latest . get ( " shortLiqUsd " , 0 )) total = long_liq + short_liq return { " long_liq_usd " : long_liq , " short_liq_usd " : short_liq , " total_liq_usd " : total , " dominant_side " : " longs " if long_liq > short_liq else " shorts " , " imbalance_ratio " : round ( max ( long_liq , short_liq ) / min ( long_liq , short_liq ), 2 ) if min ( long_liq , short_liq ) > 0 else 0 , " timestamp_ms " : latest . get ( " t " ) } # ── Long / Short Ratios ─────────────────────────────────────────────────────── def get_global_long_short_ratio ( symbol : str = " BTC " , interval : str = " h1 " , limit : int = 1 ) -> dict : """ Fetch global (all accounts) long/short account ratio history.

Endpoint: GET /api/futures/global-long-short-account-ratio/history
Params:
    symbol   (str): Asset symbol
    interval (str): Candle size — m5 m15 m30 h1 h4 h8 h24
    limit    (int): Number of candles
Response fields per item:
    t          — timestamp (ms)
    longRatio  — Fraction of accounts long (0 to 1)
    shortRatio — Fraction of accounts short (0 to 1) """ data = _get ( " /api/futures/global-long-short-account-ratio/history " , { " symbol " : symbol , " interval " : interval , " limit " : limit } ) if not data : return {} latest = data [ - 1 ] return { " long_pct " : round ( float ( latest . get ( " longRatio " , 0 )) * 100 , 2 ), " short_pct " : round ( float ( latest . get ( " shortRatio " , 0 )) * 100 , 2 ), " timestamp_ms " : latest . get ( " t " ) } def get_top_trader_long_short_ratio ( symbol : str = " BTC " , interval : str = " h1 " , limit : int = 1 ) -> dict : """ Fetch top trader (large accounts) long/short account ratio history.

Endpoint: GET /api/futures/top-long-short-account-ratio/history
Params: identical to global ratio endpoint above
Response fields: identical to global ratio endpoint above """ data = _get ( " /api/futures/top-long-short-account-ratio/history " , { " symbol " : symbol , " interval " : interval , " limit " : limit } ) if not data : return {} latest = data [ - 1 ] return { " long_pct " : round ( float ( latest . get ( " longRatio " , 0 )) * 100 , 2 ), " short_pct " : round ( float ( latest . get ( " shortRatio " , 0 )) * 100 , 2 ), " timestamp_ms " : latest . get ( " t " ) } def get_taker_buy_sell_volume ( symbol : str = " BTC " , interval : str = " h1 " , limit : int = 1 ) -> dict : """ Fetch taker (market order) buy vs. sell volume history.

Endpoint: GET /api/futures/taker-buy-sell-volume/history
Params:
    symbol   (str): Asset symbol
    interval (str): Candle size — m5 m15 m30 h1 h4 h8 h24
    limit    (int): Number of candles
Response fields per item:
    t       — timestamp (ms)
    buyVol  — Taker buy volume in USD
    sellVol — Taker sell volume in USD """ data = _get ( " /api/futures/taker-buy-sell-volume/history " , { " symbol " : symbol , " interval " : interval , " limit " : limit } ) if not data : return {} latest = data [ - 1 ] buy_vol = float ( latest . get ( " buyVol " , 0 )) sell_vol = float ( latest . get ( " sellVol " , 0 )) total = buy_vol + sell_vol return { " buy_vol_usd " : buy_vol , " sell_vol_usd " : sell_vol , " buy_pct " : round ( buy_vol / total * 100 , 2 ) if total else 50.0 , " sell_pct " : round ( sell_vol / total * 100 , 2 ) if total else 50.0 , " bias " : " buy-side " if buy_vol > sell_vol else " sell-side " , " timestamp_ms " : latest . get ( " t " ) } # ── Composite Data Package ──────────────────────────────────────────────────── def build_market_snapshot ( symbol : str = " BTC " ) -> dict : """ Assemble a complete derivatives market snapshot for one symbol.
Calls all five endpoints and returns a unified dict. """ logger . info ( f " Fetching snapshot for { symbol } ... " ) return { " symbol " : symbol , " timestamp " : datetime . utcnow (). isoformat () + " Z " , " funding_rate " : get_funding_rate_by_exchange ( symbol ), " open_interest " : get_open_interest_history ( symbol ), " liquidation " : get_liquidation_history ( symbol ), " global_ls " : get_global_long_short_ratio ( symbol ), " top_trader_ls " : get_top_trader_long_short_ratio ( symbol ), " taker_volume " : get_taker_buy_sell_volume ( symbol ), } Building the Signal Engine The signal engine reads the snapshot and produces structured alerts. No AI required — just clean conditional logic based on the thresholds we defined in config.py . # signal_engine.py from dataclasses import dataclass , field from typing import List from config import ( FUNDING_RATE_HIGH , FUNDING_RATE_LOW , OI_CHANGE_THRESHOLD , LIQ_THRESHOLD_USD , LONG_RATIO_HIGH , SHORT_RATIO_HIGH , TOP_DIVERGENCE_GAP ) @dataclass class Signal : level : str # "HIGH" | "MEDIUM" | "LOW" title : str message : str emoji : str = " ⚪ " def __post_init__ ( self ): self . emoji = { " HIGH " : " 🔴 " , " MEDIUM " : " 🟡 " , " LOW " : " 🟢 " }. get ( self . level , " ⚪ " ) @dataclass class MarketSignals : symbol : str signals : List [ Signal ] = field ( default_factory = list ) max_level : str = " NONE " def add ( self , signal : Signal ): self . signals . append ( signal ) priority = { " HIGH " : 3 , " MEDIUM " : 2 , " LOW " : 1 , " NONE " : 0 } if priority . get ( signal . level , 0 ) > priority . get ( self . max_level , 0 ): self . max_level = signal . level @property def has_alerts ( self ) -> bool : return len ( self . signals ) > 0 @property def top_emoji ( self ) -> str : return { " HIGH " : " 🔴 " , " MEDIUM " : " 🟡 " , " LOW " : " 🟢 " , " NONE " : " ⚪ " }. get ( self . max_level , " ⚪ " ) def analyze_snapshot ( snapshot : dict ) -> MarketSignals : """ Run all signal checks against a market snapshot.
Returns a MarketSignals object containing all triggered signals. """ symbol = snapshot . get ( " symbol " , " ??? " ) result = MarketSignals ( symbol = symbol ) fr = snapshot . get ( " funding_rate " , {}) oi = snapshot . get ( " open_interest " , {}) liq = snapshot . get ( " liquidation " , {}) gls = snapshot . get ( " global_ls " , {}) tls = snapshot . get ( " top_trader_ls " , {}) tkv = snapshot . get ( " taker_volume " , {}) # ── Signal 1: Funding Rate Extremes ────────────────────────────────────── avg_fr = fr . get ( " avg_funding_rate_pct " , 0 ) if avg_fr > FUNDING_RATE_HIGH : result . add ( Signal ( level = " HIGH " , title = " Extreme Positive Funding Rate " , message = ( f " Average funding rate across { fr . get ( ' exchange_count ' , ' ? ' ) } exchanges " f " is ** { avg