"""Performance metrics calculation for backtesting.""" from typing import List from decimal import Decimal import math def calculate_total_return(initial_value: Decimal, final_value: Decimal) -> float: """ 총 수익률 계산. Args: initial_value: 초기 자산 final_value: 최종 자산 Returns: 총 수익률 (%) """ if initial_value == 0: return 0.0 return float((final_value - initial_value) / initial_value * 100) def calculate_cagr(initial_value: Decimal, final_value: Decimal, years: float) -> float: """ 연평균 복리 수익률(CAGR) 계산. Args: initial_value: 초기 자산 final_value: 최종 자산 years: 투자 기간 (년) Returns: CAGR (%) """ if initial_value == 0 or years == 0: return 0.0 return float((pow(float(final_value / initial_value), 1 / years) - 1) * 100) def calculate_max_drawdown(equity_curve: List[Decimal]) -> float: """ 최대 낙폭(MDD) 계산. Args: equity_curve: 자산 곡선 리스트 Returns: MDD (%) """ if not equity_curve: return 0.0 max_dd = 0.0 peak = equity_curve[0] for value in equity_curve: if value > peak: peak = value drawdown = float((peak - value) / peak * 100) if peak > 0 else 0.0 max_dd = max(max_dd, drawdown) return max_dd def calculate_sharpe_ratio(returns: List[float], risk_free_rate: float = 0.0) -> float: """ 샤프 비율 계산 (연율화). Args: returns: 일별 수익률 리스트 (%) risk_free_rate: 무위험 이자율 (기본 0%) Returns: 샤프 비율 """ if not returns or len(returns) < 2: return 0.0 # 평균 수익률 mean_return = sum(returns) / len(returns) # 표준편차 variance = sum((r - mean_return) ** 2 for r in returns) / (len(returns) - 1) std_dev = math.sqrt(variance) if std_dev == 0: return 0.0 # 샤프 비율 (연율화: sqrt(252) - 주식 시장 거래일 수) sharpe = (mean_return - risk_free_rate) / std_dev * math.sqrt(252) return sharpe def calculate_sortino_ratio(returns: List[float], risk_free_rate: float = 0.0) -> float: """ 소르티노 비율 계산 (연율화). Args: returns: 일별 수익률 리스트 (%) risk_free_rate: 무위험 이자율 (기본 0%) Returns: 소르티노 비율 """ if not returns or len(returns) < 2: return 0.0 # 평균 수익률 mean_return = sum(returns) / len(returns) # 하방 편차 (Downside Deviation) downside_returns = [r for r in returns if r < risk_free_rate] if not downside_returns: return float('inf') # 손실이 없는 경우 downside_variance = sum((r - risk_free_rate) ** 2 for r in downside_returns) / len(downside_returns) downside_std = math.sqrt(downside_variance) if downside_std == 0: return 0.0 # 소르티노 비율 (연율화) sortino = (mean_return - risk_free_rate) / downside_std * math.sqrt(252) return sortino def calculate_win_rate(trades: List[dict]) -> float: """ 승률 계산. Args: trades: 거래 리스트 (각 거래는 pnl 필드 포함) Returns: 승률 (%) """ if not trades: return 0.0 winning_trades = sum(1 for trade in trades if trade.get('pnl', 0) > 0) total_trades = len(trades) return (winning_trades / total_trades * 100) if total_trades > 0 else 0.0 def calculate_volatility(returns: List[float]) -> float: """ 변동성 계산 (연율화). Args: returns: 일별 수익률 리스트 (%) Returns: 연율화 변동성 (%) """ if not returns or len(returns) < 2: return 0.0 mean_return = sum(returns) / len(returns) variance = sum((r - mean_return) ** 2 for r in returns) / (len(returns) - 1) std_dev = math.sqrt(variance) # 연율화 annualized_volatility = std_dev * math.sqrt(252) return annualized_volatility def calculate_calmar_ratio(total_return_pct: float, max_drawdown_pct: float, years: float) -> float: """ 칼마 비율 계산. Args: total_return_pct: 총 수익률 (%) max_drawdown_pct: MDD (%) years: 투자 기간 (년) Returns: 칼마 비율 """ if max_drawdown_pct == 0 or years == 0: return 0.0 cagr = (math.pow(1 + total_return_pct / 100, 1 / years) - 1) * 100 calmar = cagr / max_drawdown_pct return calmar