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"
}
}

View File

@ -0,0 +1,580 @@
# Modular Chart Layers System
The Modular Chart Layers System is a flexible, strategy-driven chart system that supports technical indicator overlays, subplot management, and future bot signal integration. This system replaces basic chart functionality with a modular architecture that adapts to different trading strategies and their specific indicator requirements.
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Quick Start](#quick-start)
- [Components](#components)
- [Configuration System](#configuration-system)
- [Example Strategies](#example-strategies)
- [Validation System](#validation-system)
- [API Reference](#api-reference)
- [Examples](#examples)
- [Best Practices](#best-practices)
## Overview
### Key Features
- **Modular Architecture**: Chart layers can be independently tested and composed
- **Strategy-Driven Configuration**: JSON-based configurations for different trading strategies
- **Comprehensive Validation**: 10+ validation rules with detailed error reporting
- **Example Strategies**: 5 real-world trading strategy templates
- **Indicator Support**: 26+ professionally configured indicator presets
- **Extensible Design**: Easy to add new indicators, strategies, and chart types
### Supported Indicators
**Trend Indicators:**
- Simple Moving Average (SMA) - Multiple periods
- Exponential Moving Average (EMA) - Multiple periods
- Bollinger Bands - Various configurations
**Momentum Indicators:**
- Relative Strength Index (RSI) - Multiple periods
- MACD - Various speed configurations
**Volume Indicators:**
- Volume analysis and confirmation
## Architecture
```
components/charts/
├── config/ # Configuration management
│ ├── indicator_defs.py # Indicator schemas and validation
│ ├── defaults.py # Default configurations and presets
│ ├── strategy_charts.py # Strategy-specific configurations
│ ├── validation.py # Validation system
│ ├── example_strategies.py # Real-world strategy examples
│ └── __init__.py # Package exports
├── layers/ # Chart layer implementation
│ ├── base.py # Base layer system
│ ├── indicators.py # Indicator overlays
│ ├── subplots.py # Subplot management
│ └── signals.py # Signal overlays (future)
├── builder.py # Main chart builder
└── utils.py # Chart utilities
```
## Quick Start
### Basic Usage
```python
from components.charts.config import (
create_ema_crossover_strategy,
get_strategy_config,
validate_configuration
)
# Get a pre-built strategy
strategy = create_ema_crossover_strategy()
config = strategy.config
# Validate the configuration
report = validate_configuration(config)
if report.is_valid:
print("Configuration is valid!")
else:
print(f"Errors: {[str(e) for e in report.errors]}")
# Use with dashboard
# chart = create_chart(config, market_data)
```
### Custom Strategy Creation
```python
from components.charts.config import (
StrategyChartConfig,
SubplotConfig,
ChartStyle,
TradingStrategy,
SubplotType
)
# Create custom strategy
config = StrategyChartConfig(
strategy_name="My Custom Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Custom day trading strategy",
timeframes=["15m", "1h"],
overlay_indicators=["ema_12", "ema_26", "bb_20_20"],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2,
indicators=["rsi_14"]
)
]
)
# Validate and use
is_valid, errors = config.validate()
```
## Components
### 1. Configuration System
The configuration system provides schema validation, default presets, and strategy management.
**Key Files:**
- `indicator_defs.py` - Core schemas and validation
- `defaults.py` - 26+ indicator presets organized by category
- `strategy_charts.py` - Complete strategy configurations
**Features:**
- Type-safe indicator definitions
- Parameter validation with ranges
- Category-based organization (trend, momentum, volatility)
- Strategy-specific recommendations
### 2. Validation System
Comprehensive validation with 10 validation rules:
1. **Required Fields** - Essential configuration validation
2. **Height Ratios** - Chart layout validation
3. **Indicator Existence** - Indicator availability check
4. **Timeframe Format** - Valid timeframe patterns
5. **Chart Style** - Color and styling validation
6. **Subplot Config** - Subplot compatibility check
7. **Strategy Consistency** - Strategy-timeframe alignment
8. **Performance Impact** - Resource usage warnings
9. **Indicator Conflicts** - Redundancy detection
10. **Resource Usage** - Memory and rendering estimates
**Usage:**
```python
from components.charts.config import validate_configuration
report = validate_configuration(config)
print(f"Valid: {report.is_valid}")
print(f"Errors: {len(report.errors)}")
print(f"Warnings: {len(report.warnings)}")
```
### 3. Example Strategies
Five professionally configured trading strategies:
1. **EMA Crossover** (Intermediate, Medium Risk)
- Classic trend-following with EMA crossovers
- Best for trending markets, 15m-4h timeframes
2. **Momentum Breakout** (Advanced, High Risk)
- Fast indicators for momentum capture
- Volume confirmation, best for volatile markets
3. **Mean Reversion** (Intermediate, Medium Risk)
- Oversold/overbought conditions
- Multiple RSI periods, best for ranging markets
4. **Scalping** (Advanced, High Risk)
- Ultra-fast indicators for 1m-5m trading
- Tight risk management, high frequency
5. **Swing Trading** (Beginner, Medium Risk)
- Medium-term trend following
- 4h-1d timeframes, suitable for part-time traders
## Configuration System
### Indicator Definitions
Each indicator has a complete schema definition:
```python
@dataclass
class ChartIndicatorConfig:
indicator_type: IndicatorType
parameters: Dict[str, Any]
display_name: str
color: str
line_style: LineStyle
line_width: int
display_type: DisplayType
```
### Strategy Configuration
Complete strategy definitions include:
```python
@dataclass
class StrategyChartConfig:
strategy_name: str
strategy_type: TradingStrategy
description: str
timeframes: List[str]
layout: ChartLayout
main_chart_height: float
overlay_indicators: List[str]
subplot_configs: List[SubplotConfig]
chart_style: ChartStyle
```
### Default Configurations
26+ indicator presets organized by category:
- **Trend Indicators**: 13 SMA/EMA presets
- **Momentum Indicators**: 9 RSI/MACD presets
- **Volatility Indicators**: 4 Bollinger Bands configurations
Access via:
```python
from components.charts.config import get_all_default_indicators
indicators = get_all_default_indicators()
trend_indicators = get_indicators_by_category(IndicatorCategory.TREND)
```
## Example Strategies
### EMA Crossover Strategy
```python
from components.charts.config import create_ema_crossover_strategy
strategy = create_ema_crossover_strategy()
config = strategy.config
# Strategy includes:
# - EMA 12, 26, 50 for trend analysis
# - RSI 14 for momentum confirmation
# - MACD for signal confirmation
# - Bollinger Bands for volatility context
```
### Custom Strategy Creation
```python
from components.charts.config import create_custom_strategy_config
config, errors = create_custom_strategy_config(
strategy_name="My Strategy",
strategy_type=TradingStrategy.MOMENTUM,
description="Custom momentum strategy",
timeframes=["5m", "15m"],
overlay_indicators=["ema_8", "ema_21"],
subplot_configs=[{
"subplot_type": "rsi",
"height_ratio": 0.2,
"indicators": ["rsi_7"]
}]
)
```
## Validation System
### Comprehensive Validation
```python
from components.charts.config import validate_configuration
# Full validation with detailed reporting
report = validate_configuration(config)
# Check results
if report.is_valid:
print("✅ Configuration is valid")
else:
print("❌ Configuration has errors:")
for error in report.errors:
print(f" • {error}")
# Check warnings
if report.warnings:
print("⚠️ Warnings:")
for warning in report.warnings:
print(f" • {warning}")
```
### Validation Rules Information
```python
from components.charts.config import get_validation_rules_info
rules = get_validation_rules_info()
for rule, info in rules.items():
print(f"{info['name']}: {info['description']}")
```
## API Reference
### Core Classes
#### `StrategyChartConfig`
Main configuration class for chart strategies.
**Methods:**
- `validate()``tuple[bool, List[str]]` - Basic validation
- `validate_comprehensive()``ValidationReport` - Detailed validation
- `get_all_indicators()``List[str]` - Get all indicator names
- `get_indicator_configs()``Dict[str, ChartIndicatorConfig]` - Get configurations
#### `StrategyExample`
Container for example strategies with metadata.
**Properties:**
- `config: StrategyChartConfig` - The strategy configuration
- `description: str` - Detailed strategy description
- `difficulty: str` - Beginner/Intermediate/Advanced
- `risk_level: str` - Low/Medium/High
- `market_conditions: List[str]` - Suitable market conditions
### Utility Functions
#### Configuration Access
```python
# Get all example strategies
get_all_example_strategies() → Dict[str, StrategyExample]
# Filter by criteria
get_strategies_by_difficulty("Intermediate") → List[StrategyExample]
get_strategies_by_risk_level("Medium") → List[StrategyExample]
get_strategies_by_market_condition("Trending") → List[StrategyExample]
# Get strategy summary
get_strategy_summary() → Dict[str, Dict[str, str]]
```
#### JSON Export/Import
```python
# Export to JSON
export_strategy_config_to_json(config) → str
export_example_strategies_to_json() → str
# Import from JSON
load_strategy_config_from_json(json_data) → tuple[StrategyChartConfig, List[str]]
```
#### Validation
```python
# Comprehensive validation
validate_configuration(config, rules=None, strict=False) → ValidationReport
# Get validation rules info
get_validation_rules_info() → Dict[ValidationRule, Dict[str, str]]
```
## Examples
### Example 1: Using Pre-built Strategy
```python
from components.charts.config import get_example_strategy
# Get a specific strategy
strategy = get_example_strategy("ema_crossover")
print(f"Strategy: {strategy.config.strategy_name}")
print(f"Difficulty: {strategy.difficulty}")
print(f"Risk Level: {strategy.risk_level}")
print(f"Timeframes: {strategy.config.timeframes}")
print(f"Indicators: {strategy.config.overlay_indicators}")
# Validate before use
is_valid, errors = strategy.config.validate()
if is_valid:
# Use in dashboard
pass
```
### Example 2: Creating Custom Configuration
```python
from components.charts.config import (
StrategyChartConfig, SubplotConfig, ChartStyle,
TradingStrategy, SubplotType, ChartLayout
)
# Create custom configuration
config = StrategyChartConfig(
strategy_name="Custom Momentum Strategy",
strategy_type=TradingStrategy.MOMENTUM,
description="Fast momentum strategy with volume confirmation",
timeframes=["5m", "15m"],
layout=ChartLayout.MAIN_WITH_SUBPLOTS,
main_chart_height=0.65,
overlay_indicators=["ema_8", "ema_21", "bb_20_25"],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.15,
indicators=["rsi_7"],
title="Fast RSI"
),
SubplotConfig(
subplot_type=SubplotType.VOLUME,
height_ratio=0.2,
indicators=[],
title="Volume Confirmation"
)
],
chart_style=ChartStyle(
theme="plotly_white",
candlestick_up_color="#00d4aa",
candlestick_down_color="#fe6a85"
)
)
# Comprehensive validation
report = config.validate_comprehensive()
print(f"Validation: {report.summary()}")
```
### Example 3: Filtering Strategies
```python
from components.charts.config import (
get_strategies_by_difficulty,
get_strategies_by_market_condition
)
# Get beginner-friendly strategies
beginner_strategies = get_strategies_by_difficulty("Beginner")
print("Beginner Strategies:")
for strategy in beginner_strategies:
print(f" • {strategy.config.strategy_name}")
# Get strategies for trending markets
trending_strategies = get_strategies_by_market_condition("Trending")
print("\nTrending Market Strategies:")
for strategy in trending_strategies:
print(f" • {strategy.config.strategy_name}")
```
### Example 4: Validation with Error Handling
```python
from components.charts.config import validate_configuration, ValidationLevel
# Validate with comprehensive reporting
report = validate_configuration(config)
# Handle different severity levels
if report.errors:
print("🚨 ERRORS (must fix):")
for error in report.errors:
print(f" • {error}")
if report.warnings:
print("\n⚠ WARNINGS (recommended fixes):")
for warning in report.warnings:
print(f" • {warning}")
if report.info:
print("\n INFO (optimization suggestions):")
for info in report.info:
print(f" • {info}")
# Check specific validation rules
height_issues = report.get_issues_by_rule(ValidationRule.HEIGHT_RATIOS)
if height_issues:
print(f"\nHeight ratio issues: {len(height_issues)}")
```
## Best Practices
### 1. Configuration Design
- **Use meaningful names**: Strategy names should be descriptive
- **Validate early**: Always validate configurations before use
- **Consider timeframes**: Match timeframes to strategy type
- **Height ratios**: Ensure total height ≤ 1.0
### 2. Indicator Selection
- **Avoid redundancy**: Don't use multiple similar indicators
- **Performance impact**: Limit complex indicators (>3 Bollinger Bands)
- **Category balance**: Mix trend, momentum, and volume indicators
- **Timeframe alignment**: Use appropriate indicator periods
### 3. Strategy Development
- **Start simple**: Begin with proven strategies like EMA crossover
- **Test thoroughly**: Validate both technically and with market data
- **Document well**: Include entry/exit rules and market conditions
- **Consider risk**: Match complexity to experience level
### 4. Validation Usage
- **Use comprehensive validation**: Get detailed reports with suggestions
- **Handle warnings**: Address performance and usability warnings
- **Test edge cases**: Validate with extreme configurations
- **Monitor updates**: Re-validate when changing configurations
### 5. Performance Optimization
- **Limit indicators**: Keep total indicators <10 for performance
- **Monitor memory**: Check resource usage warnings
- **Optimize rendering**: Consider visual complexity
- **Cache configurations**: Reuse validated configurations
## Error Handling
### Common Issues and Solutions
1. **"Indicator not found in defaults"**
```python
# Check available indicators
from components.charts.config import get_all_default_indicators
available = get_all_default_indicators()
print(list(available.keys()))
```
2. **"Total height exceeds 1.0"**
```python
# Adjust height ratios
config.main_chart_height = 0.7
for subplot in config.subplot_configs:
subplot.height_ratio = 0.1
```
3. **"Invalid timeframe format"**
```python
# Use standard formats
config.timeframes = ["1m", "5m", "15m", "1h", "4h", "1d", "1w"]
```
## Testing
The system includes comprehensive tests:
- **112+ test cases** across all components
- **Unit tests** for individual components
- **Integration tests** for system interactions
- **Validation tests** for error handling
Run tests:
```bash
uv run pytest tests/test_*_strategies.py -v
uv run pytest tests/test_validation.py -v
uv run pytest tests/test_defaults.py -v
```
## Future Enhancements
- **Signal Layer Integration**: Bot trade signals and alerts
- **Custom Indicators**: User-defined technical indicators
- **Advanced Layouts**: Multi-chart and grid layouts
- **Real-time Updates**: Live chart updates with indicator toggling
- **Performance Monitoring**: Advanced resource usage tracking
## Support
For issues, questions, or contributions:
1. Check existing configurations in `example_strategies.py`
2. Review validation rules in `validation.py`
3. Test with comprehensive validation
4. Refer to this documentation
The modular chart system is designed to be extensible and maintainable, providing a solid foundation for advanced trading chart functionality.

View File

@ -0,0 +1,752 @@
# Chart Configuration System
The Chart Configuration System provides comprehensive management of chart settings, indicator definitions, and trading strategy configurations. It includes schema validation, default presets, and extensible configuration patterns.
## Table of Contents
- [Overview](#overview)
- [Indicator Definitions](#indicator-definitions)
- [Default Configurations](#default-configurations)
- [Strategy Configurations](#strategy-configurations)
- [Validation System](#validation-system)
- [Configuration Files](#configuration-files)
- [Usage Examples](#usage-examples)
- [Extension Guide](#extension-guide)
## Overview
The configuration system is built around three core concepts:
1. **Indicator Definitions** - Schema and validation for technical indicators
2. **Default Configurations** - Pre-built indicator presets organized by category
3. **Strategy Configurations** - Complete chart setups for trading strategies
### Architecture
```
components/charts/config/
├── indicator_defs.py # Core schemas and validation
├── defaults.py # Default indicator presets
├── strategy_charts.py # Strategy configurations
├── validation.py # Validation system
├── example_strategies.py # Real-world examples
└── __init__.py # Package exports
```
## Indicator Definitions
### Core Classes
#### `ChartIndicatorConfig`
The main configuration class for individual indicators:
```python
@dataclass
class ChartIndicatorConfig:
indicator_type: IndicatorType
parameters: Dict[str, Any]
display_name: str
color: str
line_style: LineStyle = LineStyle.SOLID
line_width: int = 2
display_type: DisplayType = DisplayType.OVERLAY
opacity: float = 1.0
show_legend: bool = True
```
#### Enums
**IndicatorType**
```python
class IndicatorType(str, Enum):
SMA = "sma"
EMA = "ema"
RSI = "rsi"
MACD = "macd"
BOLLINGER_BANDS = "bollinger_bands"
VOLUME = "volume"
```
**DisplayType**
```python
class DisplayType(str, Enum):
OVERLAY = "overlay" # Overlaid on price chart
SUBPLOT = "subplot" # Separate subplot
HISTOGRAM = "histogram" # Histogram display
```
**LineStyle**
```python
class LineStyle(str, Enum):
SOLID = "solid"
DASHED = "dash"
DOTTED = "dot"
DASH_DOT = "dashdot"
```
### Schema Validation
#### `IndicatorParameterSchema`
Defines validation rules for indicator parameters:
```python
@dataclass
class IndicatorParameterSchema:
name: str
type: type
required: bool = True
min_value: Optional[Union[int, float]] = None
max_value: Optional[Union[int, float]] = None
default_value: Any = None
description: str = ""
valid_values: Optional[List[Any]] = None
```
#### `IndicatorSchema`
Complete schema for an indicator type:
```python
@dataclass
class IndicatorSchema:
indicator_type: IndicatorType
display_type: DisplayType
parameters: List[IndicatorParameterSchema]
description: str
calculation_description: str
usage_notes: List[str] = field(default_factory=list)
```
### Schema Definitions
The system includes complete schemas for all supported indicators:
```python
INDICATOR_SCHEMAS = {
IndicatorType.SMA: IndicatorSchema(
indicator_type=IndicatorType.SMA,
display_type=DisplayType.OVERLAY,
parameters=[
IndicatorParameterSchema(
name="period",
type=int,
min_value=1,
max_value=200,
default_value=20,
description="Number of periods for the moving average"
),
IndicatorParameterSchema(
name="price_column",
type=str,
required=False,
default_value="close",
valid_values=["open", "high", "low", "close"],
description="Price column to use for calculation"
)
],
description="Simple Moving Average - arithmetic mean of prices",
calculation_description="Sum of closing prices divided by period"
),
# ... more schemas
}
```
### Utility Functions
#### Validation Functions
```python
# Validate individual indicator configuration
def validate_indicator_configuration(config: ChartIndicatorConfig) -> tuple[bool, List[str]]
# Create indicator configuration with validation
def create_indicator_config(
indicator_type: IndicatorType,
parameters: Dict[str, Any],
**kwargs
) -> tuple[Optional[ChartIndicatorConfig], List[str]]
# Get schema for indicator type
def get_indicator_schema(indicator_type: IndicatorType) -> Optional[IndicatorSchema]
# Get available indicator types
def get_available_indicator_types() -> List[IndicatorType]
# Validate parameters for specific type
def validate_parameters_for_type(
indicator_type: IndicatorType,
parameters: Dict[str, Any]
) -> tuple[bool, List[str]]
```
## Default Configurations
### Organization
Default configurations are organized by category and trading strategy:
#### Categories
```python
class IndicatorCategory(str, Enum):
TREND = "trend"
MOMENTUM = "momentum"
VOLATILITY = "volatility"
VOLUME = "volume"
SUPPORT_RESISTANCE = "support_resistance"
```
#### Trading Strategies
```python
class TradingStrategy(str, Enum):
SCALPING = "scalping"
DAY_TRADING = "day_trading"
SWING_TRADING = "swing_trading"
POSITION_TRADING = "position_trading"
MOMENTUM = "momentum"
MEAN_REVERSION = "mean_reversion"
```
### Indicator Presets
#### `IndicatorPreset`
Container for pre-configured indicators:
```python
@dataclass
class IndicatorPreset:
name: str
config: ChartIndicatorConfig
category: IndicatorCategory
description: str
recommended_timeframes: List[str]
suitable_strategies: List[TradingStrategy]
notes: List[str] = field(default_factory=list)
```
### Available Presets
**Trend Indicators (13 presets)**
- `sma_5`, `sma_10`, `sma_20`, `sma_50`, `sma_100`, `sma_200`
- `ema_5`, `ema_12`, `ema_21`, `ema_26`, `ema_50`, `ema_100`, `ema_200`
**Momentum Indicators (9 presets)**
- `rsi_7`, `rsi_14`, `rsi_21`
- `macd_5_13_4`, `macd_8_17_6`, `macd_12_26_9`, `macd_19_39_13`
**Volatility Indicators (4 presets)**
- `bb_10_15`, `bb_20_15`, `bb_20_20`, `bb_50_20`
### Color Schemes
Organized color palettes by category:
```python
CATEGORY_COLORS = {
IndicatorCategory.TREND: {
"primary": "#2E86C1", # Blue
"secondary": "#5DADE2", # Light Blue
"accent": "#1F618D" # Dark Blue
},
IndicatorCategory.MOMENTUM: {
"primary": "#E74C3C", # Red
"secondary": "#F1948A", # Light Red
"accent": "#C0392B" # Dark Red
},
# ... more colors
}
```
### Access Functions
```python
# Get all default indicators
def get_all_default_indicators() -> Dict[str, IndicatorPreset]
# Filter by category
def get_indicators_by_category(category: IndicatorCategory) -> Dict[str, IndicatorPreset]
# Filter by timeframe
def get_indicators_for_timeframe(timeframe: str) -> Dict[str, IndicatorPreset]
# Get strategy-specific indicators
def get_strategy_indicators(strategy: TradingStrategy) -> Dict[str, IndicatorPreset]
# Create custom preset
def create_custom_preset(
name: str,
indicator_type: IndicatorType,
parameters: Dict[str, Any],
category: IndicatorCategory,
**kwargs
) -> tuple[Optional[IndicatorPreset], List[str]]
```
## Strategy Configurations
### Core Classes
#### `StrategyChartConfig`
Complete chart configuration for a trading strategy:
```python
@dataclass
class StrategyChartConfig:
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)
```
#### `SubplotConfig`
Configuration for chart subplots:
```python
@dataclass
class SubplotConfig:
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
```
#### `ChartStyle`
Comprehensive chart styling:
```python
@dataclass
class ChartStyle:
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
```
### Default Strategy Configurations
Pre-built strategy configurations for common trading approaches:
1. **Scalping Strategy**
- Ultra-fast indicators (EMA 5, 12, 21)
- Fast RSI (7) and MACD (5,13,4)
- 1m-5m timeframes
2. **Day Trading Strategy**
- Balanced indicators (SMA 20, EMA 12/26, BB 20,2.0)
- Standard RSI (14) and MACD (12,26,9)
- 5m-1h timeframes
3. **Swing Trading Strategy**
- Longer-term indicators (SMA 50, EMA 21/50, BB 20,2.0)
- Standard momentum indicators
- 1h-1d timeframes
### Configuration Functions
```python
# Create default strategy configurations
def create_default_strategy_configurations() -> Dict[str, StrategyChartConfig]
# Create custom strategy
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]],
**kwargs
) -> tuple[Optional[StrategyChartConfig], List[str]]
# JSON import/export
def load_strategy_config_from_json(json_data: Union[str, Dict[str, Any]]) -> tuple[Optional[StrategyChartConfig], List[str]]
def export_strategy_config_to_json(config: StrategyChartConfig) -> str
# Access functions
def get_strategy_config(strategy_name: str) -> Optional[StrategyChartConfig]
def get_all_strategy_configs() -> Dict[str, StrategyChartConfig]
def get_available_strategy_names() -> List[str]
```
## Validation System
### Validation Rules
The system includes 10 comprehensive validation rules:
1. **REQUIRED_FIELDS** - Validates essential configuration fields
2. **HEIGHT_RATIOS** - Ensures chart height ratios sum correctly
3. **INDICATOR_EXISTENCE** - Checks indicator availability
4. **TIMEFRAME_FORMAT** - Validates timeframe patterns
5. **CHART_STYLE** - Validates styling options
6. **SUBPLOT_CONFIG** - Validates subplot configurations
7. **STRATEGY_CONSISTENCY** - Checks strategy-timeframe alignment
8. **PERFORMANCE_IMPACT** - Warns about performance issues
9. **INDICATOR_CONFLICTS** - Detects redundant indicators
10. **RESOURCE_USAGE** - Estimates resource consumption
### Validation Classes
#### `ValidationReport`
Comprehensive validation results:
```python
@dataclass
class ValidationReport:
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)
```
#### `ValidationIssue`
Individual validation issue:
```python
@dataclass
class ValidationIssue:
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)
```
### Validation Usage
```python
from components.charts.config import validate_configuration
# Comprehensive validation
report = validate_configuration(config)
# Check results
if report.is_valid:
print("✅ Configuration is valid")
else:
print("❌ Configuration has errors:")
for error in report.errors:
print(f" • {error}")
# Handle warnings
if report.warnings:
print("⚠️ Warnings:")
for warning in report.warnings:
print(f" • {warning}")
```
## Configuration Files
### File Structure
```
components/charts/config/
├── __init__.py # Package exports and public API
├── indicator_defs.py # Core indicator schemas and validation
├── defaults.py # Default indicator presets and categories
├── strategy_charts.py # Strategy configuration classes and defaults
├── validation.py # Validation system and rules
└── example_strategies.py # Real-world trading strategy examples
```
### Key Exports
From `__init__.py`:
```python
# Core classes
from .indicator_defs import (
IndicatorType, DisplayType, LineStyle, PriceColumn,
IndicatorParameterSchema, IndicatorSchema, ChartIndicatorConfig
)
# Default configurations
from .defaults import (
IndicatorCategory, TradingStrategy, IndicatorPreset,
get_all_default_indicators, get_indicators_by_category
)
# Strategy configurations
from .strategy_charts import (
ChartLayout, SubplotType, SubplotConfig, ChartStyle, StrategyChartConfig,
create_default_strategy_configurations
)
# Validation system
from .validation import (
ValidationLevel, ValidationRule, ValidationIssue, ValidationReport,
validate_configuration
)
# Example strategies
from .example_strategies import (
StrategyExample, create_ema_crossover_strategy,
get_all_example_strategies
)
```
## Usage Examples
### Example 1: Creating Custom Indicator
```python
from components.charts.config import (
create_indicator_config, IndicatorType
)
# Create custom EMA configuration
config, errors = create_indicator_config(
indicator_type=IndicatorType.EMA,
parameters={"period": 21, "price_column": "close"},
display_name="EMA 21",
color="#2E86C1",
line_width=2
)
if config:
print(f"Created: {config.display_name}")
else:
print(f"Errors: {errors}")
```
### Example 2: Using Default Presets
```python
from components.charts.config import (
get_all_default_indicators,
get_indicators_by_category,
IndicatorCategory
)
# Get all available indicators
all_indicators = get_all_default_indicators()
print(f"Available indicators: {len(all_indicators)}")
# Get trend indicators only
trend_indicators = get_indicators_by_category(IndicatorCategory.TREND)
for name, preset in trend_indicators.items():
print(f"{name}: {preset.description}")
```
### Example 3: Strategy Configuration
```python
from components.charts.config import (
create_custom_strategy_config,
TradingStrategy
)
# Create custom momentum strategy
config, errors = create_custom_strategy_config(
strategy_name="Custom Momentum",
strategy_type=TradingStrategy.MOMENTUM,
description="Fast momentum trading strategy",
timeframes=["5m", "15m"],
overlay_indicators=["ema_8", "ema_21"],
subplot_configs=[{
"subplot_type": "rsi",
"height_ratio": 0.2,
"indicators": ["rsi_7"]
}]
)
if config:
print(f"Created strategy: {config.strategy_name}")
is_valid, validation_errors = config.validate()
if is_valid:
print("Strategy is valid!")
else:
print(f"Validation errors: {validation_errors}")
```
### Example 4: Comprehensive Validation
```python
from components.charts.config import (
validate_configuration,
ValidationRule
)
# Validate with specific rules
rules = {ValidationRule.REQUIRED_FIELDS, ValidationRule.HEIGHT_RATIOS}
report = validate_configuration(config, rules=rules)
# Detailed error handling
for error in report.errors:
print(f"ERROR: {error.message}")
if error.suggestion:
print(f" Suggestion: {error.suggestion}")
if error.auto_fix:
print(f" Auto-fix: {error.auto_fix}")
# Performance warnings
performance_issues = report.get_issues_by_rule(ValidationRule.PERFORMANCE_IMPACT)
if performance_issues:
print(f"Performance concerns: {len(performance_issues)}")
```
## Extension Guide
### Adding New Indicators
1. **Define Indicator Type**
```python
# Add to IndicatorType enum
class IndicatorType(str, Enum):
# ... existing types
STOCHASTIC = "stochastic"
```
2. **Create Schema**
```python
# Add to INDICATOR_SCHEMAS
INDICATOR_SCHEMAS[IndicatorType.STOCHASTIC] = IndicatorSchema(
indicator_type=IndicatorType.STOCHASTIC,
display_type=DisplayType.SUBPLOT,
parameters=[
IndicatorParameterSchema(
name="k_period",
type=int,
min_value=1,
max_value=100,
default_value=14
),
# ... more parameters
],
description="Stochastic Oscillator",
calculation_description="Momentum indicator comparing closing price to price range"
)
```
3. **Create Default Presets**
```python
# Add to defaults.py
def create_momentum_indicators():
# ... existing indicators
indicators["stoch_14"] = IndicatorPreset(
name="stoch_14",
config=create_indicator_config(
IndicatorType.STOCHASTIC,
{"k_period": 14, "d_period": 3},
display_name="Stochastic %K(14,%D(3))",
color=CATEGORY_COLORS[IndicatorCategory.MOMENTUM]["primary"]
)[0],
category=IndicatorCategory.MOMENTUM,
description="Standard Stochastic oscillator",
recommended_timeframes=["15m", "1h", "4h"],
suitable_strategies=[TradingStrategy.SWING_TRADING]
)
```
### Adding New Validation Rules
1. **Define Rule**
```python
# Add to ValidationRule enum
class ValidationRule(str, Enum):
# ... existing rules
CUSTOM_RULE = "custom_rule"
```
2. **Implement Validation**
```python
# Add to ConfigurationValidator
def _validate_custom_rule(self, config: StrategyChartConfig, report: ValidationReport) -> None:
# Custom validation logic
if some_condition:
report.add_issue(ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.CUSTOM_RULE,
message="Custom validation message",
suggestion="Suggested fix"
))
```
3. **Add to Validator**
```python
# Add to validate_strategy_config method
if ValidationRule.CUSTOM_RULE in self.enabled_rules:
self._validate_custom_rule(config, report)
```
### Adding New Strategy Types
1. **Define Strategy Type**
```python
# Add to TradingStrategy enum
class TradingStrategy(str, Enum):
# ... existing strategies
GRID_TRADING = "grid_trading"
```
2. **Create Strategy Configuration**
```python
# Add to create_default_strategy_configurations()
strategy_configs["grid_trading"] = StrategyChartConfig(
strategy_name="Grid Trading Strategy",
strategy_type=TradingStrategy.GRID_TRADING,
description="Grid trading with support/resistance levels",
timeframes=["1h", "4h"],
overlay_indicators=["sma_20", "sma_50"],
# ... complete configuration
)
```
3. **Add Example Strategy**
```python
# Create in example_strategies.py
def create_grid_trading_strategy() -> StrategyExample:
config = StrategyChartConfig(...)
return StrategyExample(
config=config,
description="Grid trading strategy description...",
difficulty="Intermediate",
risk_level="Medium"
)
```
The configuration system is designed to be highly extensible while maintaining type safety and comprehensive validation. All additions should follow the established patterns and include appropriate tests.

View File

@ -0,0 +1,280 @@
# Chart System Quick Reference
## Quick Start
### Import Everything You Need
```python
from components.charts.config import (
# Example strategies
create_ema_crossover_strategy,
get_all_example_strategies,
# Configuration
StrategyChartConfig,
create_custom_strategy_config,
validate_configuration,
# Indicators
get_all_default_indicators,
get_indicators_by_category,
IndicatorCategory,
TradingStrategy
)
```
### Use Pre-built Strategy
```python
# Get EMA crossover strategy
strategy = create_ema_crossover_strategy()
config = strategy.config
# Validate before use
report = validate_configuration(config)
if report.is_valid:
print("✅ Ready to use!")
else:
print(f"❌ Errors: {[str(e) for e in report.errors]}")
```
### Create Custom Strategy
```python
config, errors = create_custom_strategy_config(
strategy_name="My Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Custom day trading strategy",
timeframes=["15m", "1h"],
overlay_indicators=["ema_12", "ema_26"],
subplot_configs=[{
"subplot_type": "rsi",
"height_ratio": 0.2,
"indicators": ["rsi_14"]
}]
)
```
## Available Indicators
### Trend Indicators
- `sma_5`, `sma_10`, `sma_20`, `sma_50`, `sma_100`, `sma_200`
- `ema_5`, `ema_12`, `ema_21`, `ema_26`, `ema_50`, `ema_100`, `ema_200`
### Momentum Indicators
- `rsi_7`, `rsi_14`, `rsi_21`
- `macd_5_13_4`, `macd_8_17_6`, `macd_12_26_9`, `macd_19_39_13`
### Volatility Indicators
- `bb_10_15`, `bb_20_15`, `bb_20_20`, `bb_50_20`
## Example Strategies
### 1. EMA Crossover (Intermediate, Medium Risk)
```python
strategy = create_ema_crossover_strategy()
# Uses: EMA 12/26/50, RSI 14, MACD, Bollinger Bands
# Best for: Trending markets, 15m-4h timeframes
```
### 2. Momentum Breakout (Advanced, High Risk)
```python
strategy = create_momentum_breakout_strategy()
# Uses: EMA 8/21, Fast RSI/MACD, Volume
# Best for: Volatile markets, 5m-1h timeframes
```
### 3. Mean Reversion (Intermediate, Medium Risk)
```python
strategy = create_mean_reversion_strategy()
# Uses: SMA 20/50, Multiple RSI, Tight BB
# Best for: Ranging markets, 15m-4h timeframes
```
### 4. Scalping (Advanced, High Risk)
```python
strategy = create_scalping_strategy()
# Uses: Ultra-fast EMAs, RSI 7, Fast MACD
# Best for: High liquidity, 1m-5m timeframes
```
### 5. Swing Trading (Beginner, Medium Risk)
```python
strategy = create_swing_trading_strategy()
# Uses: SMA 20/50, Standard indicators
# Best for: Trending markets, 4h-1d timeframes
```
## Strategy Filtering
### By Difficulty
```python
beginner = get_strategies_by_difficulty("Beginner")
intermediate = get_strategies_by_difficulty("Intermediate")
advanced = get_strategies_by_difficulty("Advanced")
```
### By Risk Level
```python
low_risk = get_strategies_by_risk_level("Low")
medium_risk = get_strategies_by_risk_level("Medium")
high_risk = get_strategies_by_risk_level("High")
```
### By Market Condition
```python
trending = get_strategies_by_market_condition("Trending")
sideways = get_strategies_by_market_condition("Sideways")
volatile = get_strategies_by_market_condition("Volatile")
```
## Validation Quick Checks
### Basic Validation
```python
is_valid, errors = config.validate()
if not is_valid:
for error in errors:
print(f"❌ {error}")
```
### Comprehensive Validation
```python
report = validate_configuration(config)
# Errors (must fix)
for error in report.errors:
print(f"🚨 {error}")
# Warnings (recommended)
for warning in report.warnings:
print(f"⚠️ {warning}")
# Info (optional)
for info in report.info:
print(f" {info}")
```
## JSON Export/Import
### Export Strategy
```python
json_data = export_strategy_config_to_json(config)
```
### Import Strategy
```python
config, errors = load_strategy_config_from_json(json_data)
```
### Export All Examples
```python
all_strategies_json = export_example_strategies_to_json()
```
## Common Patterns
### Get Strategy Summary
```python
summary = get_strategy_summary()
for name, info in summary.items():
print(f"{name}: {info['difficulty']} - {info['risk_level']}")
```
### List Available Indicators
```python
indicators = get_all_default_indicators()
for name, preset in indicators.items():
print(f"{name}: {preset.description}")
```
### Filter by Category
```python
trend_indicators = get_indicators_by_category(IndicatorCategory.TREND)
momentum_indicators = get_indicators_by_category(IndicatorCategory.MOMENTUM)
```
## Configuration Structure
### Strategy Config
```python
StrategyChartConfig(
strategy_name="Strategy Name",
strategy_type=TradingStrategy.DAY_TRADING,
description="Strategy description",
timeframes=["15m", "1h"],
overlay_indicators=["ema_12", "ema_26"],
subplot_configs=[
{
"subplot_type": "rsi",
"height_ratio": 0.2,
"indicators": ["rsi_14"]
}
]
)
```
### Subplot Types
- `"rsi"` - RSI oscillator
- `"macd"` - MACD with histogram
- `"volume"` - Volume bars
### Timeframe Formats
- `"1m"`, `"5m"`, `"15m"`, `"30m"`
- `"1h"`, `"2h"`, `"4h"`, `"6h"`, `"12h"`
- `"1d"`, `"1w"`, `"1M"`
## Error Handling
### Common Errors
1. **"Indicator not found"** - Check available indicators list
2. **"Height ratios exceed 1.0"** - Adjust main_chart_height and subplot ratios
3. **"Invalid timeframe"** - Use standard timeframe formats
### Validation Rules
1. Required fields present
2. Height ratios sum ≤ 1.0
3. Indicators exist in defaults
4. Valid timeframe formats
5. Chart style validation
6. Subplot configuration
7. Strategy consistency
8. Performance impact
9. Indicator conflicts
10. Resource usage
## Best Practices
### Strategy Design
- Start with proven strategies (EMA crossover)
- Match timeframes to strategy type
- Balance indicator categories (trend + momentum + volume)
- Consider performance impact (<10 indicators)
### Validation
- Always validate before use
- Address all errors
- Consider warnings for optimization
- Test with edge cases
### Performance
- Limit complex indicators (Bollinger Bands)
- Monitor resource usage warnings
- Cache validated configurations
- Use appropriate timeframes for strategy type
## Testing Commands
```bash
# Test all chart components
uv run pytest tests/test_*_strategies.py -v
uv run pytest tests/test_validation.py -v
uv run pytest tests/test_defaults.py -v
# Test specific component
uv run pytest tests/test_example_strategies.py::TestEMACrossoverStrategy -v
```
## File Locations
- **Main config**: `components/charts/config/`
- **Documentation**: `docs/components/charts/`
- **Tests**: `tests/test_*_strategies.py`
- **Examples**: `components/charts/config/example_strategies.py`

View File

@ -12,6 +12,9 @@ Implementation of a flexible, strategy-driven chart system that supports technic
- `components/charts/config/indicator_defs.py` - Base indicator definitions, schemas, and default parameters
- `components/charts/config/strategy_charts.py` - Strategy-specific chart configurations and presets
- `components/charts/config/defaults.py` - Default chart configurations and fallback settings
- `components/charts/config/validation.py` - Configuration validation and error handling system
- `components/charts/config/example_strategies.py` - Real-world trading strategy examples (EMA crossover, momentum, etc.)
- `components/charts/config/error_handling.py` - Enhanced error handling and user guidance system
- `components/charts/layers/__init__.py` - Chart layers package initialization with base layer exports
- `components/charts/layers/base.py` - Base layer system with CandlestickLayer, VolumeLayer, and LayerManager
- `components/charts/layers/indicators.py` - Indicator overlay rendering (SMA, EMA, Bollinger Bands)
@ -22,6 +25,13 @@ Implementation of a flexible, strategy-driven chart system that supports technic
- `tests/test_chart_builder.py` - Unit tests for ChartBuilder class functionality
- `tests/test_chart_layers.py` - Unit tests for individual chart layer components
- `tests/test_chart_integration.py` - Integration tests for full chart creation workflow
- `tests/test_indicator_schema.py` - Schema validation tests (16 tests)
- `tests/test_defaults.py` - Defaults system tests (19 tests)
- `tests/test_strategy_charts.py` - Strategy configuration tests (28 tests)
- `tests/test_validation.py` - Validation system tests (28 tests)
- `tests/test_example_strategies.py` - Example strategy tests (20 tests)
- `tests/test_error_handling.py` - Error handling tests (28 tests)
- `tests/test_configuration_integration.py` - Comprehensive integration tests (18 tests)
### Notes
@ -51,16 +61,16 @@ Implementation of a flexible, strategy-driven chart system that supports technic
- [x] 2.6 Add MACD subplot with signal line and histogram
- [x] 2.7 Create indicator calculation integration with market data
- [x] 2.8 Add comprehensive error handling for insufficient data scenarios
- [ ] 2.9 Unit test all indicator layer components
- [x] 2.9 Unit test all indicator layer components
- [ ] 3.0 Strategy Configuration System
- [ ] 3.1 Design indicator definition schema and validation
- [ ] 3.2 Create default indicator configurations and parameters
- [ ] 3.3 Implement strategy-specific chart configuration system
- [ ] 3.4 Add configuration validation and error handling
- [ ] 3.5 Create example strategy configurations (EMA crossover, momentum)
- [ ] 3.6 Add configuration fallback mechanisms for missing strategies
- [ ] 3.7 Unit test configuration system and validation
- [x] 3.0 Strategy Configuration System
- [x] 3.1 Design indicator definition schema and validation
- [x] 3.2 Create default indicator configurations and parameters
- [x] 3.3 Implement strategy-specific chart configuration system
- [x] 3.4 Add configuration validation and error handling
- [x] 3.5 Create example strategy configurations (EMA crossover, momentum)
- [x] 3.6 Add enhanced error handling and user guidance for missing strategies and indicators
- [x] 3.7 Unit test configuration system and validation
- [ ] 4.0 Dashboard Integration and UI Controls
- [ ] 4.1 Add indicator selection checkboxes to dashboard layout

View File

@ -0,0 +1,519 @@
"""
Comprehensive Integration Tests for Configuration System
Tests the entire configuration system end-to-end, ensuring all components
work together seamlessly including validation, error handling, and strategy creation.
"""
import pytest
import json
from typing import Dict, List, Any
from components.charts.config import (
# Core configuration classes
StrategyChartConfig,
SubplotConfig,
SubplotType,
ChartStyle,
ChartLayout,
TradingStrategy,
IndicatorCategory,
# Configuration functions
create_custom_strategy_config,
validate_configuration,
validate_configuration_strict,
check_configuration_health,
# Example strategies
create_ema_crossover_strategy,
create_momentum_breakout_strategy,
create_mean_reversion_strategy,
create_scalping_strategy,
create_swing_trading_strategy,
get_all_example_strategies,
# Indicator management
get_all_default_indicators,
get_indicators_by_category,
create_indicator_config,
# Error handling
ErrorSeverity,
ConfigurationError,
validate_strategy_name,
get_indicator_suggestions,
# Validation
ValidationLevel,
ConfigurationValidator
)
class TestConfigurationSystemIntegration:
"""Test the entire configuration system working together."""
def test_complete_strategy_creation_workflow(self):
"""Test complete workflow from strategy creation to validation."""
# 1. Create a custom strategy configuration
config, errors = create_custom_strategy_config(
strategy_name="Integration Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="A comprehensive test strategy",
timeframes=["15m", "1h", "4h"],
overlay_indicators=["ema_12", "ema_26", "sma_50"],
subplot_configs=[
{
"subplot_type": "rsi",
"height_ratio": 0.25,
"indicators": ["rsi_14"],
"title": "RSI Momentum"
},
{
"subplot_type": "macd",
"height_ratio": 0.25,
"indicators": ["macd_12_26_9"],
"title": "MACD Convergence"
}
]
)
# 2. Validate configuration was created successfully
# Note: Config might be None if indicators don't exist in test environment
if config is not None:
assert config.strategy_name == "Integration Test Strategy"
assert len(config.overlay_indicators) == 3
assert len(config.subplot_configs) == 2
# 3. Validate the configuration using basic validation
is_valid, validation_errors = config.validate()
# 4. Perform strict validation
error_report = validate_configuration_strict(config)
# 5. Check configuration health
health_check = check_configuration_health(config)
assert "is_healthy" in health_check
assert "total_indicators" in health_check
else:
# Configuration failed to create - check that we got errors
assert len(errors) > 0
def test_example_strategies_integration(self):
"""Test all example strategies work with the validation system."""
strategies = get_all_example_strategies()
assert len(strategies) >= 5 # We created 5 example strategies
for strategy_name, strategy_example in strategies.items():
config = strategy_example.config
# Test configuration is valid
assert isinstance(config, StrategyChartConfig)
assert config.strategy_name is not None
assert config.strategy_type is not None
assert len(config.overlay_indicators) > 0 or len(config.subplot_configs) > 0
# Test validation passes (using the main validation function)
validation_report = validate_configuration(config)
# Note: May have warnings in test environment due to missing indicators
assert isinstance(validation_report.is_valid, bool)
# Test health check
health = check_configuration_health(config)
assert "is_healthy" in health
assert "total_indicators" in health
def test_indicator_system_integration(self):
"""Test indicator system integration with configurations."""
# Get all available indicators
indicators = get_all_default_indicators()
assert len(indicators) > 20 # Should have many indicators
# Test indicators by category
for category in IndicatorCategory:
category_indicators = get_indicators_by_category(category)
assert isinstance(category_indicators, dict)
# Test creating configurations for each indicator
for indicator_name, indicator_preset in list(category_indicators.items())[:3]: # Test first 3
# Test that indicator preset has required properties
assert hasattr(indicator_preset, 'config')
assert hasattr(indicator_preset, 'name')
assert hasattr(indicator_preset, 'category')
def test_error_handling_integration(self):
"""Test error handling integration across the system."""
# Test with invalid strategy name
error = validate_strategy_name("nonexistent_strategy")
assert error is not None
assert error.severity == ErrorSeverity.CRITICAL
assert len(error.suggestions) > 0
# Test with invalid configuration
invalid_config = StrategyChartConfig(
strategy_name="Invalid Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Strategy with missing indicators",
timeframes=["1h"],
overlay_indicators=["nonexistent_indicator_999"]
)
# Validate with strict validation
error_report = validate_configuration_strict(invalid_config)
assert not error_report.is_usable
assert len(error_report.missing_indicators) > 0
# Check that error handling provides suggestions
suggestions = get_indicator_suggestions("nonexistent")
assert isinstance(suggestions, list)
def test_validation_system_integration(self):
"""Test validation system with different validation approaches."""
# Create a configuration with potential issues
config = StrategyChartConfig(
strategy_name="Test Validation",
strategy_type=TradingStrategy.SCALPING,
description="Test strategy",
timeframes=["1d"], # Wrong timeframe for scalping
overlay_indicators=["ema_12", "sma_20"]
)
# Test main validation function
validation_report = validate_configuration(config)
assert isinstance(validation_report.is_valid, bool)
# Test strict validation
strict_report = validate_configuration_strict(config)
assert hasattr(strict_report, 'is_usable')
# Test basic validation
is_valid, errors = config.validate()
assert isinstance(is_valid, bool)
assert isinstance(errors, list)
def test_json_serialization_integration(self):
"""Test JSON serialization/deserialization of configurations."""
# Create a strategy
strategy = create_ema_crossover_strategy()
config = strategy.config
# Convert to dict (simulating JSON serialization)
config_dict = {
"strategy_name": config.strategy_name,
"strategy_type": config.strategy_type.value,
"description": config.description,
"timeframes": config.timeframes,
"overlay_indicators": config.overlay_indicators,
"subplot_configs": [
{
"subplot_type": subplot.subplot_type.value,
"height_ratio": subplot.height_ratio,
"indicators": subplot.indicators,
"title": subplot.title
}
for subplot in config.subplot_configs
]
}
# Verify serialization works
json_str = json.dumps(config_dict)
assert len(json_str) > 0
# Verify deserialization works
restored_dict = json.loads(json_str)
assert restored_dict["strategy_name"] == config.strategy_name
assert restored_dict["strategy_type"] == config.strategy_type.value
def test_configuration_modification_workflow(self):
"""Test modifying and re-validating configurations."""
# Start with a valid configuration
config = create_swing_trading_strategy().config
# Verify it's initially valid (may have issues due to missing indicators in test env)
initial_health = check_configuration_health(config)
assert "is_healthy" in initial_health
# Modify the configuration (add an invalid indicator)
config.overlay_indicators.append("invalid_indicator_999")
# Verify it's now invalid
modified_health = check_configuration_health(config)
assert not modified_health["is_healthy"]
assert modified_health["missing_indicators"] > 0
# Remove the invalid indicator
config.overlay_indicators.remove("invalid_indicator_999")
# Verify it's valid again (or at least better)
final_health = check_configuration_health(config)
# Note: May still have issues due to test environment
assert final_health["missing_indicators"] < modified_health["missing_indicators"]
def test_multi_timeframe_strategy_integration(self):
"""Test strategies with multiple timeframes."""
config, errors = create_custom_strategy_config(
strategy_name="Multi-Timeframe Strategy",
strategy_type=TradingStrategy.SWING_TRADING,
description="Strategy using multiple timeframes",
timeframes=["1h", "4h", "1d"],
overlay_indicators=["ema_21", "sma_50", "sma_200"],
subplot_configs=[
{
"subplot_type": "rsi",
"height_ratio": 0.2,
"indicators": ["rsi_14"],
"title": "RSI (14)"
}
]
)
if config is not None:
assert len(config.timeframes) == 3
# Validate the multi-timeframe strategy
validation_report = validate_configuration(config)
health_check = check_configuration_health(config)
# Should be valid and healthy (or at least structured correctly)
assert isinstance(validation_report.is_valid, bool)
assert "total_indicators" in health_check
else:
# Configuration failed - check we got errors
assert len(errors) > 0
def test_strategy_type_consistency_integration(self):
"""Test strategy type consistency validation across the system."""
test_cases = [
{
"strategy_type": TradingStrategy.SCALPING,
"timeframes": ["1m", "5m"],
"expected_consistent": True
},
{
"strategy_type": TradingStrategy.SCALPING,
"timeframes": ["1d", "1w"],
"expected_consistent": False
},
{
"strategy_type": TradingStrategy.SWING_TRADING,
"timeframes": ["4h", "1d"],
"expected_consistent": True
},
{
"strategy_type": TradingStrategy.SWING_TRADING,
"timeframes": ["1m", "5m"],
"expected_consistent": False
}
]
for case in test_cases:
config = StrategyChartConfig(
strategy_name=f"Test {case['strategy_type'].value}",
strategy_type=case["strategy_type"],
description="Test strategy for consistency",
timeframes=case["timeframes"],
overlay_indicators=["ema_12", "sma_20"]
)
# Check validation report
validation_report = validate_configuration(config)
error_report = validate_configuration_strict(config)
# Just verify the system processes the configurations
assert isinstance(validation_report.is_valid, bool)
assert hasattr(error_report, 'is_usable')
class TestConfigurationSystemPerformance:
"""Test performance and scalability of the configuration system."""
def test_large_configuration_performance(self):
"""Test system performance with large configurations."""
# Create a configuration with many indicators
large_config, errors = create_custom_strategy_config(
strategy_name="Large Configuration Test",
strategy_type=TradingStrategy.DAY_TRADING,
description="Strategy with many indicators",
timeframes=["5m", "15m", "1h", "4h"],
overlay_indicators=[
"ema_12", "ema_26", "ema_50", "sma_20", "sma_50", "sma_200"
],
subplot_configs=[
{
"subplot_type": "rsi",
"height_ratio": 0.15,
"indicators": ["rsi_7", "rsi_14", "rsi_21"],
"title": "RSI Multi-Period"
},
{
"subplot_type": "macd",
"height_ratio": 0.15,
"indicators": ["macd_12_26_9"],
"title": "MACD"
}
]
)
if large_config is not None:
assert len(large_config.overlay_indicators) == 6
assert len(large_config.subplot_configs) == 2
# Validate performance is acceptable
import time
start_time = time.time()
# Perform multiple operations
for _ in range(10):
validate_configuration_strict(large_config)
check_configuration_health(large_config)
end_time = time.time()
execution_time = end_time - start_time
# Should complete in reasonable time (less than 5 seconds for 10 iterations)
assert execution_time < 5.0
else:
# Large configuration failed - verify we got errors
assert len(errors) > 0
def test_multiple_strategies_performance(self):
"""Test performance when working with multiple strategies."""
# Get all example strategies
strategies = get_all_example_strategies()
# Time the validation of all strategies
import time
start_time = time.time()
for strategy_name, strategy_example in strategies.items():
config = strategy_example.config
validate_configuration_strict(config)
check_configuration_health(config)
end_time = time.time()
execution_time = end_time - start_time
# Should complete in reasonable time
assert execution_time < 3.0
class TestConfigurationSystemRobustness:
"""Test system robustness and edge cases."""
def test_empty_configuration_handling(self):
"""Test handling of empty configurations."""
empty_config = StrategyChartConfig(
strategy_name="Empty Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Empty strategy",
timeframes=["1h"],
overlay_indicators=[],
subplot_configs=[]
)
# System should handle empty config gracefully
error_report = validate_configuration_strict(empty_config)
assert not error_report.is_usable # Should be unusable
assert len(error_report.errors) > 0 # Should have errors
health_check = check_configuration_health(empty_config)
assert not health_check["is_healthy"]
assert health_check["total_indicators"] == 0
def test_invalid_data_handling(self):
"""Test handling of invalid data types and values."""
# Test with None values - basic validation
try:
config = StrategyChartConfig(
strategy_name="Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Test with edge cases",
timeframes=["1h"],
overlay_indicators=["ema_12"]
)
# Should handle gracefully
error_report = validate_configuration_strict(config)
assert isinstance(error_report.is_usable, bool)
except (TypeError, ValueError):
# Also acceptable to raise an error
pass
def test_configuration_boundary_cases(self):
"""Test boundary cases in configuration."""
# Test with single indicator
minimal_config = StrategyChartConfig(
strategy_name="Minimal Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Minimal viable strategy",
timeframes=["1h"],
overlay_indicators=["ema_12"]
)
error_report = validate_configuration_strict(minimal_config)
health_check = check_configuration_health(minimal_config)
# Should be processed without crashing
assert isinstance(error_report.is_usable, bool)
assert health_check["total_indicators"] >= 0
assert len(health_check["recommendations"]) >= 0
def test_configuration_versioning_compatibility(self):
"""Test that configurations are forward/backward compatible."""
# Create a basic configuration
config = create_ema_crossover_strategy().config
# Verify all required fields are present
required_fields = [
'strategy_name', 'strategy_type', 'description',
'timeframes', 'overlay_indicators', 'subplot_configs'
]
for field in required_fields:
assert hasattr(config, field)
assert getattr(config, field) is not None
class TestConfigurationSystemDocumentation:
"""Test that configuration system is well-documented and discoverable."""
def test_available_indicators_discovery(self):
"""Test that available indicators can be discovered."""
indicators = get_all_default_indicators()
assert len(indicators) > 0
# Test that indicators are categorized
for category in IndicatorCategory:
category_indicators = get_indicators_by_category(category)
assert isinstance(category_indicators, dict)
def test_available_strategies_discovery(self):
"""Test that available strategies can be discovered."""
strategies = get_all_example_strategies()
assert len(strategies) >= 5
# Each strategy should have required metadata
for strategy_name, strategy_example in strategies.items():
# Check for core attributes (these are the actual attributes)
assert hasattr(strategy_example, 'config')
assert hasattr(strategy_example, 'description')
assert hasattr(strategy_example, 'difficulty')
assert hasattr(strategy_example, 'risk_level')
assert hasattr(strategy_example, 'author')
def test_error_message_quality(self):
"""Test that error messages are helpful and informative."""
# Test missing strategy error
error = validate_strategy_name("nonexistent_strategy")
assert error is not None
assert len(error.message) > 10 # Should be descriptive
assert len(error.suggestions) > 0 # Should have suggestions
assert len(error.recovery_steps) > 0 # Should have recovery steps
# Test missing indicator suggestions
suggestions = get_indicator_suggestions("nonexistent_indicator")
assert isinstance(suggestions, list)
if __name__ == "__main__":
pytest.main([__file__, "-v"])

366
tests/test_defaults.py Normal file
View File

@ -0,0 +1,366 @@
"""
Tests for Default Indicator Configurations System
Tests the comprehensive default indicator configurations, categories,
trading strategies, and preset management functionality.
"""
import pytest
from typing import Dict, Any
from components.charts.config.defaults import (
IndicatorCategory,
TradingStrategy,
IndicatorPreset,
CATEGORY_COLORS,
create_trend_indicators,
create_momentum_indicators,
create_volatility_indicators,
create_strategy_presets,
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
)
from components.charts.config.indicator_defs import (
ChartIndicatorConfig,
validate_indicator_configuration
)
class TestIndicatorCategories:
"""Test indicator category functionality."""
def test_trend_indicators_creation(self):
"""Test creation of trend indicators."""
trend_indicators = create_trend_indicators()
# Should have multiple SMA and EMA configurations
assert len(trend_indicators) > 10
# Check specific indicators exist
assert "sma_20" in trend_indicators
assert "sma_50" in trend_indicators
assert "ema_12" in trend_indicators
assert "ema_26" in trend_indicators
# Validate all configurations
for name, preset in trend_indicators.items():
assert isinstance(preset, IndicatorPreset)
assert preset.category == IndicatorCategory.TREND
# Validate the actual configuration
is_valid, errors = validate_indicator_configuration(preset.config)
assert is_valid, f"Invalid trend indicator {name}: {errors}"
def test_momentum_indicators_creation(self):
"""Test creation of momentum indicators."""
momentum_indicators = create_momentum_indicators()
# Should have multiple RSI and MACD configurations
assert len(momentum_indicators) > 8
# Check specific indicators exist
assert "rsi_14" in momentum_indicators
assert "macd_12_26_9" in momentum_indicators
# Validate all configurations
for name, preset in momentum_indicators.items():
assert isinstance(preset, IndicatorPreset)
assert preset.category == IndicatorCategory.MOMENTUM
is_valid, errors = validate_indicator_configuration(preset.config)
assert is_valid, f"Invalid momentum indicator {name}: {errors}"
def test_volatility_indicators_creation(self):
"""Test creation of volatility indicators."""
volatility_indicators = create_volatility_indicators()
# Should have multiple Bollinger Bands configurations
assert len(volatility_indicators) > 3
# Check specific indicators exist
assert "bb_20_20" in volatility_indicators
# Validate all configurations
for name, preset in volatility_indicators.items():
assert isinstance(preset, IndicatorPreset)
assert preset.category == IndicatorCategory.VOLATILITY
is_valid, errors = validate_indicator_configuration(preset.config)
assert is_valid, f"Invalid volatility indicator {name}: {errors}"
class TestStrategyPresets:
"""Test trading strategy preset functionality."""
def test_strategy_presets_creation(self):
"""Test creation of strategy presets."""
strategy_presets = create_strategy_presets()
# Should have all strategy types
expected_strategies = [strategy.value for strategy in TradingStrategy]
for strategy in expected_strategies:
assert strategy in strategy_presets
preset = strategy_presets[strategy]
assert "name" in preset
assert "description" in preset
assert "timeframes" in preset
assert "indicators" in preset
assert len(preset["indicators"]) > 0
def test_get_strategy_indicators(self):
"""Test getting indicators for specific strategies."""
scalping_indicators = get_strategy_indicators(TradingStrategy.SCALPING)
assert len(scalping_indicators) > 0
assert "ema_5" in scalping_indicators
assert "rsi_7" in scalping_indicators
day_trading_indicators = get_strategy_indicators(TradingStrategy.DAY_TRADING)
assert len(day_trading_indicators) > 0
assert "sma_20" in day_trading_indicators
assert "rsi_14" in day_trading_indicators
def test_get_strategy_info(self):
"""Test getting complete strategy information."""
scalping_info = get_strategy_info(TradingStrategy.SCALPING)
assert "name" in scalping_info
assert "description" in scalping_info
assert "timeframes" in scalping_info
assert "indicators" in scalping_info
assert "1m" in scalping_info["timeframes"]
assert "5m" in scalping_info["timeframes"]
class TestDefaultIndicators:
"""Test default indicator functionality."""
def test_get_all_default_indicators(self):
"""Test getting all default indicators."""
all_indicators = get_all_default_indicators()
# Should have indicators from all categories
assert len(all_indicators) > 20
# Validate all indicators
for name, preset in all_indicators.items():
assert isinstance(preset, IndicatorPreset)
assert preset.category in [cat for cat in IndicatorCategory]
is_valid, errors = validate_indicator_configuration(preset.config)
assert is_valid, f"Invalid default indicator {name}: {errors}"
def test_get_indicators_by_category(self):
"""Test filtering indicators by category."""
trend_indicators = get_indicators_by_category(IndicatorCategory.TREND)
momentum_indicators = get_indicators_by_category(IndicatorCategory.MOMENTUM)
volatility_indicators = get_indicators_by_category(IndicatorCategory.VOLATILITY)
# All should have indicators
assert len(trend_indicators) > 0
assert len(momentum_indicators) > 0
assert len(volatility_indicators) > 0
# Check categories are correct
for preset in trend_indicators.values():
assert preset.category == IndicatorCategory.TREND
for preset in momentum_indicators.values():
assert preset.category == IndicatorCategory.MOMENTUM
for preset in volatility_indicators.values():
assert preset.category == IndicatorCategory.VOLATILITY
def test_get_indicators_for_timeframe(self):
"""Test filtering indicators by timeframe."""
scalping_indicators = get_indicators_for_timeframe("1m")
day_trading_indicators = get_indicators_for_timeframe("1h")
position_indicators = get_indicators_for_timeframe("1d")
# All should have some indicators
assert len(scalping_indicators) > 0
assert len(day_trading_indicators) > 0
assert len(position_indicators) > 0
# Check timeframes are included
for preset in scalping_indicators.values():
assert "1m" in preset.recommended_timeframes
for preset in day_trading_indicators.values():
assert "1h" in preset.recommended_timeframes
class TestUtilityFunctions:
"""Test utility functions for defaults system."""
def test_get_available_strategies(self):
"""Test getting available trading strategies."""
strategies = get_available_strategies()
# Should have all strategy types
assert len(strategies) == len(TradingStrategy)
for strategy in strategies:
assert "value" in strategy
assert "name" in strategy
assert "description" in strategy
assert "timeframes" in strategy
def test_get_available_categories(self):
"""Test getting available indicator categories."""
categories = get_available_categories()
# Should have all category types
assert len(categories) == len(IndicatorCategory)
for category in categories:
assert "value" in category
assert "name" in category
assert "description" in category
def test_create_custom_preset(self):
"""Test creating custom indicator presets."""
custom_configs = [
{
"name": "Custom SMA",
"indicator_type": "sma",
"parameters": {"period": 15},
"color": "#123456"
},
{
"name": "Custom RSI",
"indicator_type": "rsi",
"parameters": {"period": 10},
"color": "#654321"
}
]
custom_presets = create_custom_preset(
name="Test Custom",
description="Test custom preset",
category=IndicatorCategory.TREND,
indicator_configs=custom_configs,
recommended_timeframes=["5m", "15m"]
)
# Should create presets for valid configurations
assert len(custom_presets) == 2
for preset in custom_presets.values():
assert preset.category == IndicatorCategory.TREND
assert "5m" in preset.recommended_timeframes
assert "15m" in preset.recommended_timeframes
class TestColorSchemes:
"""Test color scheme functionality."""
def test_category_colors_exist(self):
"""Test that color schemes exist for categories."""
required_categories = [
IndicatorCategory.TREND,
IndicatorCategory.MOMENTUM,
IndicatorCategory.VOLATILITY
]
for category in required_categories:
assert category in CATEGORY_COLORS
colors = CATEGORY_COLORS[category]
# Should have multiple color options
assert "primary" in colors
assert "secondary" in colors
assert "tertiary" in colors
assert "quaternary" in colors
# Colors should be valid hex codes
for color_name, color_value in colors.items():
assert color_value.startswith("#")
assert len(color_value) == 7
class TestIntegration:
"""Test integration with existing systems."""
def test_default_indicators_match_schema(self):
"""Test that default indicators match their schemas."""
all_indicators = get_all_default_indicators()
for name, preset in all_indicators.items():
config = preset.config
# Should validate against schema
is_valid, errors = validate_indicator_configuration(config)
assert is_valid, f"Default indicator {name} validation failed: {errors}"
def test_strategy_indicators_exist_in_defaults(self):
"""Test that strategy indicators exist in default configurations."""
all_indicators = get_all_default_indicators()
for strategy in TradingStrategy:
strategy_indicators = get_strategy_indicators(strategy)
for indicator_name in strategy_indicators:
# Each strategy indicator should exist in defaults
# Note: Some might not exist yet, but most should
if indicator_name in all_indicators:
preset = all_indicators[indicator_name]
assert isinstance(preset, IndicatorPreset)
def test_timeframe_recommendations_valid(self):
"""Test that timeframe recommendations are valid."""
all_indicators = get_all_default_indicators()
valid_timeframes = ["1m", "5m", "15m", "1h", "4h", "1d", "1w"]
for name, preset in all_indicators.items():
for timeframe in preset.recommended_timeframes:
assert timeframe in valid_timeframes, f"Invalid timeframe {timeframe} for {name}"
class TestPresetValidation:
"""Test that all presets are properly validated."""
def test_all_trend_indicators_valid(self):
"""Test that all trend indicators are valid."""
trend_indicators = create_trend_indicators()
for name, preset in trend_indicators.items():
# Test the preset structure
assert isinstance(preset.name, str)
assert isinstance(preset.description, str)
assert preset.category == IndicatorCategory.TREND
assert isinstance(preset.recommended_timeframes, list)
assert len(preset.recommended_timeframes) > 0
# Test the configuration
config = preset.config
is_valid, errors = validate_indicator_configuration(config)
assert is_valid, f"Trend indicator {name} failed validation: {errors}"
def test_all_momentum_indicators_valid(self):
"""Test that all momentum indicators are valid."""
momentum_indicators = create_momentum_indicators()
for name, preset in momentum_indicators.items():
config = preset.config
is_valid, errors = validate_indicator_configuration(config)
assert is_valid, f"Momentum indicator {name} failed validation: {errors}"
def test_all_volatility_indicators_valid(self):
"""Test that all volatility indicators are valid."""
volatility_indicators = create_volatility_indicators()
for name, preset in volatility_indicators.items():
config = preset.config
is_valid, errors = validate_indicator_configuration(config)
assert is_valid, f"Volatility indicator {name} failed validation: {errors}"
if __name__ == "__main__":
pytest.main([__file__])

View File

@ -0,0 +1,570 @@
"""
Tests for Enhanced Error Handling and User Guidance System
Tests the comprehensive error handling system including error detection,
suggestions, recovery guidance, and configuration validation.
"""
import pytest
from typing import Set, List
from components.charts.config.error_handling import (
ErrorSeverity,
ErrorCategory,
ConfigurationError,
ErrorReport,
ConfigurationErrorHandler,
validate_configuration_strict,
validate_strategy_name,
get_indicator_suggestions,
get_strategy_suggestions,
check_configuration_health
)
from components.charts.config.strategy_charts import (
StrategyChartConfig,
SubplotConfig,
ChartStyle,
ChartLayout,
SubplotType
)
from components.charts.config.defaults import TradingStrategy
class TestConfigurationError:
"""Test ConfigurationError class."""
def test_configuration_error_creation(self):
"""Test ConfigurationError creation with all fields."""
error = ConfigurationError(
category=ErrorCategory.MISSING_INDICATOR,
severity=ErrorSeverity.HIGH,
message="Test error message",
field_path="overlay_indicators[ema_99]",
missing_item="ema_99",
suggestions=["Use ema_12 instead", "Try different period"],
alternatives=["ema_12", "ema_26"],
recovery_steps=["Replace with ema_12", "Check available indicators"]
)
assert error.category == ErrorCategory.MISSING_INDICATOR
assert error.severity == ErrorSeverity.HIGH
assert error.message == "Test error message"
assert error.field_path == "overlay_indicators[ema_99]"
assert error.missing_item == "ema_99"
assert len(error.suggestions) == 2
assert len(error.alternatives) == 2
assert len(error.recovery_steps) == 2
def test_configuration_error_string_representation(self):
"""Test string representation with emojis and formatting."""
error = ConfigurationError(
category=ErrorCategory.MISSING_INDICATOR,
severity=ErrorSeverity.CRITICAL,
message="Indicator 'ema_99' not found",
suggestions=["Use ema_12"],
alternatives=["ema_12", "ema_26"],
recovery_steps=["Replace with available indicator"]
)
error_str = str(error)
assert "🚨" in error_str # Critical severity emoji
assert "Indicator 'ema_99' not found" in error_str
assert "💡 Suggestions:" in error_str
assert "🔄 Alternatives:" in error_str
assert "🔧 Recovery steps:" in error_str
class TestErrorReport:
"""Test ErrorReport class."""
def test_error_report_creation(self):
"""Test ErrorReport creation and basic functionality."""
report = ErrorReport(is_usable=True)
assert report.is_usable is True
assert len(report.errors) == 0
assert len(report.missing_strategies) == 0
assert len(report.missing_indicators) == 0
assert report.report_time is not None
def test_add_error_updates_usability(self):
"""Test that adding critical/high errors updates usability."""
report = ErrorReport(is_usable=True)
# Add medium error - should remain usable
medium_error = ConfigurationError(
category=ErrorCategory.INVALID_PARAMETER,
severity=ErrorSeverity.MEDIUM,
message="Medium error"
)
report.add_error(medium_error)
assert report.is_usable is True
# Add critical error - should become unusable
critical_error = ConfigurationError(
category=ErrorCategory.MISSING_STRATEGY,
severity=ErrorSeverity.CRITICAL,
message="Critical error",
missing_item="test_strategy"
)
report.add_error(critical_error)
assert report.is_usable is False
assert "test_strategy" in report.missing_strategies
def test_add_missing_indicator_tracking(self):
"""Test tracking of missing indicators."""
report = ErrorReport(is_usable=True)
error = ConfigurationError(
category=ErrorCategory.MISSING_INDICATOR,
severity=ErrorSeverity.HIGH,
message="Indicator missing",
missing_item="ema_99"
)
report.add_error(error)
assert "ema_99" in report.missing_indicators
assert report.is_usable is False # High severity
def test_get_critical_and_high_priority_errors(self):
"""Test filtering errors by severity."""
report = ErrorReport(is_usable=True)
# Add different severity errors
report.add_error(ConfigurationError(
category=ErrorCategory.MISSING_INDICATOR,
severity=ErrorSeverity.CRITICAL,
message="Critical error"
))
report.add_error(ConfigurationError(
category=ErrorCategory.MISSING_INDICATOR,
severity=ErrorSeverity.HIGH,
message="High error"
))
report.add_error(ConfigurationError(
category=ErrorCategory.INVALID_PARAMETER,
severity=ErrorSeverity.MEDIUM,
message="Medium error"
))
critical_errors = report.get_critical_errors()
high_errors = report.get_high_priority_errors()
assert len(critical_errors) == 1
assert len(high_errors) == 1
assert critical_errors[0].message == "Critical error"
assert high_errors[0].message == "High error"
def test_summary_generation(self):
"""Test error report summary."""
# Empty report
empty_report = ErrorReport(is_usable=True)
assert "✅ No configuration errors found" in empty_report.summary()
# Report with errors
report = ErrorReport(is_usable=False)
report.add_error(ConfigurationError(
category=ErrorCategory.MISSING_INDICATOR,
severity=ErrorSeverity.CRITICAL,
message="Critical error"
))
report.add_error(ConfigurationError(
category=ErrorCategory.INVALID_PARAMETER,
severity=ErrorSeverity.MEDIUM,
message="Medium error"
))
summary = report.summary()
assert "❌ Cannot proceed" in summary
assert "2 errors" in summary
assert "1 critical" in summary
class TestConfigurationErrorHandler:
"""Test ConfigurationErrorHandler class."""
def test_handler_initialization(self):
"""Test error handler initialization."""
handler = ConfigurationErrorHandler()
assert len(handler.indicator_names) > 0
assert len(handler.strategy_names) > 0
assert "ema_12" in handler.indicator_names
assert "ema_crossover" in handler.strategy_names
def test_validate_existing_strategy(self):
"""Test validation of existing strategy."""
handler = ConfigurationErrorHandler()
# Test existing strategy
error = handler.validate_strategy_exists("ema_crossover")
assert error is None
def test_validate_missing_strategy(self):
"""Test validation of missing strategy with suggestions."""
handler = ConfigurationErrorHandler()
# Test missing strategy
error = handler.validate_strategy_exists("non_existent_strategy")
assert error is not None
assert error.category == ErrorCategory.MISSING_STRATEGY
assert error.severity == ErrorSeverity.CRITICAL
assert "non_existent_strategy" in error.message
assert len(error.recovery_steps) > 0
def test_validate_similar_strategy_name(self):
"""Test suggestions for similar strategy names."""
handler = ConfigurationErrorHandler()
# Test typo in strategy name
error = handler.validate_strategy_exists("ema_cross") # Similar to "ema_crossover"
assert error is not None
assert len(error.alternatives) > 0
assert "ema_crossover" in error.alternatives or any("ema" in alt for alt in error.alternatives)
def test_validate_existing_indicator(self):
"""Test validation of existing indicator."""
handler = ConfigurationErrorHandler()
# Test existing indicator
error = handler.validate_indicator_exists("ema_12")
assert error is None
def test_validate_missing_indicator(self):
"""Test validation of missing indicator with suggestions."""
handler = ConfigurationErrorHandler()
# Test missing indicator
error = handler.validate_indicator_exists("ema_999")
assert error is not None
assert error.category == ErrorCategory.MISSING_INDICATOR
assert error.severity in [ErrorSeverity.CRITICAL, ErrorSeverity.HIGH]
assert "ema_999" in error.message
assert len(error.recovery_steps) > 0
def test_indicator_category_suggestions(self):
"""Test category-based suggestions for missing indicators."""
handler = ConfigurationErrorHandler()
# Test SMA suggestion
sma_error = handler.validate_indicator_exists("sma_999")
assert sma_error is not None
# Check for SMA-related suggestions in any form
assert any("sma" in suggestion.lower() or "trend" in suggestion.lower()
for suggestion in sma_error.suggestions)
# Test RSI suggestion
rsi_error = handler.validate_indicator_exists("rsi_999")
assert rsi_error is not None
# Check that RSI alternatives contain actual RSI indicators
assert any("rsi_" in alternative for alternative in rsi_error.alternatives)
# Test MACD suggestion
macd_error = handler.validate_indicator_exists("macd_999")
assert macd_error is not None
# Check that MACD alternatives contain actual MACD indicators
assert any("macd_" in alternative for alternative in macd_error.alternatives)
def test_validate_strategy_configuration_empty(self):
"""Test validation of empty configuration."""
handler = ConfigurationErrorHandler()
# Empty configuration
config = StrategyChartConfig(
strategy_name="Empty Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Empty strategy",
timeframes=["1h"],
overlay_indicators=[],
subplot_configs=[]
)
report = handler.validate_strategy_configuration(config)
assert not report.is_usable
assert len(report.errors) > 0
assert any(error.category == ErrorCategory.CONFIGURATION_CORRUPT
for error in report.errors)
def test_validate_strategy_configuration_with_missing_indicators(self):
"""Test validation with missing indicators."""
handler = ConfigurationErrorHandler()
config = StrategyChartConfig(
strategy_name="Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Test strategy",
timeframes=["1h"],
overlay_indicators=["ema_999", "sma_888"], # Missing indicators
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
indicators=["rsi_777"] # Missing indicator
)
]
)
report = handler.validate_strategy_configuration(config)
assert not report.is_usable
assert len(report.missing_indicators) == 3
assert "ema_999" in report.missing_indicators
assert "sma_888" in report.missing_indicators
assert "rsi_777" in report.missing_indicators
def test_strategy_consistency_validation(self):
"""Test strategy type consistency validation."""
handler = ConfigurationErrorHandler()
# Scalping strategy with wrong timeframes
config = StrategyChartConfig(
strategy_name="Scalping Strategy",
strategy_type=TradingStrategy.SCALPING,
description="Scalping strategy",
timeframes=["1d", "1w"], # Wrong for scalping
overlay_indicators=["ema_12"]
)
report = handler.validate_strategy_configuration(config)
# Should have consistency warning
consistency_errors = [e for e in report.errors
if e.category == ErrorCategory.INVALID_PARAMETER]
assert len(consistency_errors) > 0
def test_suggest_alternatives_for_missing_indicators(self):
"""Test alternative suggestions for missing indicators."""
handler = ConfigurationErrorHandler()
missing_indicators = {"ema_999", "rsi_777", "unknown_indicator"}
suggestions = handler.suggest_alternatives_for_missing_indicators(missing_indicators)
assert "ema_999" in suggestions
assert "rsi_777" in suggestions
# Should have EMA alternatives for ema_999
assert any("ema_" in alt for alt in suggestions.get("ema_999", []))
# Should have RSI alternatives for rsi_777
assert any("rsi_" in alt for alt in suggestions.get("rsi_777", []))
class TestUtilityFunctions:
"""Test utility functions."""
def test_validate_configuration_strict(self):
"""Test strict configuration validation."""
# Valid configuration
valid_config = StrategyChartConfig(
strategy_name="Valid Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Valid strategy",
timeframes=["1h"],
overlay_indicators=["ema_12", "sma_20"]
)
report = validate_configuration_strict(valid_config)
assert report.is_usable
# Invalid configuration
invalid_config = StrategyChartConfig(
strategy_name="Invalid Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Invalid strategy",
timeframes=["1h"],
overlay_indicators=["ema_999"] # Missing indicator
)
report = validate_configuration_strict(invalid_config)
assert not report.is_usable
assert len(report.missing_indicators) > 0
def test_validate_strategy_name_function(self):
"""Test strategy name validation function."""
# Valid strategy
error = validate_strategy_name("ema_crossover")
assert error is None
# Invalid strategy
error = validate_strategy_name("non_existent_strategy")
assert error is not None
assert error.category == ErrorCategory.MISSING_STRATEGY
def test_get_indicator_suggestions(self):
"""Test indicator suggestions."""
# Test exact match suggestions
suggestions = get_indicator_suggestions("ema")
assert len(suggestions) > 0
assert any("ema_" in suggestion for suggestion in suggestions)
# Test partial match
suggestions = get_indicator_suggestions("ema_1")
assert len(suggestions) > 0
# Test no match
suggestions = get_indicator_suggestions("xyz_999")
# Should return some suggestions even for no match
assert isinstance(suggestions, list)
def test_get_strategy_suggestions(self):
"""Test strategy suggestions."""
# Test exact match suggestions
suggestions = get_strategy_suggestions("ema")
assert len(suggestions) > 0
# Test partial match
suggestions = get_strategy_suggestions("cross")
assert len(suggestions) > 0
# Test no match
suggestions = get_strategy_suggestions("xyz_999")
assert isinstance(suggestions, list)
def test_check_configuration_health(self):
"""Test configuration health check."""
# Healthy configuration
healthy_config = StrategyChartConfig(
strategy_name="Healthy Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Healthy strategy",
timeframes=["1h"],
overlay_indicators=["ema_12", "sma_20"],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
indicators=["rsi_14"]
)
]
)
health = check_configuration_health(healthy_config)
assert "is_healthy" in health
assert "error_report" in health
assert "total_indicators" in health
assert "has_trend_indicators" in health
assert "has_momentum_indicators" in health
assert "recommendations" in health
assert health["total_indicators"] == 3
assert health["has_trend_indicators"] is True
assert health["has_momentum_indicators"] is True
# Unhealthy configuration
unhealthy_config = StrategyChartConfig(
strategy_name="Unhealthy Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Unhealthy strategy",
timeframes=["1h"],
overlay_indicators=["ema_999"] # Missing indicator
)
health = check_configuration_health(unhealthy_config)
assert health["is_healthy"] is False
assert health["missing_indicators"] > 0
assert len(health["recommendations"]) > 0
class TestErrorSeverityAndCategories:
"""Test error severity and category enums."""
def test_error_severity_values(self):
"""Test ErrorSeverity enum values."""
assert ErrorSeverity.CRITICAL == "critical"
assert ErrorSeverity.HIGH == "high"
assert ErrorSeverity.MEDIUM == "medium"
assert ErrorSeverity.LOW == "low"
def test_error_category_values(self):
"""Test ErrorCategory enum values."""
assert ErrorCategory.MISSING_STRATEGY == "missing_strategy"
assert ErrorCategory.MISSING_INDICATOR == "missing_indicator"
assert ErrorCategory.INVALID_PARAMETER == "invalid_parameter"
assert ErrorCategory.DEPENDENCY_MISSING == "dependency_missing"
assert ErrorCategory.CONFIGURATION_CORRUPT == "configuration_corrupt"
class TestRecoveryGeneration:
"""Test recovery configuration generation."""
def test_recovery_configuration_generation(self):
"""Test generating recovery configurations."""
handler = ConfigurationErrorHandler()
# Configuration with missing indicators
config = StrategyChartConfig(
strategy_name="Broken Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Strategy with missing indicators",
timeframes=["1h"],
overlay_indicators=["ema_999", "ema_12"], # One missing, one valid
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
indicators=["rsi_777"] # Missing
)
]
)
# Validate to get error report
error_report = handler.validate_strategy_configuration(config)
# Generate recovery
recovery_config, recovery_notes = handler.generate_recovery_configuration(config, error_report)
assert recovery_config is not None
assert len(recovery_notes) > 0
assert "(Recovery)" in recovery_config.strategy_name
# Should have valid indicators only
for indicator in recovery_config.overlay_indicators:
assert indicator in handler.indicator_names
for subplot in recovery_config.subplot_configs:
for indicator in subplot.indicators:
assert indicator in handler.indicator_names
class TestIntegrationWithExistingSystems:
"""Test integration with existing validation and configuration systems."""
def test_integration_with_strategy_validation(self):
"""Test integration with existing strategy validation."""
from components.charts.config import create_ema_crossover_strategy
# Get a known good strategy
strategy = create_ema_crossover_strategy()
config = strategy.config
# Test with error handler
report = validate_configuration_strict(config)
# Should be usable (might have warnings about missing indicators in test environment)
assert isinstance(report, ErrorReport)
assert hasattr(report, 'is_usable')
assert hasattr(report, 'errors')
def test_error_handling_with_custom_configuration(self):
"""Test error handling with custom configurations."""
from components.charts.config import create_custom_strategy_config
# Try to create config with missing indicators
config, errors = create_custom_strategy_config(
strategy_name="Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Test strategy",
timeframes=["1h"],
overlay_indicators=["ema_999"], # Missing indicator
subplot_configs=[{
"subplot_type": "rsi",
"height_ratio": 0.2,
"indicators": ["rsi_777"] # Missing indicator
}]
)
if config: # If config was created despite missing indicators
report = validate_configuration_strict(config)
assert not report.is_usable
assert len(report.missing_indicators) > 0
if __name__ == "__main__":
pytest.main([__file__])

View File

@ -0,0 +1,537 @@
"""
Tests for Example Strategy Configurations
Tests the example trading strategies including EMA crossover, momentum,
mean reversion, scalping, and swing trading strategies.
"""
import pytest
import json
from typing import Dict, List
from components.charts.config.example_strategies import (
StrategyExample,
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 components.charts.config.strategy_charts import StrategyChartConfig
from components.charts.config.defaults import TradingStrategy
class TestStrategyExample:
"""Test StrategyExample dataclass."""
def test_strategy_example_creation(self):
"""Test StrategyExample creation with defaults."""
# Create a minimal config for testing
config = StrategyChartConfig(
strategy_name="Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Test strategy",
timeframes=["1h"]
)
example = StrategyExample(
config=config,
description="Test description"
)
assert example.config == config
assert example.description == "Test description"
assert example.author == "TCPDashboard"
assert example.difficulty == "Beginner"
assert example.risk_level == "Medium"
assert example.market_conditions == ["Trending"] # Default
assert example.notes == [] # Default
assert example.references == [] # Default
def test_strategy_example_with_custom_values(self):
"""Test StrategyExample with custom values."""
config = StrategyChartConfig(
strategy_name="Custom Strategy",
strategy_type=TradingStrategy.SCALPING,
description="Custom strategy",
timeframes=["1m"]
)
example = StrategyExample(
config=config,
description="Custom description",
author="Custom Author",
difficulty="Advanced",
expected_return="10% monthly",
risk_level="High",
market_conditions=["Volatile", "High Volume"],
notes=["Note 1", "Note 2"],
references=["Reference 1"]
)
assert example.author == "Custom Author"
assert example.difficulty == "Advanced"
assert example.expected_return == "10% monthly"
assert example.risk_level == "High"
assert example.market_conditions == ["Volatile", "High Volume"]
assert example.notes == ["Note 1", "Note 2"]
assert example.references == ["Reference 1"]
class TestEMACrossoverStrategy:
"""Test EMA Crossover strategy."""
def test_ema_crossover_creation(self):
"""Test EMA crossover strategy creation."""
strategy = create_ema_crossover_strategy()
assert isinstance(strategy, StrategyExample)
assert isinstance(strategy.config, StrategyChartConfig)
# Check strategy specifics
assert strategy.config.strategy_name == "EMA Crossover Strategy"
assert strategy.config.strategy_type == TradingStrategy.DAY_TRADING
assert "15m" in strategy.config.timeframes
assert "1h" in strategy.config.timeframes
assert "4h" in strategy.config.timeframes
# Check indicators
assert "ema_12" in strategy.config.overlay_indicators
assert "ema_26" in strategy.config.overlay_indicators
assert "ema_50" in strategy.config.overlay_indicators
assert "bb_20_20" in strategy.config.overlay_indicators
# Check subplots
assert len(strategy.config.subplot_configs) == 2
assert any(subplot.subplot_type.value == "rsi" for subplot in strategy.config.subplot_configs)
assert any(subplot.subplot_type.value == "macd" for subplot in strategy.config.subplot_configs)
# Check metadata
assert strategy.difficulty == "Intermediate"
assert strategy.risk_level == "Medium"
assert "Trending" in strategy.market_conditions
assert len(strategy.notes) > 0
assert len(strategy.references) > 0
def test_ema_crossover_validation(self):
"""Test EMA crossover strategy validation."""
strategy = create_ema_crossover_strategy()
is_valid, errors = strategy.config.validate()
# Strategy should be valid or have minimal issues
assert isinstance(is_valid, bool)
assert isinstance(errors, list)
class TestMomentumBreakoutStrategy:
"""Test Momentum Breakout strategy."""
def test_momentum_breakout_creation(self):
"""Test momentum breakout strategy creation."""
strategy = create_momentum_breakout_strategy()
assert isinstance(strategy, StrategyExample)
assert strategy.config.strategy_name == "Momentum Breakout Strategy"
assert strategy.config.strategy_type == TradingStrategy.MOMENTUM
# Check for momentum-specific indicators
assert "ema_8" in strategy.config.overlay_indicators
assert "ema_21" in strategy.config.overlay_indicators
assert "bb_20_25" in strategy.config.overlay_indicators
# Check for fast indicators
rsi_subplot = next((s for s in strategy.config.subplot_configs if s.subplot_type.value == "rsi"), None)
assert rsi_subplot is not None
assert "rsi_7" in rsi_subplot.indicators
assert "rsi_14" in rsi_subplot.indicators
# Check volume subplot
volume_subplot = next((s for s in strategy.config.subplot_configs if s.subplot_type.value == "volume"), None)
assert volume_subplot is not None
# Check metadata
assert strategy.difficulty == "Advanced"
assert strategy.risk_level == "High"
assert "Volatile" in strategy.market_conditions
class TestMeanReversionStrategy:
"""Test Mean Reversion strategy."""
def test_mean_reversion_creation(self):
"""Test mean reversion strategy creation."""
strategy = create_mean_reversion_strategy()
assert isinstance(strategy, StrategyExample)
assert strategy.config.strategy_name == "Mean Reversion Strategy"
assert strategy.config.strategy_type == TradingStrategy.MEAN_REVERSION
# Check for mean reversion indicators
assert "sma_20" in strategy.config.overlay_indicators
assert "sma_50" in strategy.config.overlay_indicators
assert "bb_20_20" in strategy.config.overlay_indicators
assert "bb_20_15" in strategy.config.overlay_indicators
# Check RSI configurations
rsi_subplot = next((s for s in strategy.config.subplot_configs if s.subplot_type.value == "rsi"), None)
assert rsi_subplot is not None
assert "rsi_14" in rsi_subplot.indicators
assert "rsi_21" in rsi_subplot.indicators
# Check metadata
assert strategy.difficulty == "Intermediate"
assert strategy.risk_level == "Medium"
assert "Sideways" in strategy.market_conditions
class TestScalpingStrategy:
"""Test Scalping strategy."""
def test_scalping_creation(self):
"""Test scalping strategy creation."""
strategy = create_scalping_strategy()
assert isinstance(strategy, StrategyExample)
assert strategy.config.strategy_name == "Scalping Strategy"
assert strategy.config.strategy_type == TradingStrategy.SCALPING
# Check fast timeframes
assert "1m" in strategy.config.timeframes
assert "5m" in strategy.config.timeframes
# Check very fast indicators
assert "ema_5" in strategy.config.overlay_indicators
assert "ema_12" in strategy.config.overlay_indicators
assert "ema_21" in strategy.config.overlay_indicators
# Check fast RSI
rsi_subplot = next((s for s in strategy.config.subplot_configs if s.subplot_type.value == "rsi"), None)
assert rsi_subplot is not None
assert "rsi_7" in rsi_subplot.indicators
# Check metadata
assert strategy.difficulty == "Advanced"
assert strategy.risk_level == "High"
assert "High Liquidity" in strategy.market_conditions
class TestSwingTradingStrategy:
"""Test Swing Trading strategy."""
def test_swing_trading_creation(self):
"""Test swing trading strategy creation."""
strategy = create_swing_trading_strategy()
assert isinstance(strategy, StrategyExample)
assert strategy.config.strategy_name == "Swing Trading Strategy"
assert strategy.config.strategy_type == TradingStrategy.SWING_TRADING
# Check longer timeframes
assert "4h" in strategy.config.timeframes
assert "1d" in strategy.config.timeframes
# Check swing trading indicators
assert "sma_20" in strategy.config.overlay_indicators
assert "sma_50" in strategy.config.overlay_indicators
assert "ema_21" in strategy.config.overlay_indicators
assert "bb_20_20" in strategy.config.overlay_indicators
# Check metadata
assert strategy.difficulty == "Beginner"
assert strategy.risk_level == "Medium"
assert "Trending" in strategy.market_conditions
class TestStrategyAccessors:
"""Test strategy accessor functions."""
def test_get_all_example_strategies(self):
"""Test getting all example strategies."""
strategies = get_all_example_strategies()
assert isinstance(strategies, dict)
assert len(strategies) == 5 # Should have 5 strategies
expected_strategies = [
"ema_crossover", "momentum_breakout", "mean_reversion",
"scalping", "swing_trading"
]
for strategy_name in expected_strategies:
assert strategy_name in strategies
assert isinstance(strategies[strategy_name], StrategyExample)
def test_get_example_strategy(self):
"""Test getting a specific example strategy."""
# Test existing strategy
ema_strategy = get_example_strategy("ema_crossover")
assert ema_strategy is not None
assert isinstance(ema_strategy, StrategyExample)
assert ema_strategy.config.strategy_name == "EMA Crossover Strategy"
# Test non-existing strategy
non_existent = get_example_strategy("non_existent_strategy")
assert non_existent is None
def test_get_strategies_by_difficulty(self):
"""Test filtering strategies by difficulty."""
# Test beginner strategies
beginner_strategies = get_strategies_by_difficulty("Beginner")
assert isinstance(beginner_strategies, list)
assert len(beginner_strategies) > 0
for strategy in beginner_strategies:
assert strategy.difficulty == "Beginner"
# Test intermediate strategies
intermediate_strategies = get_strategies_by_difficulty("Intermediate")
assert isinstance(intermediate_strategies, list)
assert len(intermediate_strategies) > 0
for strategy in intermediate_strategies:
assert strategy.difficulty == "Intermediate"
# Test advanced strategies
advanced_strategies = get_strategies_by_difficulty("Advanced")
assert isinstance(advanced_strategies, list)
assert len(advanced_strategies) > 0
for strategy in advanced_strategies:
assert strategy.difficulty == "Advanced"
# Test non-existent difficulty
empty_strategies = get_strategies_by_difficulty("Expert")
assert isinstance(empty_strategies, list)
assert len(empty_strategies) == 0
def test_get_strategies_by_risk_level(self):
"""Test filtering strategies by risk level."""
# Test medium risk strategies
medium_risk = get_strategies_by_risk_level("Medium")
assert isinstance(medium_risk, list)
assert len(medium_risk) > 0
for strategy in medium_risk:
assert strategy.risk_level == "Medium"
# Test high risk strategies
high_risk = get_strategies_by_risk_level("High")
assert isinstance(high_risk, list)
assert len(high_risk) > 0
for strategy in high_risk:
assert strategy.risk_level == "High"
# Test non-existent risk level
empty_strategies = get_strategies_by_risk_level("Ultra High")
assert isinstance(empty_strategies, list)
assert len(empty_strategies) == 0
def test_get_strategies_by_market_condition(self):
"""Test filtering strategies by market condition."""
# Test trending market strategies
trending_strategies = get_strategies_by_market_condition("Trending")
assert isinstance(trending_strategies, list)
assert len(trending_strategies) > 0
for strategy in trending_strategies:
assert "Trending" in strategy.market_conditions
# Test volatile market strategies
volatile_strategies = get_strategies_by_market_condition("Volatile")
assert isinstance(volatile_strategies, list)
assert len(volatile_strategies) > 0
for strategy in volatile_strategies:
assert "Volatile" in strategy.market_conditions
# Test sideways market strategies
sideways_strategies = get_strategies_by_market_condition("Sideways")
assert isinstance(sideways_strategies, list)
assert len(sideways_strategies) > 0
for strategy in sideways_strategies:
assert "Sideways" in strategy.market_conditions
class TestStrategyUtilities:
"""Test strategy utility functions."""
def test_get_strategy_summary(self):
"""Test getting strategy summary."""
summary = get_strategy_summary()
assert isinstance(summary, dict)
assert len(summary) == 5 # Should have 5 strategies
# Check summary structure
for strategy_name, strategy_info in summary.items():
assert isinstance(strategy_info, dict)
required_fields = [
"name", "type", "difficulty", "risk_level",
"timeframes", "market_conditions", "expected_return"
]
for field in required_fields:
assert field in strategy_info
assert isinstance(strategy_info[field], str)
# Check specific strategy
assert "ema_crossover" in summary
ema_summary = summary["ema_crossover"]
assert ema_summary["name"] == "EMA Crossover Strategy"
assert ema_summary["type"] == "day_trading"
assert ema_summary["difficulty"] == "Intermediate"
def test_export_example_strategies_to_json(self):
"""Test exporting strategies to JSON."""
json_str = export_example_strategies_to_json()
# Should be valid JSON
data = json.loads(json_str)
assert isinstance(data, dict)
assert len(data) == 5 # Should have 5 strategies
# Check structure
for strategy_name, strategy_data in data.items():
assert "config" in strategy_data
assert "metadata" in strategy_data
# Check config structure
config = strategy_data["config"]
assert "strategy_name" in config
assert "strategy_type" in config
assert "timeframes" in config
# Check metadata structure
metadata = strategy_data["metadata"]
assert "description" in metadata
assert "author" in metadata
assert "difficulty" in metadata
assert "risk_level" in metadata
# Check specific strategy
assert "ema_crossover" in data
ema_data = data["ema_crossover"]
assert ema_data["config"]["strategy_name"] == "EMA Crossover Strategy"
assert ema_data["metadata"]["difficulty"] == "Intermediate"
class TestStrategyValidation:
"""Test validation of example strategies."""
def test_all_strategies_have_required_fields(self):
"""Test that all strategies have required fields."""
strategies = get_all_example_strategies()
for strategy_name, strategy in strategies.items():
# Check StrategyExample fields
assert strategy.config is not None
assert strategy.description is not None
assert strategy.author is not None
assert strategy.difficulty in ["Beginner", "Intermediate", "Advanced"]
assert strategy.risk_level in ["Low", "Medium", "High"]
assert isinstance(strategy.market_conditions, list)
assert isinstance(strategy.notes, list)
assert isinstance(strategy.references, list)
# Check StrategyChartConfig fields
config = strategy.config
assert config.strategy_name is not None
assert config.strategy_type is not None
assert isinstance(config.timeframes, list)
assert len(config.timeframes) > 0
assert isinstance(config.overlay_indicators, list)
assert isinstance(config.subplot_configs, list)
def test_strategy_configurations_are_valid(self):
"""Test that all strategy configurations are valid."""
strategies = get_all_example_strategies()
for strategy_name, strategy in strategies.items():
# Test basic validation
is_valid, errors = strategy.config.validate()
# Should be valid or have minimal issues (like missing indicators in test environment)
assert isinstance(is_valid, bool)
assert isinstance(errors, list)
# If there are errors, they should be reasonable (like missing indicators)
if not is_valid:
for error in errors:
# Common acceptable errors in test environment
acceptable_errors = [
"not found in defaults", # Missing indicators
"not found", # Missing indicators
]
assert any(acceptable in error for acceptable in acceptable_errors), \
f"Unexpected error in {strategy_name}: {error}"
def test_strategy_timeframes_match_types(self):
"""Test that strategy timeframes match their types."""
strategies = get_all_example_strategies()
# Expected timeframes for different strategy types
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"]
}
for strategy_name, strategy in strategies.items():
strategy_type = strategy.config.strategy_type
timeframes = strategy.config.timeframes
if strategy_type in expected_timeframes:
expected = expected_timeframes[strategy_type]
# Should have some overlap with expected timeframes
overlap = set(timeframes) & set(expected)
assert len(overlap) > 0, \
f"Strategy {strategy_name} timeframes {timeframes} don't match type {strategy_type}"
class TestStrategyIntegration:
"""Test integration with other systems."""
def test_strategy_configs_work_with_validation(self):
"""Test that strategy configs work with validation system."""
from components.charts.config.validation import validate_configuration
strategies = get_all_example_strategies()
for strategy_name, strategy in strategies.items():
try:
report = validate_configuration(strategy.config)
assert hasattr(report, 'is_valid')
assert hasattr(report, 'errors')
assert hasattr(report, 'warnings')
except Exception as e:
pytest.fail(f"Validation failed for {strategy_name}: {e}")
def test_strategy_json_roundtrip(self):
"""Test JSON export and import roundtrip."""
from components.charts.config.strategy_charts import (
export_strategy_config_to_json,
load_strategy_config_from_json
)
# Test one strategy for roundtrip
original_strategy = create_ema_crossover_strategy()
# Export to JSON
json_str = export_strategy_config_to_json(original_strategy.config)
# Import from JSON
loaded_config, errors = load_strategy_config_from_json(json_str)
if loaded_config:
# Compare key fields
assert loaded_config.strategy_name == original_strategy.config.strategy_name
assert loaded_config.strategy_type == original_strategy.config.strategy_type
assert loaded_config.timeframes == original_strategy.config.timeframes
assert loaded_config.overlay_indicators == original_strategy.config.overlay_indicators
if __name__ == "__main__":
pytest.main([__file__])

View File

@ -0,0 +1,316 @@
"""
Tests for Indicator Schema Validation System
Tests the new indicator definition schema and validation functionality
to ensure robust parameter validation and error handling.
"""
import pytest
from typing import Dict, Any
from components.charts.config.indicator_defs import (
IndicatorType,
DisplayType,
LineStyle,
IndicatorParameterSchema,
IndicatorSchema,
ChartIndicatorConfig,
INDICATOR_SCHEMAS,
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
)
class TestIndicatorParameterSchema:
"""Test individual parameter schema validation."""
def test_required_parameter_validation(self):
"""Test validation of required parameters."""
schema = IndicatorParameterSchema(
name="period",
type=int,
required=True,
min_value=1,
max_value=100
)
# Valid value
is_valid, error = schema.validate(20)
assert is_valid
assert error == ""
# Missing required parameter
is_valid, error = schema.validate(None)
assert not is_valid
assert "required" in error.lower()
# Wrong type
is_valid, error = schema.validate("20")
assert not is_valid
assert "type" in error.lower()
# Out of range
is_valid, error = schema.validate(0)
assert not is_valid
assert ">=" in error
is_valid, error = schema.validate(101)
assert not is_valid
assert "<=" in error
def test_optional_parameter_validation(self):
"""Test validation of optional parameters."""
schema = IndicatorParameterSchema(
name="price_column",
type=str,
required=False,
default="close"
)
# Valid value
is_valid, error = schema.validate("high")
assert is_valid
# None is valid for optional
is_valid, error = schema.validate(None)
assert is_valid
class TestIndicatorSchema:
"""Test complete indicator schema validation."""
def test_sma_schema_validation(self):
"""Test SMA indicator schema validation."""
schema = INDICATOR_SCHEMAS[IndicatorType.SMA]
# Valid parameters
params = {"period": 20, "price_column": "close"}
is_valid, errors = schema.validate_parameters(params)
assert is_valid
assert len(errors) == 0
# Missing required parameter
params = {"price_column": "close"}
is_valid, errors = schema.validate_parameters(params)
assert not is_valid
assert any("period" in error and "required" in error for error in errors)
# Invalid parameter value
params = {"period": 0, "price_column": "close"}
is_valid, errors = schema.validate_parameters(params)
assert not is_valid
assert any(">=" in error for error in errors)
# Unknown parameter
params = {"period": 20, "unknown_param": "test"}
is_valid, errors = schema.validate_parameters(params)
assert not is_valid
assert any("unknown" in error.lower() for error in errors)
def test_macd_schema_validation(self):
"""Test MACD indicator schema validation."""
schema = INDICATOR_SCHEMAS[IndicatorType.MACD]
# Valid parameters
params = {
"fast_period": 12,
"slow_period": 26,
"signal_period": 9,
"price_column": "close"
}
is_valid, errors = schema.validate_parameters(params)
assert is_valid
# Missing required parameters
params = {"fast_period": 12}
is_valid, errors = schema.validate_parameters(params)
assert not is_valid
assert len(errors) >= 2 # Missing slow_period and signal_period
class TestChartIndicatorConfig:
"""Test chart indicator configuration validation."""
def test_valid_config_validation(self):
"""Test validation of a valid configuration."""
config = ChartIndicatorConfig(
name="SMA (20)",
indicator_type="sma",
parameters={"period": 20, "price_column": "close"},
display_type="overlay",
color="#007bff",
line_style="solid",
line_width=2,
opacity=1.0,
visible=True
)
is_valid, errors = config.validate()
assert is_valid
assert len(errors) == 0
def test_invalid_indicator_type(self):
"""Test validation with invalid indicator type."""
config = ChartIndicatorConfig(
name="Invalid Indicator",
indicator_type="invalid_type",
parameters={},
display_type="overlay",
color="#007bff"
)
is_valid, errors = config.validate()
assert not is_valid
assert any("unsupported indicator type" in error.lower() for error in errors)
def test_invalid_display_properties(self):
"""Test validation of display properties."""
config = ChartIndicatorConfig(
name="SMA (20)",
indicator_type="sma",
parameters={"period": 20},
display_type="invalid_display",
color="#007bff",
line_style="invalid_style",
line_width=-1,
opacity=2.0
)
is_valid, errors = config.validate()
assert not is_valid
# Check for multiple validation errors
error_text = " ".join(errors).lower()
assert "display_type" in error_text
assert "line_style" in error_text
assert "line_width" in error_text
assert "opacity" in error_text
class TestUtilityFunctions:
"""Test utility functions for indicator management."""
def test_create_indicator_config(self):
"""Test creating indicator configuration."""
config, errors = create_indicator_config(
name="SMA (20)",
indicator_type="sma",
parameters={"period": 20},
color="#007bff"
)
assert config is not None
assert len(errors) == 0
assert config.name == "SMA (20)"
assert config.indicator_type == "sma"
assert config.parameters["period"] == 20
assert config.parameters["price_column"] == "close" # Default filled in
def test_create_indicator_config_invalid(self):
"""Test creating invalid indicator configuration."""
config, errors = create_indicator_config(
name="Invalid SMA",
indicator_type="sma",
parameters={"period": 0}, # Invalid period
color="#007bff"
)
assert config is None
assert len(errors) > 0
assert any(">=" in error for error in errors)
def test_get_indicator_schema(self):
"""Test getting indicator schema."""
schema = get_indicator_schema("sma")
assert schema is not None
assert schema.indicator_type == IndicatorType.SMA
schema = get_indicator_schema("invalid_type")
assert schema is None
def test_get_available_indicator_types(self):
"""Test getting available indicator types."""
types = get_available_indicator_types()
assert "sma" in types
assert "ema" in types
assert "rsi" in types
assert "macd" in types
assert "bollinger_bands" in types
def test_get_indicator_parameter_info(self):
"""Test getting parameter information."""
info = get_indicator_parameter_info("sma")
assert "period" in info
assert info["period"]["type"] == "int"
assert info["period"]["required"]
assert "price_column" in info
assert not info["price_column"]["required"]
def test_validate_parameters_for_type(self):
"""Test parameter validation for specific type."""
is_valid, errors = validate_parameters_for_type("sma", {"period": 20})
assert is_valid
is_valid, errors = validate_parameters_for_type("sma", {"period": 0})
assert not is_valid
is_valid, errors = validate_parameters_for_type("invalid_type", {})
assert not is_valid
def test_create_configuration_from_json(self):
"""Test creating configuration from JSON."""
json_data = {
"name": "SMA (20)",
"indicator_type": "sma",
"parameters": {"period": 20},
"color": "#007bff"
}
config, errors = create_configuration_from_json(json_data)
assert config is not None
assert len(errors) == 0
# Test with JSON string
import json
json_string = json.dumps(json_data)
config, errors = create_configuration_from_json(json_string)
assert config is not None
assert len(errors) == 0
# Test with missing fields
invalid_json = {"name": "SMA"}
config, errors = create_configuration_from_json(invalid_json)
assert config is None
assert len(errors) > 0
class TestIndicatorSchemaIntegration:
"""Test integration with existing indicator system."""
def test_schema_matches_built_in_indicators(self):
"""Test that schemas match built-in indicator definitions."""
from components.charts.config.indicator_defs import INDICATOR_DEFINITIONS
for indicator_name, config in INDICATOR_DEFINITIONS.items():
# Validate each built-in configuration
is_valid, errors = config.validate()
if not is_valid:
print(f"Validation errors for {indicator_name}: {errors}")
assert is_valid, f"Built-in indicator {indicator_name} failed validation: {errors}"
def test_parameter_schema_completeness(self):
"""Test that all indicator types have complete schemas."""
for indicator_type in IndicatorType:
schema = INDICATOR_SCHEMAS.get(indicator_type)
assert schema is not None, f"Missing schema for {indicator_type.value}"
assert schema.indicator_type == indicator_type
assert len(schema.required_parameters) > 0 or len(schema.optional_parameters) > 0
if __name__ == "__main__":
pytest.main([__file__])

View File

@ -0,0 +1,525 @@
"""
Tests for Strategy Chart Configuration System
Tests the comprehensive strategy chart configuration system including
chart layouts, subplot management, indicator combinations, and JSON serialization.
"""
import pytest
import json
from typing import Dict, List, Any
from datetime import datetime
from components.charts.config.strategy_charts import (
ChartLayout,
SubplotType,
SubplotConfig,
ChartStyle,
StrategyChartConfig,
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 components.charts.config.defaults import TradingStrategy
class TestChartLayoutComponents:
"""Test chart layout component classes."""
def test_chart_layout_enum(self):
"""Test ChartLayout enum values."""
layouts = [layout.value for layout in ChartLayout]
expected_layouts = ["single_chart", "main_with_subplots", "multi_chart", "grid_layout"]
for expected in expected_layouts:
assert expected in layouts
def test_subplot_type_enum(self):
"""Test SubplotType enum values."""
subplot_types = [subplot_type.value for subplot_type in SubplotType]
expected_types = ["volume", "rsi", "macd", "momentum", "custom"]
for expected in expected_types:
assert expected in subplot_types
def test_subplot_config_creation(self):
"""Test SubplotConfig creation and defaults."""
subplot = SubplotConfig(subplot_type=SubplotType.RSI)
assert subplot.subplot_type == SubplotType.RSI
assert subplot.height_ratio == 0.3
assert subplot.indicators == []
assert subplot.title is None
assert subplot.y_axis_label is None
assert subplot.show_grid is True
assert subplot.show_legend is True
assert subplot.background_color is None
def test_chart_style_defaults(self):
"""Test ChartStyle creation and defaults."""
style = ChartStyle()
assert style.theme == "plotly_white"
assert style.background_color == "#ffffff"
assert style.grid_color == "#e6e6e6"
assert style.text_color == "#2c3e50"
assert style.font_family == "Arial, sans-serif"
assert style.font_size == 12
assert style.candlestick_up_color == "#26a69a"
assert style.candlestick_down_color == "#ef5350"
assert style.volume_color == "#78909c"
assert style.show_volume is True
assert style.show_grid is True
assert style.show_legend is True
assert style.show_toolbar is True
class TestStrategyChartConfig:
"""Test StrategyChartConfig class functionality."""
def create_test_config(self) -> StrategyChartConfig:
"""Create a test strategy configuration."""
return StrategyChartConfig(
strategy_name="Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Test strategy for unit testing",
timeframes=["5m", "15m", "1h"],
layout=ChartLayout.MAIN_WITH_SUBPLOTS,
main_chart_height=0.7,
overlay_indicators=["sma_20", "ema_12"],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2,
indicators=["rsi_14"],
title="RSI",
y_axis_label="RSI"
),
SubplotConfig(
subplot_type=SubplotType.VOLUME,
height_ratio=0.1,
indicators=[],
title="Volume"
)
],
tags=["test", "day-trading"]
)
def test_strategy_config_creation(self):
"""Test StrategyChartConfig creation."""
config = self.create_test_config()
assert config.strategy_name == "Test Strategy"
assert config.strategy_type == TradingStrategy.DAY_TRADING
assert config.description == "Test strategy for unit testing"
assert config.timeframes == ["5m", "15m", "1h"]
assert config.layout == ChartLayout.MAIN_WITH_SUBPLOTS
assert config.main_chart_height == 0.7
assert config.overlay_indicators == ["sma_20", "ema_12"]
assert len(config.subplot_configs) == 2
assert config.tags == ["test", "day-trading"]
def test_strategy_config_validation_success(self):
"""Test successful validation of strategy configuration."""
config = self.create_test_config()
is_valid, errors = config.validate()
# Note: This might fail if the indicators don't exist in defaults
# but we'll test the validation logic
assert isinstance(is_valid, bool)
assert isinstance(errors, list)
def test_strategy_config_validation_missing_name(self):
"""Test validation with missing strategy name."""
config = self.create_test_config()
config.strategy_name = ""
is_valid, errors = config.validate()
assert not is_valid
assert "Strategy name is required" in errors
def test_strategy_config_validation_invalid_height_ratios(self):
"""Test validation with invalid height ratios."""
config = self.create_test_config()
config.main_chart_height = 0.8
config.subplot_configs[0].height_ratio = 0.3 # Total = 1.1 > 1.0
is_valid, errors = config.validate()
assert not is_valid
assert any("height ratios exceed 1.0" in error for error in errors)
def test_strategy_config_validation_invalid_main_height(self):
"""Test validation with invalid main chart height."""
config = self.create_test_config()
config.main_chart_height = 1.5 # Invalid: > 1.0
is_valid, errors = config.validate()
assert not is_valid
assert any("Main chart height must be between 0 and 1.0" in error for error in errors)
def test_strategy_config_validation_invalid_subplot_height(self):
"""Test validation with invalid subplot height."""
config = self.create_test_config()
config.subplot_configs[0].height_ratio = -0.1 # Invalid: <= 0
is_valid, errors = config.validate()
assert not is_valid
assert any("height ratio must be between 0 and 1.0" in error for error in errors)
def test_get_all_indicators(self):
"""Test getting all indicators from configuration."""
config = self.create_test_config()
all_indicators = config.get_all_indicators()
expected = ["sma_20", "ema_12", "rsi_14"]
assert len(all_indicators) == len(expected)
for indicator in expected:
assert indicator in all_indicators
def test_get_indicator_configs(self):
"""Test getting indicator configuration objects."""
config = self.create_test_config()
indicator_configs = config.get_indicator_configs()
# Should return a dictionary
assert isinstance(indicator_configs, dict)
# Results depend on what indicators exist in defaults
class TestDefaultStrategyConfigurations:
"""Test default strategy configuration creation."""
def test_create_default_strategy_configurations(self):
"""Test creation of default strategy configurations."""
strategy_configs = create_default_strategy_configurations()
# Should have configurations for all strategy types
expected_strategies = ["scalping", "day_trading", "swing_trading",
"position_trading", "momentum", "mean_reversion"]
for strategy in expected_strategies:
assert strategy in strategy_configs
config = strategy_configs[strategy]
assert isinstance(config, StrategyChartConfig)
# Validate each configuration
is_valid, errors = config.validate()
# Note: Some validations might fail due to missing indicators in test environment
assert isinstance(is_valid, bool)
assert isinstance(errors, list)
def test_scalping_strategy_config(self):
"""Test scalping strategy configuration specifics."""
strategy_configs = create_default_strategy_configurations()
scalping = strategy_configs["scalping"]
assert scalping.strategy_name == "Scalping Strategy"
assert scalping.strategy_type == TradingStrategy.SCALPING
assert "1m" in scalping.timeframes
assert "5m" in scalping.timeframes
assert scalping.main_chart_height == 0.6
assert len(scalping.overlay_indicators) > 0
assert len(scalping.subplot_configs) > 0
assert "scalping" in scalping.tags
def test_day_trading_strategy_config(self):
"""Test day trading strategy configuration specifics."""
strategy_configs = create_default_strategy_configurations()
day_trading = strategy_configs["day_trading"]
assert day_trading.strategy_name == "Day Trading Strategy"
assert day_trading.strategy_type == TradingStrategy.DAY_TRADING
assert "5m" in day_trading.timeframes
assert "15m" in day_trading.timeframes
assert "1h" in day_trading.timeframes
assert len(day_trading.overlay_indicators) > 0
assert len(day_trading.subplot_configs) > 0
def test_position_trading_strategy_config(self):
"""Test position trading strategy configuration specifics."""
strategy_configs = create_default_strategy_configurations()
position = strategy_configs["position_trading"]
assert position.strategy_name == "Position Trading Strategy"
assert position.strategy_type == TradingStrategy.POSITION_TRADING
assert "4h" in position.timeframes
assert "1d" in position.timeframes
assert "1w" in position.timeframes
assert position.chart_style.show_volume is False # Less important for long-term
class TestCustomStrategyCreation:
"""Test custom strategy configuration creation."""
def test_create_custom_strategy_config_success(self):
"""Test successful creation of custom strategy configuration."""
subplot_configs = [
{
"subplot_type": "rsi",
"height_ratio": 0.2,
"indicators": ["rsi_14"],
"title": "Custom RSI"
}
]
config, errors = create_custom_strategy_config(
strategy_name="Custom Test Strategy",
strategy_type=TradingStrategy.SWING_TRADING,
description="Custom strategy for testing",
timeframes=["1h", "4h"],
overlay_indicators=["sma_50"],
subplot_configs=subplot_configs,
tags=["custom", "test"]
)
if config: # Only test if creation succeeded
assert config.strategy_name == "Custom Test Strategy"
assert config.strategy_type == TradingStrategy.SWING_TRADING
assert config.description == "Custom strategy for testing"
assert config.timeframes == ["1h", "4h"]
assert config.overlay_indicators == ["sma_50"]
assert len(config.subplot_configs) == 1
assert config.tags == ["custom", "test"]
assert config.created_at is not None
def test_create_custom_strategy_config_with_style(self):
"""Test custom strategy creation with chart style."""
chart_style = {
"theme": "plotly_dark",
"font_size": 14,
"candlestick_up_color": "#00ff00",
"candlestick_down_color": "#ff0000"
}
config, errors = create_custom_strategy_config(
strategy_name="Styled Strategy",
strategy_type=TradingStrategy.MOMENTUM,
description="Strategy with custom styling",
timeframes=["15m"],
overlay_indicators=[],
subplot_configs=[],
chart_style=chart_style
)
if config: # Only test if creation succeeded
assert config.chart_style.theme == "plotly_dark"
assert config.chart_style.font_size == 14
assert config.chart_style.candlestick_up_color == "#00ff00"
assert config.chart_style.candlestick_down_color == "#ff0000"
class TestJSONSerialization:
"""Test JSON serialization and deserialization."""
def create_test_config_for_json(self) -> StrategyChartConfig:
"""Create a simple test configuration for JSON testing."""
return StrategyChartConfig(
strategy_name="JSON Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Strategy for JSON testing",
timeframes=["15m", "1h"],
overlay_indicators=["ema_12"],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.25,
indicators=["rsi_14"],
title="RSI Test"
)
],
tags=["json", "test"]
)
def test_export_strategy_config_to_json(self):
"""Test exporting strategy configuration to JSON."""
config = self.create_test_config_for_json()
json_str = export_strategy_config_to_json(config)
# Should be valid JSON
data = json.loads(json_str)
# Check key fields
assert data["strategy_name"] == "JSON Test Strategy"
assert data["strategy_type"] == "day_trading"
assert data["description"] == "Strategy for JSON testing"
assert data["timeframes"] == ["15m", "1h"]
assert data["overlay_indicators"] == ["ema_12"]
assert len(data["subplot_configs"]) == 1
assert data["tags"] == ["json", "test"]
# Check subplot configuration
subplot = data["subplot_configs"][0]
assert subplot["subplot_type"] == "rsi"
assert subplot["height_ratio"] == 0.25
assert subplot["indicators"] == ["rsi_14"]
assert subplot["title"] == "RSI Test"
def test_load_strategy_config_from_json_dict(self):
"""Test loading strategy configuration from JSON dictionary."""
json_data = {
"strategy_name": "JSON Loaded Strategy",
"strategy_type": "swing_trading",
"description": "Strategy loaded from JSON",
"timeframes": ["1h", "4h"],
"overlay_indicators": ["sma_20"],
"subplot_configs": [
{
"subplot_type": "macd",
"height_ratio": 0.3,
"indicators": ["macd_12_26_9"],
"title": "MACD Test"
}
],
"tags": ["loaded", "test"]
}
config, errors = load_strategy_config_from_json(json_data)
if config: # Only test if loading succeeded
assert config.strategy_name == "JSON Loaded Strategy"
assert config.strategy_type == TradingStrategy.SWING_TRADING
assert config.description == "Strategy loaded from JSON"
assert config.timeframes == ["1h", "4h"]
assert config.overlay_indicators == ["sma_20"]
assert len(config.subplot_configs) == 1
assert config.tags == ["loaded", "test"]
def test_load_strategy_config_from_json_string(self):
"""Test loading strategy configuration from JSON string."""
json_data = {
"strategy_name": "String Loaded Strategy",
"strategy_type": "momentum",
"description": "Strategy loaded from JSON string",
"timeframes": ["5m", "15m"]
}
json_str = json.dumps(json_data)
config, errors = load_strategy_config_from_json(json_str)
if config: # Only test if loading succeeded
assert config.strategy_name == "String Loaded Strategy"
assert config.strategy_type == TradingStrategy.MOMENTUM
def test_load_strategy_config_missing_fields(self):
"""Test loading strategy configuration with missing required fields."""
json_data = {
"strategy_name": "Incomplete Strategy",
# Missing strategy_type, description, timeframes
}
config, errors = load_strategy_config_from_json(json_data)
assert config is None
assert len(errors) > 0
assert any("Missing required fields" in error for error in errors)
def test_load_strategy_config_invalid_strategy_type(self):
"""Test loading strategy configuration with invalid strategy type."""
json_data = {
"strategy_name": "Invalid Strategy",
"strategy_type": "invalid_strategy_type",
"description": "Strategy with invalid type",
"timeframes": ["1h"]
}
config, errors = load_strategy_config_from_json(json_data)
assert config is None
assert len(errors) > 0
assert any("Invalid strategy type" in error for error in errors)
def test_roundtrip_json_serialization(self):
"""Test roundtrip JSON serialization (export then import)."""
original_config = self.create_test_config_for_json()
# Export to JSON
json_str = export_strategy_config_to_json(original_config)
# Import from JSON
loaded_config, errors = load_strategy_config_from_json(json_str)
if loaded_config: # Only test if roundtrip succeeded
# Compare key fields (some fields like created_at won't match)
assert loaded_config.strategy_name == original_config.strategy_name
assert loaded_config.strategy_type == original_config.strategy_type
assert loaded_config.description == original_config.description
assert loaded_config.timeframes == original_config.timeframes
assert loaded_config.overlay_indicators == original_config.overlay_indicators
assert len(loaded_config.subplot_configs) == len(original_config.subplot_configs)
assert loaded_config.tags == original_config.tags
class TestStrategyConfigAccessors:
"""Test strategy configuration accessor functions."""
def test_get_strategy_config(self):
"""Test getting strategy configuration by name."""
config = get_strategy_config("day_trading")
if config:
assert isinstance(config, StrategyChartConfig)
assert config.strategy_type == TradingStrategy.DAY_TRADING
# Test non-existent strategy
non_existent = get_strategy_config("non_existent_strategy")
assert non_existent is None
def test_get_all_strategy_configs(self):
"""Test getting all strategy configurations."""
all_configs = get_all_strategy_configs()
assert isinstance(all_configs, dict)
assert len(all_configs) > 0
# Check that all values are StrategyChartConfig instances
for config in all_configs.values():
assert isinstance(config, StrategyChartConfig)
def test_get_available_strategy_names(self):
"""Test getting available strategy names."""
strategy_names = get_available_strategy_names()
assert isinstance(strategy_names, list)
assert len(strategy_names) > 0
# Should include expected strategy names
expected_names = ["scalping", "day_trading", "swing_trading",
"position_trading", "momentum", "mean_reversion"]
for expected in expected_names:
assert expected in strategy_names
class TestValidationFunction:
"""Test standalone validation function."""
def test_validate_strategy_configuration_function(self):
"""Test the standalone validation function."""
config = StrategyChartConfig(
strategy_name="Validation Test",
strategy_type=TradingStrategy.DAY_TRADING,
description="Test validation function",
timeframes=["1h"],
main_chart_height=0.8,
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2
)
]
)
is_valid, errors = validate_strategy_configuration(config)
assert isinstance(is_valid, bool)
assert isinstance(errors, list)
# This should be valid (total height = 1.0)
# Note: Validation might fail due to missing indicators in test environment
if __name__ == "__main__":
pytest.main([__file__])

539
tests/test_validation.py Normal file
View File

@ -0,0 +1,539 @@
"""
Tests for Configuration Validation and Error Handling System
Tests the comprehensive validation system including validation rules,
error reporting, warnings, and detailed diagnostics.
"""
import pytest
from typing import Set
from datetime import datetime
from components.charts.config.validation import (
ValidationLevel,
ValidationRule,
ValidationIssue,
ValidationReport,
ConfigurationValidator,
validate_configuration,
get_validation_rules_info
)
from components.charts.config.strategy_charts import (
StrategyChartConfig,
SubplotConfig,
ChartStyle,
ChartLayout,
SubplotType
)
from components.charts.config.defaults import TradingStrategy
class TestValidationComponents:
"""Test validation component classes."""
def test_validation_level_enum(self):
"""Test ValidationLevel enum values."""
levels = [level.value for level in ValidationLevel]
expected_levels = ["error", "warning", "info", "debug"]
for expected in expected_levels:
assert expected in levels
def test_validation_rule_enum(self):
"""Test ValidationRule enum values."""
rules = [rule.value for rule in ValidationRule]
expected_rules = [
"required_fields", "height_ratios", "indicator_existence",
"timeframe_format", "chart_style", "subplot_config",
"strategy_consistency", "performance_impact", "indicator_conflicts",
"resource_usage"
]
for expected in expected_rules:
assert expected in rules
def test_validation_issue_creation(self):
"""Test ValidationIssue creation and string representation."""
issue = ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.REQUIRED_FIELDS,
message="Test error message",
field_path="test.field",
suggestion="Test suggestion"
)
assert issue.level == ValidationLevel.ERROR
assert issue.rule == ValidationRule.REQUIRED_FIELDS
assert issue.message == "Test error message"
assert issue.field_path == "test.field"
assert issue.suggestion == "Test suggestion"
# Test string representation
issue_str = str(issue)
assert "[ERROR]" in issue_str
assert "Test error message" in issue_str
assert "test.field" in issue_str
assert "Test suggestion" in issue_str
def test_validation_report_creation(self):
"""Test ValidationReport creation and methods."""
report = ValidationReport(is_valid=True)
assert report.is_valid is True
assert len(report.errors) == 0
assert len(report.warnings) == 0
assert len(report.info) == 0
assert len(report.debug) == 0
# Test adding issues
error_issue = ValidationIssue(
level=ValidationLevel.ERROR,
rule=ValidationRule.REQUIRED_FIELDS,
message="Error message"
)
warning_issue = ValidationIssue(
level=ValidationLevel.WARNING,
rule=ValidationRule.HEIGHT_RATIOS,
message="Warning message"
)
report.add_issue(error_issue)
report.add_issue(warning_issue)
assert not report.is_valid # Should be False after adding error
assert len(report.errors) == 1
assert len(report.warnings) == 1
assert report.has_errors()
assert report.has_warnings()
# Test get_all_issues
all_issues = report.get_all_issues()
assert len(all_issues) == 2
# Test get_issues_by_rule
field_issues = report.get_issues_by_rule(ValidationRule.REQUIRED_FIELDS)
assert len(field_issues) == 1
assert field_issues[0] == error_issue
# Test summary
summary = report.summary()
assert "1 errors" in summary
assert "1 warnings" in summary
class TestConfigurationValidator:
"""Test ConfigurationValidator class."""
def create_valid_config(self) -> StrategyChartConfig:
"""Create a valid test configuration."""
return StrategyChartConfig(
strategy_name="Valid Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Valid strategy for testing",
timeframes=["5m", "15m", "1h"],
main_chart_height=0.7,
overlay_indicators=["sma_20"], # Using simple indicators
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2,
indicators=[], # Empty to avoid indicator existence issues
title="RSI"
)
]
)
def test_validator_initialization(self):
"""Test validator initialization."""
# Test with all rules
validator = ConfigurationValidator()
assert len(validator.enabled_rules) == len(ValidationRule)
# Test with specific rules
specific_rules = {ValidationRule.REQUIRED_FIELDS, ValidationRule.HEIGHT_RATIOS}
validator = ConfigurationValidator(enabled_rules=specific_rules)
assert validator.enabled_rules == specific_rules
def test_validate_strategy_config_valid(self):
"""Test validation of a valid configuration."""
config = self.create_valid_config()
validator = ConfigurationValidator()
report = validator.validate_strategy_config(config)
# Should have some validation applied
assert isinstance(report, ValidationReport)
assert report.validation_time is not None
assert len(report.rules_applied) > 0
def test_required_fields_validation(self):
"""Test required fields validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.REQUIRED_FIELDS})
# Test missing strategy name
config.strategy_name = ""
report = validator.validate_strategy_config(config)
assert not report.is_valid
assert len(report.errors) > 0
assert any("Strategy name is required" in str(error) for error in report.errors)
# Test short strategy name (should be warning)
config.strategy_name = "AB"
report = validator.validate_strategy_config(config)
assert len(report.warnings) > 0
assert any("very short" in str(warning) for warning in report.warnings)
# Test missing timeframes
config.strategy_name = "Valid Name"
config.timeframes = []
report = validator.validate_strategy_config(config)
assert not report.is_valid
assert any("timeframe must be specified" in str(error) for error in report.errors)
def test_height_ratios_validation(self):
"""Test height ratios validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.HEIGHT_RATIOS})
# Test invalid main chart height
config.main_chart_height = 1.5 # Invalid: > 1.0
report = validator.validate_strategy_config(config)
assert not report.is_valid
assert any("Main chart height" in str(error) for error in report.errors)
# Test total height exceeding 1.0
config.main_chart_height = 0.8
config.subplot_configs[0].height_ratio = 0.3 # Total = 1.1
report = validator.validate_strategy_config(config)
assert not report.is_valid
assert any("exceeds 1.0" in str(error) for error in report.errors)
# Test very small main chart height (should be warning)
config.main_chart_height = 0.1
config.subplot_configs[0].height_ratio = 0.2
report = validator.validate_strategy_config(config)
assert len(report.warnings) > 0
assert any("very small" in str(warning) for warning in report.warnings)
def test_timeframe_format_validation(self):
"""Test timeframe format validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.TIMEFRAME_FORMAT})
# Test invalid timeframe format
config.timeframes = ["invalid", "1h", "5m"]
report = validator.validate_strategy_config(config)
assert not report.is_valid
assert any("Invalid timeframe format" in str(error) for error in report.errors)
# Test valid but uncommon timeframe (should be warning)
config.timeframes = ["7m", "1h"] # 7m is valid format but uncommon
report = validator.validate_strategy_config(config)
assert len(report.warnings) > 0
assert any("not in common list" in str(warning) for warning in report.warnings)
def test_chart_style_validation(self):
"""Test chart style validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.CHART_STYLE})
# Test invalid color format
config.chart_style.background_color = "invalid_color"
report = validator.validate_strategy_config(config)
assert not report.is_valid
assert any("Invalid color format" in str(error) for error in report.errors)
# Test extreme font size (should be warning or error)
config.chart_style.background_color = "#ffffff" # Fix color
config.chart_style.font_size = 2 # Too small
report = validator.validate_strategy_config(config)
assert len(report.errors) > 0 or len(report.warnings) > 0
# Test unsupported theme (should be warning)
config.chart_style.font_size = 12 # Fix font size
config.chart_style.theme = "unsupported_theme"
report = validator.validate_strategy_config(config)
assert len(report.warnings) > 0
assert any("may not be supported" in str(warning) for warning in report.warnings)
def test_subplot_config_validation(self):
"""Test subplot configuration validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.SUBPLOT_CONFIG})
# Test duplicate subplot types
config.subplot_configs.append(SubplotConfig(
subplot_type=SubplotType.RSI, # Duplicate
height_ratio=0.1,
indicators=[],
title="RSI 2"
))
report = validator.validate_strategy_config(config)
assert len(report.warnings) > 0
assert any("Duplicate subplot type" in str(warning) for warning in report.warnings)
def test_strategy_consistency_validation(self):
"""Test strategy consistency validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.STRATEGY_CONSISTENCY})
# Test mismatched timeframes for scalping strategy
config.strategy_type = TradingStrategy.SCALPING
config.timeframes = ["4h", "1d"] # Not optimal for scalping
report = validator.validate_strategy_config(config)
assert len(report.info) > 0
assert any("may not be optimal" in str(info) for info in report.info)
def test_performance_impact_validation(self):
"""Test performance impact validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.PERFORMANCE_IMPACT})
# Test high indicator count
config.overlay_indicators = [f"indicator_{i}" for i in range(12)] # 12 indicators
report = validator.validate_strategy_config(config)
assert len(report.warnings) > 0
assert any("may impact performance" in str(warning) for warning in report.warnings)
def test_indicator_conflicts_validation(self):
"""Test indicator conflicts validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.INDICATOR_CONFLICTS})
# Test multiple SMA indicators
config.overlay_indicators = ["sma_5", "sma_10", "sma_20", "sma_50"] # 4 SMA indicators
report = validator.validate_strategy_config(config)
assert len(report.info) > 0
assert any("visual clutter" in str(info) for info in report.info)
def test_resource_usage_validation(self):
"""Test resource usage validation."""
config = self.create_valid_config()
validator = ConfigurationValidator(enabled_rules={ValidationRule.RESOURCE_USAGE})
# Test high memory usage configuration
config.overlay_indicators = [f"indicator_{i}" for i in range(10)]
config.subplot_configs = [
SubplotConfig(subplot_type=SubplotType.RSI, height_ratio=0.1, indicators=[])
for _ in range(10)
] # Many subplots
report = validator.validate_strategy_config(config)
assert len(report.warnings) > 0 or len(report.info) > 0
class TestValidationFunctions:
"""Test standalone validation functions."""
def create_test_config(self) -> StrategyChartConfig:
"""Create a test configuration."""
return StrategyChartConfig(
strategy_name="Test Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Test strategy",
timeframes=["15m", "1h"],
main_chart_height=0.8,
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2,
indicators=[]
)
]
)
def test_validate_configuration_function(self):
"""Test the standalone validate_configuration function."""
config = self.create_test_config()
# Test with default rules
report = validate_configuration(config)
assert isinstance(report, ValidationReport)
assert report.validation_time is not None
# Test with specific rules
specific_rules = {ValidationRule.REQUIRED_FIELDS, ValidationRule.HEIGHT_RATIOS}
report = validate_configuration(config, rules=specific_rules)
assert report.rules_applied == specific_rules
# Test strict mode
config.strategy_name = "AB" # Short name (should be warning)
report = validate_configuration(config, strict=False)
normal_errors = len(report.errors)
report = validate_configuration(config, strict=True)
strict_errors = len(report.errors)
assert strict_errors >= normal_errors # Strict mode may have more errors
def test_get_validation_rules_info(self):
"""Test getting validation rules information."""
rules_info = get_validation_rules_info()
assert isinstance(rules_info, dict)
assert len(rules_info) == len(ValidationRule)
# Check that all rules have information
for rule in ValidationRule:
assert rule in rules_info
rule_info = rules_info[rule]
assert "name" in rule_info
assert "description" in rule_info
assert isinstance(rule_info["name"], str)
assert isinstance(rule_info["description"], str)
class TestValidationIntegration:
"""Test integration with existing systems."""
def test_strategy_config_validate_method(self):
"""Test the updated validate method in StrategyChartConfig."""
config = StrategyChartConfig(
strategy_name="Integration Test",
strategy_type=TradingStrategy.DAY_TRADING,
description="Integration test strategy",
timeframes=["15m"],
main_chart_height=0.8,
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.2,
indicators=[]
)
]
)
# Test basic validate method (backward compatibility)
is_valid, errors = config.validate()
assert isinstance(is_valid, bool)
assert isinstance(errors, list)
# Test comprehensive validation method
report = config.validate_comprehensive()
assert isinstance(report, ValidationReport)
assert report.validation_time is not None
def test_validation_with_invalid_config(self):
"""Test validation with an invalid configuration."""
config = StrategyChartConfig(
strategy_name="", # Invalid: empty name
strategy_type=TradingStrategy.DAY_TRADING,
description="", # Warning: empty description
timeframes=[], # Invalid: no timeframes
main_chart_height=1.5, # Invalid: > 1.0
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=-0.1, # Invalid: negative
indicators=[]
)
]
)
# Test basic validation
is_valid, errors = config.validate()
assert not is_valid
assert len(errors) > 0
# Test comprehensive validation
report = config.validate_comprehensive()
assert not report.is_valid
assert len(report.errors) > 0
assert len(report.warnings) > 0 # Should have warnings too
def test_validation_error_handling(self):
"""Test validation error handling."""
config = StrategyChartConfig(
strategy_name="Error Test",
strategy_type=TradingStrategy.DAY_TRADING,
description="Error test strategy",
timeframes=["15m"],
main_chart_height=0.8,
subplot_configs=[]
)
# The validation should handle errors gracefully
is_valid, errors = config.validate()
assert isinstance(is_valid, bool)
assert isinstance(errors, list)
class TestValidationEdgeCases:
"""Test edge cases and boundary conditions."""
def test_empty_configuration(self):
"""Test validation with minimal configuration."""
config = StrategyChartConfig(
strategy_name="Minimal",
strategy_type=TradingStrategy.DAY_TRADING,
description="Minimal config",
timeframes=["1h"],
overlay_indicators=[],
subplot_configs=[]
)
report = validate_configuration(config)
# Should be valid even with minimal configuration
assert isinstance(report, ValidationReport)
def test_maximum_configuration(self):
"""Test validation with maximum complexity configuration."""
config = StrategyChartConfig(
strategy_name="Maximum Complexity Strategy",
strategy_type=TradingStrategy.DAY_TRADING,
description="Strategy with maximum complexity for testing",
timeframes=["1m", "5m", "15m", "1h", "4h"],
main_chart_height=0.4,
overlay_indicators=[f"indicator_{i}" for i in range(15)],
subplot_configs=[
SubplotConfig(
subplot_type=SubplotType.RSI,
height_ratio=0.15,
indicators=[f"rsi_{i}" for i in range(5)]
),
SubplotConfig(
subplot_type=SubplotType.MACD,
height_ratio=0.15,
indicators=[f"macd_{i}" for i in range(5)]
),
SubplotConfig(
subplot_type=SubplotType.VOLUME,
height_ratio=0.1,
indicators=[]
),
SubplotConfig(
subplot_type=SubplotType.MOMENTUM,
height_ratio=0.2,
indicators=[f"momentum_{i}" for i in range(3)]
)
]
)
report = validate_configuration(config)
# Should have warnings about performance and complexity
assert len(report.warnings) > 0 or len(report.info) > 0
def test_boundary_values(self):
"""Test validation with boundary values."""
config = StrategyChartConfig(
strategy_name="Boundary Test",
strategy_type=TradingStrategy.DAY_TRADING,
description="Boundary test strategy",
timeframes=["1h"],
main_chart_height=1.0, # Maximum allowed
subplot_configs=[] # No subplots (total height = 1.0)
)
report = validate_configuration(config)
# Should be valid with exact boundary values
assert isinstance(report, ValidationReport)
if __name__ == "__main__":
pytest.main([__file__])