605 lines
24 KiB
Python
605 lines
24 KiB
Python
|
|
"""
|
|||
|
|
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
|