253 lines
9.2 KiB
Python
253 lines
9.2 KiB
Python
"""
|
|
Exchange Factory for creating data collectors.
|
|
|
|
This module provides a factory pattern for creating data collectors
|
|
from different exchanges based on configuration.
|
|
"""
|
|
|
|
import importlib
|
|
from typing import Dict, List, Optional, Any, Type, Tuple
|
|
from dataclasses import dataclass, field
|
|
|
|
from utils.logger import get_logger
|
|
from ..collector.base_collector import BaseDataCollector
|
|
from ..common.data_types import DataType
|
|
from ..common import CandleProcessingConfig
|
|
from .registry import EXCHANGE_REGISTRY, get_supported_exchanges, get_exchange_info
|
|
from .exceptions import (
|
|
ExchangeError,
|
|
ExchangeNotSupportedError,
|
|
InvalidConfigurationError,
|
|
CollectorCreationError,
|
|
ValidationError
|
|
)
|
|
|
|
# Initialize logger
|
|
logger = get_logger('default_logger')
|
|
|
|
@dataclass
|
|
class ExchangeCollectorConfig:
|
|
"""Configuration for creating an exchange collector."""
|
|
exchange: str
|
|
symbol: str
|
|
data_types: List[DataType]
|
|
timeframes: List[str] = field(default_factory=lambda: ['1m', '5m']) # Default timeframes
|
|
auto_restart: bool = True
|
|
health_check_interval: float = 30.0
|
|
store_raw_data: bool = True
|
|
custom_params: Optional[Dict[str, Any]] = None
|
|
|
|
def __post_init__(self):
|
|
"""Validate configuration after initialization."""
|
|
if not self.exchange:
|
|
raise InvalidConfigurationError("Exchange name cannot be empty")
|
|
if not self.symbol:
|
|
raise InvalidConfigurationError("Symbol cannot be empty")
|
|
if not self.data_types:
|
|
raise InvalidConfigurationError("At least one data type must be specified")
|
|
if not self.timeframes:
|
|
raise InvalidConfigurationError("At least one timeframe must be specified")
|
|
logger.debug(f"Created collector config for {self.exchange} {self.symbol}")
|
|
|
|
|
|
class ExchangeFactory:
|
|
"""Factory for creating exchange-specific data collectors."""
|
|
|
|
@staticmethod
|
|
def create_collector(config: ExchangeCollectorConfig) -> BaseDataCollector:
|
|
"""
|
|
Create a data collector for the specified exchange.
|
|
|
|
Args:
|
|
config: Configuration for the collector
|
|
|
|
Returns:
|
|
Instance of the appropriate collector class
|
|
|
|
Raises:
|
|
ExchangeNotSupportedError: If exchange is not supported
|
|
CollectorCreationError: If collector creation fails
|
|
"""
|
|
exchange_name = config.exchange.lower()
|
|
logger.info(f"Creating collector for {exchange_name} {config.symbol}")
|
|
|
|
if exchange_name not in EXCHANGE_REGISTRY:
|
|
supported = get_supported_exchanges()
|
|
error_msg = f"Exchange '{config.exchange}' not supported. Supported exchanges: {supported}"
|
|
logger.error(error_msg)
|
|
raise ExchangeNotSupportedError(error_msg)
|
|
|
|
exchange_info = get_exchange_info(exchange_name)
|
|
collector_class_path = exchange_info['collector']
|
|
|
|
# Parse module and class name
|
|
module_path, class_name = collector_class_path.rsplit('.', 1)
|
|
|
|
try:
|
|
# Import the module
|
|
logger.debug(f"Importing collector module {module_path}")
|
|
module = importlib.import_module(module_path)
|
|
|
|
# Get the collector class
|
|
collector_class = getattr(module, class_name)
|
|
|
|
# Prepare collector arguments
|
|
collector_args = {
|
|
'symbol': config.symbol,
|
|
'data_types': config.data_types,
|
|
'auto_restart': config.auto_restart,
|
|
'health_check_interval': config.health_check_interval,
|
|
'store_raw_data': config.store_raw_data,
|
|
'timeframes': config.timeframes # Pass timeframes to collector
|
|
}
|
|
|
|
# Add any custom parameters
|
|
if config.custom_params:
|
|
# If custom_params contains a candle_config key, use it, otherwise create one
|
|
if 'candle_config' not in config.custom_params:
|
|
config.custom_params['candle_config'] = CandleProcessingConfig(
|
|
timeframes=config.timeframes
|
|
)
|
|
collector_args.update(config.custom_params)
|
|
else:
|
|
# Create default candle config if no custom params
|
|
collector_args['candle_config'] = CandleProcessingConfig(
|
|
timeframes=config.timeframes
|
|
)
|
|
|
|
# Create and return the collector instance
|
|
logger.info(f"Successfully created collector for {exchange_name} {config.symbol}")
|
|
return collector_class(**collector_args)
|
|
|
|
except ImportError as e:
|
|
error_msg = f"Failed to import collector class '{collector_class_path}': {e}"
|
|
logger.error(error_msg)
|
|
raise CollectorCreationError(error_msg) from e
|
|
except Exception as e:
|
|
error_msg = f"Failed to create collector for '{config.exchange}': {e}"
|
|
logger.error(error_msg)
|
|
raise CollectorCreationError(error_msg) from e
|
|
|
|
@staticmethod
|
|
def create_multiple_collectors(configs: List[ExchangeCollectorConfig]) -> List[BaseDataCollector]:
|
|
"""
|
|
Create multiple collectors from a list of configurations.
|
|
|
|
Args:
|
|
configs: List of collector configurations
|
|
|
|
Returns:
|
|
List of collector instances
|
|
"""
|
|
collectors = []
|
|
logger.info(f"Creating {len(configs)} collectors")
|
|
|
|
for config in configs:
|
|
try:
|
|
collector = ExchangeFactory.create_collector(config)
|
|
collectors.append(collector)
|
|
logger.debug(f"Successfully created collector for {config.exchange} {config.symbol}")
|
|
except ExchangeError as e:
|
|
logger.error(f"Failed to create collector for {config.exchange} {config.symbol}: {e}")
|
|
|
|
logger.info(f"Successfully created {len(collectors)} out of {len(configs)} collectors")
|
|
return collectors
|
|
|
|
@staticmethod
|
|
def get_supported_pairs(exchange: str) -> List[str]:
|
|
"""
|
|
Get supported trading pairs for an exchange.
|
|
|
|
Args:
|
|
exchange: Exchange name
|
|
|
|
Returns:
|
|
List of supported trading pairs
|
|
"""
|
|
exchange_info = get_exchange_info(exchange)
|
|
if exchange_info:
|
|
return exchange_info.get('supported_pairs', [])
|
|
return []
|
|
|
|
@staticmethod
|
|
def get_supported_data_types(exchange: str) -> List[str]:
|
|
"""
|
|
Get supported data types for an exchange.
|
|
|
|
Args:
|
|
exchange: Exchange name
|
|
|
|
Returns:
|
|
List of supported data types
|
|
"""
|
|
exchange_info = get_exchange_info(exchange)
|
|
if exchange_info:
|
|
return exchange_info.get('supported_data_types', [])
|
|
return []
|
|
|
|
@staticmethod
|
|
def validate_config(config: ExchangeCollectorConfig) -> Tuple[bool, List[str]]:
|
|
"""
|
|
Validate collector configuration.
|
|
|
|
Args:
|
|
config: Configuration to validate
|
|
|
|
Returns:
|
|
Tuple of (is_valid, list_of_errors)
|
|
"""
|
|
logger.debug(f"Validating configuration for {config.exchange} {config.symbol}")
|
|
errors = []
|
|
|
|
# Check if exchange is supported
|
|
if config.exchange.lower() not in EXCHANGE_REGISTRY:
|
|
errors.append(f"Exchange '{config.exchange}' not supported")
|
|
|
|
# Check if symbol is supported
|
|
supported_pairs = ExchangeFactory.get_supported_pairs(config.exchange)
|
|
if supported_pairs and config.symbol not in supported_pairs:
|
|
errors.append(f"Symbol '{config.symbol}' not supported for {config.exchange}")
|
|
|
|
# Check if data types are supported
|
|
supported_data_types = ExchangeFactory.get_supported_data_types(config.exchange)
|
|
if supported_data_types:
|
|
for data_type in config.data_types:
|
|
if data_type.value not in supported_data_types:
|
|
errors.append(f"Data type '{data_type.value}' not supported for {config.exchange}")
|
|
|
|
is_valid = len(errors) == 0
|
|
if not is_valid:
|
|
logger.warning(f"Configuration validation failed for {config.exchange}: {errors}")
|
|
else:
|
|
logger.debug(f"Configuration validation passed for {config.exchange}")
|
|
|
|
return is_valid, errors
|
|
|
|
|
|
def create_okx_collector(symbol: str,
|
|
data_types: Optional[List[DataType]] = None,
|
|
**kwargs) -> BaseDataCollector:
|
|
"""
|
|
Convenience function to create an OKX collector.
|
|
|
|
Args:
|
|
symbol: Trading pair symbol (e.g., 'BTC-USDT')
|
|
data_types: List of data types to collect
|
|
**kwargs: Additional collector parameters
|
|
|
|
Returns:
|
|
OKX collector instance
|
|
"""
|
|
if data_types is None:
|
|
data_types = [DataType.TRADE, DataType.ORDERBOOK]
|
|
|
|
logger.debug(f"Creating OKX collector for {symbol}")
|
|
|
|
config = ExchangeCollectorConfig(
|
|
exchange='okx',
|
|
symbol=symbol,
|
|
data_types=data_types,
|
|
**kwargs
|
|
)
|
|
|
|
return ExchangeFactory.create_collector(config) |