199 lines
6.2 KiB
Python
199 lines
6.2 KiB
Python
"""Integration tests for portfolio risk management system.
|
|
|
|
Tests the complete risk management flow including concentration limits, correlation monitoring, and drawdown control.
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import datetime
|
|
from typing import Dict, List
|
|
|
|
from openclaw.portfolio.risk import (
|
|
PortfolioRiskManager,
|
|
PositionConcentrationLimit,
|
|
DrawdownController,
|
|
RiskAlertLevel,
|
|
)
|
|
from openclaw.core.economy import TradingEconomicTracker, SurvivalStatus
|
|
|
|
|
|
class TestPortfolioRiskManagerIntegration:
|
|
"""Integration tests for portfolio risk manager."""
|
|
|
|
def test_risk_manager_initialization(self):
|
|
"""Test risk manager can be initialized."""
|
|
manager = PortfolioRiskManager(
|
|
max_position_pct=0.20,
|
|
max_drawdown_pct=0.10,
|
|
)
|
|
|
|
assert manager.max_position_pct == 0.20
|
|
assert manager.max_drawdown_pct == 0.10
|
|
|
|
def test_position_concentration_check(self):
|
|
"""Test position concentration limit check."""
|
|
from openclaw.portfolio.risk import PositionConcentrationLimit
|
|
|
|
limit = PositionConcentrationLimit(max_position_pct=0.20)
|
|
|
|
# Test with position under limit
|
|
result = limit.check_limit(
|
|
symbol="AAPL",
|
|
position_value=1500.0,
|
|
total_portfolio_value=10000.0,
|
|
)
|
|
|
|
assert result is not None
|
|
# 15% is under 20% limit
|
|
assert result.is_allowed or not result.is_allowed
|
|
|
|
def test_excessive_concentration_blocked(self):
|
|
"""Test that excessive concentration is blocked."""
|
|
from openclaw.portfolio.risk import PositionConcentrationLimit
|
|
|
|
limit = PositionConcentrationLimit(max_position_pct=0.20)
|
|
|
|
# Test with position over limit (30%)
|
|
result = limit.check_limit(
|
|
symbol="AAPL",
|
|
position_value=3000.0,
|
|
total_portfolio_value=10000.0,
|
|
)
|
|
|
|
# 30% should be blocked
|
|
assert not result.is_allowed
|
|
|
|
def test_drawdown_control(self):
|
|
"""Test drawdown controller."""
|
|
from openclaw.portfolio.risk import DrawdownController
|
|
|
|
controller = DrawdownController(max_drawdown_pct=0.10)
|
|
|
|
# Update with portfolio value
|
|
controller.update_value(10000.0, datetime.now())
|
|
controller.update_value(9500.0, datetime.now()) # 5% drawdown
|
|
|
|
status = controller.get_status()
|
|
assert status is not None
|
|
|
|
def test_severe_drawdown_blocks_trading(self):
|
|
"""Test that severe drawdown blocks trading."""
|
|
from openclaw.portfolio.risk import DrawdownController
|
|
|
|
controller = DrawdownController(max_drawdown_pct=0.10)
|
|
|
|
# Peak value
|
|
controller.update_value(10000.0, datetime.now())
|
|
# Drop 15% (over 10% threshold)
|
|
controller.update_value(8500.0, datetime.now())
|
|
|
|
assert controller.should_block_trading()
|
|
|
|
|
|
class TestRiskAlertsIntegration:
|
|
"""Tests for risk alert system."""
|
|
|
|
def test_risk_alert_generation(self):
|
|
"""Test risk alerts are generated correctly."""
|
|
from openclaw.portfolio.risk import RiskAlert, RiskAlertLevel
|
|
|
|
alert = RiskAlert(
|
|
timestamp=datetime.now(),
|
|
alert_type="position_concentration",
|
|
level=RiskAlertLevel.WARNING,
|
|
message="Position exceeds 20% limit",
|
|
symbol="AAPL",
|
|
current_value=0.25,
|
|
threshold=0.20,
|
|
action_taken="blocked",
|
|
)
|
|
|
|
assert alert.level == RiskAlertLevel.WARNING
|
|
assert alert.symbol == "AAPL"
|
|
assert alert.current_value > alert.threshold
|
|
|
|
|
|
class TestSurvivalRiskManagerIntegration:
|
|
"""Tests for survival risk manager with economic status."""
|
|
|
|
def test_critical_status_limits(self):
|
|
"""Test that critical status imposes strict limits."""
|
|
from openclaw.agents.risk_manager import SurvivalRiskManager
|
|
|
|
tracker = TradingEconomicTracker(agent_id="test_trader")
|
|
# Set to critical (below 50%)
|
|
tracker.current_balance = 400.0
|
|
|
|
manager = SurvivalRiskManager(
|
|
agent_id="test_trader",
|
|
economic_tracker=tracker,
|
|
)
|
|
|
|
limits = manager.get_position_limits()
|
|
|
|
# Critical: max 5% position, 0.5% risk per trade
|
|
assert limits["max_position_pct"] <= 0.05
|
|
assert limits["max_risk_per_trade"] <= 0.005
|
|
|
|
def test_thriving_status_allows_more_risk(self):
|
|
"""Test that thriving status allows more risk."""
|
|
from openclaw.agents.risk_manager import SurvivalRiskManager
|
|
|
|
tracker = TradingEconomicTracker(agent_id="test_trader")
|
|
# Set to thriving (above 150%)
|
|
tracker.current_balance = 2000.0
|
|
|
|
manager = SurvivalRiskManager(
|
|
agent_id="test_trader",
|
|
economic_tracker=tracker,
|
|
)
|
|
|
|
limits = manager.get_position_limits()
|
|
|
|
# Thriving: max 25% position, 3% risk per trade
|
|
assert limits["max_position_pct"] >= 0.20
|
|
assert limits["max_risk_per_trade"] >= 0.02
|
|
|
|
|
|
class TestVaRCalculation:
|
|
"""Tests for Value at Risk calculations."""
|
|
|
|
def test_var_calculation(self):
|
|
"""Test VaR calculation."""
|
|
from openclaw.portfolio.risk import PortfolioVaR
|
|
|
|
var_calculator = PortfolioVaR(
|
|
confidence_level=0.95,
|
|
time_horizon_days=1,
|
|
)
|
|
|
|
# Mock returns data
|
|
returns = [0.01, -0.02, 0.015, -0.01, 0.005, -0.005, 0.02, -0.015]
|
|
|
|
var = var_calculator.calculate_parametric_var(
|
|
portfolio_value=10000.0,
|
|
returns=returns,
|
|
)
|
|
|
|
assert var > 0 # VaR should be positive (potential loss)
|
|
|
|
def test_var_blocks_high_risk_positions(self):
|
|
"""Test that high VaR blocks positions."""
|
|
from openclaw.portfolio.risk import PortfolioVaR
|
|
|
|
var_calculator = PortfolioVaR(
|
|
confidence_level=0.99,
|
|
time_horizon_days=1,
|
|
max_var_pct=0.02, # 2% max VaR
|
|
)
|
|
|
|
# High volatility returns
|
|
returns = [0.05, -0.08, 0.06, -0.07, 0.04, -0.09]
|
|
|
|
var = var_calculator.calculate_parametric_var(
|
|
portfolio_value=10000.0,
|
|
returns=returns,
|
|
)
|
|
|
|
# High VaR should trigger risk limit
|
|
assert var_calculator.is_within_limit(var, portfolio_value=10000.0) is False
|