""" Strategy-Specific Chart Configuration System This module provides complete chart configurations for different trading strategies, including indicator combinations, chart layouts, subplot arrangements, and display settings. """ from typing import Dict, List, Any, Optional, Union from dataclasses import dataclass, field from enum import Enum import json from datetime import datetime from .indicator_defs import ChartIndicatorConfig, create_indicator_config, validate_indicator_configuration from .defaults import ( TradingStrategy, IndicatorCategory, get_all_default_indicators, get_strategy_indicators, get_strategy_info ) from utils.logger import get_logger # Initialize logger logger = get_logger("strategy_charts") class ChartLayout(str, Enum): """Chart layout types.""" SINGLE_CHART = "single_chart" MAIN_WITH_SUBPLOTS = "main_with_subplots" MULTI_CHART = "multi_chart" GRID_LAYOUT = "grid_layout" class SubplotType(str, Enum): """Types of subplots available.""" VOLUME = "volume" RSI = "rsi" MACD = "macd" MOMENTUM = "momentum" CUSTOM = "custom" @dataclass class SubplotConfig: """Configuration for a chart subplot.""" subplot_type: SubplotType height_ratio: float = 0.3 indicators: List[str] = field(default_factory=list) title: Optional[str] = None y_axis_label: Optional[str] = None show_grid: bool = True show_legend: bool = True background_color: Optional[str] = None @dataclass class ChartStyle: """Chart styling configuration.""" theme: str = "plotly_white" background_color: str = "#ffffff" grid_color: str = "#e6e6e6" text_color: str = "#2c3e50" font_family: str = "Arial, sans-serif" font_size: int = 12 candlestick_up_color: str = "#26a69a" candlestick_down_color: str = "#ef5350" volume_color: str = "#78909c" show_volume: bool = True show_grid: bool = True show_legend: bool = True show_toolbar: bool = True @dataclass class StrategyChartConfig: """Complete chart configuration for a trading strategy.""" strategy_name: str strategy_type: TradingStrategy description: str timeframes: List[str] # Chart layout layout: ChartLayout = ChartLayout.MAIN_WITH_SUBPLOTS main_chart_height: float = 0.7 # Indicators overlay_indicators: List[str] = field(default_factory=list) subplot_configs: List[SubplotConfig] = field(default_factory=list) # Style chart_style: ChartStyle = field(default_factory=ChartStyle) # Metadata created_at: Optional[datetime] = None updated_at: Optional[datetime] = None version: str = "1.0" tags: List[str] = field(default_factory=list) def validate(self) -> tuple[bool, List[str]]: """ Validate the strategy chart configuration. Returns: Tuple of (is_valid, list_of_error_messages) """ # Use the new comprehensive validation system from .validation import validate_configuration try: report = validate_configuration(self) # Convert validation report to simple format for backward compatibility error_messages = [str(issue) for issue in report.errors] return report.is_valid, error_messages except ImportError: # Fallback to original validation if new system unavailable logger.warning("Enhanced validation system unavailable, using basic validation") return self._basic_validate() except Exception as e: logger.error(f"Validation error: {e}") return False, [f"Validation system error: {e}"] def validate_comprehensive(self) -> 'ValidationReport': """ Perform comprehensive validation with detailed reporting. Returns: Detailed validation report with errors, warnings, and suggestions """ from .validation import validate_configuration return validate_configuration(self) def _basic_validate(self) -> tuple[bool, List[str]]: """ Basic validation method (fallback). Returns: Tuple of (is_valid, list_of_error_messages) """ errors = [] # Validate basic fields if not self.strategy_name: errors.append("Strategy name is required") if not isinstance(self.strategy_type, TradingStrategy): errors.append("Invalid strategy type") if not self.timeframes: errors.append("At least one timeframe must be specified") # Validate height ratios total_subplot_height = sum(config.height_ratio for config in self.subplot_configs) if self.main_chart_height + total_subplot_height > 1.0: errors.append("Total chart height ratios exceed 1.0") if self.main_chart_height <= 0 or self.main_chart_height > 1.0: errors.append("Main chart height must be between 0 and 1.0") # Validate indicators exist try: all_default_indicators = get_all_default_indicators() for indicator_name in self.overlay_indicators: if indicator_name not in all_default_indicators: errors.append(f"Overlay indicator '{indicator_name}' not found in defaults") for subplot_config in self.subplot_configs: for indicator_name in subplot_config.indicators: if indicator_name not in all_default_indicators: errors.append(f"Subplot indicator '{indicator_name}' not found in defaults") except Exception as e: logger.warning(f"Could not validate indicator existence: {e}") # Validate subplot height ratios for i, subplot_config in enumerate(self.subplot_configs): if subplot_config.height_ratio <= 0 or subplot_config.height_ratio > 1.0: errors.append(f"Subplot {i} height ratio must be between 0 and 1.0") return len(errors) == 0, errors def get_all_indicators(self) -> List[str]: """Get all indicators used in this strategy configuration.""" all_indicators = list(self.overlay_indicators) for subplot_config in self.subplot_configs: all_indicators.extend(subplot_config.indicators) return list(set(all_indicators)) def get_indicator_configs(self) -> Dict[str, ChartIndicatorConfig]: """ Get the actual indicator configuration objects for all indicators. Returns: Dictionary mapping indicator names to their configurations """ all_default_indicators = get_all_default_indicators() indicator_configs = {} for indicator_name in self.get_all_indicators(): if indicator_name in all_default_indicators: preset = all_default_indicators[indicator_name] indicator_configs[indicator_name] = preset.config return indicator_configs def create_default_strategy_configurations() -> Dict[str, StrategyChartConfig]: """Create default chart configurations for all trading strategies.""" strategy_configs = {} # Scalping Strategy strategy_configs["scalping"] = StrategyChartConfig( strategy_name="Scalping Strategy", strategy_type=TradingStrategy.SCALPING, description="Fast-paced trading with quick entry/exit on 1-5 minute charts", timeframes=["1m", "5m"], layout=ChartLayout.MAIN_WITH_SUBPLOTS, main_chart_height=0.6, overlay_indicators=["ema_5", "ema_12", "ema_21", "bb_10_15"], subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.2, indicators=["rsi_7"], title="RSI (7)", y_axis_label="RSI", show_grid=True ), SubplotConfig( subplot_type=SubplotType.MACD, height_ratio=0.2, indicators=["macd_5_13_4"], title="MACD Fast", y_axis_label="MACD", show_grid=True ) ], chart_style=ChartStyle( theme="plotly_white", font_size=10, show_volume=True, candlestick_up_color="#00d4aa", candlestick_down_color="#fe6a85" ), tags=["scalping", "short-term", "fast"] ) # Day Trading Strategy strategy_configs["day_trading"] = StrategyChartConfig( strategy_name="Day Trading Strategy", strategy_type=TradingStrategy.DAY_TRADING, description="Intraday trading with balanced indicator mix for 5m-1h charts", timeframes=["5m", "15m", "1h"], layout=ChartLayout.MAIN_WITH_SUBPLOTS, main_chart_height=0.65, overlay_indicators=["sma_20", "ema_12", "ema_26", "bb_20_20"], subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.15, indicators=["rsi_14"], title="RSI (14)", y_axis_label="RSI" ), SubplotConfig( subplot_type=SubplotType.MACD, height_ratio=0.2, indicators=["macd_12_26_9"], title="MACD", y_axis_label="MACD" ) ], chart_style=ChartStyle( theme="plotly_white", font_size=12, show_volume=True ), tags=["day-trading", "intraday", "balanced"] ) # Swing Trading Strategy strategy_configs["swing_trading"] = StrategyChartConfig( strategy_name="Swing Trading Strategy", strategy_type=TradingStrategy.SWING_TRADING, description="Medium-term trading for multi-day holds on 1h-1d charts", timeframes=["1h", "4h", "1d"], layout=ChartLayout.MAIN_WITH_SUBPLOTS, main_chart_height=0.7, overlay_indicators=["sma_50", "ema_21", "ema_50", "bb_20_20"], subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.15, indicators=["rsi_14", "rsi_21"], title="RSI Comparison", y_axis_label="RSI" ), SubplotConfig( subplot_type=SubplotType.MACD, height_ratio=0.15, indicators=["macd_12_26_9"], title="MACD", y_axis_label="MACD" ) ], chart_style=ChartStyle( theme="plotly_white", font_size=12, show_volume=True ), tags=["swing-trading", "medium-term", "multi-day"] ) # Position Trading Strategy strategy_configs["position_trading"] = StrategyChartConfig( strategy_name="Position Trading Strategy", strategy_type=TradingStrategy.POSITION_TRADING, description="Long-term trading for weeks/months holds on 4h-1w charts", timeframes=["4h", "1d", "1w"], layout=ChartLayout.MAIN_WITH_SUBPLOTS, main_chart_height=0.75, overlay_indicators=["sma_100", "sma_200", "ema_50", "ema_100", "bb_50_20"], subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.12, indicators=["rsi_21"], title="RSI (21)", y_axis_label="RSI" ), SubplotConfig( subplot_type=SubplotType.MACD, height_ratio=0.13, indicators=["macd_19_39_13"], title="MACD Slow", y_axis_label="MACD" ) ], chart_style=ChartStyle( theme="plotly_white", font_size=14, show_volume=False # Less important for long-term ), tags=["position-trading", "long-term", "weeks-months"] ) # Momentum Strategy strategy_configs["momentum"] = StrategyChartConfig( strategy_name="Momentum Strategy", strategy_type=TradingStrategy.MOMENTUM, description="Trend-following momentum strategy for strong directional moves", timeframes=["15m", "1h", "4h"], layout=ChartLayout.MAIN_WITH_SUBPLOTS, main_chart_height=0.6, overlay_indicators=["ema_12", "ema_26"], subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.15, indicators=["rsi_7", "rsi_14"], title="RSI Momentum", y_axis_label="RSI" ), SubplotConfig( subplot_type=SubplotType.MACD, height_ratio=0.25, indicators=["macd_8_17_6", "macd_12_26_9"], title="MACD Momentum", y_axis_label="MACD" ) ], chart_style=ChartStyle( theme="plotly_white", font_size=12, candlestick_up_color="#26a69a", candlestick_down_color="#ef5350" ), tags=["momentum", "trend-following", "directional"] ) # Mean Reversion Strategy strategy_configs["mean_reversion"] = StrategyChartConfig( strategy_name="Mean Reversion Strategy", strategy_type=TradingStrategy.MEAN_REVERSION, description="Counter-trend strategy for oversold/overbought conditions", timeframes=["15m", "1h", "4h"], layout=ChartLayout.MAIN_WITH_SUBPLOTS, main_chart_height=0.65, overlay_indicators=["sma_20", "sma_50", "bb_20_20", "bb_20_25"], subplot_configs=[ SubplotConfig( subplot_type=SubplotType.RSI, height_ratio=0.2, indicators=["rsi_14", "rsi_21"], title="RSI Mean Reversion", y_axis_label="RSI" ), SubplotConfig( subplot_type=SubplotType.VOLUME, height_ratio=0.15, indicators=[], title="Volume", y_axis_label="Volume" ) ], chart_style=ChartStyle( theme="plotly_white", font_size=12, show_volume=True ), tags=["mean-reversion", "counter-trend", "oversold-overbought"] ) return strategy_configs def validate_strategy_configuration(config: StrategyChartConfig) -> tuple[bool, List[str]]: """ Validate a strategy chart configuration. Args: config: Strategy chart configuration to validate Returns: Tuple of (is_valid, list_of_error_messages) """ return config.validate() def create_custom_strategy_config( strategy_name: str, strategy_type: TradingStrategy, description: str, timeframes: List[str], overlay_indicators: List[str], subplot_configs: List[Dict[str, Any]], chart_style: Optional[Dict[str, Any]] = None, **kwargs ) -> tuple[Optional[StrategyChartConfig], List[str]]: """ Create a custom strategy chart configuration. Args: strategy_name: Name of the strategy strategy_type: Type of trading strategy description: Strategy description timeframes: List of recommended timeframes overlay_indicators: List of overlay indicator names subplot_configs: List of subplot configuration dictionaries chart_style: Optional chart style configuration **kwargs: Additional configuration options Returns: Tuple of (config_object_or_None, list_of_error_messages) """ try: # Create subplot configurations subplots = [] for subplot_data in subplot_configs: subplot_type = SubplotType(subplot_data.get("subplot_type", "custom")) subplot = SubplotConfig( subplot_type=subplot_type, height_ratio=subplot_data.get("height_ratio", 0.2), indicators=subplot_data.get("indicators", []), title=subplot_data.get("title"), y_axis_label=subplot_data.get("y_axis_label"), show_grid=subplot_data.get("show_grid", True), show_legend=subplot_data.get("show_legend", True), background_color=subplot_data.get("background_color") ) subplots.append(subplot) # Create chart style style = ChartStyle() if chart_style: for key, value in chart_style.items(): if hasattr(style, key): setattr(style, key, value) # Create configuration config = StrategyChartConfig( strategy_name=strategy_name, strategy_type=strategy_type, description=description, timeframes=timeframes, layout=ChartLayout(kwargs.get("layout", ChartLayout.MAIN_WITH_SUBPLOTS.value)), main_chart_height=kwargs.get("main_chart_height", 0.7), overlay_indicators=overlay_indicators, subplot_configs=subplots, chart_style=style, created_at=datetime.now(), version=kwargs.get("version", "1.0"), tags=kwargs.get("tags", []) ) # Validate configuration is_valid, errors = config.validate() if not is_valid: return None, errors return config, [] except Exception as e: return None, [f"Error creating strategy configuration: {e}"] def load_strategy_config_from_json(json_data: Union[str, Dict[str, Any]]) -> tuple[Optional[StrategyChartConfig], List[str]]: """ Load strategy configuration from JSON data. Args: json_data: JSON string or dictionary with configuration data Returns: Tuple of (config_object_or_None, list_of_error_messages) """ try: if isinstance(json_data, str): data = json.loads(json_data) else: data = json_data # Extract required fields required_fields = ["strategy_name", "strategy_type", "description", "timeframes"] missing_fields = [field for field in required_fields if field not in data] if missing_fields: return None, [f"Missing required fields: {', '.join(missing_fields)}"] # Convert strategy type try: strategy_type = TradingStrategy(data["strategy_type"]) except ValueError: return None, [f"Invalid strategy type: {data['strategy_type']}"] return create_custom_strategy_config( strategy_name=data["strategy_name"], strategy_type=strategy_type, description=data["description"], timeframes=data["timeframes"], overlay_indicators=data.get("overlay_indicators", []), subplot_configs=data.get("subplot_configs", []), chart_style=data.get("chart_style"), **{k: v for k, v in data.items() if k not in required_fields + ["overlay_indicators", "subplot_configs", "chart_style"]} ) except json.JSONDecodeError as e: return None, [f"Invalid JSON: {e}"] except Exception as e: return None, [f"Error loading configuration: {e}"] def export_strategy_config_to_json(config: StrategyChartConfig) -> str: """ Export strategy configuration to JSON string. Args: config: Strategy configuration to export Returns: JSON string representation of the configuration """ # Convert to dictionary config_dict = { "strategy_name": config.strategy_name, "strategy_type": config.strategy_type.value, "description": config.description, "timeframes": config.timeframes, "layout": config.layout.value, "main_chart_height": config.main_chart_height, "overlay_indicators": config.overlay_indicators, "subplot_configs": [ { "subplot_type": subplot.subplot_type.value, "height_ratio": subplot.height_ratio, "indicators": subplot.indicators, "title": subplot.title, "y_axis_label": subplot.y_axis_label, "show_grid": subplot.show_grid, "show_legend": subplot.show_legend, "background_color": subplot.background_color } for subplot in config.subplot_configs ], "chart_style": { "theme": config.chart_style.theme, "background_color": config.chart_style.background_color, "grid_color": config.chart_style.grid_color, "text_color": config.chart_style.text_color, "font_family": config.chart_style.font_family, "font_size": config.chart_style.font_size, "candlestick_up_color": config.chart_style.candlestick_up_color, "candlestick_down_color": config.chart_style.candlestick_down_color, "volume_color": config.chart_style.volume_color, "show_volume": config.chart_style.show_volume, "show_grid": config.chart_style.show_grid, "show_legend": config.chart_style.show_legend, "show_toolbar": config.chart_style.show_toolbar }, "version": config.version, "tags": config.tags } return json.dumps(config_dict, indent=2) def get_strategy_config(strategy_name: str) -> Optional[StrategyChartConfig]: """ Get a default strategy configuration by name. Args: strategy_name: Name of the strategy Returns: Strategy configuration or None if not found """ default_configs = create_default_strategy_configurations() return default_configs.get(strategy_name) def get_all_strategy_configs() -> Dict[str, StrategyChartConfig]: """ Get all default strategy configurations. Returns: Dictionary mapping strategy names to their configurations """ return create_default_strategy_configurations() def get_available_strategy_names() -> List[str]: """ Get list of available default strategy names. Returns: List of strategy names """ return list(create_default_strategy_configurations().keys())