315 lines
9.7 KiB
Python
315 lines
9.7 KiB
Python
"""
|
|
CCXT Crypto Data Vendor - Multi-exchange cryptocurrency market data
|
|
Supports: Binance, Coinbase, Kraken, and 100+ other exchanges
|
|
"""
|
|
import ccxt
|
|
import pandas as pd
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional, Dict, List, Any
|
|
import os
|
|
|
|
|
|
class CCXTVendor:
|
|
"""Wrapper for CCXT library to fetch crypto market data from multiple exchanges."""
|
|
|
|
def __init__(self, exchange_id: str = "binance", api_key: str = None, api_secret: str = None):
|
|
"""
|
|
Initialize CCXT exchange connection.
|
|
|
|
Args:
|
|
exchange_id: Exchange name (binance, coinbase, kraken, etc.)
|
|
api_key: API key for authenticated endpoints (optional)
|
|
api_secret: API secret for authenticated endpoints (optional)
|
|
"""
|
|
self.exchange_id = exchange_id
|
|
|
|
# Get API credentials from environment if not provided
|
|
if api_key is None:
|
|
api_key = os.getenv(f"{exchange_id.upper()}_API_KEY", "")
|
|
if api_secret is None:
|
|
api_secret = os.getenv(f"{exchange_id.upper()}_API_SECRET", "")
|
|
|
|
# Initialize exchange
|
|
exchange_class = getattr(ccxt, exchange_id)
|
|
self.exchange = exchange_class({
|
|
'apiKey': api_key,
|
|
'secret': api_secret,
|
|
'enableRateLimit': True, # Respect rate limits
|
|
'options': {
|
|
'defaultType': 'spot', # Default to spot markets
|
|
}
|
|
})
|
|
|
|
# Load markets
|
|
self.exchange.load_markets()
|
|
|
|
def get_ohlcv(
|
|
self,
|
|
symbol: str,
|
|
timeframe: str = '1d',
|
|
since: Optional[str] = None,
|
|
limit: int = 100
|
|
) -> pd.DataFrame:
|
|
"""
|
|
Fetch OHLCV (Open, High, Low, Close, Volume) data.
|
|
|
|
Args:
|
|
symbol: Trading pair (e.g., 'BTC/USDT', 'ETH/USD')
|
|
timeframe: Candle timeframe ('1m', '5m', '15m', '1h', '4h', '1d', '1w')
|
|
since: Start date (ISO format or timestamp)
|
|
limit: Number of candles to fetch
|
|
|
|
Returns:
|
|
DataFrame with columns: timestamp, open, high, low, close, volume
|
|
"""
|
|
# Convert since to timestamp if provided
|
|
since_ts = None
|
|
if since:
|
|
if isinstance(since, str):
|
|
since_dt = pd.to_datetime(since)
|
|
since_ts = int(since_dt.timestamp() * 1000)
|
|
else:
|
|
since_ts = since
|
|
|
|
# Fetch OHLCV data
|
|
ohlcv = self.exchange.fetch_ohlcv(
|
|
symbol=symbol,
|
|
timeframe=timeframe,
|
|
since=since_ts,
|
|
limit=limit
|
|
)
|
|
|
|
# Convert to DataFrame
|
|
df = pd.DataFrame(
|
|
ohlcv,
|
|
columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
|
|
)
|
|
|
|
# Convert timestamp to datetime
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
|
df.set_index('timestamp', inplace=True)
|
|
|
|
return df
|
|
|
|
def get_ticker(self, symbol: str) -> Dict[str, Any]:
|
|
"""
|
|
Fetch current ticker information (24h stats).
|
|
|
|
Args:
|
|
symbol: Trading pair (e.g., 'BTC/USDT')
|
|
|
|
Returns:
|
|
Dictionary with current price, volume, changes, etc.
|
|
"""
|
|
ticker = self.exchange.fetch_ticker(symbol)
|
|
return ticker
|
|
|
|
def get_order_book(self, symbol: str, limit: int = 20) -> Dict[str, Any]:
|
|
"""
|
|
Fetch order book (bids and asks).
|
|
|
|
Args:
|
|
symbol: Trading pair
|
|
limit: Depth of order book
|
|
|
|
Returns:
|
|
Dictionary with 'bids' and 'asks' arrays
|
|
"""
|
|
order_book = self.exchange.fetch_order_book(symbol, limit=limit)
|
|
return order_book
|
|
|
|
def get_trades(self, symbol: str, since: Optional[str] = None, limit: int = 100) -> pd.DataFrame:
|
|
"""
|
|
Fetch recent trades.
|
|
|
|
Args:
|
|
symbol: Trading pair
|
|
since: Start time (ISO format or timestamp)
|
|
limit: Number of trades
|
|
|
|
Returns:
|
|
DataFrame with trade history
|
|
"""
|
|
# Convert since to timestamp if provided
|
|
since_ts = None
|
|
if since:
|
|
if isinstance(since, str):
|
|
since_dt = pd.to_datetime(since)
|
|
since_ts = int(since_dt.timestamp() * 1000)
|
|
else:
|
|
since_ts = since
|
|
|
|
trades = self.exchange.fetch_trades(symbol, since=since_ts, limit=limit)
|
|
|
|
df = pd.DataFrame(trades)
|
|
if 'timestamp' in df.columns:
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
|
|
|
return df
|
|
|
|
def get_markets(self) -> List[str]:
|
|
"""
|
|
Get list of available trading pairs.
|
|
|
|
Returns:
|
|
List of trading pair symbols
|
|
"""
|
|
return list(self.exchange.markets.keys())
|
|
|
|
def get_balance(self) -> Dict[str, Any]:
|
|
"""
|
|
Fetch account balance (requires API key).
|
|
|
|
Returns:
|
|
Dictionary with account balances
|
|
"""
|
|
balance = self.exchange.fetch_balance()
|
|
return balance
|
|
|
|
|
|
# Convenience functions for integration with existing dataflow interface
|
|
|
|
def get_crypto_ohlcv(
|
|
symbol: str,
|
|
timeframe: str = '1d',
|
|
since: Optional[str] = None,
|
|
limit: int = 100,
|
|
exchange: str = "binance"
|
|
) -> str:
|
|
"""
|
|
Get crypto OHLCV data formatted as string.
|
|
|
|
Args:
|
|
symbol: Trading pair (e.g., 'BTC/USDT')
|
|
timeframe: Candle timeframe
|
|
since: Start date
|
|
limit: Number of candles
|
|
exchange: Exchange name
|
|
|
|
Returns:
|
|
Formatted string with OHLCV data
|
|
"""
|
|
vendor = CCXTVendor(exchange_id=exchange)
|
|
df = vendor.get_ohlcv(symbol, timeframe, since, limit)
|
|
|
|
# Format as string for LLM consumption
|
|
result = f"OHLCV Data for {symbol} on {exchange} ({timeframe} timeframe):\n\n"
|
|
result += df.to_string()
|
|
result += f"\n\nLatest Price: ${df['close'].iloc[-1]:.2f}"
|
|
result += f"\n24h Change: {((df['close'].iloc[-1] / df['close'].iloc[-2] - 1) * 100):.2f}%"
|
|
result += f"\n24h High: ${df['high'].iloc[-1]:.2f}"
|
|
result += f"\n24h Low: ${df['low'].iloc[-1]:.2f}"
|
|
result += f"\n24h Volume: {df['volume'].iloc[-1]:,.0f}"
|
|
|
|
return result
|
|
|
|
|
|
def get_crypto_ticker(symbol: str, exchange: str = "binance") -> str:
|
|
"""
|
|
Get current crypto ticker information.
|
|
|
|
Args:
|
|
symbol: Trading pair
|
|
exchange: Exchange name
|
|
|
|
Returns:
|
|
Formatted string with ticker data
|
|
"""
|
|
vendor = CCXTVendor(exchange_id=exchange)
|
|
ticker = vendor.get_ticker(symbol)
|
|
|
|
result = f"Ticker for {symbol} on {exchange}:\n\n"
|
|
result += f"Last Price: ${ticker.get('last', 0):.2f}\n"
|
|
result += f"Bid: ${ticker.get('bid', 0):.2f}\n"
|
|
result += f"Ask: ${ticker.get('ask', 0):.2f}\n"
|
|
result += f"24h High: ${ticker.get('high', 0):.2f}\n"
|
|
result += f"24h Low: ${ticker.get('low', 0):.2f}\n"
|
|
result += f"24h Volume: {ticker.get('quoteVolume', 0):,.0f}\n"
|
|
result += f"24h Change: {ticker.get('percentage', 0):.2f}%\n"
|
|
|
|
return result
|
|
|
|
|
|
def get_crypto_order_book(symbol: str, limit: int = 20, exchange: str = "binance") -> str:
|
|
"""
|
|
Get order book depth.
|
|
|
|
Args:
|
|
symbol: Trading pair
|
|
limit: Order book depth
|
|
exchange: Exchange name
|
|
|
|
Returns:
|
|
Formatted string with order book data
|
|
"""
|
|
vendor = CCXTVendor(exchange_id=exchange)
|
|
order_book = vendor.get_order_book(symbol, limit)
|
|
|
|
result = f"Order Book for {symbol} on {exchange} (Top {limit}):\n\n"
|
|
|
|
result += "ASKS (Sell Orders):\n"
|
|
for price, amount in order_book['asks'][:10]:
|
|
result += f" ${price:.2f} - {amount:.4f}\n"
|
|
|
|
result += "\nBIDS (Buy Orders):\n"
|
|
for price, amount in order_book['bids'][:10]:
|
|
result += f" ${price:.2f} - {amount:.4f}\n"
|
|
|
|
# Calculate spread
|
|
if order_book['bids'] and order_book['asks']:
|
|
best_bid = order_book['bids'][0][0]
|
|
best_ask = order_book['asks'][0][0]
|
|
spread = best_ask - best_bid
|
|
spread_pct = (spread / best_bid) * 100
|
|
result += f"\nSpread: ${spread:.2f} ({spread_pct:.3f}%)"
|
|
|
|
return result
|
|
|
|
|
|
def get_crypto_fundamentals(symbol: str, exchange: str = "binance") -> str:
|
|
"""
|
|
Get crypto fundamental metrics (volume, liquidity, market data).
|
|
|
|
Note: For true on-chain fundamentals, use Glassnode or Messari.
|
|
This provides exchange-level trading fundamentals.
|
|
|
|
Args:
|
|
symbol: Trading pair
|
|
exchange: Exchange name
|
|
|
|
Returns:
|
|
Formatted string with fundamental data
|
|
"""
|
|
vendor = CCXTVendor(exchange_id=exchange)
|
|
|
|
# Get ticker for current stats
|
|
ticker = vendor.get_ticker(symbol)
|
|
|
|
# Get order book for liquidity analysis
|
|
order_book = vendor.get_order_book(symbol, limit=100)
|
|
|
|
# Calculate liquidity metrics
|
|
bid_liquidity = sum(price * amount for price, amount in order_book['bids'][:20])
|
|
ask_liquidity = sum(price * amount for price, amount in order_book['asks'][:20])
|
|
total_liquidity = bid_liquidity + ask_liquidity
|
|
|
|
result = f"Fundamental Metrics for {symbol} on {exchange}:\n\n"
|
|
result += f"Market Price: ${ticker.get('last', 0):.2f}\n"
|
|
result += f"24h Volume (USD): ${ticker.get('quoteVolume', 0):,.0f}\n"
|
|
result += f"24h Trades: {ticker.get('info', {}).get('count', 'N/A')}\n"
|
|
result += f"Bid Liquidity (Top 20): ${bid_liquidity:,.0f}\n"
|
|
result += f"Ask Liquidity (Top 20): ${ask_liquidity:,.0f}\n"
|
|
result += f"Total Liquidity: ${total_liquidity:,.0f}\n"
|
|
result += f"Bid/Ask Ratio: {bid_liquidity/ask_liquidity if ask_liquidity > 0 else 0:.2f}\n"
|
|
|
|
# Calculate volatility from recent data
|
|
try:
|
|
df = vendor.get_ohlcv(symbol, timeframe='1h', limit=24)
|
|
returns = df['close'].pct_change().dropna()
|
|
volatility = returns.std() * 100
|
|
result += f"1-Day Volatility: {volatility:.2f}%\n"
|
|
except:
|
|
pass
|
|
|
|
return result
|