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

@@ -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__])