387 lines
13 KiB
Python
387 lines
13 KiB
Python
"""
|
|
Messari API Vendor - Crypto fundamental data and research
|
|
Provides: Asset profiles, metrics, tokenomics, project info
|
|
"""
|
|
import requests
|
|
import pandas as pd
|
|
from typing import Optional, Dict, List, Any
|
|
import os
|
|
|
|
|
|
class MessariVendor:
|
|
"""Wrapper for Messari API to fetch crypto fundamental data."""
|
|
|
|
BASE_URL = "https://data.messari.io/api"
|
|
|
|
def __init__(self, api_key: str = None):
|
|
"""
|
|
Initialize Messari API client.
|
|
|
|
Args:
|
|
api_key: Messari API key (optional for basic endpoints)
|
|
"""
|
|
self.api_key = api_key or os.getenv("MESSARI_API_KEY", "")
|
|
self.headers = {}
|
|
if self.api_key:
|
|
self.headers['x-messari-api-key'] = self.api_key
|
|
|
|
def _make_request(self, endpoint: str, params: Dict = None) -> Dict:
|
|
"""
|
|
Make API request to Messari.
|
|
|
|
Args:
|
|
endpoint: API endpoint path
|
|
params: Query parameters
|
|
|
|
Returns:
|
|
JSON response data
|
|
"""
|
|
url = f"{self.BASE_URL}/{endpoint}"
|
|
|
|
try:
|
|
response = requests.get(url, headers=self.headers, params=params, timeout=30)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
print(f"Messari API error for {endpoint}: {e}")
|
|
return {}
|
|
|
|
def get_asset_profile(self, asset_key: str) -> Dict[str, Any]:
|
|
"""
|
|
Get comprehensive asset profile.
|
|
|
|
Args:
|
|
asset_key: Asset slug (e.g., 'bitcoin', 'ethereum')
|
|
|
|
Returns:
|
|
Dictionary with asset profile data
|
|
"""
|
|
endpoint = f"v2/assets/{asset_key}/profile"
|
|
data = self._make_request(endpoint)
|
|
return data.get('data', {})
|
|
|
|
def get_asset_metrics(self, asset_key: str) -> Dict[str, Any]:
|
|
"""
|
|
Get current asset metrics (price, market cap, volume, etc.).
|
|
|
|
Args:
|
|
asset_key: Asset slug
|
|
|
|
Returns:
|
|
Dictionary with current metrics
|
|
"""
|
|
endpoint = f"v1/assets/{asset_key}/metrics"
|
|
data = self._make_request(endpoint)
|
|
return data.get('data', {})
|
|
|
|
def get_market_data(self, asset_key: str) -> Dict[str, Any]:
|
|
"""
|
|
Get detailed market data.
|
|
|
|
Args:
|
|
asset_key: Asset slug
|
|
|
|
Returns:
|
|
Dictionary with market data
|
|
"""
|
|
endpoint = f"v1/assets/{asset_key}/metrics/market-data"
|
|
data = self._make_request(endpoint)
|
|
return data.get('data', {})
|
|
|
|
def get_all_assets(self, limit: int = 100) -> List[Dict]:
|
|
"""
|
|
Get list of all assets.
|
|
|
|
Args:
|
|
limit: Number of assets to return
|
|
|
|
Returns:
|
|
List of asset dictionaries
|
|
"""
|
|
endpoint = "v2/assets"
|
|
params = {'limit': limit}
|
|
data = self._make_request(endpoint, params)
|
|
return data.get('data', [])
|
|
|
|
def get_news(self, asset_key: str = None, limit: int = 20) -> List[Dict]:
|
|
"""
|
|
Get crypto news.
|
|
|
|
Args:
|
|
asset_key: Asset slug (optional, for asset-specific news)
|
|
limit: Number of news items
|
|
|
|
Returns:
|
|
List of news items
|
|
"""
|
|
if asset_key:
|
|
endpoint = f"v1/assets/{asset_key}/news"
|
|
else:
|
|
endpoint = "v1/news"
|
|
|
|
params = {'limit': limit}
|
|
data = self._make_request(endpoint, params)
|
|
return data.get('data', [])
|
|
|
|
def get_timeseries(
|
|
self,
|
|
asset_key: str,
|
|
metric_id: str,
|
|
start: str = None,
|
|
end: str = None,
|
|
interval: str = '1d'
|
|
) -> pd.DataFrame:
|
|
"""
|
|
Get historical timeseries data for a metric.
|
|
|
|
Args:
|
|
asset_key: Asset slug
|
|
metric_id: Metric ID (e.g., 'price', 'volume', 'active_addresses')
|
|
start: Start date (YYYY-MM-DD)
|
|
end: End date (YYYY-MM-DD)
|
|
interval: Data interval (1d, 1w, 1m)
|
|
|
|
Returns:
|
|
DataFrame with timeseries data
|
|
"""
|
|
endpoint = f"v1/assets/{asset_key}/metrics/{metric_id}/time-series"
|
|
params = {
|
|
'interval': interval
|
|
}
|
|
if start:
|
|
params['start'] = start
|
|
if end:
|
|
params['end'] = end
|
|
|
|
data = self._make_request(endpoint, params)
|
|
values = data.get('data', {}).get('values', [])
|
|
|
|
if values:
|
|
df = pd.DataFrame(values, columns=['timestamp', 'value'])
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
|
df.set_index('timestamp', inplace=True)
|
|
return df
|
|
else:
|
|
return pd.DataFrame()
|
|
|
|
|
|
# Convenience functions for integration with existing dataflow interface
|
|
|
|
def get_crypto_fundamentals_messari(asset_key: str) -> str:
|
|
"""
|
|
Get crypto fundamental data from Messari.
|
|
|
|
Args:
|
|
asset_key: Asset slug (e.g., 'bitcoin', 'ethereum', 'solana')
|
|
|
|
Returns:
|
|
Formatted string with fundamental data
|
|
"""
|
|
vendor = MessariVendor()
|
|
|
|
result = f"Fundamental Analysis for {asset_key.upper()} (Messari Data):\n\n"
|
|
|
|
try:
|
|
# Get asset profile
|
|
profile = vendor.get_asset_profile(asset_key)
|
|
|
|
if profile:
|
|
general = profile.get('profile', {}).get('general', {})
|
|
economics = profile.get('profile', {}).get('economics', {})
|
|
technology = profile.get('profile', {}).get('technology', {})
|
|
|
|
# General Info
|
|
result += "=== PROJECT OVERVIEW ===\n"
|
|
result += f"Name: {general.get('overview', {}).get('project_name', 'N/A')}\n"
|
|
result += f"Tagline: {general.get('overview', {}).get('tagline', 'N/A')}\n"
|
|
result += f"Category: {general.get('overview', {}).get('category', 'N/A')}\n"
|
|
result += f"Sector: {general.get('overview', {}).get('sector', 'N/A')}\n\n"
|
|
|
|
# Tokenomics
|
|
result += "=== TOKENOMICS ===\n"
|
|
token_type = economics.get('token', {}).get('token_type', 'N/A')
|
|
token_usage = economics.get('token', {}).get('token_usage', 'N/A')
|
|
result += f"Token Type: {token_type}\n"
|
|
result += f"Token Usage: {token_usage}\n"
|
|
|
|
# Launch info
|
|
launch = economics.get('launch', {})
|
|
if launch:
|
|
result += f"Launch Date: {launch.get('general', {}).get('launch_date', 'N/A')}\n"
|
|
result += f"Launch Style: {launch.get('general', {}).get('launch_style', 'N/A')}\n"
|
|
|
|
# Consensus
|
|
result += f"\n=== TECHNOLOGY ===\n"
|
|
result += f"Consensus: {technology.get('overview', {}).get('consensus_mechanism', 'N/A')}\n"
|
|
result += f"Hashing Algorithm: {technology.get('overview', {}).get('hashing_algorithm', 'N/A')}\n"
|
|
|
|
# Get current metrics
|
|
metrics = vendor.get_asset_metrics(asset_key)
|
|
|
|
if metrics:
|
|
market_data = metrics.get('market_data', {})
|
|
marketcap = metrics.get('marketcap', {})
|
|
supply = metrics.get('supply', {})
|
|
|
|
result += f"\n=== MARKET METRICS ===\n"
|
|
result += f"Price (USD): ${market_data.get('price_usd', 0):,.2f}\n"
|
|
result += f"Market Cap: ${marketcap.get('current_marketcap_usd', 0):,.0f}\n"
|
|
result += f"24h Volume: ${market_data.get('real_volume_last_24_hours', 0):,.0f}\n"
|
|
result += f"24h Change: {market_data.get('percent_change_usd_last_24_hours', 0):.2f}%\n"
|
|
|
|
result += f"\n=== SUPPLY METRICS ===\n"
|
|
result += f"Circulating Supply: {supply.get('circulating', 0):,.0f}\n"
|
|
result += f"Total Supply: {supply.get('y_2050', 0):,.0f}\n"
|
|
result += f"Max Supply: {supply.get('max', 'Unlimited') if supply.get('max') else 'Unlimited'}\n"
|
|
|
|
# Calculate inflation rate
|
|
circulating = supply.get('circulating', 0)
|
|
y_plus_10 = supply.get('y_plus_ten', 0)
|
|
if circulating > 0 and y_plus_10 > circulating:
|
|
annual_inflation = ((y_plus_10 / circulating) ** 0.1 - 1) * 100
|
|
result += f"Est. Annual Inflation: {annual_inflation:.2f}%\n"
|
|
|
|
# ROI metrics
|
|
roi_data = metrics.get('roi_data', {})
|
|
if roi_data:
|
|
result += f"\n=== ROI METRICS ===\n"
|
|
result += f"ATH Price: ${roi_data.get('price_at_ath', 0):,.2f}\n"
|
|
result += f"% Down from ATH: {roi_data.get('percent_down_from_ath', 0):.1f}%\n"
|
|
|
|
except Exception as e:
|
|
result += f"\nError fetching Messari data: {e}\n"
|
|
result += "Note: Some data may require Messari API key."
|
|
|
|
return result
|
|
|
|
|
|
def get_crypto_news_messari(asset_key: str = None, limit: int = 10) -> str:
|
|
"""
|
|
Get crypto news from Messari.
|
|
|
|
Args:
|
|
asset_key: Asset slug (optional, for asset-specific news)
|
|
limit: Number of news items
|
|
|
|
Returns:
|
|
Formatted string with news
|
|
"""
|
|
vendor = MessariVendor()
|
|
|
|
if asset_key:
|
|
result = f"Latest News for {asset_key.upper()} (Messari):\n\n"
|
|
else:
|
|
result = f"Latest Crypto News (Messari):\n\n"
|
|
|
|
try:
|
|
news_items = vendor.get_news(asset_key, limit)
|
|
|
|
for i, item in enumerate(news_items[:limit], 1):
|
|
title = item.get('title', 'No title')
|
|
published = item.get('published_at', 'Unknown date')
|
|
url = item.get('url', '')
|
|
|
|
result += f"{i}. {title}\n"
|
|
result += f" Published: {published}\n"
|
|
result += f" URL: {url}\n\n"
|
|
|
|
except Exception as e:
|
|
result += f"Error fetching news: {e}\n"
|
|
|
|
return result
|
|
|
|
|
|
def get_crypto_market_overview(limit: int = 20) -> str:
|
|
"""
|
|
Get overview of top crypto assets.
|
|
|
|
Args:
|
|
limit: Number of assets to include
|
|
|
|
Returns:
|
|
Formatted string with market overview
|
|
"""
|
|
vendor = MessariVendor()
|
|
|
|
result = f"Crypto Market Overview - Top {limit} Assets (Messari):\n\n"
|
|
|
|
try:
|
|
assets = vendor.get_all_assets(limit)
|
|
|
|
result += f"{'Rank':<6}{'Symbol':<8}{'Price':<15}{'Market Cap':<20}{'24h %':<10}\n"
|
|
result += "=" * 70 + "\n"
|
|
|
|
for asset in assets:
|
|
rank = asset.get('metrics', {}).get('marketcap', {}).get('rank', 0)
|
|
symbol = asset.get('symbol', 'N/A')
|
|
price = asset.get('metrics', {}).get('market_data', {}).get('price_usd', 0)
|
|
mcap = asset.get('metrics', {}).get('marketcap', {}).get('current_marketcap_usd', 0)
|
|
change_24h = asset.get('metrics', {}).get('market_data', {}).get('percent_change_usd_last_24_hours', 0)
|
|
|
|
result += f"{rank:<6}{symbol:<8}${price:<14,.2f}${mcap:<19,.0f}{change_24h:>6.2f}%\n"
|
|
|
|
except Exception as e:
|
|
result += f"Error: {e}\n"
|
|
|
|
return result
|
|
|
|
|
|
def get_tokenomics_analysis(asset_key: str) -> str:
|
|
"""
|
|
Detailed tokenomics analysis.
|
|
|
|
Args:
|
|
asset_key: Asset slug
|
|
|
|
Returns:
|
|
Formatted string with tokenomics analysis
|
|
"""
|
|
vendor = MessariVendor()
|
|
|
|
result = f"Tokenomics Analysis for {asset_key.upper()}:\n\n"
|
|
|
|
try:
|
|
profile = vendor.get_asset_profile(asset_key)
|
|
metrics = vendor.get_asset_metrics(asset_key)
|
|
|
|
if profile and metrics:
|
|
economics = profile.get('profile', {}).get('economics', {})
|
|
supply = metrics.get('supply', {})
|
|
marketcap = metrics.get('marketcap', {})
|
|
|
|
# Supply Distribution
|
|
result += "=== SUPPLY SCHEDULE ===\n"
|
|
result += f"Circulating: {supply.get('circulating', 0):,.0f}\n"
|
|
result += f"Total Mined/Staked: {supply.get('y_2050', 0):,.0f}\n"
|
|
result += f"Maximum Supply: {supply.get('max', 'Unlimited') if supply.get('max') else 'Unlimited'}\n"
|
|
|
|
circ = supply.get('circulating', 0)
|
|
total = supply.get('y_2050', 0)
|
|
if total > 0:
|
|
circ_pct = (circ / total) * 100
|
|
result += f"% of Total Circulating: {circ_pct:.1f}%\n"
|
|
|
|
# Issuance
|
|
token_details = economics.get('token', {})
|
|
result += f"\n=== TOKEN DETAILS ===\n"
|
|
result += f"Type: {token_details.get('token_type', 'N/A')}\n"
|
|
result += f"Usage: {token_details.get('token_usage', 'N/A')}\n"
|
|
result += f"Sale Details: {token_details.get('token_sale_details', 'N/A')}\n"
|
|
|
|
# Market Cap Analysis
|
|
result += f"\n=== VALUATION ===\n"
|
|
result += f"Market Cap: ${marketcap.get('current_marketcap_usd', 0):,.0f}\n"
|
|
result += f"Rank: #{marketcap.get('rank', 'N/A')}\n"
|
|
|
|
# Calculate dilution
|
|
if circ > 0 and total > circ:
|
|
dilution = ((total - circ) / circ) * 100
|
|
result += f"Potential Dilution: {dilution:.1f}%\n"
|
|
result += f"Fully Diluted Market Cap: ${marketcap.get('current_marketcap_usd', 0) * (total / circ):,.0f}\n"
|
|
|
|
except Exception as e:
|
|
result += f"Error: {e}\n"
|
|
|
|
return result
|