""" Main aggregation engine implementation. """ from typing import Dict, List from ..interfaces.aggregation_engine import AggregationEngine from ..models.core import ( OrderBookSnapshot, PriceBuckets, HeatmapData, ImbalanceMetrics, ConsolidatedOrderBook ) from ..utils.logging import get_logger, set_correlation_id from ..utils.exceptions import AggregationError from .price_bucketer import PriceBucketer from .heatmap_generator import HeatmapGenerator from .cross_exchange_aggregator import CrossExchangeAggregator from ..processing.metrics_calculator import MetricsCalculator logger = get_logger(__name__) class StandardAggregationEngine(AggregationEngine): """ Standard implementation of aggregation engine interface. Provides: - Price bucket creation with $1 USD buckets - Heatmap generation - Cross-exchange aggregation - Imbalance calculations - Support/resistance detection """ def __init__(self): """Initialize aggregation engine with components""" self.price_bucketer = PriceBucketer() self.heatmap_generator = HeatmapGenerator() self.cross_exchange_aggregator = CrossExchangeAggregator() self.metrics_calculator = MetricsCalculator() # Processing statistics self.buckets_created = 0 self.heatmaps_generated = 0 self.consolidations_performed = 0 logger.info("Standard aggregation engine initialized") def create_price_buckets(self, orderbook: OrderBookSnapshot, bucket_size: float = None) -> PriceBuckets: """ Convert order book data to price buckets. Args: orderbook: Order book snapshot bucket_size: Size of each price bucket (uses $1 default) Returns: PriceBuckets: Aggregated price bucket data """ try: set_correlation_id() # Use provided bucket size or default $1 if bucket_size: bucketer = PriceBucketer(bucket_size) else: bucketer = self.price_bucketer buckets = bucketer.create_price_buckets(orderbook) self.buckets_created += 1 logger.debug(f"Created price buckets for {orderbook.symbol}@{orderbook.exchange}") return buckets except Exception as e: logger.error(f"Error creating price buckets: {e}") raise AggregationError(f"Price bucket creation failed: {e}", "BUCKET_ERROR") def update_heatmap(self, symbol: str, buckets: PriceBuckets) -> HeatmapData: """ Update heatmap data with new price buckets. Args: symbol: Trading symbol buckets: Price bucket data Returns: HeatmapData: Updated heatmap visualization data """ try: set_correlation_id() heatmap = self.heatmap_generator.generate_heatmap(buckets) self.heatmaps_generated += 1 logger.debug(f"Generated heatmap for {symbol}: {len(heatmap.data)} points") return heatmap except Exception as e: logger.error(f"Error updating heatmap: {e}") raise AggregationError(f"Heatmap update failed: {e}", "HEATMAP_ERROR") def calculate_imbalances(self, orderbook: OrderBookSnapshot) -> ImbalanceMetrics: """ Calculate order book imbalance metrics. Args: orderbook: Order book snapshot Returns: ImbalanceMetrics: Calculated imbalance metrics """ try: set_correlation_id() return self.metrics_calculator.calculate_imbalance_metrics(orderbook) except Exception as e: logger.error(f"Error calculating imbalances: {e}") raise AggregationError(f"Imbalance calculation failed: {e}", "IMBALANCE_ERROR") def aggregate_across_exchanges(self, symbol: str, orderbooks: List[OrderBookSnapshot]) -> ConsolidatedOrderBook: """ Aggregate order book data from multiple exchanges. Args: symbol: Trading symbol orderbooks: List of order book snapshots from different exchanges Returns: ConsolidatedOrderBook: Consolidated order book data """ try: set_correlation_id() consolidated = self.cross_exchange_aggregator.aggregate_across_exchanges( symbol, orderbooks ) self.consolidations_performed += 1 logger.debug(f"Consolidated {len(orderbooks)} order books for {symbol}") return consolidated except Exception as e: logger.error(f"Error aggregating across exchanges: {e}") raise AggregationError(f"Cross-exchange aggregation failed: {e}", "CONSOLIDATION_ERROR") def calculate_volume_weighted_price(self, orderbooks: List[OrderBookSnapshot]) -> float: """ Calculate volume-weighted average price across exchanges. Args: orderbooks: List of order book snapshots Returns: float: Volume-weighted average price """ try: set_correlation_id() return self.cross_exchange_aggregator._calculate_weighted_mid_price(orderbooks) except Exception as e: logger.error(f"Error calculating volume weighted price: {e}") raise AggregationError(f"VWAP calculation failed: {e}", "VWAP_ERROR") def get_market_depth(self, orderbook: OrderBookSnapshot, depth_levels: List[float]) -> Dict[float, Dict[str, float]]: """ Calculate market depth at different price levels. Args: orderbook: Order book snapshot depth_levels: List of depth percentages (e.g., [0.1, 0.5, 1.0]) Returns: Dict: Market depth data {level: {'bid_volume': x, 'ask_volume': y}} """ try: set_correlation_id() depth_data = {} if not orderbook.mid_price: return depth_data for level_pct in depth_levels: # Calculate price range for this depth level price_range = orderbook.mid_price * (level_pct / 100.0) min_bid_price = orderbook.mid_price - price_range max_ask_price = orderbook.mid_price + price_range # Calculate volumes within this range bid_volume = sum( bid.size for bid in orderbook.bids if bid.price >= min_bid_price ) ask_volume = sum( ask.size for ask in orderbook.asks if ask.price <= max_ask_price ) depth_data[level_pct] = { 'bid_volume': bid_volume, 'ask_volume': ask_volume, 'total_volume': bid_volume + ask_volume } logger.debug(f"Calculated market depth for {len(depth_levels)} levels") return depth_data except Exception as e: logger.error(f"Error calculating market depth: {e}") return {} def smooth_heatmap(self, heatmap: HeatmapData, smoothing_factor: float) -> HeatmapData: """ Apply smoothing to heatmap data to reduce noise. Args: heatmap: Raw heatmap data smoothing_factor: Smoothing factor (0.0 to 1.0) Returns: HeatmapData: Smoothed heatmap data """ try: set_correlation_id() return self.heatmap_generator.apply_smoothing(heatmap, smoothing_factor) except Exception as e: logger.error(f"Error smoothing heatmap: {e}") return heatmap # Return original on error def calculate_liquidity_score(self, orderbook: OrderBookSnapshot) -> float: """ Calculate liquidity score for an order book. Args: orderbook: Order book snapshot Returns: float: Liquidity score (0.0 to 1.0) """ try: set_correlation_id() return self.metrics_calculator.calculate_liquidity_score(orderbook) except Exception as e: logger.error(f"Error calculating liquidity score: {e}") return 0.0 def detect_support_resistance(self, heatmap: HeatmapData) -> Dict[str, List[float]]: """ Detect support and resistance levels from heatmap data. Args: heatmap: Heatmap data Returns: Dict: {'support': [prices], 'resistance': [prices]} """ try: set_correlation_id() return self.heatmap_generator.calculate_support_resistance(heatmap) except Exception as e: logger.error(f"Error detecting support/resistance: {e}") return {'support': [], 'resistance': []} def create_consolidated_heatmap(self, symbol: str, orderbooks: List[OrderBookSnapshot]) -> HeatmapData: """ Create consolidated heatmap from multiple exchanges. Args: symbol: Trading symbol orderbooks: List of order book snapshots Returns: HeatmapData: Consolidated heatmap data """ try: set_correlation_id() return self.cross_exchange_aggregator.create_consolidated_heatmap( symbol, orderbooks ) except Exception as e: logger.error(f"Error creating consolidated heatmap: {e}") raise AggregationError(f"Consolidated heatmap creation failed: {e}", "CONSOLIDATED_HEATMAP_ERROR") def detect_arbitrage_opportunities(self, orderbooks: List[OrderBookSnapshot]) -> List[Dict]: """ Detect arbitrage opportunities between exchanges. Args: orderbooks: List of order book snapshots Returns: List[Dict]: Arbitrage opportunities """ try: set_correlation_id() return self.cross_exchange_aggregator.detect_arbitrage_opportunities(orderbooks) except Exception as e: logger.error(f"Error detecting arbitrage opportunities: {e}") return [] def get_processing_stats(self) -> Dict[str, any]: """Get processing statistics""" return { 'buckets_created': self.buckets_created, 'heatmaps_generated': self.heatmaps_generated, 'consolidations_performed': self.consolidations_performed, 'price_bucketer_stats': self.price_bucketer.get_processing_stats(), 'heatmap_generator_stats': self.heatmap_generator.get_processing_stats(), 'cross_exchange_stats': self.cross_exchange_aggregator.get_processing_stats() } def reset_stats(self) -> None: """Reset processing statistics""" self.buckets_created = 0 self.heatmaps_generated = 0 self.consolidations_performed = 0 self.price_bucketer.reset_stats() self.heatmap_generator.reset_stats() self.cross_exchange_aggregator.reset_stats() logger.info("Aggregation engine statistics reset")