191 lines
4.5 KiB
Python

"""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