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.
316 lines
10 KiB
Python
316 lines
10 KiB
Python
"""
|
|
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__]) |