TCPDashboard/components/charts/config/strategy_charts.py
2025-06-12 13:27:30 +08:00

640 lines
22 KiB
Python

"""
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()
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("Strategy Charts: Enhanced validation system unavailable, using basic validation")
return self._basic_validate()
except Exception as e:
logger.error(f"Strategy Charts: Validation error: {e}")
return False, [f"Strategy Charts: 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"Strategy Charts: 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())