# Pricing Provider Development Guide This guide explains how to add new pricing data providers to the Crypto Trader system. ## Overview Pricing providers are responsible for fetching market data (prices, OHLCV candlestick data) without requiring API keys. They differ from exchange adapters, which handle trading operations and require authentication. The system uses a multi-tier provider strategy: - **Primary Providers**: CCXT-based providers (Kraken, Coinbase, Binance) - **Fallback Provider**: CoinGecko API ## Provider Interface All pricing providers must implement the `BasePricingProvider` interface, located in `src/data/providers/base_provider.py`. ### Required Methods 1. **`name` (property)**: Return the provider's display name 2. **`supports_websocket` (property)**: Return True if the provider supports WebSocket connections 3. **`connect()`**: Establish connection to the provider, return True if successful 4. **`disconnect()`**: Close connection and clean up resources 5. **`get_ticker(symbol: str) -> Dict`**: Get current ticker data for a symbol 6. **`get_ohlcv(symbol, timeframe, since, limit) -> List[List]`**: Get historical OHLCV data 7. **`subscribe_ticker(symbol, callback) -> bool`**: Subscribe to real-time ticker updates ### Ticker Data Format The `get_ticker()` method should return a dictionary with the following keys: ```python { 'symbol': str, # Trading pair (e.g., 'BTC/USD') 'bid': Decimal, # Best bid price 'ask': Decimal, # Best ask price 'last': Decimal, # Last traded price 'high': Decimal, # 24h high 'low': Decimal, # 24h low 'volume': Decimal, # 24h volume 'timestamp': int, # Unix timestamp in milliseconds } ``` ### OHLCV Data Format The `get_ohlcv()` method should return a list of candles, where each candle is: ```python [timestamp_ms, open, high, low, close, volume] ``` All values should be numeric (float or Decimal). ## Creating a New Provider ### Step 1: Create Provider Class Create a new file in `src/data/providers/` (e.g., `my_provider.py`): ```python """My custom pricing provider.""" from decimal import Decimal from typing import Dict, List, Optional, Any, Callable from datetime import datetime from .base_provider import BasePricingProvider from ...core.logger import get_logger logger = get_logger(__name__) class MyProvider(BasePricingProvider): """My custom pricing provider implementation.""" @property def name(self) -> str: return "MyProvider" @property def supports_websocket(self) -> bool: return False # Set to True if WebSocket supported def connect(self) -> bool: """Connect to provider.""" try: # Initialize connection self._connected = True logger.info(f"Connected to {self.name}") return True except Exception as e: logger.error(f"Failed to connect: {e}") self._connected = False return False def disconnect(self): """Disconnect from provider.""" self._connected = False logger.info(f"Disconnected from {self.name}") def get_ticker(self, symbol: str) -> Dict[str, Any]: """Get current ticker data.""" # Implementation here pass def get_ohlcv( self, symbol: str, timeframe: str = '1h', since: Optional[datetime] = None, limit: int = 100 ) -> List[List]: """Get OHLCV data.""" # Implementation here pass def subscribe_ticker(self, symbol: str, callback: Callable) -> bool: """Subscribe to ticker updates.""" # Implementation here pass ``` ### Step 2: Register Provider Update `src/data/providers/__init__.py` to include your provider: ```python from .my_provider import MyProvider __all__ = [..., 'MyProvider'] ``` ### Step 3: Add to Pricing Service Update `src/data/pricing_service.py` to include your provider in the initialization: ```python # Add to _initialize_providers method try: my_provider = MyProvider() if my_provider.connect(): self._providers[my_provider.name] = my_provider self._provider_priority.append(my_provider.name) except Exception as e: logger.error(f"Error initializing MyProvider: {e}") ``` ### Step 4: Add Configuration Update `src/core/config.py` to include configuration options for your provider: ```python "data_providers": { "primary": [ # ... existing providers ... {"name": "my_provider", "enabled": True, "priority": 4}, ], # ... } ``` ## Best Practices ### Error Handling - Always catch exceptions in provider methods - Return empty data structures (`{}` or `[]`) on error rather than raising - Log errors with appropriate detail level ### Rate Limiting - Respect API rate limits - Implement appropriate delays between requests - Use exponential backoff for retries ### Symbol Normalization - Override `normalize_symbol()` if your provider uses different symbol formats - Handle common variations (BTC/USD vs BTC-USD vs BTCUSD) ### Caching - The pricing service handles caching automatically - Focus on providing fresh data from the API - Don't implement your own caching layer ### Testing Create unit tests in `tests/unit/data/providers/test_my_provider.py`: ```python """Unit tests for MyProvider.""" import pytest from unittest.mock import Mock, patch from src.data.providers.my_provider import MyProvider class TestMyProvider: def test_connect(self): provider = MyProvider() result = provider.connect() assert result is True def test_get_ticker(self): provider = MyProvider() provider.connect() ticker = provider.get_ticker("BTC/USD") assert 'last' in ticker assert ticker['last'] > 0 ``` ## Example: CoinGecko Provider See `src/data/providers/coingecko_provider.py` for a complete example of a REST API-based provider. ## Example: CCXT Provider See `src/data/providers/ccxt_provider.py` for an example of a provider that wraps an existing library (CCXT). ## Health Monitoring The pricing service automatically monitors provider health: - Tracks success/failure rates - Measures response times - Implements circuit breaker pattern - Automatically fails over to next provider Your provider doesn't need to implement health monitoring - it's handled by the `HealthMonitor` class. ## Subscriptions For real-time updates, implement `subscribe_ticker()`. The service expects: - Subscriptions to be persistent until `unsubscribe_ticker()` is called - Callbacks to be invoked with ticker data dictionaries - Graceful handling of connection failures If WebSocket is not supported, use polling with appropriate intervals (typically 1-5 seconds for ticker data). ## Questions? For more information, see: - `src/data/providers/base_provider.py` - Base interface - `src/data/pricing_service.py` - Service implementation - `src/data/health_monitor.py` - Health monitoring