3.4 - 3.0 Strategy Configuration System

Implement comprehensive chart configuration and validation system

- Introduced a modular chart configuration system in `components/charts/config/` to manage indicator definitions, default configurations, and strategy-specific setups.
- Added new modules for error handling and validation, enhancing user guidance and error reporting capabilities.
- Implemented detailed schema validation for indicators and strategies, ensuring robust configuration management.
- Created example strategies and default configurations to facilitate user onboarding and usage.
- Enhanced documentation to provide clear guidelines on the configuration system, validation rules, and usage examples.
- Added unit tests for all new components to ensure functionality and reliability across the configuration system.
This commit is contained in:
Vasily.onl
2025-06-03 14:33:25 +08:00
parent a969defe1f
commit d71cb763bc
18 changed files with 8779 additions and 28 deletions

View File

@@ -1,37 +1,242 @@
"""
Chart Configuration Package
This package contains configuration management for the modular chart system,
including indicator definitions, strategy-specific configurations, and defaults.
This package provides configuration management for the modular chart system,
including indicator definitions, schema validation, and default configurations.
"""
from .indicator_defs import (
INDICATOR_DEFINITIONS,
# Core classes
IndicatorType,
DisplayType,
LineStyle,
PriceColumn,
IndicatorParameterSchema,
IndicatorSchema,
ChartIndicatorConfig,
calculate_indicators,
convert_database_candles_to_ohlcv,
# Schema definitions
INDICATOR_SCHEMAS,
INDICATOR_DEFINITIONS,
# Utility functions
validate_indicator_configuration,
create_indicator_config,
get_indicator_schema,
get_available_indicator_types,
get_indicator_parameter_info,
validate_parameters_for_type,
create_configuration_from_json,
# Legacy functions
get_indicator_display_config,
get_available_indicators,
get_overlay_indicators,
get_subplot_indicators,
get_default_indicator_params
get_default_indicator_params,
calculate_indicators
)
from .defaults import (
# Categories and strategies
IndicatorCategory,
TradingStrategy,
IndicatorPreset,
# Color schemes
CATEGORY_COLORS,
# Default indicators
get_all_default_indicators,
get_indicators_by_category,
get_indicators_for_timeframe,
# Strategy presets
get_strategy_indicators,
get_strategy_info,
get_available_strategies,
get_available_categories,
# Custom presets
create_custom_preset
)
from .strategy_charts import (
# Chart configuration classes
ChartLayout,
SubplotType,
SubplotConfig,
ChartStyle,
StrategyChartConfig,
# Strategy configuration functions
create_default_strategy_configurations,
validate_strategy_configuration,
create_custom_strategy_config,
load_strategy_config_from_json,
export_strategy_config_to_json,
get_strategy_config,
get_all_strategy_configs,
get_available_strategy_names
)
from .validation import (
# Validation classes
ValidationLevel,
ValidationRule,
ValidationIssue,
ValidationReport,
ConfigurationValidator,
# Validation functions
validate_configuration,
get_validation_rules_info
)
from .example_strategies import (
# Example strategy classes
StrategyExample,
# Example strategy functions
create_ema_crossover_strategy,
create_momentum_breakout_strategy,
create_mean_reversion_strategy,
create_scalping_strategy,
create_swing_trading_strategy,
get_all_example_strategies,
get_example_strategy,
get_strategies_by_difficulty,
get_strategies_by_risk_level,
get_strategies_by_market_condition,
get_strategy_summary,
export_example_strategies_to_json
)
from .error_handling import (
# Error handling classes
ErrorSeverity,
ErrorCategory,
ConfigurationError,
ErrorReport,
ConfigurationErrorHandler,
# Error handling functions
validate_configuration_strict,
validate_strategy_name,
get_indicator_suggestions,
get_strategy_suggestions,
check_configuration_health
)
# Package metadata
__version__ = "0.1.0"
__package_name__ = "config"
# Public exports
__all__ = [
"INDICATOR_DEFINITIONS",
"ChartIndicatorConfig",
"calculate_indicators",
"convert_database_candles_to_ohlcv",
"get_indicator_display_config",
"get_available_indicators",
"get_overlay_indicators",
"get_subplot_indicators",
"get_default_indicator_params"
# Core classes from indicator_defs
'IndicatorType',
'DisplayType',
'LineStyle',
'PriceColumn',
'IndicatorParameterSchema',
'IndicatorSchema',
'ChartIndicatorConfig',
# Schema and definitions
'INDICATOR_SCHEMAS',
'INDICATOR_DEFINITIONS',
# Validation and creation functions
'validate_indicator_configuration',
'create_indicator_config',
'get_indicator_schema',
'get_available_indicator_types',
'get_indicator_parameter_info',
'validate_parameters_for_type',
'create_configuration_from_json',
# Legacy compatibility functions
'get_indicator_display_config',
'get_available_indicators',
'get_overlay_indicators',
'get_subplot_indicators',
'get_default_indicator_params',
'calculate_indicators',
# Categories and strategies from defaults
'IndicatorCategory',
'TradingStrategy',
'IndicatorPreset',
'CATEGORY_COLORS',
# Default configuration functions
'get_all_default_indicators',
'get_indicators_by_category',
'get_indicators_for_timeframe',
'get_strategy_indicators',
'get_strategy_info',
'get_available_strategies',
'get_available_categories',
'create_custom_preset',
# Strategy chart configuration classes
'ChartLayout',
'SubplotType',
'SubplotConfig',
'ChartStyle',
'StrategyChartConfig',
# Strategy configuration functions
'create_default_strategy_configurations',
'validate_strategy_configuration',
'create_custom_strategy_config',
'load_strategy_config_from_json',
'export_strategy_config_to_json',
'get_strategy_config',
'get_all_strategy_configs',
'get_available_strategy_names',
# Validation classes
'ValidationLevel',
'ValidationRule',
'ValidationIssue',
'ValidationReport',
'ConfigurationValidator',
# Validation functions
'validate_configuration',
'get_validation_rules_info',
# Example strategy classes
'StrategyExample',
# Example strategy functions
'create_ema_crossover_strategy',
'create_momentum_breakout_strategy',
'create_mean_reversion_strategy',
'create_scalping_strategy',
'create_swing_trading_strategy',
'get_all_example_strategies',
'get_example_strategy',
'get_strategies_by_difficulty',
'get_strategies_by_risk_level',
'get_strategies_by_market_condition',
'get_strategy_summary',
'export_example_strategies_to_json',
# Error handling classes
'ErrorSeverity',
'ErrorCategory',
'ConfigurationError',
'ErrorReport',
'ConfigurationErrorHandler',
# Error handling functions
'validate_configuration_strict',
'validate_strategy_name',
'get_indicator_suggestions',
'get_strategy_suggestions',
'check_configuration_health'
]
# Legacy function names for backward compatibility

View File

