Spaces:
Running
Running
File size: 15,594 Bytes
b41bee3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 | #!/usr/bin/env python3
"""
π GLASSNODE API CLIENT π
==========================
Real on-chain data for whale tracking and market intelligence.
Glassnode API provides:
- BTC/ETH whale wallet movements
- Exchange inflows/outflows
- Large transaction tracking
- On-chain volume analysis
- Holder distribution metrics
API Key: $29/month (Basic tier)
Rate Limit: 1 request/second
"""
from aureon.core.aureon_baton_link import link_system as _baton_link; _baton_link(__name__)
import os
import sys
import time
import json
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass, field
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# Windows UTF-8 fix
if sys.platform == 'win32':
os.environ['PYTHONIOENCODING'] = 'utf-8'
@dataclass
class GlassnodeMetric:
"""Represents a single Glassnode metric with metadata."""
name: str
endpoint: str
description: str
asset: str = "BTC"
frequency: str = "1h" # 1h, 24h, etc.
tier: str = "free" # free, basic, premium
@dataclass
class WhaleMovement:
"""Large wallet movement detection."""
timestamp: int
asset: str
from_address: str
to_address: str
amount: float
usd_value: float
transaction_hash: str
exchange_related: bool = False
@dataclass
class ExchangeFlow:
"""Exchange inflow/outflow data."""
timestamp: int
asset: str
inflow_usd: float
outflow_usd: float
net_flow_usd: float
exchange: str
class GlassnodeClient:
"""
Glassnode API client for real on-chain intelligence.
Key metrics for whale tracking:
- Large transactions (> $100K)
- Exchange wallet movements
- Holder distribution changes
- On-chain volume spikes
"""
BASE_URL = "https://api.glassnode.com/v1/metrics"
API_KEY_ENV = "GLASSNODE_API_KEY"
# Essential metrics for whale tracking (Basic tier)
WHALE_METRICS = [
GlassnodeMetric(
name="large_transactions",
endpoint="transactions/transfers_volume_excluding_exchanges",
description="Large transaction volume excluding exchanges",
asset="BTC",
frequency="1h"
),
GlassnodeMetric(
name="exchange_inflows",
endpoint="indicators/exchange_inflow_volume",
description="Exchange inflow volume",
asset="BTC",
frequency="1h"
),
GlassnodeMetric(
name="exchange_outflows",
endpoint="indicators/exchange_outflow_volume",
description="Exchange outflow volume",
asset="BTC",
frequency="1h"
),
GlassnodeMetric(
name="whale_addresses",
endpoint="distribution/balance_exchanges",
description="Balance held by exchanges",
asset="BTC",
frequency="24h"
),
GlassnodeMetric(
name="holder_distribution",
endpoint="distribution/balance_holders",
description="Balance distribution by holder size",
asset="BTC",
frequency="24h"
)
]
def __init__(self, api_key: Optional[str] = None):
"""Initialize Glassnode client."""
self.api_key = api_key or os.getenv(self.API_KEY_ENV)
if not self.api_key:
raise ValueError(f"Glassnode API key not found. Set {self.API_KEY_ENV} environment variable.")
# Setup HTTP client with retries
self.session = requests.Session()
retry_strategy = Retry(
total=3,
status_forcelist=[429, 500, 502, 503, 504],
backoff_factor=1
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
# Rate limiting (1 req/sec)
self.last_request_time = 0
self.rate_limit_delay = 1.0
self.logger = logging.getLogger(__name__)
def _rate_limit_wait(self):
"""Enforce rate limiting."""
elapsed = time.time() - self.last_request_time
if elapsed < self.rate_limit_delay:
time.sleep(self.rate_limit_delay - elapsed)
self.last_request_time = time.time()
def _make_request(self, endpoint: str, params: Dict[str, Any]) -> Dict:
"""Make authenticated API request."""
self._rate_limit_wait()
url = f"{self.BASE_URL}/{endpoint}"
params['api_key'] = self.api_key
try:
response = self.session.get(url, params=params, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
self.logger.error(f"Glassnode API request failed: {e}")
raise
def get_metric_data(self,
metric: GlassnodeMetric,
since: Optional[int] = None,
until: Optional[int] = None) -> List[Dict]:
"""
Get metric data from Glassnode.
Args:
metric: GlassnodeMetric to fetch
since: Unix timestamp start (default: 24h ago)
until: Unix timestamp end (default: now)
Returns:
List of data points with timestamp and value
"""
if since is None:
since = int((datetime.now() - timedelta(hours=24)).timestamp())
if until is None:
until = int(datetime.now().timestamp())
params = {
'a': metric.asset,
's': since,
'u': until,
'i': metric.frequency
}
data = self._make_request(metric.endpoint, params)
# Glassnode returns data as list of [timestamp, value] pairs
result = []
for point in data:
if len(point) >= 2:
result.append({
'timestamp': point[0],
'value': point[1],
'metric': metric.name,
'asset': metric.asset
})
return result
def detect_whale_movements(self,
min_usd_value: float = 100000,
hours_back: int = 24) -> List[WhaleMovement]:
"""
Detect large whale movements using transaction data.
Args:
min_usd_value: Minimum USD value to consider a whale movement
hours_back: Hours to look back
Returns:
List of WhaleMovement objects
"""
movements = []
# Get large transaction volume data
large_tx_metric = GlassnodeMetric(
name="large_transactions",
endpoint="transactions/transfers_volume_excluding_exchanges",
description="Large transaction volume",
asset="BTC"
)
try:
data = self.get_metric_data(large_tx_metric,
since=int((datetime.now() - timedelta(hours=hours_back)).timestamp()))
for point in data:
volume_btc = point['value']
# Estimate USD value (rough approximation)
btc_price = self._get_btc_price_at_timestamp(point['timestamp'])
usd_value = volume_btc * btc_price
if usd_value >= min_usd_value:
# This is a simplified detection - in reality you'd need
# more detailed transaction analysis
movement = WhaleMovement(
timestamp=point['timestamp'],
asset="BTC",
from_address="unknown", # Would need tx details
to_address="unknown", # Would need tx details
amount=volume_btc,
usd_value=usd_value,
transaction_hash="unknown" # Would need tx details
)
movements.append(movement)
except Exception as e:
self.logger.error(f"Failed to detect whale movements: {e}")
return movements
def get_exchange_flows(self, hours_back: int = 24) -> List[ExchangeFlow]:
"""
Get exchange inflow/outflow data.
Returns:
List of ExchangeFlow objects
"""
flows = []
try:
# Get inflow data
inflow_metric = GlassnodeMetric(
name="exchange_inflows",
endpoint="indicators/exchange_inflow_volume",
description="Exchange inflow volume",
asset="BTC"
)
inflow_data = self.get_metric_data(inflow_metric,
since=int((datetime.now() - timedelta(hours=hours_back)).timestamp()))
# Get outflow data
outflow_metric = GlassnodeMetric(
name="exchange_outflows",
endpoint="indicators/exchange_outflow_volume",
description="Exchange outflow volume",
asset="BTC"
)
outflow_data = self.get_metric_data(outflow_metric,
since=int((datetime.now() - timedelta(hours=hours_back)).timestamp()))
# Combine inflow and outflow data
inflow_dict = {point['timestamp']: point['value'] for point in inflow_data}
outflow_dict = {point['timestamp']: point['value'] for point in outflow_data}
all_timestamps = set(inflow_dict.keys()) | set(outflow_dict.keys())
for ts in sorted(all_timestamps):
inflow_btc = inflow_dict.get(ts, 0)
outflow_btc = outflow_dict.get(ts, 0)
# Estimate USD values
btc_price = self._get_btc_price_at_timestamp(ts)
inflow_usd = inflow_btc * btc_price
outflow_usd = outflow_btc * btc_price
net_flow_usd = inflow_usd - outflow_usd
flow = ExchangeFlow(
timestamp=ts,
asset="BTC",
inflow_usd=inflow_usd,
outflow_usd=outflow_usd,
net_flow_usd=net_flow_usd,
exchange="all_exchanges" # Glassnode aggregates all exchanges
)
flows.append(flow)
except Exception as e:
self.logger.error(f"Failed to get exchange flows: {e}")
return flows
def _get_btc_price_at_timestamp(self, timestamp: int) -> float:
"""
Get BTC price at a specific timestamp.
Pulls a fresh BTC/USD spot price via the real-data fallback chain
(unified cache β CoinGecko cache β Kraken/Binance REST) and uses
that as a proxy for the historical timestamp. Returns 0.0 only
when every real source fails β callers downstream multiply
whale flows by this value, so 0.0 is the safe-fail sentinel
(whale_flow_usd = 0 is honest "unknown" vs hardcoded $45k which
looked real).
For true historical accuracy a per-timestamp price feed (e.g.
CoinGecko market_chart endpoint) is the right answer; until that
is wired, current spot is closer to truth than a 2024-era
hardcoded $45,000.
"""
try:
from aureon.observer.real_price_fallback import get_real_price
price = get_real_price('BTC/USD', timeout_sec=3.0)
if price and price > 0:
return float(price)
except Exception as exc:
self.logger.warning("[glassnode] _get_btc_price_at_timestamp fallback "
"chain raised: %s; returning 0.0", exc)
# Every real source failed β return 0.0 sentinel rather than
# hardcoded $45,000. Downstream whale_flow_usd will be 0, which
# operators can detect as "no real BTC reference price available".
return 0.0
def get_whale_intelligence_summary(self) -> Dict[str, Any]:
"""
Get comprehensive whale intelligence summary.
Returns:
Dict with whale activity metrics
"""
summary = {
'timestamp': int(datetime.now().timestamp()),
'whale_movements_24h': [],
'exchange_flows_24h': [],
'large_tx_volume_24h': 0,
'net_exchange_flow_24h': 0,
'risk_level': 'unknown'
}
try:
# Get whale movements
movements = self.detect_whale_movements(hours_back=24)
summary['whale_movements_24h'] = [
{
'timestamp': m.timestamp,
'amount_btc': m.amount,
'usd_value': m.usd_value
} for m in movements
]
# Get exchange flows
flows = self.get_exchange_flows(hours_back=24)
summary['exchange_flows_24h'] = [
{
'timestamp': f.timestamp,
'net_flow_usd': f.net_flow_usd
} for f in flows
]
# Calculate aggregates
summary['large_tx_volume_24h'] = sum(m.amount for m in movements)
summary['net_exchange_flow_24h'] = sum(f.net_flow_usd for f in flows)
# Determine risk level based on activity
total_activity = abs(summary['net_exchange_flow_24h']) + summary['large_tx_volume_24h'] * 45000
if total_activity > 1000000000: # $1B+ activity
summary['risk_level'] = 'high'
elif total_activity > 500000000: # $500M+ activity
summary['risk_level'] = 'medium'
else:
summary['risk_level'] = 'low'
except Exception as e:
self.logger.error(f"Failed to generate whale intelligence summary: {e}")
return summary
# Test function
def test_glassnode_client():
"""Test Glassnode client functionality."""
print("π TESTING GLASSNODE API CLIENT π")
print("=" * 50)
# Check if API key is available
api_key = os.getenv("GLASSNODE_API_KEY")
if not api_key:
print("β GLASSNODE_API_KEY environment variable not set")
print(" Get your API key from: https://glassnode.com")
print(" Basic tier: $29/month")
return
try:
client = GlassnodeClient(api_key)
print("β
Glassnode client initialized")
# Test basic connectivity with a free metric
print("\nπ Testing API connectivity...")
# Using a free metric that doesn't require authentication for basic test
# Note: Most whale metrics require paid tier
print("β
Glassnode API client ready for whale tracking!")
print("\nπ Available metrics:")
for metric in client.WHALE_METRICS:
print(f" β’ {metric.name}: {metric.description}")
except Exception as e:
print(f"β Glassnode client test failed: {e}")
if __name__ == "__main__":
test_glassnode_client() |