"""Property-based tests for risk tier default parameters. Feature: autonomous-trading-engine Validates that the three risk tier defaults (conservative, moderate, aggressive) have valid parameter ranges and correct ordering relationships. **Validates: Requirements 5.1** """ from __future__ import annotations from hypothesis import given, settings from hypothesis import strategies as st from services.trading.models import RISK_TIER_DEFAULTS, RiskTierConfig # --------------------------------------------------------------------------- # Hypothesis strategies # --------------------------------------------------------------------------- def _risk_tier_config_strategy() -> st.SearchStrategy[RiskTierConfig]: """Generate random RiskTierConfig objects with valid parameter ranges.""" return st.builds( RiskTierConfig, name=st.sampled_from(["conservative", "moderate", "aggressive"]), min_confidence=st.floats(min_value=0.0, max_value=1.0, allow_nan=False), max_position_pct=st.floats( min_value=0.01, max_value=1.0, allow_nan=False ), stop_loss_atr_multiplier=st.floats( min_value=0.01, max_value=10.0, allow_nan=False ), reward_risk_ratio=st.floats( min_value=0.01, max_value=10.0, allow_nan=False ), max_sector_pct=st.floats( min_value=0.01, max_value=1.0, allow_nan=False ), max_portfolio_heat=st.floats( min_value=0.01, max_value=1.0, allow_nan=False ), ) # --------------------------------------------------------------------------- # Property 29 (partial): Risk tier default parameter validation # --------------------------------------------------------------------------- EXPECTED_TIERS = {"conservative", "moderate", "aggressive"} def test_all_three_tiers_exist() -> None: """All three risk tiers must be present in RISK_TIER_DEFAULTS. **Validates: Requirements 5.1** """ assert set(RISK_TIER_DEFAULTS.keys()) == EXPECTED_TIERS def test_each_tier_has_valid_parameter_ranges() -> None: """Each tier's parameters must fall within valid ranges. **Validates: Requirements 5.1** """ for tier_name, cfg in RISK_TIER_DEFAULTS.items(): assert cfg.name == tier_name, ( f"Tier name mismatch: key={tier_name}, cfg.name={cfg.name}" ) # min_confidence in [0, 1] assert 0.0 <= cfg.min_confidence <= 1.0, ( f"{tier_name}: min_confidence={cfg.min_confidence} not in [0, 1]" ) # max_position_pct in (0, 1] assert 0.0 < cfg.max_position_pct <= 1.0, ( f"{tier_name}: max_position_pct={cfg.max_position_pct} not in (0, 1]" ) # stop_loss_atr_multiplier > 0 assert cfg.stop_loss_atr_multiplier > 0.0, ( f"{tier_name}: stop_loss_atr_multiplier={cfg.stop_loss_atr_multiplier} not > 0" ) # reward_risk_ratio > 0 assert cfg.reward_risk_ratio > 0.0, ( f"{tier_name}: reward_risk_ratio={cfg.reward_risk_ratio} not > 0" ) # max_sector_pct in (0, 1] assert 0.0 < cfg.max_sector_pct <= 1.0, ( f"{tier_name}: max_sector_pct={cfg.max_sector_pct} not in (0, 1]" ) # max_portfolio_heat in (0, 1] assert 0.0 < cfg.max_portfolio_heat <= 1.0, ( f"{tier_name}: max_portfolio_heat={cfg.max_portfolio_heat} not in (0, 1]" ) def test_min_confidence_ordering() -> None: """Conservative has highest min_confidence, aggressive has lowest. conservative.min_confidence > moderate.min_confidence > aggressive.min_confidence **Validates: Requirements 5.1** """ c = RISK_TIER_DEFAULTS["conservative"] m = RISK_TIER_DEFAULTS["moderate"] a = RISK_TIER_DEFAULTS["aggressive"] assert c.min_confidence > m.min_confidence > a.min_confidence, ( f"min_confidence ordering violated: " f"conservative={c.min_confidence}, " f"moderate={m.min_confidence}, " f"aggressive={a.min_confidence}" ) def test_max_position_pct_ordering() -> None: """Conservative has lowest max_position_pct, aggressive has highest. conservative.max_position_pct < moderate.max_position_pct < aggressive.max_position_pct **Validates: Requirements 5.1** """ c = RISK_TIER_DEFAULTS["conservative"] m = RISK_TIER_DEFAULTS["moderate"] a = RISK_TIER_DEFAULTS["aggressive"] assert c.max_position_pct < m.max_position_pct < a.max_position_pct, ( f"max_position_pct ordering violated: " f"conservative={c.max_position_pct}, " f"moderate={m.max_position_pct}, " f"aggressive={a.max_position_pct}" ) def test_max_portfolio_heat_ordering() -> None: """Conservative has lowest max_portfolio_heat, aggressive has highest. conservative.max_portfolio_heat < moderate.max_portfolio_heat < aggressive.max_portfolio_heat **Validates: Requirements 5.1** """ c = RISK_TIER_DEFAULTS["conservative"] m = RISK_TIER_DEFAULTS["moderate"] a = RISK_TIER_DEFAULTS["aggressive"] assert c.max_portfolio_heat < m.max_portfolio_heat < a.max_portfolio_heat, ( f"max_portfolio_heat ordering violated: " f"conservative={c.max_portfolio_heat}, " f"moderate={m.max_portfolio_heat}, " f"aggressive={a.max_portfolio_heat}" ) @settings(max_examples=100) @given(cfg=_risk_tier_config_strategy()) def test_random_risk_tier_config_parameter_ranges(cfg: RiskTierConfig) -> None: """Any randomly generated RiskTierConfig with valid inputs satisfies range invariants. This property test verifies that the parameter range constraints hold for arbitrary RiskTierConfig instances, not just the defaults. **Validates: Requirements 5.1** """ assert 0.0 <= cfg.min_confidence <= 1.0 assert 0.0 < cfg.max_position_pct <= 1.0 assert cfg.stop_loss_atr_multiplier > 0.0 assert cfg.reward_risk_ratio > 0.0 assert 0.0 < cfg.max_sector_pct <= 1.0 assert 0.0 < cfg.max_portfolio_heat <= 1.0