📌 이번 실습 목표
- 단순 수익률뿐만 아니라 리스크 조정 수익률 분석
- 샤프 지수, 최대 낙폭(Max Drawdown), 승률 계산
- 전략의 안정성과 지속 가능성 평가
🔍 왜 수익률만으로는 부족한가?
수익률이 높아 보여도, 그 과정에서 극단적인 손실이나 높은 변동성이 있었다면 실전 투자에는 위험합니다.
따라서 전략을 평가할 때는 아래와 같은 성과 지표를 함께 확인해야 합니다:
- 샤프 지수 (Sharpe Ratio): 수익률 / 리스크 → 리스크 대비 수익
- 최대 낙폭 (Max Drawdown): 최고점 대비 최대 하락폭 → 심리적 리스크
- 승률: 전체 거래 중 수익을 낸 비율
🧮 전략 실행 후 평가 지표 계산
import numpy as np
def evaluate_strategy(df):
df = df.copy()
returns = df['Strategy Return'].dropna()
# 샤프 지수 (무위험 수익률 0으로 가정)
sharpe_ratio = (returns.mean() / returns.std()) * np.sqrt(252)
# 최대 낙폭 계산
cum_returns = (1 + returns).cumprod()
peak = cum_returns.cummax()
drawdown = (cum_returns - peak) / peak
max_drawdown = drawdown.min()
# 승률 계산
win_rate = (returns > 0).sum() / returns.count()
return {
'Final Return': cum_returns.iloc[-1],
'Sharpe Ratio': sharpe_ratio,
'Max Drawdown': max_drawdown,
'Win Rate': win_rate
}
✅ 샤프 지수 해설
샤프 지수는 수익률을 리스크(표준편차)로 나눈 값입니다.
- 1.0 이상 → 양호한 전략
- 2.0 이상 → 우수 전략
- 3.0 이상 → 탁월한 전략
낮은 리스크로 높은 수익을 냈다는 의미이므로, 실전 투자에서 가장 많이 쓰이는 평가 지표입니다.
📉 최대 낙폭 (Max Drawdown) 해설
최대 낙폭은 수익률 곡선이 최고점에서 얼마나 크게 떨어졌는가를 측정합니다.
예: 누적 수익률이 1.4에서 0.8로 떨어졌다면 최대 낙폭은 약 -43%
낙폭이 크면 투자자가 심리적으로 이탈하거나 중도 청산할 가능성이 큽니다.
📊 전략 결과 출력 예시
# 전략 실행
def run_strategy_with_params(df, rsi_buy=30, rsi_sell=70, std_n=2.0):
df = df.copy()
# ✅ MultiIndex 처리
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
# ✅ 'Close'가 없으면 'Adj Close'로 대체
if 'Close' not in df.columns and 'Adj Close' in df.columns:
df['Close'] = df['Adj Close']
df['MA20'] = df['Close'].rolling(window=20).mean()
df['STD20'] = df['Close'].rolling(window=20).std()
df['Upper'] = df['MA20'] + (std_n * df['STD20'])
df['Lower'] = df['MA20'] - (std_n * df['STD20'])
delta = df['Close'].diff()
gain = delta.where(delta > 0, 0)
loss = -delta.where(delta < 0, 0)
avg_gain = gain.rolling(window=14).mean()
avg_loss = loss.rolling(window=14).mean()
epsilon = 1e-10
avg_loss = avg_loss.replace(0, epsilon)
rs = avg_gain / avg_loss
df['RSI'] = 100 - (100 / (1 + rs))
buy_signal = (df['RSI'] < rsi_buy) & (df['Close'] < df['Lower'])
sell_signal = (df['RSI'] > rsi_sell) | (df['Close'] > df['Upper'])
df['Position'] = 0
position = 0
for i in range(1, len(df)):
if buy_signal.iloc[i] and position == 0:
df.at[df.index[i], 'Position'] = 1
position = 1
elif sell_signal.iloc[i] and position == 1:
df.at[df.index[i], 'Position'] = 0
position = 0
else:
df.at[df.index[i], 'Position'] = df.at[df.index[i-1], 'Position']
df['Daily Return'] = df['Close'].pct_change()
df['Strategy Return'] = df['Daily Return'] * df['Position'].shift(1)
df['Cumulative Return'] = (1 + df['Strategy Return']).cumprod()
return df
df = yf.download("NVDA", period="6mo")
df = run_strategy_with_params(df, rsi_buy=30, rsi_sell=70, std_n=2.0)
eval_result = evaluate_strategy(df)
print(eval_result)
{
'Final Return': 1.0040717539588246,
'Sharpe Ratio': 0.21872294101513803,
'Max Drawdown': -0.2248705598923468,
'Win Rate': 0.13934426229508196
}
✅ 오늘의 요약
- 수익률 외에도 샤프 지수, 낙폭, 승률은 전략 검증의 핵심 지표
- 샤프 지수가 높고, 낙폭이 낮고, 승률이 높을수록 안정적인 전략
- 숫자로 전략의 투자 적합성을 정량적으로 판단 가능
'Python' 카테고리의 다른 글
[주식 분석 프로젝트] 9편 – 전략의 실전 적용 가능성 검토 & 체결 시뮬레이션 (0) | 2025.04.30 |
---|---|
[주식 분석 프로젝트] 총정리 – 한국 주식 기반 기술적 분석 (2~7편 요약) (0) | 2025.04.29 |
[주식 분석 프로젝트] 6편 – 전략 조건 최적화 실험 (0) | 2025.04.27 |
[주식 분석 프로젝트] 5편 – 전략의 종목별 확장 및 비교 (0) | 2025.04.26 |
[주식 분석 프로젝트] 4편 – 기술적 지표 전략 백테스팅 (0) | 2025.04.25 |