Add bot integration and enhanced signal layers for automated trading
- Introduced `BotIntegratedSignalLayer` and `BotIntegratedTradeLayer` to facilitate automated data fetching and visualization of bot signals and trades. - Implemented `BotDataService` for efficient retrieval of bot-related data, including filtering and performance summaries. - Added support for various bot-enhanced layers, including support/resistance and custom strategy layers, to improve trading analysis. - Updated existing signal layer components to integrate with the new bot functionalities, ensuring seamless operation. - Enhanced logging and error handling for better debugging and user feedback during bot operations. - Included comprehensive tests for new functionalities to ensure reliability and maintainability. - Updated documentation to reflect the new bot integration features and usage guidelines.
This commit is contained in:
@@ -16,6 +16,7 @@ Components:
|
||||
- MACDLayer: MACD lines and histogram subplot
|
||||
- TradingSignalLayer: Buy/sell/hold signal markers
|
||||
- TradeExecutionLayer: Trade entry/exit point visualization
|
||||
- Bot Integration: Automated data fetching and bot-integrated layers
|
||||
"""
|
||||
|
||||
from .base import (
|
||||
@@ -56,13 +57,63 @@ from .signals import (
|
||||
BaseTradeLayer,
|
||||
TradeLayerConfig,
|
||||
TradeExecutionLayer,
|
||||
BaseSupportResistanceLayer,
|
||||
SupportResistanceLayerConfig,
|
||||
SupportResistanceLayer,
|
||||
CustomStrategySignalInterface,
|
||||
BaseCustomStrategyLayer,
|
||||
CustomStrategySignalConfig,
|
||||
CustomStrategySignalLayer,
|
||||
SignalStyleConfig,
|
||||
SignalStyleManager,
|
||||
EnhancedSignalLayer,
|
||||
create_trading_signal_layer,
|
||||
create_buy_signals_only_layer,
|
||||
create_sell_signals_only_layer,
|
||||
create_high_confidence_signals_layer,
|
||||
create_trade_execution_layer,
|
||||
create_profitable_trades_only_layer,
|
||||
create_losing_trades_only_layer
|
||||
create_losing_trades_only_layer,
|
||||
create_support_resistance_layer,
|
||||
create_support_only_layer,
|
||||
create_resistance_only_layer,
|
||||
create_trend_lines_layer,
|
||||
create_key_levels_layer,
|
||||
create_custom_strategy_layer,
|
||||
create_pairs_trading_layer,
|
||||
create_momentum_strategy_layer,
|
||||
create_arbitrage_layer,
|
||||
create_mean_reversion_layer,
|
||||
create_breakout_strategy_layer,
|
||||
create_enhanced_signal_layer,
|
||||
create_professional_signal_layer,
|
||||
create_colorblind_friendly_signal_layer,
|
||||
create_dark_theme_signal_layer,
|
||||
create_minimal_signal_layer
|
||||
)
|
||||
|
||||
from .bot_integration import (
|
||||
BotFilterConfig,
|
||||
BotDataService,
|
||||
BotSignalLayerIntegration,
|
||||
bot_data_service,
|
||||
bot_integration,
|
||||
get_active_bot_signals,
|
||||
get_active_bot_trades,
|
||||
get_bot_signals_by_strategy,
|
||||
get_bot_performance_summary
|
||||
)
|
||||
|
||||
from .bot_enhanced_layers import (
|
||||
BotSignalLayerConfig,
|
||||
BotTradeLayerConfig,
|
||||
BotIntegratedSignalLayer,
|
||||
BotIntegratedTradeLayer,
|
||||
BotMultiLayerIntegration,
|
||||
bot_multi_layer,
|
||||
create_bot_signal_layer,
|
||||
create_bot_trade_layer,
|
||||
create_complete_bot_layers
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
@@ -96,6 +147,37 @@ __all__ = [
|
||||
'TradeLayerConfig',
|
||||
'TradeExecutionLayer',
|
||||
|
||||
# Support/Resistance layers
|
||||
'BaseSupportResistanceLayer',
|
||||
'SupportResistanceLayerConfig',
|
||||
'SupportResistanceLayer',
|
||||
|
||||
# Custom Strategy layers
|
||||
'CustomStrategySignalInterface',
|
||||
'BaseCustomStrategyLayer',
|
||||
'CustomStrategySignalConfig',
|
||||
'CustomStrategySignalLayer',
|
||||
|
||||
# Signal Styling
|
||||
'SignalStyleConfig',
|
||||
'SignalStyleManager',
|
||||
'EnhancedSignalLayer',
|
||||
|
||||
# Bot Integration
|
||||
'BotFilterConfig',
|
||||
'BotDataService',
|
||||
'BotSignalLayerIntegration',
|
||||
'bot_data_service',
|
||||
'bot_integration',
|
||||
|
||||
# Bot Enhanced Layers
|
||||
'BotSignalLayerConfig',
|
||||
'BotTradeLayerConfig',
|
||||
'BotIntegratedSignalLayer',
|
||||
'BotIntegratedTradeLayer',
|
||||
'BotMultiLayerIntegration',
|
||||
'bot_multi_layer',
|
||||
|
||||
# Convenience functions
|
||||
'create_sma_layer',
|
||||
'create_ema_layer',
|
||||
@@ -111,7 +193,30 @@ __all__ = [
|
||||
'create_high_confidence_signals_layer',
|
||||
'create_trade_execution_layer',
|
||||
'create_profitable_trades_only_layer',
|
||||
'create_losing_trades_only_layer'
|
||||
'create_losing_trades_only_layer',
|
||||
'create_support_resistance_layer',
|
||||
'create_support_only_layer',
|
||||
'create_resistance_only_layer',
|
||||
'create_trend_lines_layer',
|
||||
'create_key_levels_layer',
|
||||
'create_custom_strategy_layer',
|
||||
'create_pairs_trading_layer',
|
||||
'create_momentum_strategy_layer',
|
||||
'create_arbitrage_layer',
|
||||
'create_mean_reversion_layer',
|
||||
'create_breakout_strategy_layer',
|
||||
'create_enhanced_signal_layer',
|
||||
'create_professional_signal_layer',
|
||||
'create_colorblind_friendly_signal_layer',
|
||||
'create_dark_theme_signal_layer',
|
||||
'create_minimal_signal_layer',
|
||||
'get_active_bot_signals',
|
||||
'get_active_bot_trades',
|
||||
'get_bot_signals_by_strategy',
|
||||
'get_bot_performance_summary',
|
||||
'create_bot_signal_layer',
|
||||
'create_bot_trade_layer',
|
||||
'create_complete_bot_layers'
|
||||
]
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
694
components/charts/layers/bot_enhanced_layers.py
Normal file
694
components/charts/layers/bot_enhanced_layers.py
Normal file
@@ -0,0 +1,694 @@
|
||||
"""
|
||||
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("default_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("<br>".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
|
||||
)
|
||||
737
components/charts/layers/bot_integration.py
Normal file
737
components/charts/layers/bot_integration.py
Normal file
@@ -0,0 +1,737 @@
|
||||
"""
|
||||
Bot Management Integration for Chart Signal Layers
|
||||
|
||||
This module provides integration points between the signal layer system and the bot management
|
||||
system, including data fetching utilities, bot filtering, and integration helpers.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from typing import Dict, Any, Optional, List, Union, Tuple
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from database.connection import get_session
|
||||
from database.models import Bot, Signal, Trade, BotPerformance
|
||||
from database.operations import DatabaseOperationError
|
||||
from utils.logger import get_logger
|
||||
|
||||
# Initialize logger
|
||||
logger = get_logger("default_logger")
|
||||
|
||||
|
||||
@dataclass
|
||||
class BotFilterConfig:
|
||||
"""Configuration for filtering bot data for chart layers"""
|
||||
bot_ids: Optional[List[int]] = None # Specific bot IDs to include
|
||||
bot_names: Optional[List[str]] = None # Specific bot names to include
|
||||
strategies: Optional[List[str]] = None # Specific strategies to include
|
||||
symbols: Optional[List[str]] = None # Specific symbols to include
|
||||
statuses: Optional[List[str]] = None # Bot statuses to include
|
||||
date_range: Optional[Tuple[datetime, datetime]] = None # Date range filter
|
||||
active_only: bool = False # Only include active bots
|
||||
|
||||
def __post_init__(self):
|
||||
if self.statuses is None:
|
||||
self.statuses = ['active', 'inactive', 'paused'] # Exclude 'error' by default
|
||||
|
||||
|
||||
class BotDataService:
|
||||
"""
|
||||
Service for fetching bot-related data for chart layers.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize bot data service."""
|
||||
self.logger = logger
|
||||
|
||||
def get_bots(self, filter_config: BotFilterConfig = None) -> pd.DataFrame:
|
||||
"""
|
||||
Get bot information based on filter configuration.
|
||||
|
||||
Args:
|
||||
filter_config: Filter configuration (optional)
|
||||
|
||||
Returns:
|
||||
DataFrame with bot information
|
||||
"""
|
||||
try:
|
||||
if filter_config is None:
|
||||
filter_config = BotFilterConfig()
|
||||
|
||||
with get_session() as session:
|
||||
query = session.query(Bot)
|
||||
|
||||
# Apply filters
|
||||
if filter_config.bot_ids:
|
||||
query = query.filter(Bot.id.in_(filter_config.bot_ids))
|
||||
|
||||
if filter_config.bot_names:
|
||||
query = query.filter(Bot.name.in_(filter_config.bot_names))
|
||||
|
||||
if filter_config.strategies:
|
||||
query = query.filter(Bot.strategy_name.in_(filter_config.strategies))
|
||||
|
||||
if filter_config.symbols:
|
||||
query = query.filter(Bot.symbol.in_(filter_config.symbols))
|
||||
|
||||
if filter_config.statuses:
|
||||
query = query.filter(Bot.status.in_(filter_config.statuses))
|
||||
|
||||
if filter_config.active_only:
|
||||
query = query.filter(Bot.status == 'active')
|
||||
|
||||
# Execute query
|
||||
bots = query.all()
|
||||
|
||||
# Convert to DataFrame
|
||||
bot_data = []
|
||||
for bot in bots:
|
||||
bot_data.append({
|
||||
'id': bot.id,
|
||||
'name': bot.name,
|
||||
'strategy_name': bot.strategy_name,
|
||||
'symbol': bot.symbol,
|
||||
'timeframe': bot.timeframe,
|
||||
'status': bot.status,
|
||||
'config_file': bot.config_file,
|
||||
'virtual_balance': float(bot.virtual_balance) if bot.virtual_balance else 0.0,
|
||||
'current_balance': float(bot.current_balance) if bot.current_balance else 0.0,
|
||||
'pnl': float(bot.pnl) if bot.pnl else 0.0,
|
||||
'is_active': bot.is_active,
|
||||
'last_heartbeat': bot.last_heartbeat,
|
||||
'created_at': bot.created_at,
|
||||
'updated_at': bot.updated_at
|
||||
})
|
||||
|
||||
df = pd.DataFrame(bot_data)
|
||||
self.logger.info(f"Bot Integration: Retrieved {len(df)} bots with filters: {filter_config}")
|
||||
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Bot Integration: Error retrieving bots: {e}")
|
||||
raise DatabaseOperationError(f"Failed to retrieve bots: {e}")
|
||||
|
||||
def get_signals_for_bots(self,
|
||||
bot_ids: Union[int, List[int]] = None,
|
||||
start_time: datetime = None,
|
||||
end_time: datetime = None,
|
||||
signal_types: List[str] = None,
|
||||
min_confidence: float = 0.0) -> pd.DataFrame:
|
||||
"""
|
||||
Get signals for specific bots or all bots.
|
||||
|
||||
Args:
|
||||
bot_ids: Bot ID(s) to fetch signals for (None for all bots)
|
||||
start_time: Start time for signal filtering
|
||||
end_time: End time for signal filtering
|
||||
signal_types: Signal types to include (['buy', 'sell', 'hold'])
|
||||
min_confidence: Minimum confidence threshold
|
||||
|
||||
Returns:
|
||||
DataFrame with signal data
|
||||
"""
|
||||
try:
|
||||
# Default time range if not provided
|
||||
if end_time is None:
|
||||
end_time = datetime.now()
|
||||
if start_time is None:
|
||||
start_time = end_time - timedelta(days=7) # Last 7 days by default
|
||||
|
||||
# Normalize bot_ids to list
|
||||
if isinstance(bot_ids, int):
|
||||
bot_ids = [bot_ids]
|
||||
|
||||
with get_session() as session:
|
||||
query = session.query(Signal)
|
||||
|
||||
# Apply filters
|
||||
if bot_ids is not None:
|
||||
query = query.filter(Signal.bot_id.in_(bot_ids))
|
||||
|
||||
query = query.filter(
|
||||
Signal.timestamp >= start_time,
|
||||
Signal.timestamp <= end_time
|
||||
)
|
||||
|
||||
if signal_types:
|
||||
query = query.filter(Signal.signal_type.in_(signal_types))
|
||||
|
||||
if min_confidence > 0:
|
||||
query = query.filter(Signal.confidence >= min_confidence)
|
||||
|
||||
# Order by timestamp
|
||||
query = query.order_by(Signal.timestamp.asc())
|
||||
|
||||
# Execute query
|
||||
signals = query.all()
|
||||
|
||||
# Convert to DataFrame
|
||||
signal_data = []
|
||||
for signal in signals:
|
||||
signal_data.append({
|
||||
'id': signal.id,
|
||||
'bot_id': signal.bot_id,
|
||||
'timestamp': signal.timestamp,
|
||||
'signal_type': signal.signal_type,
|
||||
'price': float(signal.price) if signal.price else None,
|
||||
'confidence': float(signal.confidence) if signal.confidence else None,
|
||||
'indicators': signal.indicators, # JSONB data
|
||||
'created_at': signal.created_at
|
||||
})
|
||||
|
||||
df = pd.DataFrame(signal_data)
|
||||
self.logger.info(f"Bot Integration: Retrieved {len(df)} signals for bots: {bot_ids}")
|
||||
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Bot Integration: Error retrieving signals: {e}")
|
||||
raise DatabaseOperationError(f"Failed to retrieve signals: {e}")
|
||||
|
||||
def get_trades_for_bots(self,
|
||||
bot_ids: Union[int, List[int]] = None,
|
||||
start_time: datetime = None,
|
||||
end_time: datetime = None,
|
||||
sides: List[str] = None) -> pd.DataFrame:
|
||||
"""
|
||||
Get trades for specific bots or all bots.
|
||||
|
||||
Args:
|
||||
bot_ids: Bot ID(s) to fetch trades for (None for all bots)
|
||||
start_time: Start time for trade filtering
|
||||
end_time: End time for trade filtering
|
||||
sides: Trade sides to include (['buy', 'sell'])
|
||||
|
||||
Returns:
|
||||
DataFrame with trade data
|
||||
"""
|
||||
try:
|
||||
# Default time range if not provided
|
||||
if end_time is None:
|
||||
end_time = datetime.now()
|
||||
if start_time is None:
|
||||
start_time = end_time - timedelta(days=7) # Last 7 days by default
|
||||
|
||||
# Normalize bot_ids to list
|
||||
if isinstance(bot_ids, int):
|
||||
bot_ids = [bot_ids]
|
||||
|
||||
with get_session() as session:
|
||||
query = session.query(Trade)
|
||||
|
||||
# Apply filters
|
||||
if bot_ids is not None:
|
||||
query = query.filter(Trade.bot_id.in_(bot_ids))
|
||||
|
||||
query = query.filter(
|
||||
Trade.timestamp >= start_time,
|
||||
Trade.timestamp <= end_time
|
||||
)
|
||||
|
||||
if sides:
|
||||
query = query.filter(Trade.side.in_(sides))
|
||||
|
||||
# Order by timestamp
|
||||
query = query.order_by(Trade.timestamp.asc())
|
||||
|
||||
# Execute query
|
||||
trades = query.all()
|
||||
|
||||
# Convert to DataFrame
|
||||
trade_data = []
|
||||
for trade in trades:
|
||||
trade_data.append({
|
||||
'id': trade.id,
|
||||
'bot_id': trade.bot_id,
|
||||
'signal_id': trade.signal_id,
|
||||
'timestamp': trade.timestamp,
|
||||
'side': trade.side,
|
||||
'price': float(trade.price),
|
||||
'quantity': float(trade.quantity),
|
||||
'fees': float(trade.fees),
|
||||
'pnl': float(trade.pnl) if trade.pnl else None,
|
||||
'balance_after': float(trade.balance_after) if trade.balance_after else None,
|
||||
'trade_value': float(trade.trade_value),
|
||||
'net_pnl': float(trade.net_pnl),
|
||||
'created_at': trade.created_at
|
||||
})
|
||||
|
||||
df = pd.DataFrame(trade_data)
|
||||
self.logger.info(f"Bot Integration: Retrieved {len(df)} trades for bots: {bot_ids}")
|
||||
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Bot Integration: Error retrieving trades: {e}")
|
||||
raise DatabaseOperationError(f"Failed to retrieve trades: {e}")
|
||||
|
||||
def get_bot_performance(self,
|
||||
bot_ids: Union[int, List[int]] = None,
|
||||
start_time: datetime = None,
|
||||
end_time: datetime = None) -> pd.DataFrame:
|
||||
"""
|
||||
Get performance data for specific bots.
|
||||
|
||||
Args:
|
||||
bot_ids: Bot ID(s) to fetch performance for (None for all bots)
|
||||
start_time: Start time for performance filtering
|
||||
end_time: End time for performance filtering
|
||||
|
||||
Returns:
|
||||
DataFrame with performance data
|
||||
"""
|
||||
try:
|
||||
# Default time range if not provided
|
||||
if end_time is None:
|
||||
end_time = datetime.now()
|
||||
if start_time is None:
|
||||
start_time = end_time - timedelta(days=30) # Last 30 days by default
|
||||
|
||||
# Normalize bot_ids to list
|
||||
if isinstance(bot_ids, int):
|
||||
bot_ids = [bot_ids]
|
||||
|
||||
with get_session() as session:
|
||||
query = session.query(BotPerformance)
|
||||
|
||||
# Apply filters
|
||||
if bot_ids is not None:
|
||||
query = query.filter(BotPerformance.bot_id.in_(bot_ids))
|
||||
|
||||
query = query.filter(
|
||||
BotPerformance.timestamp >= start_time,
|
||||
BotPerformance.timestamp <= end_time
|
||||
)
|
||||
|
||||
# Order by timestamp
|
||||
query = query.order_by(BotPerformance.timestamp.asc())
|
||||
|
||||
# Execute query
|
||||
performance_records = query.all()
|
||||
|
||||
# Convert to DataFrame
|
||||
performance_data = []
|
||||
for perf in performance_records:
|
||||
performance_data.append({
|
||||
'id': perf.id,
|
||||
'bot_id': perf.bot_id,
|
||||
'timestamp': perf.timestamp,
|
||||
'total_value': float(perf.total_value),
|
||||
'cash_balance': float(perf.cash_balance),
|
||||
'crypto_balance': float(perf.crypto_balance),
|
||||
'total_trades': perf.total_trades,
|
||||
'winning_trades': perf.winning_trades,
|
||||
'total_fees': float(perf.total_fees),
|
||||
'win_rate': perf.win_rate,
|
||||
'portfolio_allocation': perf.portfolio_allocation,
|
||||
'created_at': perf.created_at
|
||||
})
|
||||
|
||||
df = pd.DataFrame(performance_data)
|
||||
self.logger.info(f"Bot Integration: Retrieved {len(df)} performance records for bots: {bot_ids}")
|
||||
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Bot Integration: Error retrieving bot performance: {e}")
|
||||
raise DatabaseOperationError(f"Failed to retrieve bot performance: {e}")
|
||||
|
||||
|
||||
class BotSignalLayerIntegration:
|
||||
"""
|
||||
Integration utilities for signal layers with bot management system.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize bot signal layer integration."""
|
||||
self.data_service = BotDataService()
|
||||
self.logger = logger
|
||||
|
||||
def get_signals_for_chart(self,
|
||||
symbol: str,
|
||||
timeframe: str = None,
|
||||
bot_filter: BotFilterConfig = None,
|
||||
time_range: Tuple[datetime, datetime] = None,
|
||||
signal_types: List[str] = None,
|
||||
min_confidence: float = 0.0) -> pd.DataFrame:
|
||||
"""
|
||||
Get signals filtered by chart context (symbol, timeframe) and bot criteria.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol for the chart
|
||||
timeframe: Chart timeframe (optional)
|
||||
bot_filter: Bot filtering configuration
|
||||
time_range: (start_time, end_time) tuple
|
||||
signal_types: Signal types to include
|
||||
min_confidence: Minimum confidence threshold
|
||||
|
||||
Returns:
|
||||
DataFrame with signals ready for chart rendering
|
||||
"""
|
||||
try:
|
||||
# Get relevant bots for this symbol/timeframe
|
||||
if bot_filter is None:
|
||||
bot_filter = BotFilterConfig()
|
||||
|
||||
# Add symbol filter
|
||||
if bot_filter.symbols is None:
|
||||
bot_filter.symbols = [symbol]
|
||||
elif symbol not in bot_filter.symbols:
|
||||
bot_filter.symbols.append(symbol)
|
||||
|
||||
# Get bots matching criteria
|
||||
bots_df = self.data_service.get_bots(bot_filter)
|
||||
|
||||
if bots_df.empty:
|
||||
self.logger.info(f"No bots found for symbol {symbol}")
|
||||
return pd.DataFrame()
|
||||
|
||||
bot_ids = bots_df['id'].tolist()
|
||||
|
||||
# Get time range
|
||||
start_time, end_time = time_range if time_range else (None, None)
|
||||
|
||||
# Get signals for these bots
|
||||
signals_df = self.data_service.get_signals_for_bots(
|
||||
bot_ids=bot_ids,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
signal_types=signal_types,
|
||||
min_confidence=min_confidence
|
||||
)
|
||||
|
||||
# Enrich signals with bot information
|
||||
if not signals_df.empty:
|
||||
signals_df = signals_df.merge(
|
||||
bots_df[['id', 'name', 'strategy_name', 'status']],
|
||||
left_on='bot_id',
|
||||
right_on='id',
|
||||
suffixes=('', '_bot')
|
||||
)
|
||||
|
||||
# Add metadata fields for chart rendering
|
||||
signals_df['bot_name'] = signals_df['name']
|
||||
signals_df['strategy'] = signals_df['strategy_name']
|
||||
signals_df['bot_status'] = signals_df['status']
|
||||
|
||||
# Clean up duplicate columns
|
||||
signals_df = signals_df.drop(['id_bot', 'name', 'strategy_name', 'status'], axis=1)
|
||||
|
||||
self.logger.info(f"Bot Integration: Retrieved {len(signals_df)} signals for chart {symbol} from {len(bot_ids)} bots")
|
||||
return signals_df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Bot Integration: Error getting signals for chart: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
def get_trades_for_chart(self,
|
||||
symbol: str,
|
||||
timeframe: str = None,
|
||||
bot_filter: BotFilterConfig = None,
|
||||
time_range: Tuple[datetime, datetime] = None,
|
||||
sides: List[str] = None) -> pd.DataFrame:
|
||||
"""
|
||||
Get trades filtered by chart context (symbol, timeframe) and bot criteria.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol for the chart
|
||||
timeframe: Chart timeframe (optional)
|
||||
bot_filter: Bot filtering configuration
|
||||
time_range: (start_time, end_time) tuple
|
||||
sides: Trade sides to include
|
||||
|
||||
Returns:
|
||||
DataFrame with trades ready for chart rendering
|
||||
"""
|
||||
try:
|
||||
# Get relevant bots for this symbol/timeframe
|
||||
if bot_filter is None:
|
||||
bot_filter = BotFilterConfig()
|
||||
|
||||
# Add symbol filter
|
||||
if bot_filter.symbols is None:
|
||||
bot_filter.symbols = [symbol]
|
||||
elif symbol not in bot_filter.symbols:
|
||||
bot_filter.symbols.append(symbol)
|
||||
|
||||
# Get bots matching criteria
|
||||
bots_df = self.data_service.get_bots(bot_filter)
|
||||
|
||||
if bots_df.empty:
|
||||
self.logger.info(f"No bots found for symbol {symbol}")
|
||||
return pd.DataFrame()
|
||||
|
||||
bot_ids = bots_df['id'].tolist()
|
||||
|
||||
# Get time range
|
||||
start_time, end_time = time_range if time_range else (None, None)
|
||||
|
||||
# Get trades for these bots
|
||||
trades_df = self.data_service.get_trades_for_bots(
|
||||
bot_ids=bot_ids,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
sides=sides
|
||||
)
|
||||
|
||||
# Enrich trades with bot information
|
||||
if not trades_df.empty:
|
||||
trades_df = trades_df.merge(
|
||||
bots_df[['id', 'name', 'strategy_name', 'status']],
|
||||
left_on='bot_id',
|
||||
right_on='id',
|
||||
suffixes=('', '_bot')
|
||||
)
|
||||
|
||||
# Add metadata fields for chart rendering
|
||||
trades_df['bot_name'] = trades_df['name']
|
||||
trades_df['strategy'] = trades_df['strategy_name']
|
||||
trades_df['bot_status'] = trades_df['status']
|
||||
|
||||
# Clean up duplicate columns
|
||||
trades_df = trades_df.drop(['id_bot', 'name', 'strategy_name', 'status'], axis=1)
|
||||
|
||||
self.logger.info(f"Bot Integration: Retrieved {len(trades_df)} trades for chart {symbol} from {len(bot_ids)} bots")
|
||||
return trades_df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Bot Integration: Error getting trades for chart: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
def get_bot_summary_stats(self, bot_ids: List[int] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Get summary statistics for bots.
|
||||
|
||||
Args:
|
||||
bot_ids: Specific bot IDs (None for all bots)
|
||||
|
||||
Returns:
|
||||
Dictionary with summary statistics
|
||||
"""
|
||||
try:
|
||||
# Get bots
|
||||
bot_filter = BotFilterConfig(bot_ids=bot_ids) if bot_ids else BotFilterConfig()
|
||||
bots_df = self.data_service.get_bots(bot_filter)
|
||||
|
||||
if bots_df.empty:
|
||||
return {
|
||||
'total_bots': 0,
|
||||
'active_bots': 0,
|
||||
'total_balance': 0.0,
|
||||
'total_pnl': 0.0,
|
||||
'strategies': [],
|
||||
'symbols': []
|
||||
}
|
||||
|
||||
# Calculate statistics
|
||||
stats = {
|
||||
'total_bots': len(bots_df),
|
||||
'active_bots': len(bots_df[bots_df['status'] == 'active']),
|
||||
'inactive_bots': len(bots_df[bots_df['status'] == 'inactive']),
|
||||
'paused_bots': len(bots_df[bots_df['status'] == 'paused']),
|
||||
'error_bots': len(bots_df[bots_df['status'] == 'error']),
|
||||
'total_virtual_balance': bots_df['virtual_balance'].sum(),
|
||||
'total_current_balance': bots_df['current_balance'].sum(),
|
||||
'total_pnl': bots_df['pnl'].sum(),
|
||||
'average_pnl': bots_df['pnl'].mean(),
|
||||
'best_performing_bot': None,
|
||||
'worst_performing_bot': None,
|
||||
'strategies': bots_df['strategy_name'].unique().tolist(),
|
||||
'symbols': bots_df['symbol'].unique().tolist(),
|
||||
'timeframes': bots_df['timeframe'].unique().tolist()
|
||||
}
|
||||
|
||||
# Get best and worst performing bots
|
||||
if not bots_df.empty:
|
||||
best_bot = bots_df.loc[bots_df['pnl'].idxmax()]
|
||||
worst_bot = bots_df.loc[bots_df['pnl'].idxmin()]
|
||||
|
||||
stats['best_performing_bot'] = {
|
||||
'id': best_bot['id'],
|
||||
'name': best_bot['name'],
|
||||
'pnl': best_bot['pnl']
|
||||
}
|
||||
|
||||
stats['worst_performing_bot'] = {
|
||||
'id': worst_bot['id'],
|
||||
'name': worst_bot['name'],
|
||||
'pnl': worst_bot['pnl']
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Bot Integration: Error getting bot summary stats: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
# Global instances for easy access
|
||||
bot_data_service = BotDataService()
|
||||
bot_integration = BotSignalLayerIntegration()
|
||||
|
||||
|
||||
# Convenience functions for common use cases
|
||||
|
||||
def get_active_bot_signals(symbol: str,
|
||||
timeframe: str = None,
|
||||
days_back: int = 7,
|
||||
signal_types: List[str] = None,
|
||||
min_confidence: float = 0.3) -> pd.DataFrame:
|
||||
"""
|
||||
Get signals from active bots for a specific symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
timeframe: Chart timeframe (optional)
|
||||
days_back: Number of days to look back
|
||||
signal_types: Signal types to include
|
||||
min_confidence: Minimum confidence threshold
|
||||
|
||||
Returns:
|
||||
DataFrame with signals from active bots
|
||||
"""
|
||||
end_time = datetime.now()
|
||||
start_time = end_time - timedelta(days=days_back)
|
||||
|
||||
bot_filter = BotFilterConfig(
|
||||
symbols=[symbol],
|
||||
active_only=True
|
||||
)
|
||||
|
||||
return bot_integration.get_signals_for_chart(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
bot_filter=bot_filter,
|
||||
time_range=(start_time, end_time),
|
||||
signal_types=signal_types,
|
||||
min_confidence=min_confidence
|
||||
)
|
||||
|
||||
|
||||
def get_active_bot_trades(symbol: str,
|
||||
timeframe: str = None,
|
||||
days_back: int = 7,
|
||||
sides: List[str] = None) -> pd.DataFrame:
|
||||
"""
|
||||
Get trades from active bots for a specific symbol.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
timeframe: Chart timeframe (optional)
|
||||
days_back: Number of days to look back
|
||||
sides: Trade sides to include
|
||||
|
||||
Returns:
|
||||
DataFrame with trades from active bots
|
||||
"""
|
||||
end_time = datetime.now()
|
||||
start_time = end_time - timedelta(days=days_back)
|
||||
|
||||
bot_filter = BotFilterConfig(
|
||||
symbols=[symbol],
|
||||
active_only=True
|
||||
)
|
||||
|
||||
return bot_integration.get_trades_for_chart(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
bot_filter=bot_filter,
|
||||
time_range=(start_time, end_time),
|
||||
sides=sides
|
||||
)
|
||||
|
||||
|
||||
def get_bot_signals_by_strategy(strategy_name: str,
|
||||
symbol: str = None,
|
||||
days_back: int = 7,
|
||||
signal_types: List[str] = None) -> pd.DataFrame:
|
||||
"""
|
||||
Get signals from bots using a specific strategy.
|
||||
|
||||
Args:
|
||||
strategy_name: Strategy name to filter by
|
||||
symbol: Trading symbol (optional)
|
||||
days_back: Number of days to look back
|
||||
signal_types: Signal types to include
|
||||
|
||||
Returns:
|
||||
DataFrame with signals from strategy bots
|
||||
"""
|
||||
end_time = datetime.now()
|
||||
start_time = end_time - timedelta(days=days_back)
|
||||
|
||||
bot_filter = BotFilterConfig(
|
||||
strategies=[strategy_name],
|
||||
symbols=[symbol] if symbol else None
|
||||
)
|
||||
|
||||
# Get bots for this strategy
|
||||
bots_df = bot_data_service.get_bots(bot_filter)
|
||||
|
||||
if bots_df.empty:
|
||||
return pd.DataFrame()
|
||||
|
||||
bot_ids = bots_df['id'].tolist()
|
||||
|
||||
return bot_data_service.get_signals_for_bots(
|
||||
bot_ids=bot_ids,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
signal_types=signal_types
|
||||
)
|
||||
|
||||
|
||||
def get_bot_performance_summary(bot_id: int = None,
|
||||
days_back: int = 30) -> Dict[str, Any]:
|
||||
"""
|
||||
Get performance summary for a specific bot or all bots.
|
||||
|
||||
Args:
|
||||
bot_id: Specific bot ID (None for all bots)
|
||||
days_back: Number of days to analyze
|
||||
|
||||
Returns:
|
||||
Dictionary with performance summary
|
||||
"""
|
||||
end_time = datetime.now()
|
||||
start_time = end_time - timedelta(days=days_back)
|
||||
|
||||
# Get bot summary stats
|
||||
bot_ids = [bot_id] if bot_id else None
|
||||
bot_stats = bot_integration.get_bot_summary_stats(bot_ids)
|
||||
|
||||
# Get signals and trades for performance analysis
|
||||
signals_df = bot_data_service.get_signals_for_bots(
|
||||
bot_ids=bot_ids,
|
||||
start_time=start_time,
|
||||
end_time=end_time
|
||||
)
|
||||
|
||||
trades_df = bot_data_service.get_trades_for_bots(
|
||||
bot_ids=bot_ids,
|
||||
start_time=start_time,
|
||||
end_time=end_time
|
||||
)
|
||||
|
||||
# Calculate additional performance metrics
|
||||
performance = {
|
||||
'bot_stats': bot_stats,
|
||||
'signal_count': len(signals_df),
|
||||
'trade_count': len(trades_df),
|
||||
'signals_by_type': signals_df['signal_type'].value_counts().to_dict() if not signals_df.empty else {},
|
||||
'trades_by_side': trades_df['side'].value_counts().to_dict() if not trades_df.empty else {},
|
||||
'total_trade_volume': trades_df['trade_value'].sum() if not trades_df.empty else 0.0,
|
||||
'total_fees': trades_df['fees'].sum() if not trades_df.empty else 0.0,
|
||||
'profitable_trades': len(trades_df[trades_df['pnl'] > 0]) if not trades_df.empty else 0,
|
||||
'losing_trades': len(trades_df[trades_df['pnl'] < 0]) if not trades_df.empty else 0,
|
||||
'win_rate': (len(trades_df[trades_df['pnl'] > 0]) / len(trades_df) * 100) if not trades_df.empty else 0.0,
|
||||
'time_range': {
|
||||
'start': start_time.isoformat(),
|
||||
'end': end_time.isoformat(),
|
||||
'days': days_back
|
||||
}
|
||||
}
|
||||
|
||||
return performance
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user