90 lines
3.6 KiB
Python
90 lines
3.6 KiB
Python
from typing import Dict, Tuple
|
|
|
|
class NavErosionService:
|
|
def _calculate_nav_risk(self, etf_data: Dict, etf_type: ETFType) -> Tuple[float, Dict]:
|
|
"""Calculate NAV risk components with ETF-type specific adjustments"""
|
|
components = {}
|
|
|
|
# Base risk calculation with ETF-type specific thresholds
|
|
if etf_data.get('max_drawdown') is not None:
|
|
if etf_type == ETFType.INCOME:
|
|
# Income ETFs typically have lower drawdowns
|
|
if etf_data['max_drawdown'] > 0.25:
|
|
components['drawdown'] = 7
|
|
elif etf_data['max_drawdown'] > 0.15:
|
|
components['drawdown'] = 5
|
|
elif etf_data['max_drawdown'] > 0.10:
|
|
components['drawdown'] = 3
|
|
else:
|
|
components['drawdown'] = 2
|
|
elif etf_type == ETFType.GROWTH:
|
|
# Growth ETFs typically have higher drawdowns
|
|
if etf_data['max_drawdown'] > 0.35:
|
|
components['drawdown'] = 7
|
|
elif etf_data['max_drawdown'] > 0.25:
|
|
components['drawdown'] = 5
|
|
elif etf_data['max_drawdown'] > 0.15:
|
|
components['drawdown'] = 3
|
|
else:
|
|
components['drawdown'] = 2
|
|
else: # BALANCED
|
|
# Balanced ETFs have moderate drawdowns
|
|
if etf_data['max_drawdown'] > 0.30:
|
|
components['drawdown'] = 7
|
|
elif etf_data['max_drawdown'] > 0.20:
|
|
components['drawdown'] = 5
|
|
elif etf_data['max_drawdown'] > 0.12:
|
|
components['drawdown'] = 3
|
|
else:
|
|
components['drawdown'] = 2
|
|
else:
|
|
components['drawdown'] = 4 # Default medium risk if no data
|
|
|
|
# Rest of the method remains unchanged
|
|
if etf_data.get('volatility') is not None:
|
|
if etf_data['volatility'] > 0.40:
|
|
components['volatility'] = 7
|
|
elif etf_data['volatility'] > 0.25:
|
|
components['volatility'] = 5
|
|
elif etf_data['volatility'] > 0.15:
|
|
components['volatility'] = 3
|
|
else:
|
|
components['volatility'] = 2
|
|
else:
|
|
components['volatility'] = 4
|
|
|
|
if etf_data.get('sharpe_ratio') is not None:
|
|
if etf_data['sharpe_ratio'] >= 2.0:
|
|
components['sharpe'] = 1
|
|
elif etf_data['sharpe_ratio'] >= 1.5:
|
|
components['sharpe'] = 2
|
|
elif etf_data['sharpe_ratio'] >= 1.0:
|
|
components['sharpe'] = 3
|
|
elif etf_data['sharpe_ratio'] >= 0.5:
|
|
components['sharpe'] = 4
|
|
else:
|
|
components['sharpe'] = 5
|
|
else:
|
|
components['sharpe'] = 4
|
|
|
|
if etf_data.get('sortino_ratio') is not None:
|
|
if etf_data['sortino_ratio'] >= 2.0:
|
|
components['sortino'] = 1
|
|
elif etf_data['sortino_ratio'] >= 1.5:
|
|
components['sortino'] = 2
|
|
elif etf_data['sortino_ratio'] >= 1.0:
|
|
components['sortino'] = 3
|
|
elif etf_data['sortino_ratio'] >= 0.5:
|
|
components['sortino'] = 4
|
|
else:
|
|
components['sortino'] = 5
|
|
else:
|
|
components['sortino'] = 4
|
|
|
|
# Calculate weighted NAV risk
|
|
nav_risk = sum(
|
|
components[component] * weight
|
|
for component, weight in self.NAV_COMPONENT_WEIGHTS.items()
|
|
)
|
|
|
|
return nav_risk, components |