feat: autonomous trading engine — full implementation
- Database migration 018 with 13 tables for trading engine state - Trading engine service (services/trading/) with 12 pure computation modules: position sizer, stop-loss manager, reserve pool, circuit breaker, risk tier controller, correlation matrix, tax lots, trading window, gradual entry, notifications, micro-trading, backtester - Core TradingEngine with pre-trade evaluation pipeline and integration wiring - FastAPI HTTP service with 14 endpoints (health, config, decisions, metrics, backtest) - Performance tracker with Sharpe ratio, drawdown, profit factor computation - 194 Python tests (165 property-based + 29 integration) - Frontend: 13 TanStack Query hooks, 7 dashboard panels, tabbed Trading Engine page - Helm chart entry, network policy, nginx proxy, ingress for trading-engine - Shared infrastructure: enums, Redis keys, TradingConfig in AppConfig
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user