-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbacktester.py
More file actions
139 lines (119 loc) · 4.98 KB
/
Copy pathbacktester.py
File metadata and controls
139 lines (119 loc) · 4.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"""백테스팅 엔진 - 포트폴리오 시뮬레이션 및 성과 지표"""
import numpy as np
import pandas as pd
import config
def run_backtest(
df: pd.DataFrame,
signals_df: pd.DataFrame,
initial_capital: float = config.INITIAL_CAPITAL,
commission_pct: float = config.COMMISSION_PCT,
) -> dict:
"""
Long-only 백테스터: BUY → 전량 매수, SELL → 전량 매도, HOLD → 유지.
"""
print(f"[백테스트] 초기자본 ${initial_capital:,.0f}, 수수료 {commission_pct*100:.1f}%")
cash = initial_capital
shares = 0
portfolio_values = []
trades = []
entry_price = 0.0
entry_date = None
for i in range(len(df)):
date = df.index[i]
close = df.iloc[i]["Close"]
position = signals_df.iloc[i]["Position"]
# 매수: 현금 보유 중 + BUY 신호
if position == "BUY" and shares == 0:
cost = cash * (1 - commission_pct)
shares = cost / close
entry_price = close
entry_date = date
cash = 0.0
# 매도: 주식 보유 중 + SELL 신호
elif position == "SELL" and shares > 0:
proceeds = shares * close * (1 - commission_pct)
trade_return = (close - entry_price) / entry_price * 100
trades.append({
"entry_date": entry_date,
"exit_date": date,
"entry_price": entry_price,
"exit_price": close,
"return_pct": trade_return,
"profit": proceeds - (shares * entry_price),
})
cash = proceeds
shares = 0
# 포트폴리오 가치
portfolio_value = cash + shares * close
portfolio_values.append({"Date": date, "Value": portfolio_value})
portfolio_df = pd.DataFrame(portfolio_values).set_index("Date")
pv = portfolio_df["Value"]
# 성과 지표 계산
total_return = (pv.iloc[-1] - initial_capital) / initial_capital * 100
trading_days = len(pv)
years = trading_days / config.TRADING_DAYS_PER_YEAR
annual_return = ((pv.iloc[-1] / initial_capital) ** (1 / years) - 1) * 100 if years > 0 else 0
daily_returns = pv.pct_change().dropna()
sharpe = calculate_sharpe_ratio(daily_returns)
max_dd = calculate_max_drawdown(pv)
# Buy & Hold 비교
bh_return = (df.iloc[-1]["Close"] - df.iloc[0]["Close"]) / df.iloc[0]["Close"] * 100
# 승률
if trades:
wins = [t for t in trades if t["return_pct"] > 0]
losses = [t for t in trades if t["return_pct"] <= 0]
win_rate = len(wins) / len(trades) * 100
gross_profit = sum(t["profit"] for t in wins) if wins else 0
gross_loss = abs(sum(t["profit"] for t in losses)) if losses else 1
profit_factor = gross_profit / gross_loss if gross_loss > 0 else float("inf")
else:
win_rate = 0
profit_factor = 0
result = {
"total_return_pct": round(total_return, 2),
"annual_return_pct": round(annual_return, 2),
"sharpe_ratio": round(sharpe, 2),
"max_drawdown_pct": round(max_dd, 2),
"total_trades": len(trades),
"win_rate": round(win_rate, 1),
"profit_factor": round(profit_factor, 2),
"buy_and_hold_return_pct": round(bh_return, 2),
"portfolio_values": pv,
"trades": trades,
"years": round(years, 1),
}
return result
def calculate_sharpe_ratio(
daily_returns: pd.Series,
risk_free_rate: float = config.RISK_FREE_RATE,
) -> float:
"""연간화 샤프 비율"""
if daily_returns.std() == 0:
return 0.0
excess_return = daily_returns.mean() * config.TRADING_DAYS_PER_YEAR - risk_free_rate
annual_std = daily_returns.std() * np.sqrt(config.TRADING_DAYS_PER_YEAR)
return excess_return / annual_std
def calculate_max_drawdown(portfolio_values: pd.Series) -> float:
"""최대 낙폭 (%)"""
running_max = portfolio_values.cummax()
drawdown = (portfolio_values - running_max) / running_max * 100
return drawdown.min()
def print_summary(result: dict) -> None:
"""백테스트 결과 콘솔 출력"""
print("\n" + "=" * 60)
print(" QuantMiroFish 백테스트 결과")
print("=" * 60)
print(f" 분석 기간 : {result['years']}년")
print(f" 총 수익률 : {result['total_return_pct']:+.2f}%")
print(f" 연간 수익률 : {result['annual_return_pct']:+.2f}%")
print(f" 샤프 비율 : {result['sharpe_ratio']:.2f}")
print(f" 최대 낙폭 (MDD) : {result['max_drawdown_pct']:.2f}%")
print("-" * 60)
print(f" 총 거래 횟수 : {result['total_trades']}회")
print(f" 승률 : {result['win_rate']:.1f}%")
print(f" 수익/손실 비율 : {result['profit_factor']:.2f}")
print("-" * 60)
print(f" Buy & Hold 수익률 : {result['buy_and_hold_return_pct']:+.2f}%")
alpha = result['total_return_pct'] - result['buy_and_hold_return_pct']
print(f" 초과 수익 (Alpha) : {alpha:+.2f}%")
print("=" * 60)