@@ -0,0 +1,460 @@
"""
Default Indicator Configurations and Parameters
This module provides comprehensive default indicator configurations
organized by categories, trading strategies, and common use cases.
"""
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from enum import Enum
from .indicator_defs import ChartIndicatorConfig, create_indicator_config, IndicatorType
class IndicatorCategory(str, Enum):
"""Categories for organizing indicators."""
TREND = "trend"
MOMENTUM = "momentum"
VOLATILITY = "volatility"
VOLUME = "volume"
SUPPORT_RESISTANCE = "support_resistance"
class TradingStrategy(str, Enum):
"""Common trading strategy types."""
SCALPING = "scalping"
DAY_TRADING = "day_trading"
SWING_TRADING = "swing_trading"
POSITION_TRADING = "position_trading"
MOMENTUM = "momentum"
MEAN_REVERSION = "mean_reversion"
@dataclass
class IndicatorPreset:
"""
Predefined indicator configuration preset.
"""
name: str
description: str
category: IndicatorCategory
recommended_timeframes: List[str]
config: ChartIndicatorConfig
# Color schemes for different indicator categories
CATEGORY_COLORS = {
IndicatorCategory.TREND: {
'primary': '#007bff', # Blue
'secondary': '#28a745', # Green
'tertiary': '#17a2b8', # Cyan
'quaternary': '#6c757d' # Gray
},
IndicatorCategory.MOMENTUM: {
'primary': '#dc3545', # Red
'secondary': '#fd7e14', # Orange
'tertiary': '#e83e8c', # Pink
'quaternary': '#6f42c1' # Purple
},
IndicatorCategory.VOLATILITY: {
'primary': '#6f42c1', # Purple
'secondary': '#e83e8c', # Pink
'tertiary': '#20c997', # Teal
'quaternary': '#ffc107' # Yellow
}
}
def create_trend_indicators() -> Dict[str, IndicatorPreset]:
"""Create default trend indicator configurations."""
trend_indicators = {}
# Simple Moving Averages
sma_configs = [
(5, "Very Short Term", ['1m', '5m']),
(10, "Short Term", ['5m', '15m']),
(20, "Short-Medium Term", ['15m', '1h']),
(50, "Medium Term", ['1h', '4h']),
(100, "Long Term", ['4h', '1d']),
(200, "Very Long Term", ['1d', '1w'])
]
for period, desc, timeframes in sma_configs:
config, _ = create_indicator_config(
name=f"SMA ({period})",
indicator_type="sma",
parameters={"period": period},
color=CATEGORY_COLORS[IndicatorCategory.TREND]['primary'] if period <= 20 else
CATEGORY_COLORS[IndicatorCategory.TREND]['secondary'] if period <= 50 else
CATEGORY_COLORS[IndicatorCategory.TREND]['tertiary'],
line_width=2 if period <= 50 else 3
)
trend_indicators[f"sma_{period}"] = IndicatorPreset(
name=f"SMA {period}",
description=f"{desc} Simple Moving Average - {period} periods",
category=IndicatorCategory.TREND,
recommended_timeframes=timeframes,
config=config
)
# Exponential Moving Averages
ema_configs = [
(5, "Very Short Term", ['1m', '5m']),
(12, "Short Term (MACD Fast)", ['5m', '15m', '1h']),
(21, "Fibonacci Short Term", ['15m', '1h']),
(26, "Medium Term (MACD Slow)", ['1h', '4h']),
(50, "Medium-Long Term", ['4h', '1d']),
(100, "Long Term", ['1d', '1w']),
(200, "Very Long Term", ['1d', '1w'])
]
for period, desc, timeframes in ema_configs:
config, _ = create_indicator_config(
name=f"EMA ({period})",
indicator_type="ema",
parameters={"period": period},
color=CATEGORY_COLORS[IndicatorCategory.TREND]['secondary'] if period <= 21 else
CATEGORY_COLORS[IndicatorCategory.TREND]['tertiary'] if period <= 50 else
CATEGORY_COLORS[IndicatorCategory.TREND]['quaternary'],
line_width=2,
line_style='dash' if period in [12, 26] else 'solid'
)
trend_indicators[f"ema_{period}"] = IndicatorPreset(
name=f"EMA {period}",
description=f"{desc} Exponential Moving Average - {period} periods",
category=IndicatorCategory.TREND,
recommended_timeframes=timeframes,
config=config
)
return trend_indicators
def create_momentum_indicators() -> Dict[str, IndicatorPreset]:
"""Create default momentum indicator configurations."""
momentum_indicators = {}
# RSI configurations
rsi_configs = [
(7, "Fast RSI", ['1m', '5m', '15m']),
(14, "Standard RSI", ['15m', '1h', '4h']),
(21, "Slow RSI", ['1h', '4h', '1d']),
(30, "Very Slow RSI", ['4h', '1d', '1w'])
]
for period, desc, timeframes in rsi_configs:
config, _ = create_indicator_config(
name=f"RSI ({period})",
indicator_type="rsi",
parameters={"period": period},
color=CATEGORY_COLORS[IndicatorCategory.MOMENTUM]['primary'] if period == 14 else
CATEGORY_COLORS[IndicatorCategory.MOMENTUM]['secondary'],
line_width=2,
subplot_height_ratio=0.25
)
momentum_indicators[f"rsi_{period}"] = IndicatorPreset(
name=f"RSI {period}",
description=f"{desc} - Relative Strength Index with {period} periods",
category=IndicatorCategory.MOMENTUM,
recommended_timeframes=timeframes,
config=config
)
# MACD configurations
macd_configs = [
((5, 13, 4), "Fast MACD", ['1m', '5m']),
((8, 17, 6), "Scalping MACD", ['5m', '15m']),
((12, 26, 9), "Standard MACD", ['15m', '1h', '4h']),
((19, 39, 13), "Slow MACD", ['1h', '4h', '1d']),
((26, 52, 18), "Very Slow MACD", ['4h', '1d', '1w'])
]
for (fast, slow, signal), desc, timeframes in macd_configs:
config, _ = create_indicator_config(
name=f"MACD ({fast},{slow},{signal})",
indicator_type="macd",
parameters={
"fast_period": fast,
"slow_period": slow,
"signal_period": signal
},
color=CATEGORY_COLORS[IndicatorCategory.MOMENTUM]['secondary'] if (fast, slow, signal) == (12, 26, 9) else
CATEGORY_COLORS[IndicatorCategory.MOMENTUM]['tertiary'],
line_width=2,
subplot_height_ratio=0.3
)
momentum_indicators[f"macd_{fast}_{slow}_{signal}"] = IndicatorPreset(
name=f"MACD {fast}/{slow}/{signal}",
description=f"{desc} - MACD with {fast}/{slow}/{signal} periods",
category=IndicatorCategory.MOMENTUM,
recommended_timeframes=timeframes,
config=config
)
return momentum_indicators
def create_volatility_indicators() -> Dict[str, IndicatorPreset]:
"""Create default volatility indicator configurations."""
volatility_indicators = {}
# Bollinger Bands configurations
bb_configs = [
((10, 1.5), "Tight Bollinger Bands", ['1m', '5m']),
((20, 2.0), "Standard Bollinger Bands", ['15m', '1h', '4h']),
((20, 2.5), "Wide Bollinger Bands", ['1h', '4h']),
((50, 2.0), "Long-term Bollinger Bands", ['4h', '1d', '1w'])
]
for (period, std_dev), desc, timeframes in bb_configs:
config, _ = create_indicator_config(
name=f"BB ({period}, {std_dev})",
indicator_type="bollinger_bands",
parameters={"period": period, "std_dev": std_dev},
color=CATEGORY_COLORS[IndicatorCategory.VOLATILITY]['primary'] if (period, std_dev) == (20, 2.0) else
CATEGORY_COLORS[IndicatorCategory.VOLATILITY]['secondary'],
line_width=1,
opacity=0.7
)
volatility_indicators[f"bb_{period}_{int(std_dev*10)}"] = IndicatorPreset(
name=f"Bollinger Bands {period}/{std_dev}",
description=f"{desc} - {period} period with {std_dev} standard deviations",
category=IndicatorCategory.VOLATILITY,
recommended_timeframes=timeframes,
config=config
)
return volatility_indicators
def create_strategy_presets() -> Dict[str, Dict[str, List[str]]]:
"""Create predefined indicator combinations for common trading strategies."""
strategy_presets = {
TradingStrategy.SCALPING.value: {
"name": "Scalping Strategy",
"description": "Fast indicators for 1-5 minute scalping",
"timeframes": ["1m", "5m"],
"indicators": [
"ema_5", "ema_12", "ema_21",
"rsi_7", "macd_5_13_4",
"bb_10_15"
]
},
TradingStrategy.DAY_TRADING.value: {
"name": "Day Trading Strategy",
"description": "Balanced indicators for intraday trading",
"timeframes": ["5m", "15m", "1h"],
"indicators": [
"sma_20", "ema_12", "ema_26",
"rsi_14", "macd_12_26_9",
"bb_20_20"
]
},
TradingStrategy.SWING_TRADING.value: {
"name": "Swing Trading Strategy",
"description": "Medium-term indicators for swing trading",
"timeframes": ["1h", "4h", "1d"],
"indicators": [
"sma_50", "ema_21", "ema_50",
"rsi_14", "rsi_21", "macd_12_26_9",
"bb_20_20"
]
},
TradingStrategy.POSITION_TRADING.value: {
"name": "Position Trading Strategy",
"description": "Long-term indicators for position trading",
"timeframes": ["4h", "1d", "1w"],
"indicators": [
"sma_100", "sma_200", "ema_50", "ema_100",
"rsi_21", "macd_19_39_13",
"bb_50_20"
]
},
TradingStrategy.MOMENTUM.value: {
"name": "Momentum Strategy",
"description": "Momentum-focused indicators",
"timeframes": ["15m", "1h", "4h"],
"indicators": [
"ema_12", "ema_26",
"rsi_7", "rsi_14", "macd_8_17_6", "macd_12_26_9"
]
},
TradingStrategy.MEAN_REVERSION.value: {
"name": "Mean Reversion Strategy",
"description": "Indicators for mean reversion trading",
"timeframes": ["15m", "1h", "4h"],
"indicators": [
"sma_20", "sma_50", "bb_20_20", "bb_20_25",
"rsi_14", "rsi_21"
]
}
}
return strategy_presets
def get_all_default_indicators() -> Dict[str, IndicatorPreset]:
"""
Get all default indicator configurations.
Returns:
Dictionary mapping indicator names to their preset configurations
"""
all_indicators = {}
# Combine all indicator categories
all_indicators.update(create_trend_indicators())
all_indicators.update(create_momentum_indicators())
all_indicators.update(create_volatility_indicators())
return all_indicators
def get_indicators_by_category(category: IndicatorCategory) -> Dict[str, IndicatorPreset]:
"""
Get default indicators filtered by category.
Args:
category: Indicator category to filter by
Returns:
Dictionary of indicators in the specified category
"""
all_indicators = get_all_default_indicators()
return {name: preset for name, preset in all_indicators.items()
if preset.category == category}
def get_indicators_for_timeframe(timeframe: str) -> Dict[str, IndicatorPreset]:
"""
Get indicators recommended for a specific timeframe.
Args:
timeframe: Timeframe string (e.g., '1m', '5m', '1h', '4h', '1d')
Returns:
Dictionary of indicators suitable for the timeframe
"""
all_indicators = get_all_default_indicators()
return {name: preset for name, preset in all_indicators.items()
if timeframe in preset.recommended_timeframes}
def get_strategy_indicators(strategy: TradingStrategy) -> List[str]:
"""
Get indicator names for a specific trading strategy.
Args:
strategy: Trading strategy type
Returns:
List of indicator names for the strategy
"""
presets = create_strategy_presets()
strategy_config = presets.get(strategy.value, {})
return strategy_config.get("indicators", [])
def get_strategy_info(strategy: TradingStrategy) -> Dict[str, Any]:
"""
Get complete information about a trading strategy.
Args:
strategy: Trading strategy type
Returns:
Dictionary with strategy details including indicators and timeframes
"""
presets = create_strategy_presets()
return presets.get(strategy.value, {})
def create_custom_preset(
name: str,
description: str,
category: IndicatorCategory,
indicator_configs: List[Dict[str, Any]],
recommended_timeframes: Optional[List[str]] = None
) -> Dict[str, IndicatorPreset]:
"""
Create custom indicator presets.
Args:
name: Preset name
description: Preset description
category: Indicator category
indicator_configs: List of indicator configuration dictionaries
recommended_timeframes: Optional list of recommended timeframes
Returns:
Dictionary of created indicator presets
"""
custom_presets = {}
for i, config_data in enumerate(indicator_configs):
try:
config, errors = create_indicator_config(**config_data)
if errors:
continue
preset_name = f"{name.lower().replace(' ', '_')}_{i}"
custom_presets[preset_name] = IndicatorPreset(
name=f"{name} {i+1}",
description=description,
category=category,
recommended_timeframes=recommended_timeframes or ["15m", "1h", "4h"],
config=config
)
except Exception:
continue
return custom_presets
def get_available_strategies() -> List[Dict[str, str]]:
"""
Get list of available trading strategies.
Returns:
List of dictionaries with strategy information
"""
presets = create_strategy_presets()
return [
{
"value": strategy,
"name": info["name"],
"description": info["description"],
"timeframes": ", ".join(info["timeframes"])
}
for strategy, info in presets.items()
]
def get_available_categories() -> List[Dict[str, str]]:
"""
Get list of available indicator categories.
Returns:
List of dictionaries with category information
"""
return [
{
"value": category.value,
"name": category.value.replace("_", " ").title(),
"description": f"Indicators for {category.value.replace('_', ' ')} analysis"
}
for category in IndicatorCategory
]

View File

