""" Bot-Enhanced Signal Layers This module provides enhanced versions of signal layers that automatically integrate with the bot management system, making it easier to display bot signals and trades without manual data fetching. """ import pandas as pd import plotly.graph_objects as go from typing import Dict, Any, Optional, List, Union, Tuple from dataclasses import dataclass from datetime import datetime, timedelta from .signals import ( TradingSignalLayer, TradeExecutionLayer, EnhancedSignalLayer, SignalLayerConfig, TradeLayerConfig, SignalStyleConfig ) from .bot_integration import ( BotFilterConfig, BotSignalLayerIntegration, bot_integration, get_active_bot_signals, get_active_bot_trades ) from utils.logger import get_logger # Initialize logger logger = get_logger() @dataclass class BotSignalLayerConfig(SignalLayerConfig): """Extended configuration for bot-integrated signal layers""" # Bot filtering options bot_filter: Optional[BotFilterConfig] = None auto_fetch_data: bool = True # Automatically fetch bot data time_window_days: int = 7 # Time window for data fetching active_bots_only: bool = True # Only show signals from active bots include_bot_info: bool = True # Include bot info in hover text group_by_strategy: bool = False # Group signals by strategy def __post_init__(self): super().__post_init__() if self.bot_filter is None: self.bot_filter = BotFilterConfig(active_only=self.active_bots_only) @dataclass class BotTradeLayerConfig(TradeLayerConfig): """Extended configuration for bot-integrated trade layers""" # Bot filtering options bot_filter: Optional[BotFilterConfig] = None auto_fetch_data: bool = True # Automatically fetch bot data time_window_days: int = 7 # Time window for data fetching active_bots_only: bool = True # Only show trades from active bots include_bot_info: bool = True # Include bot info in hover text group_by_strategy: bool = False # Group trades by strategy def __post_init__(self): super().__post_init__() if self.bot_filter is None: self.bot_filter = BotFilterConfig(active_only=self.active_bots_only) class BotIntegratedSignalLayer(TradingSignalLayer): """ Signal layer that automatically integrates with bot management system. """ def __init__(self, config: BotSignalLayerConfig = None): """ Initialize bot-integrated signal layer. Args: config: Bot signal layer configuration (optional) """ if config is None: config = BotSignalLayerConfig( name="Bot Signals", enabled=True, signal_types=['buy', 'sell'], confidence_threshold=0.3, auto_fetch_data=True, active_bots_only=True ) # Convert to base config for parent class base_config = SignalLayerConfig( name=config.name, enabled=config.enabled, signal_types=config.signal_types, confidence_threshold=config.confidence_threshold, show_confidence=config.show_confidence, marker_size=config.marker_size, show_price_labels=config.show_price_labels, bot_id=config.bot_id ) super().__init__(base_config) self.bot_config = config self.integration = BotSignalLayerIntegration() self.logger.info(f"Bot Enhanced Signal Layer: Initialized BotIntegratedSignalLayer: {config.name}") def render(self, fig: go.Figure, data: pd.DataFrame, signals: pd.DataFrame = None, **kwargs) -> go.Figure: """ Render bot signals on the chart with automatic data fetching. Args: fig: Plotly figure to render onto data: Market data (OHLCV format) signals: Optional manual signal data (if not provided, will auto-fetch) **kwargs: Additional rendering parameters including 'symbol' and 'timeframe' Returns: Updated figure with bot signal overlays """ try: # Auto-fetch bot signals if not provided and auto_fetch is enabled if signals is None and self.bot_config.auto_fetch_data: symbol = kwargs.get('symbol') timeframe = kwargs.get('timeframe') if not symbol: self.logger.warning("No symbol provided and no manual signals - cannot auto-fetch bot signals") return fig # Calculate time range end_time = datetime.now() start_time = end_time - timedelta(days=self.bot_config.time_window_days) time_range = (start_time, end_time) # Fetch signals from bots signals = self.integration.get_signals_for_chart( symbol=symbol, timeframe=timeframe, bot_filter=self.bot_config.bot_filter, time_range=time_range, signal_types=self.bot_config.signal_types, min_confidence=self.bot_config.confidence_threshold ) if signals.empty: self.logger.info(f"No bot signals found for {symbol}") return fig self.logger.info(f"Auto-fetched {len(signals)} bot signals for {symbol}") # Enhance signals with bot information if available if signals is not None and not signals.empty and self.bot_config.include_bot_info: signals = self._enhance_signals_with_bot_info(signals) # Use parent render method return super().render(fig, data, signals, **kwargs) except Exception as e: self.logger.error(f"Error rendering bot-integrated signals: {e}") # Add error annotation fig.add_annotation( text=f"Bot Signal Error: {str(e)}", x=0.5, y=0.95, xref="paper", yref="paper", showarrow=False, font=dict(color="red", size=10) ) return fig def _enhance_signals_with_bot_info(self, signals: pd.DataFrame) -> pd.DataFrame: """ Enhance signals with additional bot information for better visualization. Args: signals: Signal data Returns: Enhanced signal data """ if 'bot_name' in signals.columns and 'strategy' in signals.columns: # Signals already enhanced return signals # If we have bot info columns, enhance hover text would be handled in trace creation return signals def create_signal_traces(self, signals: pd.DataFrame) -> List[go.Scatter]: """ Create enhanced signal traces with bot information. Args: signals: Filtered signal data Returns: List of enhanced Plotly traces """ traces = [] try: if signals.empty: return traces # Group by strategy if enabled if self.bot_config.group_by_strategy and 'strategy' in signals.columns: for strategy in signals['strategy'].unique(): strategy_signals = signals[signals['strategy'] == strategy] strategy_traces = self._create_strategy_traces(strategy_signals, strategy) traces.extend(strategy_traces) else: # Use parent method for standard signal grouping traces = super().create_signal_traces(signals) # Enhance traces with bot information if self.bot_config.include_bot_info: traces = self._enhance_traces_with_bot_info(traces, signals) return traces except Exception as e: self.logger.error(f"Error creating bot signal traces: {e}") error_trace = self.create_error_trace(f"Error displaying bot signals: {str(e)}") return [error_trace] def _create_strategy_traces(self, signals: pd.DataFrame, strategy: str) -> List[go.Scatter]: """ Create traces grouped by strategy. Args: signals: Signal data for specific strategy strategy: Strategy name Returns: List of traces for this strategy """ traces = [] # Group by signal type within strategy for signal_type in signals['signal_type'].unique(): type_signals = signals[signals['signal_type'] == signal_type] if type_signals.empty: continue # Enhanced hover text with bot and strategy info hover_text = [] for _, signal in type_signals.iterrows(): hover_parts = [ f"Signal: {signal['signal_type'].upper()}", f"Price: ${signal['price']:.4f}", f"Time: {signal['timestamp']}", f"Strategy: {strategy}" ] if 'confidence' in signal and signal['confidence'] is not None: hover_parts.append(f"Confidence: {signal['confidence']:.1%}") if 'bot_name' in signal and signal['bot_name']: hover_parts.append(f"Bot: {signal['bot_name']}") if 'bot_status' in signal and signal['bot_status']: hover_parts.append(f"Status: {signal['bot_status']}") hover_text.append("
".join(hover_parts)) # Create trace for this signal type in strategy trace = go.Scatter( x=type_signals['timestamp'], y=type_signals['price'], mode='markers', marker=dict( symbol=self.signal_symbols.get(signal_type, 'circle'), size=self.config.marker_size, color=self.signal_colors.get(signal_type, '#666666'), line=dict(width=1, color='white'), opacity=0.8 ), name=f"{strategy} - {signal_type.upper()}", text=hover_text, hoverinfo='text', showlegend=True, legendgroup=f"strategy_{strategy}_{signal_type}" ) traces.append(trace) return traces def _enhance_traces_with_bot_info(self, traces: List[go.Scatter], signals: pd.DataFrame) -> List[go.Scatter]: """ Enhance existing traces with bot information. Args: traces: Original traces signals: Signal data with bot info Returns: Enhanced traces """ # This would be implemented to modify hover text of existing traces # For now, return traces as-is since bot info enhancement happens in trace creation return traces class BotIntegratedTradeLayer(TradeExecutionLayer): """ Trade layer that automatically integrates with bot management system. """ def __init__(self, config: BotTradeLayerConfig = None): """ Initialize bot-integrated trade layer. Args: config: Bot trade layer configuration (optional) """ if config is None: config = BotTradeLayerConfig( name="Bot Trades", enabled=True, show_pnl=True, show_trade_lines=True, auto_fetch_data=True, active_bots_only=True ) # Convert to base config for parent class base_config = TradeLayerConfig( name=config.name, enabled=config.enabled, show_pnl=config.show_pnl, show_trade_lines=config.show_trade_lines, show_quantity=config.show_quantity, show_fees=config.show_fees, min_pnl_display=config.min_pnl_display, bot_id=config.bot_id, trade_marker_size=config.trade_marker_size ) super().__init__(base_config) self.bot_config = config self.integration = BotSignalLayerIntegration() self.logger.info(f"Bot Enhanced Trade Layer: Initialized BotIntegratedTradeLayer: {config.name}") def render(self, fig: go.Figure, data: pd.DataFrame, trades: pd.DataFrame = None, **kwargs) -> go.Figure: """ Render bot trades on the chart with automatic data fetching. Args: fig: Plotly figure to render onto data: Market data (OHLCV format) trades: Optional manual trade data (if not provided, will auto-fetch) **kwargs: Additional rendering parameters including 'symbol' and 'timeframe' Returns: Updated figure with bot trade overlays """ try: # Auto-fetch bot trades if not provided and auto_fetch is enabled if trades is None and self.bot_config.auto_fetch_data: symbol = kwargs.get('symbol') timeframe = kwargs.get('timeframe') if not symbol: self.logger.warning("Bot Enhanced Trade Layer: No symbol provided and no manual trades - cannot auto-fetch bot trades") return fig # Calculate time range end_time = datetime.now() start_time = end_time - timedelta(days=self.bot_config.time_window_days) time_range = (start_time, end_time) # Fetch trades from bots trades = self.integration.get_trades_for_chart( symbol=symbol, timeframe=timeframe, bot_filter=self.bot_config.bot_filter, time_range=time_range ) if trades.empty: self.logger.info(f"Bot Enhanced Trade Layer: No bot trades found for {symbol}") return fig self.logger.info(f"Bot Enhanced Trade Layer: Auto-fetched {len(trades)} bot trades for {symbol}") # Use parent render method return super().render(fig, data, trades, **kwargs) except Exception as e: self.logger.error(f"Bot Enhanced Trade Layer: Error rendering bot-integrated trades: {e}") # Add error annotation fig.add_annotation( text=f"Bot Trade Error: {str(e)}", x=0.5, y=0.95, xref="paper", yref="paper", showarrow=False, font=dict(color="red", size=10) ) return fig class BotMultiLayerIntegration: """ Integration utility for managing multiple bot-related chart layers. """ def __init__(self): """Initialize multi-layer bot integration.""" self.integration = BotSignalLayerIntegration() self.logger = logger def create_bot_layers_for_symbol(self, symbol: str, timeframe: str = None, bot_filter: BotFilterConfig = None, include_signals: bool = True, include_trades: bool = True, time_window_days: int = 7) -> Dict[str, Any]: """ Create a complete set of bot-integrated layers for a symbol. Args: symbol: Trading symbol timeframe: Chart timeframe (optional) bot_filter: Bot filtering configuration include_signals: Include signal layer include_trades: Include trade layer time_window_days: Time window for data Returns: Dictionary with layer instances and metadata """ layers = {} metadata = {} try: if bot_filter is None: bot_filter = BotFilterConfig(symbols=[symbol], active_only=True) # Create signal layer if include_signals: signal_config = BotSignalLayerConfig( name=f"{symbol} Bot Signals", enabled=True, bot_filter=bot_filter, time_window_days=time_window_days, signal_types=['buy', 'sell'], confidence_threshold=0.3, include_bot_info=True ) layers['signals'] = BotIntegratedSignalLayer(signal_config) metadata['signals'] = { 'layer_type': 'bot_signals', 'symbol': symbol, 'timeframe': timeframe, 'time_window_days': time_window_days } # Create trade layer if include_trades: trade_config = BotTradeLayerConfig( name=f"{symbol} Bot Trades", enabled=True, bot_filter=bot_filter, time_window_days=time_window_days, show_pnl=True, show_trade_lines=True, include_bot_info=True ) layers['trades'] = BotIntegratedTradeLayer(trade_config) metadata['trades'] = { 'layer_type': 'bot_trades', 'symbol': symbol, 'timeframe': timeframe, 'time_window_days': time_window_days } # Get bot summary for metadata bot_summary = self.integration.get_bot_summary_stats() metadata['bot_summary'] = bot_summary self.logger.info(f"Bot Enhanced Multi Layer Integration: Created {len(layers)} bot layers for {symbol}") return { 'layers': layers, 'metadata': metadata, 'symbol': symbol, 'timeframe': timeframe, 'success': True } except Exception as e: self.logger.error(f"Bot Enhanced Multi Layer Integration: Error creating bot layers for {symbol}: {e}") return { 'layers': {}, 'metadata': {}, 'symbol': symbol, 'timeframe': timeframe, 'success': False, 'error': str(e) } def create_strategy_comparison_layers(self, symbol: str, strategies: List[str], timeframe: str = None, time_window_days: int = 7) -> Dict[str, Any]: """ Create layers to compare different strategies for a symbol. Args: symbol: Trading symbol strategies: List of strategy names to compare timeframe: Chart timeframe (optional) time_window_days: Time window for data Returns: Dictionary with strategy comparison layers """ layers = {} metadata = {} try: for strategy in strategies: bot_filter = BotFilterConfig( symbols=[symbol], strategies=[strategy], active_only=False # Include all bots for comparison ) # Create signal layer for this strategy signal_config = BotSignalLayerConfig( name=f"{strategy} Signals", enabled=True, bot_filter=bot_filter, time_window_days=time_window_days, group_by_strategy=True, include_bot_info=True ) layers[f"{strategy}_signals"] = BotIntegratedSignalLayer(signal_config) # Create trade layer for this strategy trade_config = BotTradeLayerConfig( name=f"{strategy} Trades", enabled=True, bot_filter=bot_filter, time_window_days=time_window_days, group_by_strategy=True, include_bot_info=True ) layers[f"{strategy}_trades"] = BotIntegratedTradeLayer(trade_config) metadata[strategy] = { 'strategy': strategy, 'symbol': symbol, 'timeframe': timeframe, 'layer_count': 2 } self.logger.info(f"Bot Enhanced Multi Layer Integration: Created strategy comparison layers for {len(strategies)} strategies on {symbol}") return { 'layers': layers, 'metadata': metadata, 'symbol': symbol, 'strategies': strategies, 'success': True } except Exception as e: self.logger.error(f"Bot Enhanced Multi Layer Integration: Error creating strategy comparison layers: {e}") return { 'layers': {}, 'metadata': {}, 'symbol': symbol, 'strategies': strategies, 'success': False, 'error': str(e) } # Global instance for easy access bot_multi_layer = BotMultiLayerIntegration() # Convenience functions for creating bot-integrated layers def create_bot_signal_layer(symbol: str, timeframe: str = None, active_only: bool = True, confidence_threshold: float = 0.3, time_window_days: int = 7, **kwargs) -> BotIntegratedSignalLayer: """ Create a bot-integrated signal layer for a symbol. Args: symbol: Trading symbol timeframe: Chart timeframe (optional) active_only: Only include active bots confidence_threshold: Minimum confidence threshold time_window_days: Time window for data fetching **kwargs: Additional configuration options Returns: Configured BotIntegratedSignalLayer """ bot_filter = BotFilterConfig( symbols=[symbol], active_only=active_only ) config = BotSignalLayerConfig( name=f"{symbol} Bot Signals", enabled=True, bot_filter=bot_filter, confidence_threshold=confidence_threshold, time_window_days=time_window_days, signal_types=kwargs.get('signal_types', ['buy', 'sell']), include_bot_info=kwargs.get('include_bot_info', True), group_by_strategy=kwargs.get('group_by_strategy', False), **{k: v for k, v in kwargs.items() if k not in [ 'signal_types', 'include_bot_info', 'group_by_strategy' ]} ) return BotIntegratedSignalLayer(config) def create_bot_trade_layer(symbol: str, timeframe: str = None, active_only: bool = True, show_pnl: bool = True, time_window_days: int = 7, **kwargs) -> BotIntegratedTradeLayer: """ Create a bot-integrated trade layer for a symbol. Args: symbol: Trading symbol timeframe: Chart timeframe (optional) active_only: Only include active bots show_pnl: Show profit/loss information time_window_days: Time window for data fetching **kwargs: Additional configuration options Returns: Configured BotIntegratedTradeLayer """ bot_filter = BotFilterConfig( symbols=[symbol], active_only=active_only ) config = BotTradeLayerConfig( name=f"{symbol} Bot Trades", enabled=True, bot_filter=bot_filter, show_pnl=show_pnl, time_window_days=time_window_days, show_trade_lines=kwargs.get('show_trade_lines', True), include_bot_info=kwargs.get('include_bot_info', True), group_by_strategy=kwargs.get('group_by_strategy', False), **{k: v for k, v in kwargs.items() if k not in [ 'show_trade_lines', 'include_bot_info', 'group_by_strategy' ]} ) return BotIntegratedTradeLayer(config) def create_complete_bot_layers(symbol: str, timeframe: str = None, active_only: bool = True, time_window_days: int = 7) -> Dict[str, Any]: """ Create a complete set of bot-integrated layers for a symbol. Args: symbol: Trading symbol timeframe: Chart timeframe (optional) active_only: Only include active bots time_window_days: Time window for data fetching Returns: Dictionary with signal and trade layers """ return bot_multi_layer.create_bot_layers_for_symbol( symbol=symbol, timeframe=timeframe, bot_filter=BotFilterConfig(symbols=[symbol], active_only=active_only), time_window_days=time_window_days )