TradingAgents/tradingagents/dataflows/messari_vendor.py

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