@@ -0,0 +1,605 @@
"""
Enhanced Error Handling and User Guidance System
This module provides comprehensive error handling for missing strategies and indicators,
with clear error messages, suggestions, and recovery guidance rather than silent fallbacks.
"""
from typing import Dict, List, Optional, Set, Tuple, Any
from dataclasses import dataclass, field
from enum import Enum
import difflib
from datetime import datetime
from .indicator_defs import IndicatorType, ChartIndicatorConfig
from .defaults import get_all_default_indicators, IndicatorCategory, TradingStrategy
from .strategy_charts import StrategyChartConfig
from .example_strategies import get_all_example_strategies
from utils.logger import get_logger
# Initialize logger
logger = get_logger("error_handling")
class ErrorSeverity(str, Enum):
"""Severity levels for configuration errors."""
CRITICAL = "critical" # Cannot proceed at all
HIGH = "high" # Major functionality missing
MEDIUM = "medium" # Some features unavailable
LOW = "low" # Minor issues, mostly cosmetic
class ErrorCategory(str, Enum):
"""Categories of configuration errors."""
MISSING_STRATEGY = "missing_strategy"
MISSING_INDICATOR = "missing_indicator"
INVALID_PARAMETER = "invalid_parameter"
DEPENDENCY_MISSING = "dependency_missing"
CONFIGURATION_CORRUPT = "configuration_corrupt"
@dataclass
class ConfigurationError:
"""Detailed configuration error with guidance."""
category: ErrorCategory
severity: ErrorSeverity
message: str
field_path: str = ""
missing_item: str = ""
suggestions: List[str] = field(default_factory=list)
alternatives: List[str] = field(default_factory=list)
recovery_steps: List[str] = field(default_factory=list)
context: Dict[str, Any] = field(default_factory=dict)
def __str__(self) -> str:
"""String representation of the error."""
severity_emoji = {
ErrorSeverity.CRITICAL: "🚨",
ErrorSeverity.HIGH: "",
ErrorSeverity.MEDIUM: "⚠️",
ErrorSeverity.LOW: ""
}
result = f"{severity_emoji.get(self.severity, '')} {self.message}"
if self.suggestions:
result += f"\n 💡 Suggestions: {', '.join(self.suggestions)}"
if self.alternatives:
result += f"\n 🔄 Alternatives: {', '.join(self.alternatives)}"
if self.recovery_steps:
result += f"\n 🔧 Recovery steps:"
for step in self.recovery_steps:
result += f"\n{step}"
return result
@dataclass
class ErrorReport:
"""Comprehensive error report with categorized issues."""
is_usable: bool
errors: List[ConfigurationError] = field(default_factory=list)
missing_strategies: Set[str] = field(default_factory=set)
missing_indicators: Set[str] = field(default_factory=set)
report_time: datetime = field(default_factory=datetime.now)
def add_error(self, error: ConfigurationError) -> None:
"""Add an error to the report."""
self.errors.append(error)
# Track missing items
if error.category == ErrorCategory.MISSING_STRATEGY:
self.missing_strategies.add(error.missing_item)
elif error.category == ErrorCategory.MISSING_INDICATOR:
self.missing_indicators.add(error.missing_item)
# Update usability based on severity
if error.severity in [ErrorSeverity.CRITICAL, ErrorSeverity.HIGH]:
self.is_usable = False
def get_critical_errors(self) -> List[ConfigurationError]:
"""Get only critical errors that prevent usage."""
return [e for e in self.errors if e.severity == ErrorSeverity.CRITICAL]
def get_high_priority_errors(self) -> List[ConfigurationError]:
"""Get high priority errors that significantly impact functionality."""
return [e for e in self.errors if e.severity == ErrorSeverity.HIGH]
def summary(self) -> str:
"""Get a summary of the error report."""
if not self.errors:
return "✅ No configuration errors found"
critical = len(self.get_critical_errors())
high = len(self.get_high_priority_errors())
total = len(self.errors)
status = "❌ Cannot proceed" if not self.is_usable else "⚠️ Has issues but usable"
return f"{status} - {total} errors ({critical} critical, {high} high priority)"
class ConfigurationErrorHandler:
"""Enhanced error handler for configuration issues."""
def __init__(self):
"""Initialize the error handler."""
self.available_indicators = get_all_default_indicators()
self.available_strategies = get_all_example_strategies()
# Cache indicator names for fuzzy matching
self.indicator_names = set(self.available_indicators.keys())
self.strategy_names = set(self.available_strategies.keys())
logger.info(f"Error handler initialized with {len(self.indicator_names)} indicators and {len(self.strategy_names)} strategies")
def validate_strategy_exists(self, strategy_name: str) -> Optional[ConfigurationError]:
"""Check if a strategy exists and provide guidance if not."""
if strategy_name in self.strategy_names:
return None
# Find similar strategy names
similar = difflib.get_close_matches(
strategy_name,
self.strategy_names,
n=3,
cutoff=0.6
)
suggestions = []
alternatives = list(similar) if similar else []
recovery_steps = []
if similar:
suggestions.append(f"Did you mean one of: {', '.join(similar)}?")
recovery_steps.append(f"Try using: {similar[0]}")
else:
suggestions.append("Check available strategies with get_all_example_strategies()")
recovery_steps.append("List available strategies: get_strategy_summary()")
# Add general recovery steps
recovery_steps.extend([
"Create a custom strategy with create_custom_strategy_config()",
"Use a pre-built strategy like 'ema_crossover' or 'swing_trading'"
])
return ConfigurationError(
category=ErrorCategory.MISSING_STRATEGY,
severity=ErrorSeverity.CRITICAL,
message=f"Strategy '{strategy_name}' not found",
missing_item=strategy_name,
suggestions=suggestions,
alternatives=alternatives,
recovery_steps=recovery_steps,
context={"available_count": len(self.strategy_names)}
)
def validate_indicator_exists(self, indicator_name: str) -> Optional[ConfigurationError]:
"""Check if an indicator exists and provide guidance if not."""
if indicator_name in self.indicator_names:
return None
# Find similar indicator names
similar = difflib.get_close_matches(
indicator_name,
self.indicator_names,
n=3,
cutoff=0.6
)
suggestions = []
alternatives = list(similar) if similar else []
recovery_steps = []
if similar:
suggestions.append(f"Did you mean: {', '.join(similar)}?")
recovery_steps.append(f"Try using: {similar[0]}")
else:
# Suggest by category if no close matches
suggestions.append("Check available indicators with get_all_default_indicators()")
# Try to guess category and suggest alternatives
if "sma" in indicator_name.lower() or "ema" in indicator_name.lower():
trend_indicators = [name for name in self.indicator_names if name.startswith(("sma_", "ema_"))]
alternatives.extend(trend_indicators[:3])
suggestions.append("For trend indicators, try SMA or EMA with different periods")
elif "rsi" in indicator_name.lower():
rsi_indicators = [name for name in self.indicator_names if name.startswith("rsi_")]
alternatives.extend(rsi_indicators)
suggestions.append("For RSI, try rsi_14, rsi_7, or rsi_21")
elif "macd" in indicator_name.lower():
macd_indicators = [name for name in self.indicator_names if name.startswith("macd_")]
alternatives.extend(macd_indicators)
suggestions.append("For MACD, try macd_12_26_9 or other period combinations")
elif "bb" in indicator_name.lower() or "bollinger" in indicator_name.lower():
bb_indicators = [name for name in self.indicator_names if name.startswith("bb_")]
alternatives.extend(bb_indicators)
suggestions.append("For Bollinger Bands, try bb_20_20 or bb_20_15")
# Add general recovery steps
recovery_steps.extend([
"List available indicators by category: get_indicators_by_category()",
"Create custom indicator with create_indicator_config()",
"Remove this indicator from your configuration if not essential"
])
# Determine severity based on indicator type
severity = ErrorSeverity.HIGH
if indicator_name.startswith(("sma_", "ema_")):
severity = ErrorSeverity.CRITICAL # Trend indicators are often essential
return ConfigurationError(
category=ErrorCategory.MISSING_INDICATOR,
severity=severity,
message=f"Indicator '{indicator_name}' not found",
missing_item=indicator_name,
suggestions=suggestions,
alternatives=alternatives,
recovery_steps=recovery_steps,
context={"available_count": len(self.indicator_names)}
)
def validate_strategy_configuration(self, config: StrategyChartConfig) -> ErrorReport:
"""Comprehensively validate a strategy configuration."""
report = ErrorReport(is_usable=True)
# Validate overlay indicators
for indicator in config.overlay_indicators:
error = self.validate_indicator_exists(indicator)
if error:
error.field_path = f"overlay_indicators[{indicator}]"
report.add_error(error)
# Validate subplot indicators
for i, subplot in enumerate(config.subplot_configs):
for indicator in subplot.indicators:
error = self.validate_indicator_exists(indicator)
if error:
error.field_path = f"subplot_configs[{i}].indicators[{indicator}]"
report.add_error(error)
# Check for empty configuration
total_indicators = len(config.overlay_indicators) + sum(
len(subplot.indicators) for subplot in config.subplot_configs
)
if total_indicators == 0:
report.add_error(ConfigurationError(
category=ErrorCategory.CONFIGURATION_CORRUPT,
severity=ErrorSeverity.CRITICAL,
message="Configuration has no indicators defined",
suggestions=[
"Add at least one overlay indicator (e.g., 'ema_12', 'sma_20')",
"Add subplot indicators for momentum analysis (e.g., 'rsi_14')"
],
recovery_steps=[
"Use a pre-built strategy: create_ema_crossover_strategy()",
"Add basic indicators: ['ema_12', 'ema_26'] for trend analysis",
"Add RSI subplot for momentum: subplot with 'rsi_14'"
]
))
# Validate strategy consistency
if hasattr(config, 'strategy_type'):
consistency_error = self._validate_strategy_consistency(config)
if consistency_error:
report.add_error(consistency_error)
return report
def _validate_strategy_consistency(self, config: StrategyChartConfig) -> Optional[ConfigurationError]:
"""Validate that strategy configuration is consistent with strategy type."""
strategy_type = config.strategy_type
timeframes = config.timeframes
# Define expected timeframes for different strategies
expected_timeframes = {
TradingStrategy.SCALPING: ["1m", "5m"],
TradingStrategy.DAY_TRADING: ["5m", "15m", "1h", "4h"],
TradingStrategy.SWING_TRADING: ["1h", "4h", "1d"],
TradingStrategy.MOMENTUM: ["5m", "15m", "1h"],
TradingStrategy.MEAN_REVERSION: ["15m", "1h", "4h"]
}
if strategy_type in expected_timeframes:
expected = expected_timeframes[strategy_type]
overlap = set(timeframes) & set(expected)
if not overlap:
return ConfigurationError(
category=ErrorCategory.INVALID_PARAMETER,
severity=ErrorSeverity.MEDIUM,
message=f"Timeframes {timeframes} may not be optimal for {strategy_type.value} strategy",
field_path="timeframes",
suggestions=[f"Consider using timeframes: {', '.join(expected)}"],
alternatives=expected,
recovery_steps=[
f"Update timeframes to include: {expected[0]}",
f"Or change strategy type to match timeframes"
]
)
return None
def suggest_alternatives_for_missing_indicators(self, missing_indicators: Set[str]) -> Dict[str, List[str]]:
"""Suggest alternative indicators for missing ones."""
suggestions = {}
for indicator in missing_indicators:
alternatives = []
# Extract base type and period if possible
parts = indicator.split('_')
if len(parts) >= 2:
base_type = parts[0]
# Find similar indicators of the same type
similar_type = [name for name in self.indicator_names
if name.startswith(f"{base_type}_")]
alternatives.extend(similar_type[:3])
# If no similar type, suggest by category
if not similar_type:
if base_type in ["sma", "ema"]:
alternatives = ["sma_20", "ema_12", "ema_26"]
elif base_type == "rsi":
alternatives = ["rsi_14", "rsi_7", "rsi_21"]
elif base_type == "macd":
alternatives = ["macd_12_26_9", "macd_8_17_6"]
elif base_type == "bb":
alternatives = ["bb_20_20", "bb_20_15"]
if alternatives:
suggestions[indicator] = alternatives
return suggestions
def generate_recovery_configuration(self, config: StrategyChartConfig, error_report: ErrorReport) -> Tuple[Optional[StrategyChartConfig], List[str]]:
"""Generate a recovery configuration with working alternatives."""
if not error_report.missing_indicators:
return config, []
recovery_notes = []
recovery_config = StrategyChartConfig(
strategy_name=f"{config.strategy_name} (Recovery)",
strategy_type=config.strategy_type,
description=f"{config.description} (Auto-recovered from missing indicators)",
timeframes=config.timeframes,
layout=config.layout,
main_chart_height=config.main_chart_height,
overlay_indicators=[],
subplot_configs=[],
chart_style=config.chart_style
)
# Replace missing overlay indicators
for indicator in config.overlay_indicators:
if indicator in error_report.missing_indicators:
# Find replacement
alternatives = self.suggest_alternatives_for_missing_indicators({indicator})
if indicator in alternatives and alternatives[indicator]:
replacement = alternatives[indicator][0]
recovery_config.overlay_indicators.append(replacement)
recovery_notes.append(f"Replaced '{indicator}' with '{replacement}'")
else:
recovery_notes.append(f"Could not find replacement for '{indicator}' - removed")
else:
recovery_config.overlay_indicators.append(indicator)
# Handle subplot configurations
for subplot in config.subplot_configs:
recovered_subplot = subplot.__class__(
subplot_type=subplot.subplot_type,
height_ratio=subplot.height_ratio,
indicators=[],
title=subplot.title,
y_axis_label=subplot.y_axis_label,
show_grid=subplot.show_grid,
show_legend=subplot.show_legend
)
for indicator in subplot.indicators:
if indicator in error_report.missing_indicators:
alternatives = self.suggest_alternatives_for_missing_indicators({indicator})
if indicator in alternatives and alternatives[indicator]:
replacement = alternatives[indicator][0]
recovered_subplot.indicators.append(replacement)
recovery_notes.append(f"In subplot: Replaced '{indicator}' with '{replacement}'")
else:
recovery_notes.append(f"In subplot: Could not find replacement for '{indicator}' - removed")
else:
recovered_subplot.indicators.append(indicator)
# Only add subplot if it has indicators
if recovered_subplot.indicators:
recovery_config.subplot_configs.append(recovered_subplot)
else:
recovery_notes.append(f"Removed empty subplot: {subplot.subplot_type.value}")
# Add fallback indicators if configuration is empty
if not recovery_config.overlay_indicators and not any(
subplot.indicators for subplot in recovery_config.subplot_configs
):
recovery_config.overlay_indicators = ["ema_12", "ema_26", "sma_20"]
recovery_notes.append("Added basic trend indicators: EMA 12, EMA 26, SMA 20")
# Add basic RSI subplot
from .strategy_charts import SubplotConfig, SubplotType
recovery_config.subplot_configs.append(
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2,
indicators=["rsi_14"],
title="RSI"
)
)
recovery_notes.append("Added basic RSI subplot")
return recovery_config, recovery_notes
def validate_configuration_strict(config: StrategyChartConfig) -> ErrorReport:
"""
Strict validation that fails on any missing dependencies.
Args:
config: Strategy configuration to validate
Returns:
ErrorReport with detailed error information
"""
handler = ConfigurationErrorHandler()
return handler.validate_strategy_configuration(config)
def validate_strategy_name(strategy_name: str) -> Optional[ConfigurationError]:
"""
Validate that a strategy name exists.
Args:
strategy_name: Name of the strategy to validate
Returns:
ConfigurationError if strategy not found, None otherwise
"""
handler = ConfigurationErrorHandler()
return handler.validate_strategy_exists(strategy_name)
def get_indicator_suggestions(partial_name: str, limit: int = 5) -> List[str]:
"""
Get indicator suggestions based on partial name.
Args:
partial_name: Partial indicator name
limit: Maximum number of suggestions
Returns:
List of suggested indicator names
"""
handler = ConfigurationErrorHandler()
# Fuzzy match against available indicators
matches = difflib.get_close_matches(
partial_name,
handler.indicator_names,
n=limit,
cutoff=0.3
)
# If no fuzzy matches, try substring matching
if not matches:
substring_matches = [
name for name in handler.indicator_names
if partial_name.lower() in name.lower()
]
matches = substring_matches[:limit]
return matches
def get_strategy_suggestions(partial_name: str, limit: int = 5) -> List[str]:
"""
Get strategy suggestions based on partial name.
Args:
partial_name: Partial strategy name
limit: Maximum number of suggestions
Returns:
List of suggested strategy names
"""
handler = ConfigurationErrorHandler()
matches = difflib.get_close_matches(
partial_name,
handler.strategy_names,
n=limit,
cutoff=0.3
)
if not matches:
substring_matches = [
name for name in handler.strategy_names
if partial_name.lower() in name.lower()
]
matches = substring_matches[:limit]
return matches
def check_configuration_health(config: StrategyChartConfig) -> Dict[str, Any]:
"""
Perform a comprehensive health check on a configuration.
Args:
config: Strategy configuration to check
Returns:
Dictionary with health check results
"""
handler = ConfigurationErrorHandler()
error_report = handler.validate_strategy_configuration(config)
# Count indicators by category
indicator_counts = {}
all_indicators = config.overlay_indicators + [
indicator for subplot in config.subplot_configs
for indicator in subplot.indicators
]
for indicator in all_indicators:
if indicator in handler.available_indicators:
category = handler.available_indicators[indicator].category.value
indicator_counts[category] = indicator_counts.get(category, 0) + 1
return {
"is_healthy": error_report.is_usable and len(error_report.errors) == 0,
"error_report": error_report,
"total_indicators": len(all_indicators),
"missing_indicators": len(error_report.missing_indicators),
"indicator_by_category": indicator_counts,
"has_trend_indicators": "trend" in indicator_counts,
"has_momentum_indicators": "momentum" in indicator_counts,
"recommendations": _generate_health_recommendations(config, error_report, indicator_counts)
}
def _generate_health_recommendations(
config: StrategyChartConfig,
error_report: ErrorReport,
indicator_counts: Dict[str, int]
) -> List[str]:
"""Generate health recommendations for a configuration."""
recommendations = []
# Missing indicators
if error_report.missing_indicators:
recommendations.append(f"Fix {len(error_report.missing_indicators)} missing indicators")
# Category balance
if not indicator_counts.get("trend", 0):
recommendations.append("Add trend indicators (SMA, EMA) for direction analysis")
if not indicator_counts.get("momentum", 0):
recommendations.append("Add momentum indicators (RSI, MACD) for entry timing")
# Strategy-specific recommendations
if config.strategy_type == TradingStrategy.SCALPING:
if "1m" not in config.timeframes and "5m" not in config.timeframes:
recommendations.append("Add short timeframes (1m, 5m) for scalping strategy")
elif config.strategy_type == TradingStrategy.SWING_TRADING:
if not any(tf in config.timeframes for tf in ["4h", "1d"]):
recommendations.append("Add longer timeframes (4h, 1d) for swing trading")
# Performance recommendations
total_indicators = sum(indicator_counts.values())
if total_indicators > 10:
recommendations.append("Consider reducing indicators for better performance")
elif total_indicators < 3:
recommendations.append("Add more indicators for comprehensive analysis")
return recommendations

