test_risk_analyzer.pyβ’14.8 kB
"""
Unit tests for RiskAnalyzer class
TDD Red Phase: Write failing tests for risk analysis engine
"""
import pytest
import asyncio
from unittest.mock import Mock, AsyncMock, patch
from datetime import datetime, date, timedelta
from typing import Dict, Any, List, Optional
# Test imports - initially will fail (TDD Red phase)
from src.tools.risk_tools import RiskAnalyzer
from src.exceptions import MCPStockDetailsError, InsufficientDataError
class TestRiskAnalyzer:
"""Test cases for RiskAnalyzer class"""
@pytest.fixture
def risk_analyzer(self):
"""Create RiskAnalyzer instance for testing"""
return RiskAnalyzer()
@pytest.fixture
def sample_financial_data(self):
"""Sample financial data for risk calculations"""
return {
"company_code": "005930",
"revenue": 258_774_000_000_000,
"total_debt": 45_000_000_000_000,
"total_equity": 100_000_000_000_000,
"current_assets": 180_000_000_000_000,
"current_liabilities": 85_000_000_000_000,
"ebitda": 28_500_000_000_000,
"interest_expense": 2_100_000_000_000,
"market_cap": 435_000_000_000_000,
"beta": 1.25
}
@pytest.fixture
def sample_price_history(self):
"""Sample price history for volatility calculations"""
return [
{"date": "2024-01-15", "close": 73000, "return": 0.007},
{"date": "2024-01-14", "close": 72500, "return": 0.010},
{"date": "2024-01-13", "close": 71800, "return": 0.018},
{"date": "2024-01-12", "close": 70500, "return": 0.010},
{"date": "2024-01-11", "close": 69800, "return": 0.014}
]
@pytest.mark.asyncio
async def test_calculate_market_risk(self, risk_analyzer, sample_financial_data, sample_price_history):
"""Test market risk calculation (Beta, VaR, volatility)"""
market_risk = await risk_analyzer.calculate_market_risk(
financial_data=sample_financial_data,
price_history=sample_price_history,
market_returns=[0.005, 0.008, 0.012, 0.006, 0.009]
)
assert market_risk is not None
assert isinstance(market_risk, dict)
# Should contain market risk metrics
assert "beta" in market_risk
assert "volatility" in market_risk
assert "var_95" in market_risk
assert "var_99" in market_risk
assert "correlation_market" in market_risk
# Beta should be reasonable
assert 0.5 <= market_risk["beta"] <= 2.0
# VaR should be positive
assert market_risk["var_95"] > 0
assert market_risk["var_99"] > market_risk["var_95"]
@pytest.mark.asyncio
async def test_calculate_credit_risk(self, risk_analyzer, sample_financial_data):
"""Test credit risk assessment"""
credit_risk = await risk_analyzer.calculate_credit_risk(
financial_data=sample_financial_data,
credit_rating="AA"
)
assert credit_risk is not None
assert isinstance(credit_risk, dict)
# Should contain credit risk components
assert "credit_rating" in credit_risk
assert "probability_default" in credit_risk
assert "debt_to_equity" in credit_risk
assert "interest_coverage" in credit_risk
assert "current_ratio" in credit_risk
assert "credit_score" in credit_risk
# Credit metrics should be reasonable
assert credit_risk["debt_to_equity"] >= 0
assert credit_risk["interest_coverage"] > 0
assert credit_risk["current_ratio"] > 0
assert 0 <= credit_risk["credit_score"] <= 100
@pytest.mark.asyncio
async def test_calculate_liquidity_risk(self, risk_analyzer, sample_financial_data):
"""Test liquidity risk assessment"""
trading_data = {
"average_volume": 15000000,
"bid_ask_spread": 0.002,
"price_impact": 0.015,
"market_cap": sample_financial_data["market_cap"]
}
liquidity_risk = await risk_analyzer.calculate_liquidity_risk(
financial_data=sample_financial_data,
trading_data=trading_data
)
assert liquidity_risk is not None
assert isinstance(liquidity_risk, dict)
# Should contain liquidity metrics
assert "liquidity_score" in liquidity_risk
assert "trading_volume" in liquidity_risk
assert "bid_ask_spread" in liquidity_risk
assert "market_impact" in liquidity_risk
assert "liquidity_risk_level" in liquidity_risk
# Liquidity score should be 0-100
assert 0 <= liquidity_risk["liquidity_score"] <= 100
# Risk level should be categorical
assert liquidity_risk["liquidity_risk_level"] in ["low", "medium", "high"]
@pytest.mark.asyncio
async def test_calculate_operational_risk(self, risk_analyzer, sample_financial_data):
"""Test operational risk assessment"""
operational_factors = {
"industry_risk": 65.0,
"regulatory_environment": 70.0,
"technology_dependence": 85.0,
"management_quality": 78.0,
"geographic_exposure": 60.0
}
operational_risk = await risk_analyzer.calculate_operational_risk(
financial_data=sample_financial_data,
operational_factors=operational_factors
)
assert operational_risk is not None
assert isinstance(operational_risk, dict)
# Should contain operational risk components
assert "business_risk_score" in operational_risk
assert "regulatory_risk" in operational_risk
assert "technology_risk" in operational_risk
assert "management_risk" in operational_risk
assert "operational_risk_level" in operational_risk
# Scores should be 0-100
for score_key in ["business_risk_score", "regulatory_risk", "technology_risk", "management_risk"]:
assert 0 <= operational_risk[score_key] <= 100
@pytest.mark.asyncio
async def test_calculate_concentration_risk(self, risk_analyzer, sample_financial_data):
"""Test concentration risk assessment"""
concentration_data = {
"geographic_breakdown": {"domestic": 0.55, "asia": 0.30, "americas": 0.10, "europe": 0.05},
"product_breakdown": {"semiconductors": 0.60, "displays": 0.25, "mobile": 0.15},
"customer_breakdown": {"customer_1": 0.22, "customer_2": 0.18, "others": 0.60}
}
concentration_risk = await risk_analyzer.calculate_concentration_risk(
financial_data=sample_financial_data,
concentration_data=concentration_data
)
assert concentration_risk is not None
assert isinstance(concentration_risk, dict)
# Should contain concentration metrics
assert "geographic_concentration" in concentration_risk
assert "product_concentration" in concentration_risk
assert "customer_concentration" in concentration_risk
assert "concentration_risk_score" in concentration_risk
# Concentration ratios should be 0-1
assert 0 <= concentration_risk["geographic_concentration"] <= 1
assert 0 <= concentration_risk["product_concentration"] <= 1
assert 0 <= concentration_risk["customer_concentration"] <= 1
@pytest.mark.asyncio
async def test_calculate_integrated_risk_score(self, risk_analyzer):
"""Test integrated risk score calculation"""
risk_components = {
"market_risk": {"score": 65.0, "weight": 0.25},
"credit_risk": {"score": 78.0, "weight": 0.20},
"liquidity_risk": {"score": 85.0, "weight": 0.15},
"operational_risk": {"score": 72.0, "weight": 0.25},
"concentration_risk": {"score": 68.0, "weight": 0.15}
}
integrated_score = await risk_analyzer.calculate_integrated_risk_score(
risk_components=risk_components
)
assert integrated_score is not None
assert isinstance(integrated_score, dict)
# Should contain integrated metrics
assert "overall_risk_score" in integrated_score
assert "risk_level" in integrated_score
assert "risk_grade" in integrated_score
assert "component_breakdown" in integrated_score
# Overall score should be 0-100
assert 0 <= integrated_score["overall_risk_score"] <= 100
# Risk level should be categorical
assert integrated_score["risk_level"] in ["very_low", "low", "medium", "high", "very_high"]
# Risk grade should be letter grade
assert integrated_score["risk_grade"] in ["A", "B", "C", "D", "F"]
@pytest.mark.asyncio
async def test_calculate_risk_adjusted_returns(self, risk_analyzer, sample_price_history):
"""Test risk-adjusted returns calculation"""
performance_data = {
"annual_return": 0.15,
"benchmark_return": 0.08,
"risk_free_rate": 0.03,
"beta": 1.25,
"volatility": 0.28
}
risk_adjusted = await risk_analyzer.calculate_risk_adjusted_returns(
performance_data=performance_data,
price_history=sample_price_history
)
assert risk_adjusted is not None
assert isinstance(risk_adjusted, dict)
# Should contain risk-adjusted metrics
assert "sharpe_ratio" in risk_adjusted
assert "treynor_ratio" in risk_adjusted
assert "jensen_alpha" in risk_adjusted
assert "information_ratio" in risk_adjusted
assert "sortino_ratio" in risk_adjusted
# Ratios should be reasonable
assert -5 <= risk_adjusted["sharpe_ratio"] <= 5
assert -5 <= risk_adjusted["treynor_ratio"] <= 5
@pytest.mark.asyncio
async def test_perform_scenario_analysis(self, risk_analyzer, sample_financial_data):
"""Test scenario analysis and stress testing"""
scenarios = {
"bull_case": {"market_change": 0.20, "sector_change": 0.25, "company_specific": 0.15},
"base_case": {"market_change": 0.08, "sector_change": 0.10, "company_specific": 0.05},
"bear_case": {"market_change": -0.15, "sector_change": -0.20, "company_specific": -0.10}
}
scenario_results = await risk_analyzer.perform_scenario_analysis(
financial_data=sample_financial_data,
scenarios=scenarios,
current_price=73000
)
assert scenario_results is not None
assert isinstance(scenario_results, dict)
# Should contain scenario results
assert "bull_case" in scenario_results
assert "base_case" in scenario_results
assert "bear_case" in scenario_results
assert "stress_test" in scenario_results
# Each scenario should have expected fields
for scenario in ["bull_case", "base_case", "bear_case"]:
assert "expected_price" in scenario_results[scenario]
assert "probability" in scenario_results[scenario]
assert "impact" in scenario_results[scenario]
@pytest.mark.asyncio
async def test_generate_risk_recommendations(self, risk_analyzer):
"""Test risk recommendation generation"""
risk_profile = {
"overall_risk_score": 72.5,
"risk_level": "medium",
"key_risks": ["market_volatility", "concentration_risk", "liquidity_risk"],
"risk_components": {
"market_risk": 65.0,
"credit_risk": 85.0,
"operational_risk": 70.0
}
}
recommendations = await risk_analyzer.generate_risk_recommendations(
risk_profile=risk_profile,
investment_horizon="long_term"
)
assert recommendations is not None
assert isinstance(recommendations, list)
assert len(recommendations) > 0
# Each recommendation should have required fields
for rec in recommendations:
assert "category" in rec
assert "recommendation" in rec
assert "priority" in rec
assert "impact" in rec
assert rec["priority"] in ["high", "medium", "low"]
@pytest.mark.asyncio
async def test_comprehensive_risk_analysis(self, risk_analyzer, sample_financial_data):
"""Test comprehensive risk analysis"""
comprehensive_analysis = await risk_analyzer.comprehensive_risk_analysis(
financial_data=sample_financial_data,
include_all_metrics=True
)
assert comprehensive_analysis is not None
assert isinstance(comprehensive_analysis, dict)
# Should contain all major risk categories
assert "market_risk" in comprehensive_analysis
assert "credit_risk" in comprehensive_analysis
assert "liquidity_risk" in comprehensive_analysis
assert "operational_risk" in comprehensive_analysis
assert "integrated_score" in comprehensive_analysis
assert "risk_summary" in comprehensive_analysis
# Should have summary metrics
assert "overall_risk_rating" in comprehensive_analysis["risk_summary"]
assert "key_risk_factors" in comprehensive_analysis["risk_summary"]
assert "recommendations" in comprehensive_analysis["risk_summary"]
@pytest.mark.asyncio
async def test_error_handling_insufficient_data(self, risk_analyzer):
"""Test error handling for insufficient data"""
# Test with insufficient financial data
insufficient_data = {"company_code": "005930"}
with pytest.raises(InsufficientDataError):
await risk_analyzer.calculate_credit_risk(insufficient_data)
@pytest.mark.asyncio
async def test_error_handling_invalid_parameters(self, risk_analyzer, sample_financial_data):
"""Test error handling for invalid parameters"""
# Test with invalid risk weights
invalid_components = {
"market_risk": {"score": 65.0, "weight": -0.25} # Negative weight
}
with pytest.raises(MCPStockDetailsError):
await risk_analyzer.calculate_integrated_risk_score(invalid_components)