676 lines
31 KiB
Python
676 lines
31 KiB
Python
"""
|
|
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()
|
|
|
|
|
|
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"Validation: 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"
|
|
}
|
|
} |