View File

@@ -0,0 +1,651 @@
"""
Example Strategy Configurations
This module provides real-world trading strategy configurations that demonstrate
how to combine indicators for specific trading approaches like EMA crossover,
momentum trading, and other popular strategies.
"""
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime
from .strategy_charts import (
StrategyChartConfig,
SubplotConfig,
ChartStyle,
ChartLayout,
SubplotType
)
from .defaults import TradingStrategy
from utils.logger import get_logger
# Initialize logger
logger = get_logger("example_strategies")
@dataclass
class StrategyExample:
"""Represents an example trading strategy with metadata."""
config: StrategyChartConfig
description: str
author: str = "TCPDashboard"
difficulty: str = "Beginner" # Beginner, Intermediate, Advanced
expected_return: Optional[str] = None
risk_level: str = "Medium" # Low, Medium, High
market_conditions: List[str] = None # Trending, Sideways, Volatile
notes: List[str] = None
references: List[str] = None
def __post_init__(self):
if self.market_conditions is None:
self.market_conditions = ["Trending"]
if self.notes is None:
self.notes = []
if self.references is None:
self.references = []
def create_ema_crossover_strategy() -> StrategyExample:
"""
Create EMA crossover strategy configuration.
Classic trend-following strategy using fast and slow EMA crossovers
for entry and exit signals.
"""
config = StrategyChartConfig(
strategy_name="EMA Crossover Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Trend-following strategy using EMA crossovers for entry/exit signals",
timeframes=["15m", "1h", "4h"],
layout=ChartLayout.MAIN_WITH_SUBPLOTS,
main_chart_height=0.65,
overlay_indicators=[
"ema_12", # Fast EMA
"ema_26", # Slow EMA
"ema_50", # Trend filter
"bb_20_20" # Bollinger Bands for volatility context
],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.15,
indicators=["rsi_14"],
title="RSI Momentum",
y_axis_label="RSI",
show_grid=True
),
SubplotConfig(
subplot_type=SubplotType.MACD,
height_ratio=0.2,
indicators=["macd_12_26_9"],
title="MACD Confirmation",
y_axis_label="MACD",
show_grid=True
)
],
chart_style=ChartStyle(
theme="plotly_white",
font_size=12,
candlestick_up_color="#26a69a",
candlestick_down_color="#ef5350",
show_volume=True,
show_grid=True
),
tags=["trend-following", "ema-crossover", "day-trading", "intermediate"]
)
return StrategyExample(
config=config,
description="""
EMA Crossover Strategy uses the crossing of fast (12-period) and slow (26-period)
exponential moving averages to generate buy and sell signals. The 50-period EMA
acts as a trend filter to avoid false signals in sideways markets.
Entry Rules:
- Buy when fast EMA crosses above slow EMA and price is above 50 EMA
- RSI should be above 30 (not oversold)
- MACD line should be above signal line
Exit Rules:
- Sell when fast EMA crosses below slow EMA
- Or when RSI reaches overbought levels (>70)
- Stop loss: 2% below entry or below recent swing low
""",
author="TCPDashboard Team",
difficulty="Intermediate",
expected_return="8-15% monthly (in trending markets)",
risk_level="Medium",
market_conditions=["Trending", "Breakout"],
notes=[
"Works best in trending markets",
"Can produce whipsaws in sideways markets",
"Use 50 EMA as trend filter to reduce false signals",
"Consider volume confirmation for stronger signals",
"Best timeframes: 15m, 1h, 4h for day trading"
],
references=[
"Moving Average Convergence Divergence - Gerald Appel",
"Technical Analysis of Financial Markets - John Murphy"
]
)
def create_momentum_breakout_strategy() -> StrategyExample:
"""
Create momentum breakout strategy configuration.
Strategy focused on capturing momentum moves using RSI,
MACD, and volume confirmation.
"""
config = StrategyChartConfig(
strategy_name="Momentum Breakout Strategy",
strategy_type=TradingStrategy.MOMENTUM,
description="Momentum strategy capturing strong directional moves with volume confirmation",
timeframes=["5m", "15m", "1h"],
layout=ChartLayout.MAIN_WITH_SUBPLOTS,
main_chart_height=0.6,
overlay_indicators=[
"ema_8", # Fast trend
"ema_21", # Medium trend
"bb_20_25" # Volatility bands (wider for breakouts)
],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.15,
indicators=["rsi_7", "rsi_14"], # Fast and standard RSI
title="RSI Momentum (7 & 14)",
y_axis_label="RSI",
show_grid=True
),
SubplotConfig(
subplot_type=SubplotType.MACD,
height_ratio=0.15,
indicators=["macd_8_17_6"], # Faster MACD for momentum
title="MACD Fast",
y_axis_label="MACD",
show_grid=True
),
SubplotConfig(
subplot_type=SubplotType.VOLUME,
height_ratio=0.1,
indicators=[],
title="Volume Confirmation",
y_axis_label="Volume",
show_grid=True
)
],
chart_style=ChartStyle(
theme="plotly_white",
font_size=11,
candlestick_up_color="#00d4aa",
candlestick_down_color="#fe6a85",
show_volume=True,
show_grid=True
),
tags=["momentum", "breakout", "volume", "short-term"]
)
return StrategyExample(
config=config,
description="""
Momentum Breakout Strategy captures strong directional moves by identifying
momentum acceleration with volume confirmation. Uses faster indicators
to catch moves early while avoiding false breakouts.
Entry Rules:
- Price breaks above/below Bollinger Bands with strong volume
- RSI-7 > 70 for bullish momentum (< 30 for bearish)
- MACD line crosses above signal line with rising histogram
- Volume should be 1.5x average volume
- EMA-8 above EMA-21 for trend confirmation
Exit Rules:
- RSI-7 reaches extreme levels (>80 or <20)
- MACD histogram starts declining
- Volume drops significantly
- Price returns inside Bollinger Bands
- Stop loss: 1.5% or below previous swing point
""",
author="TCPDashboard Team",
difficulty="Advanced",
expected_return="15-25% monthly (high volatility)",
risk_level="High",
market_conditions=["Volatile", "Breakout", "High Volume"],
notes=[
"Requires quick execution and tight risk management",
"Best during high volatility periods",
"Monitor volume closely for confirmation",
"Use smaller position sizes due to higher risk",
"Consider market hours for better volume",
"Avoid during low liquidity periods"
],
references=[
"Momentum Stock Selection - Richard Driehaus",
"High Probability Trading - Marcel Link"
]
)
def create_mean_reversion_strategy() -> StrategyExample:
"""
Create mean reversion strategy configuration.
Counter-trend strategy using oversold/overbought conditions
and support/resistance levels.
"""
config = StrategyChartConfig(
strategy_name="Mean Reversion Strategy",
strategy_type=TradingStrategy.MEAN_REVERSION,
description="Counter-trend strategy exploiting oversold/overbought conditions",
timeframes=["15m", "1h", "4h"],
layout=ChartLayout.MAIN_WITH_SUBPLOTS,
main_chart_height=0.7,
overlay_indicators=[
"sma_20", # Mean reversion line
"sma_50", # Trend context
"bb_20_20", # Standard Bollinger Bands
"bb_20_15" # Tighter bands for entry signals
],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2,
indicators=["rsi_14", "rsi_21"], # Standard and slower RSI
title="RSI Mean Reversion",
y_axis_label="RSI",
show_grid=True
),
SubplotConfig(
subplot_type=SubplotType.VOLUME,
height_ratio=0.1,
indicators=[],
title="Volume Analysis",
y_axis_label="Volume",
show_grid=True
)
],
chart_style=ChartStyle(
theme="plotly_white",
font_size=12,
candlestick_up_color="#4caf50",
candlestick_down_color="#f44336",
show_volume=True,
show_grid=True
),
tags=["mean-reversion", "counter-trend", "oversold-overbought"]
)
return StrategyExample(
config=config,
description="""
Mean Reversion Strategy exploits the tendency of prices to return to their
average after extreme moves. Uses multiple RSI periods and Bollinger Bands
to identify oversold/overbought conditions with high probability reversals.
Entry Rules (Long):
- Price touches or breaks lower Bollinger Band (20,2.0)
- RSI-14 < 30 and RSI-21 < 35 (oversold conditions)
- Price shows bullish divergence with RSI
- Volume spike on the reversal candle
- Entry on first green candle after oversold signal
Entry Rules (Short):
- Price touches or breaks upper Bollinger Band
- RSI-14 > 70 and RSI-21 > 65 (overbought conditions)
- Bearish divergence with RSI
- High volume on reversal
Exit Rules:
- Price returns to SMA-20 (mean)
- RSI reaches neutral zone (45-55)
- Stop loss: Beyond recent swing high/low
- Take profit: Opposite Bollinger Band
""",
author="TCPDashboard Team",
difficulty="Intermediate",
expected_return="10-18% monthly (ranging markets)",
risk_level="Medium",
market_conditions=["Sideways", "Ranging", "Oversold/Overbought"],
notes=[
"Works best in ranging/sideways markets",
"Avoid during strong trending periods",
"Look for divergences for higher probability setups",
"Use proper position sizing due to counter-trend nature",
"Consider market structure and support/resistance levels",
"Best during regular market hours for better volume"
],
references=[
"Mean Reversion Trading Systems - Howard Bandy",
"Contrarian Investment Strategies - David Dreman"
]
)
def create_scalping_strategy() -> StrategyExample:
"""
Create scalping strategy configuration.
High-frequency strategy for quick profits using
very fast indicators and tight risk management.
"""
config = StrategyChartConfig(
strategy_name="Scalping Strategy",
strategy_type=TradingStrategy.SCALPING,
description="High-frequency scalping strategy for quick profits",
timeframes=["1m", "5m"],
layout=ChartLayout.MAIN_WITH_SUBPLOTS,
main_chart_height=0.55,
overlay_indicators=[
"ema_5", # Very fast EMA
"ema_12", # Fast EMA
"ema_21", # Reference EMA
"bb_10_15" # Tight Bollinger Bands
],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2,
indicators=["rsi_7"], # Very fast RSI
title="RSI Fast (7)",
y_axis_label="RSI",
show_grid=True
),
SubplotConfig(
subplot_type=SubplotType.MACD,
height_ratio=0.15,
indicators=["macd_5_13_4"], # Very fast MACD
title="MACD Ultra Fast",
y_axis_label="MACD",
show_grid=True
),
SubplotConfig(
subplot_type=SubplotType.VOLUME,
height_ratio=0.1,
indicators=[],
title="Volume",
y_axis_label="Volume",
show_grid=True
)
],
chart_style=ChartStyle(
theme="plotly_white",
font_size=10,
candlestick_up_color="#00e676",
candlestick_down_color="#ff1744",
show_volume=True,
show_grid=True
),
tags=["scalping", "high-frequency", "fast", "1-minute"]
)
return StrategyExample(
config=config,
description="""
Scalping Strategy designed for rapid-fire trading with small profits
and very tight risk management. Uses ultra-fast indicators to capture
small price movements multiple times per session.
Entry Rules:
- EMA-5 crosses above EMA-12 (bullish scalp)
- Price is above EMA-21 for trend alignment
- RSI-7 between 40-60 (avoid extremes)
- MACD line above signal line
- Strong volume confirmation
- Enter on pullback to EMA-5 after crossover
Exit Rules:
- Target: 5-10 pips profit (0.1-0.2% for stocks)
- Stop loss: 3-5 pips (0.05-0.1%)
- Exit if RSI reaches extreme (>75 or <25)
- Exit if EMA-5 crosses back below EMA-12
- Maximum hold time: 5-15 minutes
""",
author="TCPDashboard Team",
difficulty="Advanced",
expected_return="Small profits, high frequency (2-5% daily)",
risk_level="High",
market_conditions=["High Liquidity", "Volatile", "Active Sessions"],
notes=[
"Requires very fast execution and low latency",
"Best during active market hours (overlapping sessions)",
"Use tight spreads and low commission brokers",
"Requires constant monitoring and quick decisions",
"Risk management is critical - small stops",
"Not suitable for beginners",
"Consider transaction costs carefully",
"Practice on demo account extensively first"
],
references=[
"A Complete Guide to Volume Price Analysis - Anna Coulling",
"The Complete Guide to Day Trading - Markus Heitkoetter"
]
)
def create_swing_trading_strategy() -> StrategyExample:
"""
Create swing trading strategy configuration.
Medium-term strategy capturing price swings over
several days to weeks using trend and momentum indicators.
"""
config = StrategyChartConfig(
strategy_name="Swing Trading Strategy",
strategy_type=TradingStrategy.SWING_TRADING,
description="Medium-term strategy capturing multi-day price swings",
timeframes=["4h", "1d"],
layout=ChartLayout.MAIN_WITH_SUBPLOTS,
main_chart_height=0.7,
overlay_indicators=[
"sma_20", # Short-term trend
"sma_50", # Medium-term trend
"ema_21", # Dynamic support/resistance
"bb_20_20" # Volatility bands
],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.15,
indicators=["rsi_14"],
title="RSI (14)",
y_axis_label="RSI",
show_grid=True
),
SubplotConfig(
subplot_type=SubplotType.MACD,
height_ratio=0.15,
indicators=["macd_12_26_9"],
title="MACD Standard",
y_axis_label="MACD",
show_grid=True
)
],
chart_style=ChartStyle(
theme="plotly_white",
font_size=13,
candlestick_up_color="#388e3c",
candlestick_down_color="#d32f2f",
show_volume=True,
show_grid=True
),
tags=["swing-trading", "medium-term", "trend-following"]
)
return StrategyExample(
config=config,
description="""
Swing Trading Strategy captures price swings over several days to weeks
by identifying trend changes and momentum shifts. Suitable for traders
who cannot monitor markets constantly but want to catch significant moves.
Entry Rules (Long):
- Price above SMA-50 (uptrend confirmation)
- SMA-20 crosses above SMA-50 or price bounces off SMA-20
- RSI pullback to 35-45 then rises above 50
- MACD line crosses above signal line
- Price finds support at EMA-21 or lower Bollinger Band
Entry Rules (Short):
- Price below SMA-50 (downtrend confirmation)
- SMA-20 crosses below SMA-50 or price rejected at SMA-20
- RSI pullback to 55-65 then falls below 50
- MACD line crosses below signal line
Exit Rules:
- Price reaches opposite Bollinger Band
- RSI reaches overbought/oversold extremes (>70/<30)
- MACD shows divergence or histogram weakens
- Stop loss: Below/above recent swing point (2-4%)
- Take profit: 1:2 or 1:3 risk-reward ratio
""",
author="TCPDashboard Team",
difficulty="Beginner",
expected_return="15-25% annually",
risk_level="Medium",
market_conditions=["Trending", "Swing Markets"],
notes=[
"Suitable for part-time traders",
"Requires patience for proper setups",
"Good for building trading discipline",
"Consider fundamental analysis for direction",
"Use proper position sizing (1-2% risk per trade)",
"Best on daily timeframes for trend following",
"Monitor weekly charts for overall direction"
],
references=[
"Swing Trading For Beginners - Matthew Maybury",
"The Master Swing Trader - Alan Farley"
]
)
def get_all_example_strategies() -> Dict[str, StrategyExample]:
"""
Get all available example strategies.
Returns:
Dictionary mapping strategy names to StrategyExample objects
"""
return {
"ema_crossover": create_ema_crossover_strategy(),
"momentum_breakout": create_momentum_breakout_strategy(),
"mean_reversion": create_mean_reversion_strategy(),
"scalping": create_scalping_strategy(),
"swing_trading": create_swing_trading_strategy()
}
def get_example_strategy(strategy_name: str) -> Optional[StrategyExample]:
"""
Get a specific example strategy by name.
Args:
strategy_name: Name of the strategy to retrieve
Returns:
StrategyExample object or None if not found
"""
strategies = get_all_example_strategies()
return strategies.get(strategy_name)
def get_strategies_by_difficulty(difficulty: str) -> List[StrategyExample]:
"""
Get example strategies filtered by difficulty level.
Args:
difficulty: Difficulty level ("Beginner", "Intermediate", "Advanced")
Returns:
List of StrategyExample objects matching the difficulty
"""
strategies = get_all_example_strategies()
return [strategy for strategy in strategies.values()
if strategy.difficulty == difficulty]
def get_strategies_by_risk_level(risk_level: str) -> List[StrategyExample]:
"""
Get example strategies filtered by risk level.
Args:
risk_level: Risk level ("Low", "Medium", "High")
Returns:
List of StrategyExample objects matching the risk level
"""
strategies = get_all_example_strategies()
return [strategy for strategy in strategies.values()
if strategy.risk_level == risk_level]
def get_strategies_by_market_condition(condition: str) -> List[StrategyExample]:
"""
Get example strategies suitable for specific market conditions.
Args:
condition: Market condition ("Trending", "Sideways", "Volatile", etc.)
Returns:
List of StrategyExample objects suitable for the condition
"""
strategies = get_all_example_strategies()
return [strategy for strategy in strategies.values()
if condition in strategy.market_conditions]
def get_strategy_summary() -> Dict[str, Dict[str, str]]:
"""
Get a summary of all example strategies with key information.
Returns:
Dictionary with strategy summaries
"""
strategies = get_all_example_strategies()
summary = {}
for name, strategy in strategies.items():
summary[name] = {
"name": strategy.config.strategy_name,
"type": strategy.config.strategy_type.value,
"difficulty": strategy.difficulty,
"risk_level": strategy.risk_level,
"timeframes": ", ".join(strategy.config.timeframes),
"market_conditions": ", ".join(strategy.market_conditions),
"expected_return": strategy.expected_return or "N/A"
}
return summary
def export_example_strategies_to_json() -> str:
"""
Export all example strategies to JSON format.
Returns:
JSON string containing all example strategies
"""
import json
from .strategy_charts import export_strategy_config_to_json
strategies = get_all_example_strategies()
export_data = {}
for name, strategy in strategies.items():
export_data[name] = {
"config": json.loads(export_strategy_config_to_json(strategy.config)),
"metadata": {
"description": strategy.description,
"author": strategy.author,
"difficulty": strategy.difficulty,
"expected_return": strategy.expected_return,
"risk_level": strategy.risk_level,
"market_conditions": strategy.market_conditions,
"notes": strategy.notes,
"references": strategy.references
}
}
return json.dumps(export_data, indent=2)

