TradingAgents/tradingagents/dataflows/glassnode_vendor.py

320 lines
12 KiB
Python

"""
Glassnode API Vendor - On-chain cryptocurrency analytics
Provides: Network health, whale activity, mining data, DeFi metrics
"""
import requests
import pandas as pd
from datetime import datetime, timedelta
from typing import Optional, Dict, List
import os
class GlassnodeVendor:
"""Wrapper for Glassnode API to fetch on-chain crypto metrics."""
BASE_URL = "https://api.glassnode.com/v1/metrics"
def __init__(self, api_key: str = None):
"""
Initialize Glassnode API client.
Args:
api_key: Glassnode API key (or use GLASSNODE_API_KEY env var)
"""
self.api_key = api_key or os.getenv("GLASSNODE_API_KEY", "")
if not self.api_key:
print("WARNING: No Glassnode API key found. Set GLASSNODE_API_KEY environment variable.")
def _make_request(
self,
endpoint: str,
asset: str = "BTC",
since: Optional[str] = None,
until: Optional[str] = None,
interval: str = "24h"
) -> pd.DataFrame:
"""
Make API request to Glassnode.
Args:
endpoint: Metric endpoint (e.g., 'addresses/active_count')
asset: Cryptocurrency symbol (BTC, ETH, etc.)
since: Start date (YYYY-MM-DD or Unix timestamp)
until: End date (YYYY-MM-DD or Unix timestamp)
interval: Data interval (1h, 24h, 1w, 1month)
Returns:
DataFrame with timestamp and value columns
"""
url = f"{self.BASE_URL}/{endpoint}"
params = {
'a': asset,
'api_key': self.api_key,
'i': interval
}
if since:
params['s'] = since
if until:
params['u'] = until
try:
response = requests.get(url, params=params, timeout=30)
response.raise_for_status()
data = response.json()
# Convert to DataFrame
df = pd.DataFrame(data)
if 't' in df.columns and 'v' in df.columns:
df['timestamp'] = pd.to_datetime(df['t'], unit='s')
df['value'] = df['v']
df = df[['timestamp', 'value']].set_index('timestamp')
return df
except requests.exceptions.RequestException as e:
print(f"Glassnode API error for {endpoint}: {e}")
return pd.DataFrame()
# Network Health Metrics
def get_active_addresses(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get number of unique active addresses."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('addresses/active_count', asset=asset, since=since)
def get_new_addresses(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get number of new addresses created."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('addresses/new_non_zero_count', asset=asset, since=since)
def get_transaction_count(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get number of transactions per day."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('transactions/count', asset=asset, since=since)
def get_hash_rate(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get network hash rate (PoW chains only)."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('mining/hash_rate_mean', asset=asset, since=since)
# Whale and Exchange Metrics
def get_exchange_balance(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get total balance on exchanges."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('distribution/balance_exchanges', asset=asset, since=since)
def get_exchange_inflow(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get inflow to exchanges (potential selling pressure)."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('transactions/transfers_volume_exchanges_in', asset=asset, since=since)
def get_exchange_outflow(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get outflow from exchanges (potential accumulation)."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('transactions/transfers_volume_exchanges_out', asset=asset, since=since)
def get_whale_balance(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get balance held by whales (>1000 BTC or equivalent)."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('distribution/balance_1pct_holders', asset=asset, since=since)
# Valuation Metrics
def get_nvt_ratio(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get NVT (Network Value to Transactions) ratio."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('indicators/nvt', asset=asset, since=since)
def get_mvrv_ratio(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get MVRV (Market Value to Realized Value) ratio."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('indicators/mvrv', asset=asset, since=since)
def get_realized_price(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get realized price (average price at which coins last moved)."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('indicators/realized_price_usd', asset=asset, since=since)
# Supply Metrics
def get_supply_in_profit(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get percentage of supply in profit."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('indicators/supply_profit_relative', asset=asset, since=since)
def get_hodl_waves(self, asset: str = "BTC", days: int = 30) -> pd.DataFrame:
"""Get HODL waves (age distribution of coins)."""
since = int((datetime.now() - timedelta(days=days)).timestamp())
return self._make_request('supply/hodl_waves', asset=asset, since=since)
# Convenience functions for integration with existing dataflow interface
def get_onchain_metrics(asset: str = "BTC", days: int = 30) -> str:
"""
Get comprehensive on-chain metrics summary.
Args:
asset: Cryptocurrency symbol (BTC, ETH, etc.)
days: Number of days of historical data
Returns:
Formatted string with on-chain metrics
"""
vendor = GlassnodeVendor()
result = f"On-Chain Metrics for {asset} (Last {days} days):\n\n"
try:
# Network Health
active_addr = vendor.get_active_addresses(asset, days)
if not active_addr.empty:
latest = active_addr['value'].iloc[-1]
change = ((active_addr['value'].iloc[-1] / active_addr['value'].iloc[0]) - 1) * 100
result += f"Active Addresses: {latest:,.0f} ({change:+.1f}%)\n"
txn_count = vendor.get_transaction_count(asset, days)
if not txn_count.empty:
latest = txn_count['value'].iloc[-1]
change = ((txn_count['value'].iloc[-1] / txn_count['value'].iloc[0]) - 1) * 100
result += f"Daily Transactions: {latest:,.0f} ({change:+.1f}%)\n"
# Exchange Flows
exchange_balance = vendor.get_exchange_balance(asset, days)
if not exchange_balance.empty:
latest = exchange_balance['value'].iloc[-1]
change = ((exchange_balance['value'].iloc[-1] / exchange_balance['value'].iloc[0]) - 1) * 100
result += f"\nExchange Balance: {latest:,.0f} {asset} ({change:+.1f}%)\n"
inflow = vendor.get_exchange_inflow(asset, days)
outflow = vendor.get_exchange_outflow(asset, days)
if not inflow.empty and not outflow.empty:
net_flow = outflow['value'].iloc[-1] - inflow['value'].iloc[-1]
flow_direction = "OUTFLOW (Bullish)" if net_flow > 0 else "INFLOW (Bearish)"
result += f"Net Exchange Flow: {abs(net_flow):,.0f} {asset} {flow_direction}\n"
# Valuation Metrics
mvrv = vendor.get_mvrv_ratio(asset, days)
if not mvrv.empty:
latest = mvrv['value'].iloc[-1]
interpretation = "Overvalued" if latest > 3 else "Undervalued" if latest < 1 else "Fair Value"
result += f"\nMVRV Ratio: {latest:.2f} ({interpretation})\n"
nvt = vendor.get_nvt_ratio(asset, days)
if not nvt.empty:
latest = nvt['value'].iloc[-1]
result += f"NVT Ratio: {latest:.2f}\n"
# Profitability
supply_profit = vendor.get_supply_in_profit(asset, days)
if not supply_profit.empty:
latest = supply_profit['value'].iloc[-1] * 100
sentiment = "Bullish" if latest > 75 else "Bearish" if latest < 50 else "Neutral"
result += f"\nSupply in Profit: {latest:.1f}% ({sentiment})\n"
result += "\n[Note: On-chain data requires Glassnode API key]"
except Exception as e:
result += f"\nError fetching on-chain data: {e}\n"
result += "Ensure GLASSNODE_API_KEY is set in environment variables."
return result
def get_exchange_flow_analysis(asset: str = "BTC", days: int = 7) -> str:
"""
Analyze exchange inflows/outflows (whale movement indicator).
Args:
asset: Cryptocurrency symbol
days: Number of days to analyze
Returns:
Formatted string with flow analysis
"""
vendor = GlassnodeVendor()
result = f"Exchange Flow Analysis for {asset} (Last {days} days):\n\n"
try:
inflow = vendor.get_exchange_inflow(asset, days)
outflow = vendor.get_exchange_outflow(asset, days)
if not inflow.empty and not outflow.empty:
# Calculate net flows
df = pd.DataFrame({
'inflow': inflow['value'],
'outflow': outflow['value']
})
df['net_flow'] = df['outflow'] - df['inflow']
total_inflow = df['inflow'].sum()
total_outflow = df['outflow'].sum()
net_flow = total_outflow - total_inflow
result += f"Total Inflow: {total_inflow:,.0f} {asset}\n"
result += f"Total Outflow: {total_outflow:,.0f} {asset}\n"
result += f"Net Flow: {net_flow:,.0f} {asset}\n\n"
if net_flow > 0:
result += "⬆️ NET OUTFLOW - Bullish Signal (Accumulation)\n"
result += "Coins moving off exchanges suggests holders are accumulating for long-term.\n"
else:
result += "⬇️ NET INFLOW - Bearish Signal (Distribution)\n"
result += "Coins moving to exchanges suggests potential selling pressure.\n"
# Calculate flow ratio
flow_ratio = total_outflow / total_inflow if total_inflow > 0 else 0
result += f"\nOutflow/Inflow Ratio: {flow_ratio:.2f}\n"
except Exception as e:
result += f"Error: {e}\n"
return result
def get_whale_activity(asset: str = "BTC", days: int = 30) -> str:
"""
Analyze whale wallet activity.
Args:
asset: Cryptocurrency symbol
days: Number of days to analyze
Returns:
Formatted string with whale analysis
"""
vendor = GlassnodeVendor()
result = f"Whale Activity Analysis for {asset} (Last {days} days):\n\n"
try:
whale_balance = vendor.get_whale_balance(asset, days)
if not whale_balance.empty:
current = whale_balance['value'].iloc[-1]
previous = whale_balance['value'].iloc[0]
change = current - previous
change_pct = (change / previous) * 100
result += f"Top 1% Holders Balance: {current:,.0f} {asset}\n"
result += f"Change: {change:+,.0f} {asset} ({change_pct:+.2f}%)\n\n"
if change_pct > 1:
result += "🐋 WHALE ACCUMULATION - Bullish Signal\n"
result += "Large holders are increasing positions.\n"
elif change_pct < -1:
result += "🐋 WHALE DISTRIBUTION - Bearish Signal\n"
result += "Large holders are reducing positions.\n"
else:
result += "🐋 WHALE NEUTRAL - No significant change\n"
except Exception as e:
result += f"Error: {e}\n"
return result