View File

@@ -5,10 +5,12 @@ This module defines indicator configurations and provides integration
with the existing data/common/indicators.py technical indicators module.
"""
from typing import Dict, List, Any, Optional, Union
from dataclasses import dataclass
from typing import Dict, List, Any, Optional, Union, Literal
from dataclasses import dataclass, field
from datetime import datetime, timezone
from decimal import Decimal
import json
from enum import Enum
from data.common.indicators import TechnicalIndicators, IndicatorResult, create_default_indicators_config, validate_indicator_config
from data.common.data_types import OHLCVCandle
@@ -18,6 +20,278 @@ from utils.logger import get_logger
logger = get_logger("indicator_defs")
class IndicatorType(str, Enum):
"""Supported indicator types."""
SMA = "sma"
EMA = "ema"
RSI = "rsi"
MACD = "macd"
BOLLINGER_BANDS = "bollinger_bands"
class DisplayType(str, Enum):
"""Chart display types for indicators."""
OVERLAY = "overlay"
SUBPLOT = "subplot"
class LineStyle(str, Enum):
"""Available line styles for chart display."""
SOLID = "solid"
DASH = "dash"
DOT = "dot"
DASHDOT = "dashdot"
class PriceColumn(str, Enum):
"""Available price columns for calculations."""
OPEN = "open"
HIGH = "high"
LOW = "low"
CLOSE = "close"
@dataclass
class IndicatorParameterSchema:
"""
Schema definition for an indicator parameter.
"""
name: str
type: type
required: bool = True
default: Any = None
min_value: Optional[Union[int, float]] = None
max_value: Optional[Union[int, float]] = None
description: str = ""
def validate(self, value: Any) -> tuple[bool, str]:
"""
Validate a parameter value against this schema.
Returns:
Tuple of (is_valid, error_message)
"""
if value is None:
if self.required:
return False, f"Parameter '{self.name}' is required"
return True, ""
# Type validation
if not isinstance(value, self.type):
return False, f"Parameter '{self.name}' must be of type {self.type.__name__}, got {type(value).__name__}"
# Range validation for numeric types
if isinstance(value, (int, float)):
if self.min_value is not None and value < self.min_value:
return False, f"Parameter '{self.name}' must be >= {self.min_value}, got {value}"
if self.max_value is not None and value > self.max_value:
return False, f"Parameter '{self.name}' must be <= {self.max_value}, got {value}"
return True, ""
@dataclass
class IndicatorSchema:
"""
Complete schema definition for an indicator type.
"""
indicator_type: IndicatorType
display_type: DisplayType
required_parameters: List[IndicatorParameterSchema]
optional_parameters: List[IndicatorParameterSchema] = field(default_factory=list)
min_data_points: int = 1
description: str = ""
def get_parameter_schema(self, param_name: str) -> Optional[IndicatorParameterSchema]:
"""Get schema for a specific parameter."""
for param in self.required_parameters + self.optional_parameters:
if param.name == param_name:
return param
return None
def validate_parameters(self, parameters: Dict[str, Any]) -> tuple[bool, List[str]]:
"""
Validate all parameters against this schema.
Returns:
Tuple of (is_valid, list_of_error_messages)
"""
errors = []
# Check required parameters
for param_schema in self.required_parameters:
value = parameters.get(param_schema.name)
is_valid, error = param_schema.validate(value)
if not is_valid:
errors.append(error)
# Check optional parameters if provided
for param_schema in self.optional_parameters:
if param_schema.name in parameters:
value = parameters[param_schema.name]
is_valid, error = param_schema.validate(value)
if not is_valid:
errors.append(error)
# Check for unknown parameters
known_params = {p.name for p in self.required_parameters + self.optional_parameters}
for param_name in parameters:
if param_name not in known_params:
errors.append(f"Unknown parameter '{param_name}' for {self.indicator_type.value} indicator")
return len(errors) == 0, errors
# Define schema for each indicator type
INDICATOR_SCHEMAS = {
IndicatorType.SMA: IndicatorSchema(
indicator_type=IndicatorType.SMA,
display_type=DisplayType.OVERLAY,
required_parameters=[
IndicatorParameterSchema(
name="period",
type=int,
min_value=1,
max_value=200,
description="Number of periods for moving average"
)
],
optional_parameters=[
IndicatorParameterSchema(
name="price_column",
type=str,
required=False,
default="close",
description="Price column to use (open, high, low, close)"
)
],
min_data_points=1,
description="Simple Moving Average - arithmetic mean of closing prices over a specified period"
),
IndicatorType.EMA: IndicatorSchema(
indicator_type=IndicatorType.EMA,
display_type=DisplayType.OVERLAY,
required_parameters=[
IndicatorParameterSchema(
name="period",
type=int,
min_value=1,
max_value=200,
description="Number of periods for exponential moving average"
)
],
optional_parameters=[
IndicatorParameterSchema(
name="price_column",
type=str,
required=False,
default="close",
description="Price column to use (open, high, low, close)"
)
],
min_data_points=1,
description="Exponential Moving Average - gives more weight to recent prices"
),
IndicatorType.RSI: IndicatorSchema(
indicator_type=IndicatorType.RSI,
display_type=DisplayType.SUBPLOT,
required_parameters=[
IndicatorParameterSchema(
name="period",
type=int,
min_value=2,
max_value=100,
description="Number of periods for RSI calculation"
)
],
optional_parameters=[
IndicatorParameterSchema(
name="price_column",
type=str,
required=False,
default="close",
description="Price column to use (open, high, low, close)"
)
],
min_data_points=2,
description="Relative Strength Index - momentum oscillator measuring speed and magnitude of price changes"
),
IndicatorType.MACD: IndicatorSchema(
indicator_type=IndicatorType.MACD,
display_type=DisplayType.SUBPLOT,
required_parameters=[
IndicatorParameterSchema(
name="fast_period",
type=int,
min_value=1,
max_value=50,
description="Fast EMA period"
),
IndicatorParameterSchema(
name="slow_period",
type=int,
min_value=1,
max_value=100,
description="Slow EMA period"
),
IndicatorParameterSchema(
name="signal_period",
type=int,
min_value=1,
max_value=50,
description="Signal line EMA period"
)
],
optional_parameters=[
IndicatorParameterSchema(
name="price_column",
type=str,
required=False,
default="close",
description="Price column to use (open, high, low, close)"
)
],
min_data_points=3,
description="Moving Average Convergence Divergence - trend-following momentum indicator"
),
IndicatorType.BOLLINGER_BANDS: IndicatorSchema(
indicator_type=IndicatorType.BOLLINGER_BANDS,
display_type=DisplayType.OVERLAY,
required_parameters=[
IndicatorParameterSchema(
name="period",
type=int,
min_value=2,
max_value=100,
description="Number of periods for moving average"
),
IndicatorParameterSchema(
name="std_dev",
type=float,
min_value=0.1,
max_value=5.0,
description="Number of standard deviations for bands"
)
],
optional_parameters=[
IndicatorParameterSchema(
name="price_column",
type=str,
required=False,
default="close",
description="Price column to use (open, high, low, close)"
)
],
min_data_points=2,
description="Bollinger Bands - volatility bands placed above and below a moving average"
)
}
@dataclass
class ChartIndicatorConfig:
"""
@@ -42,6 +316,50 @@ class ChartIndicatorConfig:
config = {'type': self.indicator_type}
config.update(self.parameters)
return config
def validate(self) -> tuple[bool, List[str]]:
"""
Validate this indicator configuration against its schema.
Returns:
Tuple of (is_valid, list_of_error_messages)
"""
errors = []
# Check if indicator type is supported
try:
indicator_type = IndicatorType(self.indicator_type)
except ValueError:
return False, [f"Unsupported indicator type: {self.indicator_type}"]
# Get schema for this indicator type
schema = INDICATOR_SCHEMAS.get(indicator_type)
if not schema:
return False, [f"No schema found for indicator type: {self.indicator_type}"]
# Validate parameters against schema
is_valid, param_errors = schema.validate_parameters(self.parameters)
if not is_valid:
errors.extend(param_errors)
# Validate display properties
if self.display_type not in [DisplayType.OVERLAY.value, DisplayType.SUBPLOT.value]:
errors.append(f"Invalid display_type: {self.display_type}")
if self.line_style not in [style.value for style in LineStyle]:
errors.append(f"Invalid line_style: {self.line_style}")
if not isinstance(self.line_width, int) or self.line_width < 1:
errors.append("line_width must be a positive integer")
if not isinstance(self.opacity, (int, float)) or not (0.0 <= self.opacity <= 1.0):
errors.append("opacity must be a number between 0.0 and 1.0")
if self.display_type == DisplayType.SUBPLOT.value:
if not isinstance(self.subplot_height_ratio, (int, float)) or not (0.1 <= self.subplot_height_ratio <= 1.0):
errors.append("subplot_height_ratio must be a number between 0.1 and 1.0")
return len(errors) == 0, errors
# Built-in indicator definitions with chart display properties
@@ -263,4 +581,206 @@ def get_default_indicator_params(indicator_type: str) -> Dict[str, Any]:
'bollinger_bands': {'period': 20, 'std_dev': 2.0, 'price_column': 'close'}
}
return defaults.get(indicator_type, {})
return defaults.get(indicator_type, {})
def validate_indicator_configuration(config: ChartIndicatorConfig) -> tuple[bool, List[str]]:
"""
Validate an indicator configuration against its schema.
Args:
config: Chart indicator configuration to validate
Returns:
Tuple of (is_valid, list_of_error_messages)
"""
return config.validate()
def create_indicator_config(
name: str,
indicator_type: str,
parameters: Dict[str, Any],
display_type: Optional[str] = None,
color: str = "#007bff",
**display_options
) -> tuple[Optional[ChartIndicatorConfig], List[str]]:
"""
Create and validate a new indicator configuration.
Args:
name: Display name for the indicator
indicator_type: Type of indicator (sma, ema, rsi, etc.)
parameters: Indicator parameters
display_type: Optional override for display type
color: Color for chart display
**display_options: Additional display configuration options
Returns:
Tuple of (config_object_or_None, list_of_error_messages)
"""
errors = []
# Validate indicator type
try:
indicator_enum = IndicatorType(indicator_type)
except ValueError:
return None, [f"Unsupported indicator type: {indicator_type}"]
# Get schema for validation
schema = INDICATOR_SCHEMAS.get(indicator_enum)
if not schema:
return None, [f"No schema found for indicator type: {indicator_type}"]
# Use schema display type if not overridden
if display_type is None:
display_type = schema.display_type.value
# Fill in default parameters
final_parameters = {}
# Add required parameters with defaults if missing
for param_schema in schema.required_parameters:
if param_schema.name in parameters:
final_parameters[param_schema.name] = parameters[param_schema.name]
elif param_schema.default is not None:
final_parameters[param_schema.name] = param_schema.default
# Required parameters without defaults will be caught by validation
# Add optional parameters
for param_schema in schema.optional_parameters:
if param_schema.name in parameters:
final_parameters[param_schema.name] = parameters[param_schema.name]
elif param_schema.default is not None:
final_parameters[param_schema.name] = param_schema.default
# Create configuration
config = ChartIndicatorConfig(
name=name,
indicator_type=indicator_type,
parameters=final_parameters,
display_type=display_type,
color=color,
line_style=display_options.get('line_style', 'solid'),
line_width=display_options.get('line_width', 2),
opacity=display_options.get('opacity', 1.0),
visible=display_options.get('visible', True),
subplot_height_ratio=display_options.get('subplot_height_ratio', 0.3)
)
# Validate the configuration
is_valid, validation_errors = config.validate()
if not is_valid:
return None, validation_errors
return config, []
def get_indicator_schema(indicator_type: str) -> Optional[IndicatorSchema]:
"""
Get the schema for an indicator type.
Args:
indicator_type: Type of indicator
Returns:
IndicatorSchema object or None if not found
"""
try:
indicator_enum = IndicatorType(indicator_type)
return INDICATOR_SCHEMAS.get(indicator_enum)
except ValueError:
return None
def get_available_indicator_types() -> List[str]:
"""
Get list of available indicator types.
Returns:
List of supported indicator type strings
"""
return [indicator_type.value for indicator_type in IndicatorType]
def get_indicator_parameter_info(indicator_type: str) -> Dict[str, Dict[str, Any]]:
"""
Get detailed parameter information for an indicator type.
Args:
indicator_type: Type of indicator
Returns:
Dictionary with parameter information including types, ranges, and descriptions
"""
schema = get_indicator_schema(indicator_type)
if not schema:
return {}
param_info = {}
for param in schema.required_parameters + schema.optional_parameters:
param_info[param.name] = {
'type': param.type.__name__,
'required': param.required,
'default': param.default,
'min_value': param.min_value,
'max_value': param.max_value,
'description': param.description
}
return param_info
def validate_parameters_for_type(indicator_type: str, parameters: Dict[str, Any]) -> tuple[bool, List[str]]:
"""
Validate parameters for a specific indicator type.
Args:
indicator_type: Type of indicator
parameters: Parameters to validate
Returns:
Tuple of (is_valid, list_of_error_messages)
"""
schema = get_indicator_schema(indicator_type)
if not schema:
return False, [f"Unknown indicator type: {indicator_type}"]
return schema.validate_parameters(parameters)
def create_configuration_from_json(json_data: Union[str, Dict[str, Any]]) -> tuple[Optional[ChartIndicatorConfig], List[str]]:
"""
Create indicator 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
required_fields = ['name', 'indicator_type', 'parameters']
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)}"]
return create_indicator_config(
name=data['name'],
indicator_type=data['indicator_type'],
parameters=data['parameters'],
display_type=data.get('display_type'),
color=data.get('color', '#007bff'),
**{k: v for k, v in data.items() if k not in ['name', 'indicator_type', 'parameters', 'display_type', 'color']}
)
except json.JSONDecodeError as e:
return None, [f"Invalid JSON: {e}"]
except Exception as e:
return None, [f"Error creating configuration: {e}"]

View File

@@ -0,0 +1,640 @@
"""
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())

View File

@@ -0,0 +1,676 @@
"""
Configuration Validation and Error Handling System
This module provides comprehensive validation for chart configurations with
detailed error reporting, warnings, and configurable validation rules.
"""
from typing import Dict, List, Any, Optional, Union, Tuple, Set
from dataclasses import dataclass, field
from enum import Enum
import re
from datetime import datetime
from .indicator_defs import ChartIndicatorConfig, INDICATOR_SCHEMAS, validate_indicator_configuration
from .defaults import get_all_default_indicators, TradingStrategy, IndicatorCategory
from .strategy_charts import StrategyChartConfig, SubplotConfig, ChartStyle, ChartLayout, SubplotType
from utils.logger import get_logger
# Initialize logger
logger = get_logger("config_validation")
class ValidationLevel(str, Enum):
"""Validation severity levels."""
ERROR = "error"
WARNING = "warning"
INFO = "info"
DEBUG = "debug"
class ValidationRule(str, Enum):
"""Available validation rules."""
REQUIRED_FIELDS = "required_fields"
HEIGHT_RATIOS = "height_ratios"
INDICATOR_EXISTENCE = "indicator_existence"
TIMEFRAME_FORMAT = "timeframe_format"
CHART_STYLE = "chart_style"
SUBPLOT_CONFIG = "subplot_config"
STRATEGY_CONSISTENCY = "strategy_consistency"
PERFORMANCE_IMPACT = "performance_impact"
INDICATOR_CONFLICTS = "indicator_conflicts"
RESOURCE_USAGE = "resource_usage"
@dataclass
class ValidationIssue:
"""Represents a validation issue."""
level: ValidationLevel
rule: ValidationRule
message: str
field_path: str = ""
suggestion: Optional[str] = None
auto_fix: Optional[str] = None
context: Dict[str, Any] = field(default_factory=dict)
def __str__(self) -> str:
"""String representation of the validation issue."""
prefix = f"[{self.level.value.upper()}]"
location = f" at {self.field_path}" if self.field_path else ""
suggestion = f" Suggestion: {self.suggestion}" if self.suggestion else ""
return f"{prefix} {self.message}{location}.{suggestion}"
@dataclass
class ValidationReport:
"""Comprehensive validation report."""
is_valid: bool
errors: List[ValidationIssue] = field(default_factory=list)
warnings: List[ValidationIssue] = field(default_factory=list)
info: List[ValidationIssue] = field(default_factory=list)
debug: List[ValidationIssue] = field(default_factory=list)
validation_time: Optional[datetime] = None
rules_applied: Set[ValidationRule] = field(default_factory=set)
def add_issue(self, issue: ValidationIssue) -> None:
"""Add a validation issue to the appropriate list."""
if issue.level == ValidationLevel.ERROR:
self.errors.append(issue)
self.is_valid = False
elif issue.level == ValidationLevel.WARNING:
self.warnings.append(issue)
elif issue.level == ValidationLevel.INFO:
self.info.append(issue)
elif issue.level == ValidationLevel.DEBUG:
self.debug.append(issue)
def get_all_issues(self) -> List[ValidationIssue]:
"""Get all validation issues sorted by severity."""
return self.errors + self.warnings + self.info + self.debug
def get_issues_by_rule(self, rule: ValidationRule) -> List[ValidationIssue]:
"""Get all issues for a specific validation rule."""
return [issue for issue in self.get_all_issues() if issue.rule == rule]
def has_errors(self) -> bool:
"""Check if there are any errors."""
return len(self.errors) > 0
def has_warnings(self) -> bool:
"""Check if there are any warnings."""
return len(self.warnings) > 0
def summary(self) -> str:
"""Get a summary of the validation report."""
total_issues = len(self.get_all_issues())
status = "INVALID" if not self.is_valid else "VALID"
return (f"Validation {status}: {len(self.errors)} errors, "
f"{len(self.warnings)} warnings, {total_issues} total issues")
class ConfigurationValidator:
"""Comprehensive configuration validator."""
def __init__(self, enabled_rules: Optional[Set[ValidationRule]] = None):
"""
Initialize validator with optional rule filtering.
Args:
enabled_rules: Set of rules to apply. If None, applies all rules.
"""
self.enabled_rules = enabled_rules or set(ValidationRule)
self.timeframe_pattern = re.compile(r'^(\d+)(m|h|d|w)$')
self.color_pattern = re.compile(r'^#[0-9a-fA-F]{6}$')
# Load indicator information for validation
self._load_indicator_info()
def _load_indicator_info(self) -> None:
"""Load indicator information for validation."""
try:
self.available_indicators = get_all_default_indicators()
self.indicator_schemas = INDICATOR_SCHEMAS
except Exception as e:
logger.warning(f"Failed to load indicator information: {e}")
self.available_indicators = {}
self.indicator_schemas = {}
def validate_strategy_config(self, config: StrategyChartConfig) -> ValidationReport:
"""
Perform comprehensive validation of a strategy configuration.
Args:
config: Strategy configuration to validate
Returns:
Detailed validation report
"""
report = ValidationReport(is_valid=True, validation_time=datetime.now())
# Apply validation rules
if ValidationRule.REQUIRED_FIELDS in self.enabled_rules:
self._validate_required_fields(config, report)
if ValidationRule.HEIGHT_RATIOS in self.enabled_rules:
self._validate_height_ratios(config, report)
if ValidationRule.INDICATOR_EXISTENCE in self.enabled_rules:
self._validate_indicator_existence(config, report)
if ValidationRule.TIMEFRAME_FORMAT in self.enabled_rules:
self._validate_timeframe_format(config, report)
if ValidationRule.CHART_STYLE in self.enabled_rules:
self._validate_chart_style(config, report)
if ValidationRule.SUBPLOT_CONFIG in self.enabled_rules:
self._validate_subplot_configs(config, report)
if ValidationRule.STRATEGY_CONSISTENCY in self.enabled_rules:
self._validate_strategy_consistency(config, report)
if ValidationRule.PERFORMANCE_IMPACT in self.enabled_rules:
self._validate_performance_impact(config, report)
if ValidationRule.INDICATOR_CONFLICTS in self.enabled_rules:
self._validate_indicator_conflicts(config, report)
if ValidationRule.RESOURCE_USAGE in self.enabled_rules:
self._validate_resource_usage(config, report)
# Update applied rules
report.rules_applied = self.enabled_rules
return report
def _validate_required_fields(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate required fields."""
# Strategy name
if not config.strategy_name or not config.strategy_name.strip():
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.REQUIRED_FIELDS,
message="Strategy name is required and cannot be empty",
field_path="strategy_name",
suggestion="Provide a descriptive name for your strategy"
))
elif len(config.strategy_name.strip()) < 3:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.REQUIRED_FIELDS,
message="Strategy name is very short",
field_path="strategy_name",
suggestion="Use a more descriptive name (at least 3 characters)"
))
# Strategy type
if not isinstance(config.strategy_type, TradingStrategy):
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.REQUIRED_FIELDS,
message="Invalid strategy type",
field_path="strategy_type",
suggestion=f"Must be one of: {[s.value for s in TradingStrategy]}"
))
# Description
if not config.description or not config.description.strip():
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.REQUIRED_FIELDS,
message="Strategy description is missing",
field_path="description",
suggestion="Add a description to help users understand the strategy"
))
# Timeframes
if not config.timeframes:
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.REQUIRED_FIELDS,
message="At least one timeframe must be specified",
field_path="timeframes",
suggestion="Add recommended timeframes for this strategy"
))
def _validate_height_ratios(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate chart height ratios."""
# Main chart height
if config.main_chart_height <= 0 or config.main_chart_height > 1.0:
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.HEIGHT_RATIOS,
message=f"Main chart height ({config.main_chart_height}) must be between 0 and 1.0",
field_path="main_chart_height",
suggestion="Set a value between 0.1 and 0.9",
auto_fix="0.7"
))
elif config.main_chart_height < 0.3:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.HEIGHT_RATIOS,
message=f"Main chart height ({config.main_chart_height}) is very small",
field_path="main_chart_height",
suggestion="Consider using at least 0.3 for better visibility"
))
# Subplot heights
total_subplot_height = sum(subplot.height_ratio for subplot in config.subplot_configs)
total_height = config.main_chart_height + total_subplot_height
if total_height > 1.0:
excess = total_height - 1.0
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.HEIGHT_RATIOS,
message=f"Total chart height ({total_height:.3f}) exceeds 1.0 by {excess:.3f}",
field_path="height_ratios",
suggestion="Reduce main chart height or subplot heights",
context={"total_height": total_height, "excess": excess}
))
elif total_height < 0.8:
unused = 1.0 - total_height
report.add_issue(ValidationIssue(
level=ValidationLevel.INFO,
rule=ValidationRule.HEIGHT_RATIOS,
message=f"Chart height ({total_height:.3f}) leaves {unused:.3f} unused space",
field_path="height_ratios",
suggestion="Consider increasing chart or subplot heights for better space utilization"
))
# Individual subplot heights
for i, subplot in enumerate(config.subplot_configs):
if subplot.height_ratio <= 0 or subplot.height_ratio > 1.0:
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.HEIGHT_RATIOS,
message=f"Subplot {i} height ratio ({subplot.height_ratio}) must be between 0 and 1.0",
field_path=f"subplot_configs[{i}].height_ratio",
suggestion="Set a value between 0.1 and 0.5"
))
elif subplot.height_ratio < 0.1:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.HEIGHT_RATIOS,
message=f"Subplot {i} height ratio ({subplot.height_ratio}) is very small",
field_path=f"subplot_configs[{i}].height_ratio",
suggestion="Consider using at least 0.1 for better readability"
))
def _validate_indicator_existence(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate that indicators exist in the available indicators."""
# Check overlay indicators
for indicator in config.overlay_indicators:
if indicator not in self.available_indicators:
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.INDICATOR_EXISTENCE,
message=f"Overlay indicator '{indicator}' not found",
field_path=f"overlay_indicators.{indicator}",
suggestion="Check indicator name or add it to defaults",
context={"available_count": len(self.available_indicators)}
))
# Check subplot indicators
for i, subplot in enumerate(config.subplot_configs):
for indicator in subplot.indicators:
if indicator not in self.available_indicators:
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.INDICATOR_EXISTENCE,
message=f"Subplot indicator '{indicator}' not found",
field_path=f"subplot_configs[{i}].indicators.{indicator}",
suggestion="Check indicator name or add it to defaults"
))
def _validate_timeframe_format(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate timeframe format."""
valid_timeframes = ['1m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w', '1M']
for timeframe in config.timeframes:
if timeframe not in valid_timeframes:
if self.timeframe_pattern.match(timeframe):
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.TIMEFRAME_FORMAT,
message=f"Timeframe '{timeframe}' format is valid but not in common list",
field_path=f"timeframes.{timeframe}",
suggestion=f"Consider using standard timeframes: {valid_timeframes[:8]}"
))
else:
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.TIMEFRAME_FORMAT,
message=f"Invalid timeframe format '{timeframe}'",
field_path=f"timeframes.{timeframe}",
suggestion="Use format like '1m', '5m', '1h', '4h', '1d', '1w'",
context={"valid_timeframes": valid_timeframes}
))
def _validate_chart_style(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate chart style configuration."""
style = config.chart_style
# Validate colors
color_fields = [
('background_color', style.background_color),
('grid_color', style.grid_color),
('text_color', style.text_color),
('candlestick_up_color', style.candlestick_up_color),
('candlestick_down_color', style.candlestick_down_color),
('volume_color', style.volume_color)
]
for field_name, color_value in color_fields:
if color_value and not self.color_pattern.match(color_value):
report.add_issue(ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.CHART_STYLE,
message=f"Invalid color format for {field_name}: '{color_value}'",
field_path=f"chart_style.{field_name}",
suggestion="Use hex color format like '#ffffff' or '#123456'"
))
# Validate font size
if style.font_size < 6 or style.font_size > 24:
level = ValidationLevel.ERROR if style.font_size < 1 or style.font_size > 48 else ValidationLevel.WARNING
report.add_issue(ValidationIssue(
level=level,
rule=ValidationRule.CHART_STYLE,
message=f"Font size {style.font_size} may cause readability issues",
field_path="chart_style.font_size",
suggestion="Use font size between 8 and 18 for optimal readability"
))
# Validate theme
valid_themes = ['plotly', 'plotly_white', 'plotly_dark', 'ggplot2', 'seaborn', 'simple_white']
if style.theme not in valid_themes:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.CHART_STYLE,
message=f"Theme '{style.theme}' may not be supported",
field_path="chart_style.theme",
suggestion=f"Consider using: {valid_themes[:3]}",
context={"valid_themes": valid_themes}
))
def _validate_subplot_configs(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate subplot configurations."""
subplot_types = [subplot.subplot_type for subplot in config.subplot_configs]
# Check for duplicate subplot types
seen_types = set()
for i, subplot in enumerate(config.subplot_configs):
if subplot.subplot_type in seen_types:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.SUBPLOT_CONFIG,
message=f"Duplicate subplot type '{subplot.subplot_type.value}' at position {i}",
field_path=f"subplot_configs[{i}].subplot_type",
suggestion="Consider using different subplot types or combining indicators"
))
seen_types.add(subplot.subplot_type)
# Validate subplot-specific indicators
if subplot.subplot_type == SubplotType.RSI and subplot.indicators:
for indicator in subplot.indicators:
if indicator in self.available_indicators:
indicator_config = self.available_indicators[indicator].config
indicator_type = indicator_config.indicator_type
# Handle both string and enum types
if hasattr(indicator_type, 'value'):
indicator_type_value = indicator_type.value
else:
indicator_type_value = str(indicator_type)
if indicator_type_value != 'rsi':
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.SUBPLOT_CONFIG,
message=f"Non-RSI indicator '{indicator}' in RSI subplot",
field_path=f"subplot_configs[{i}].indicators.{indicator}",
suggestion="Use RSI indicators in RSI subplots for consistency"
))
elif subplot.subplot_type == SubplotType.MACD and subplot.indicators:
for indicator in subplot.indicators:
if indicator in self.available_indicators:
indicator_config = self.available_indicators[indicator].config
indicator_type = indicator_config.indicator_type
# Handle both string and enum types
if hasattr(indicator_type, 'value'):
indicator_type_value = indicator_type.value
else:
indicator_type_value = str(indicator_type)
if indicator_type_value != 'macd':
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.SUBPLOT_CONFIG,
message=f"Non-MACD indicator '{indicator}' in MACD subplot",
field_path=f"subplot_configs[{i}].indicators.{indicator}",
suggestion="Use MACD indicators in MACD subplots for consistency"
))
def _validate_strategy_consistency(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate strategy consistency with indicator choices."""
strategy_type = config.strategy_type
timeframes = config.timeframes
# Check timeframe consistency with strategy
strategy_timeframe_recommendations = {
TradingStrategy.SCALPING: ['1m', '5m'],
TradingStrategy.DAY_TRADING: ['5m', '15m', '1h'],
TradingStrategy.SWING_TRADING: ['1h', '4h', '1d'],
TradingStrategy.POSITION_TRADING: ['4h', '1d', '1w'],
TradingStrategy.MOMENTUM: ['15m', '1h', '4h'],
TradingStrategy.MEAN_REVERSION: ['15m', '1h', '4h']
}
recommended = strategy_timeframe_recommendations.get(strategy_type, [])
if recommended:
mismatched_timeframes = [tf for tf in timeframes if tf not in recommended]
if mismatched_timeframes:
report.add_issue(ValidationIssue(
level=ValidationLevel.INFO,
rule=ValidationRule.STRATEGY_CONSISTENCY,
message=f"Timeframes {mismatched_timeframes} may not be optimal for {strategy_type.value}",
field_path="timeframes",
suggestion=f"Consider using: {recommended}",
context={"recommended": recommended, "current": timeframes}
))
def _validate_performance_impact(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate potential performance impact."""
total_indicators = len(config.overlay_indicators)
for subplot in config.subplot_configs:
total_indicators += len(subplot.indicators)
if total_indicators > 10:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.PERFORMANCE_IMPACT,
message=f"High indicator count ({total_indicators}) may impact performance",
field_path="indicators",
suggestion="Consider reducing the number of indicators for better performance",
context={"indicator_count": total_indicators}
))
# Check for complex indicators
complex_indicators = ['bollinger_bands', 'macd']
complex_count = 0
all_indicators = config.overlay_indicators.copy()
for subplot in config.subplot_configs:
all_indicators.extend(subplot.indicators)
for indicator in all_indicators:
if indicator in self.available_indicators:
indicator_config = self.available_indicators[indicator].config
indicator_type = indicator_config.indicator_type
# Handle both string and enum types
if hasattr(indicator_type, 'value'):
indicator_type_value = indicator_type.value
else:
indicator_type_value = str(indicator_type)
if indicator_type_value in complex_indicators:
complex_count += 1
if complex_count > 3:
report.add_issue(ValidationIssue(
level=ValidationLevel.INFO,
rule=ValidationRule.PERFORMANCE_IMPACT,
message=f"Multiple complex indicators ({complex_count}) detected",
field_path="indicators",
suggestion="Complex indicators may increase calculation time",
context={"complex_count": complex_count}
))
def _validate_indicator_conflicts(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate for potential indicator conflicts or redundancy."""
all_indicators = config.overlay_indicators.copy()
for subplot in config.subplot_configs:
all_indicators.extend(subplot.indicators)
# Check for similar indicators
sma_indicators = [ind for ind in all_indicators if 'sma_' in ind]
ema_indicators = [ind for ind in all_indicators if 'ema_' in ind]
rsi_indicators = [ind for ind in all_indicators if 'rsi_' in ind]
if len(sma_indicators) > 3:
report.add_issue(ValidationIssue(
level=ValidationLevel.INFO,
rule=ValidationRule.INDICATOR_CONFLICTS,
message=f"Multiple SMA indicators ({len(sma_indicators)}) may create visual clutter",
field_path="overlay_indicators",
suggestion="Consider using fewer SMA periods for cleaner charts"
))
if len(ema_indicators) > 3:
report.add_issue(ValidationIssue(
level=ValidationLevel.INFO,
rule=ValidationRule.INDICATOR_CONFLICTS,
message=f"Multiple EMA indicators ({len(ema_indicators)}) may create visual clutter",
field_path="overlay_indicators",
suggestion="Consider using fewer EMA periods for cleaner charts"
))
if len(rsi_indicators) > 2:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.INDICATOR_CONFLICTS,
message=f"Multiple RSI indicators ({len(rsi_indicators)}) provide redundant information",
field_path="subplot_indicators",
suggestion="Usually one or two RSI periods are sufficient"
))
def _validate_resource_usage(self, config: StrategyChartConfig, report: ValidationReport) -> None:
"""Validate estimated resource usage."""
# Estimate memory usage based on indicators and subplots
base_memory = 1.0 # Base chart memory in MB
indicator_memory = len(config.overlay_indicators) * 0.1 # 0.1 MB per overlay indicator
subplot_memory = len(config.subplot_configs) * 0.5 # 0.5 MB per subplot
total_memory = base_memory + indicator_memory + subplot_memory
if total_memory > 5.0:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.RESOURCE_USAGE,
message=f"Estimated memory usage ({total_memory:.1f} MB) is high",
field_path="configuration",
suggestion="Consider reducing indicators or subplots for lower memory usage",
context={"estimated_memory_mb": total_memory}
))
# Check for potential rendering complexity
rendering_complexity = len(config.overlay_indicators) + (len(config.subplot_configs) * 2)
if rendering_complexity > 15:
report.add_issue(ValidationIssue(
level=ValidationLevel.INFO,
rule=ValidationRule.RESOURCE_USAGE,
message=f"High rendering complexity ({rendering_complexity}) detected",
field_path="configuration",
suggestion="Complex charts may have slower rendering times"
))
def validate_configuration(
config: StrategyChartConfig,
rules: Optional[Set[ValidationRule]] = None,
strict: bool = False
) -> ValidationReport:
"""
Validate a strategy configuration with comprehensive error checking.
Args:
config: Strategy configuration to validate
rules: Optional set of validation rules to apply
strict: If True, treats warnings as errors
Returns:
Comprehensive validation report
"""
validator = ConfigurationValidator(enabled_rules=rules)
report = validator.validate_strategy_config(config)
# In strict mode, treat warnings as errors
if strict and report.warnings:
for warning in report.warnings:
warning.level = ValidationLevel.ERROR
report.errors.append(warning)
report.warnings.clear()
report.is_valid = False
return report
def get_validation_rules_info() -> Dict[ValidationRule, Dict[str, str]]:
"""
Get information about available validation rules.
Returns:
Dictionary mapping rules to their descriptions
"""
return {
ValidationRule.REQUIRED_FIELDS: {
"name": "Required Fields",
"description": "Validates that all required configuration fields are present and valid"
},
ValidationRule.HEIGHT_RATIOS: {
"name": "Height Ratios",
"description": "Validates chart and subplot height ratios sum correctly"
},
ValidationRule.INDICATOR_EXISTENCE: {
"name": "Indicator Existence",
"description": "Validates that all referenced indicators exist in the defaults"
},
ValidationRule.TIMEFRAME_FORMAT: {
"name": "Timeframe Format",
"description": "Validates timeframe format and common usage patterns"
},
ValidationRule.CHART_STYLE: {
"name": "Chart Style",
"description": "Validates chart styling options like colors, fonts, and themes"
},
ValidationRule.SUBPLOT_CONFIG: {
"name": "Subplot Configuration",
"description": "Validates subplot configurations and indicator compatibility"
},
ValidationRule.STRATEGY_CONSISTENCY: {
"name": "Strategy Consistency",
"description": "Validates that configuration matches strategy type recommendations"
},
ValidationRule.PERFORMANCE_IMPACT: {
"name": "Performance Impact",
"description": "Warns about configurations that may impact performance"
},
ValidationRule.INDICATOR_CONFLICTS: {
"name": "Indicator Conflicts",
"description": "Detects redundant or conflicting indicator combinations"
},
ValidationRule.RESOURCE_USAGE: {
"name": "Resource Usage",
"description": "Estimates and warns about high resource usage configurations"
}
}