일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- GridSearchCV
- randomforest
- sarima
- 백테스트
- lstm
- TimeSeries
- Programmers
- PolynomialFeatures
- 변동성돌파전략
- Crawling
- hackerrank
- 프로그래머스
- 비트코인
- Quant
- 토익스피킹
- docker
- 파이썬 주식
- 볼린저밴드
- 빅데이터분석기사
- backtest
- 데이터분석전문가
- 데이터분석
- 파이썬
- 실기
- 주식
- 코딩테스트
- 파트5
- SQL
- Python
- ADP
- Today
- Total
데이터 공부를 기록하는 공간
볼린저밴드-추세추종-return 계산하기(백테스트 문제점) 본문
Q. backtest 할 때 return을 제대로 산출하고 있는가?
기존ⓐ df['return'] = np.log(df['close']/df['close'].shift(1)) # 로그수익률
의문ⓑ df['return'] = np.log(df['close'].shift(-1)/df['close']) # 로그수익률
ⓐ 와 ⓑ 중 옳은 return 산출 방식은?
현재 업비트에서 실제와 주피터 노트북에서 백테스트한 결과가 같아야 한다.
실제 업비트 환경에서는 앞에서 살펴본것과 같이
조건 만족여부를 탐색할 때 가장 최근값은 dataframe 에서 거꾸로 두번째 데이터 사용한다.
즉, 바로 앞 5분 봉의 종가를 기준으로 평가하고 매수를 한다.
슬리피지가 없다고 가정하고, 매수 조건을 만족하는 경우 매수가는
바로 앞 5분 봉의 종가다.
즉, backtest 환경에서는
매수 조건을 만족하는 CLOSE 와
매도 조건을 만족하는 CLOSE 로 수익률을 계산해야 한다.
position (1또는 0)을 return에 곱해서 stragey 수익률을 계산하는 경우
기존 ⓐ를 return으로 하여 수익률을 계산하는 경우,
매수조건을 만족하는 시점의 CLOSE 가격이 아닌
매수조건을 만족하는 시점의 OPEN가격에서 (정확하게는 5분봉 전 CLOSE에서) 매수하는 것으로 계산한다.
매도조건을 만족하는 시점의 CLOSE 가격이 아닌
매도조건을 만족하는 시점의 OPEN가격에서 (정확하게는 5분봉 전 CLOSE에서) 매도하는 것으로 계산한다.
> 그 결과 수익률이 높에 나왔다. 시나리오 별로 달랐지만 5분봉 10000개로 18배 데이터까지 보았다. (슬리피지 및 수수료 제외)
의문 ⓑ로 return으로 하여 수익률을 계산하는 경우,
매수조건을 만족하는 시점의 CLOSE로 매수한 것으로 계산하고 (이것보다는 높아질 수 있다.)
매도조건을 만족하는 시점의 CLOSE로 매도한 것으로 계산한다. (이것보다는 낮아질 수 있다.)
> 아쉽게도 이렇게 계산하는 경우 수익률이 나오질 않는다... ??
의문 ⓑ 로 return을 했을 때 :
window_b = 20
window_f=10
buy_pb = 0.8
buy_mfi = 80
sell_pb = 0.2
sell_mfi=20
df = data.copy()
df['MA20'] = df['close'].rolling(window=window_b).mean()
df['stddev'] = df['close'].rolling(window=window_b).std()
df['upper'] = df['MA20'] + df['stddev']*2
df['lower'] = df['MA20'] - df['stddev']*2
### PB : %b (종가 - 하단밴드) / (상단밴드 - 하단밴드) ###
df['PB'] = (df['close'] - df['lower']) / (df['upper'] - df['lower'])
### 밴드폭 (상단밴드 - 하단밴드) / 중간밴드
df['bandwidth'] = (df['upper'] - df['lower']) / df['MA20'] * 100
### 중심가격
df['TP'] = (df['high'] + df['low'] + df['close']) / 3
### 긍정적 현금 흐름 : 중심가격이 전일보다 상승한 날들의 현금 흐름의 합
df['PMF'] = 0
### 부정적 현금 흐름 : 중심가격이 전일보다 하락한 날들의 현금 흐름의 합
df['NMF'] = 0
for i in range(len(df.close)-1):
if df.TP.values[i] < df.TP.values[i+1]: # TP값이 전날보다 증가한다면,
df.PMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
df.NMF.values[i+1] = 0
else:
df.PMF.values[i+1] = 0
df.NMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
### 현금 흐름 비율 : Money Flow Ratio
df['MFR'] = df.PMF.rolling(window=window_f).sum() / df.NMF.rolling(window=window_f).sum()
### 현금 흐름 지표 : Money Flow Index = 100 - ( 100 ÷ (1+MFR) )
df['MFI'] = 100 - 100 / (1+df['MFR'])
#df['return'] = np.log(df['close']/df['close'].shift(1)) # 로그수익률
df['return'] = np.log(df['close'].shift(-1)/df['close']) # 로그수익률
df['return2'] = df['close'].shift(-1)/df['close']
## df.PB.values[i] > 0.8 and df.MFI.values[i] > 80:
cond_buy = (df['PB'] > buy_pb) & (df['MFI'] > buy_mfi)
df.loc[cond_buy, 'position'] = 1
## df.PB.values[i] < 0.2 and df.MFI.values[i] < 20:
cond_sell = (df['PB'] < sell_pb) & (df['MFI'] < sell_mfi)
df.loc[cond_sell, 'position'] = 0
df['position'] = df['position'].fillna(method = "ffill")
df['strategy'] = df['position'] * df['return']
df['cumret'] = df['strategy'].cumsum().apply(np.exp)
df['cummax'] = df['cumret'].cummax()
df = df[max(window_b, window_f):]
drawdown = df['cummax'] - df['cumret']
mdd = drawdown.max()
result = df[['return','strategy']].sum().apply(np.exp)
strategy = result['strategy']
df[['open','close','return2','return','PB','MFI','position','strategy']][76:142]
매수가 63122000
매도가 62457000
수익률 : 0.989465 (=62457/63122)
df['strategy'][77:142].cumsum().apply(np.exp)
strategy가 모두 100% 미만이다.....
(참고 코드)
# window_b : 볼린저밴드 윈도우
# window_f : 현금흐름지표 윈도우
# buy_pb : 매수조건 퍼센트볼린저 하한
# buy_mfi : 매수조건 현금흐름지표 하한
# sell_pb : 매도조건 퍼센트볼린저 상한
# sell_mfi : 매도조건 현금흐름지표 상한
# 0 < pb < 1
# 0 < mfi < 100
def 볼린저밴드_추세추종_찾기(data=data, window_b = 20, window_f=10, buy_pb = 0.8, buy_mfi = 80, sell_pb = 0.2, sell_mfi=20, chart=False):
df = data.copy()
df['MA20'] = df['close'].rolling(window=window_b).mean()
df['stddev'] = df['close'].rolling(window=window_b).std()
df['upper'] = df['MA20'] + df['stddev']*2
df['lower'] = df['MA20'] - df['stddev']*2
### PB : %b (종가 - 하단밴드) / (상단밴드 - 하단밴드) ###
df['PB'] = (df['close'] - df['lower']) / (df['upper'] - df['lower'])
### 밴드폭 (상단밴드 - 하단밴드) / 중간밴드
df['bandwidth'] = (df['upper'] - df['lower']) / df['MA20'] * 100
### 중심가격
df['TP'] = (df['high'] + df['low'] + df['close']) / 3
### 긍정적 현금 흐름 : 중심가격이 전일보다 상승한 날들의 현금 흐름의 합
df['PMF'] = 0
### 부정적 현금 흐름 : 중심가격이 전일보다 하락한 날들의 현금 흐름의 합
df['NMF'] = 0
for i in range(len(df.close)-1):
if df.TP.values[i] < df.TP.values[i+1]: # TP값이 전날보다 증가한다면,
df.PMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
df.NMF.values[i+1] = 0
else:
df.PMF.values[i+1] = 0
df.NMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
### 현금 흐름 비율 : Money Flow Ratio
df['MFR'] = df.PMF.rolling(window=window_f).sum() / df.NMF.rolling(window=window_f).sum()
### 현금 흐름 지표 : Money Flow Index = 100 - ( 100 ÷ (1+MFR) )
df['MFI'] = 100 - 100 / (1+df['MFR'])
df['return'] = np.log(df['close']/df['close'].shift(1)) # 로그수익률
## df.PB.values[i] > 0.8 and df.MFI.values[i] > 80:
cond_buy = (df['PB'] > buy_pb) & (df['MFI'] > buy_mfi)
df.loc[cond_buy, 'position'] = 1
## df.PB.values[i] < 0.2 and df.MFI.values[i] < 20:
cond_sell = (df['PB'] < sell_pb) & (df['MFI'] < sell_mfi)
df.loc[cond_sell, 'position'] = 0
df['position'] = df['position'].fillna(method = "ffill")
df['strategy'] = df['position'] * df['return']
df['cumret'] = df['strategy'].cumsum().apply(np.exp)
df['cummax'] = df['cumret'].cummax()
df = df[max(window_b, window_f):]
drawdown = df['cummax'] - df['cumret']
mdd = drawdown.max()
result = df[['return','strategy']].sum().apply(np.exp)
strategy = result['strategy']
print("window_b = ",window_b, "window_f = " , window_f, "buy_pb = ", buy_pb, "buy_mfi = ", buy_mfi, "sell_pb = ", sell_pb, "sell_mfi = ", sell_mfi)
# print("backtest results : ")
# print(data.index[0] , " ~ ", data.index[-1])
# print("buy : sell = " + str(df.loc[cond_buy].shape[0]) + " : " + str(df.loc[cond_sell].shape[0]))
# print("return : ", np.round(result['return']*100,1),"%")
# print("strategy : ", np.round(result['strategy']*100,1),"%")
# print("mdd : △", np.round(mdd*100,0),"%")
if chart == True:
fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, figsize=(20,8))
ax1.plot(df.index, df['close'], color='#0000ff', label='close')
ax1.plot(df.index, df['upper'], 'r--', label='upper band')
ax1.plot(df.index, df['lower'], 'c--', label='lower band')
ax1.plot(df.index, df['MA20'], 'k--', label=f'MA{window_b}')
ax1.fill_between(df.index, df['upper'], df['lower'], color='0.9')
for i in range(len(df.close)):
if df.PB.values[i] > buy_pb and df.MFI.values[i] > buy_mfi:
ax1.plot(df.index.values[i], df.close.values[i], 'r^')
elif df.PB.values[i] < sell_pb and df.MFI.values[i] < sell_mfi:
ax1.plot(df.index.values[i], df.close.values[i], 'bv')
ax1.legend(loc='best')
ax2.plot(df.index, df['PB']*100, 'b', label= '%B x 100')
ax2.plot(df.index, df['MFI'], 'g--', label=f'MFI({window_f} day)')
ax2.set_yticks([-20, 0, 20, 40, 60, 80, 100, 120])
for i in range(len(df.close)):
if df.PB.values[i] > buy_pb and df.MFI.values[i] > buy_mfi:
ax2.plot(df.index.values[i], 0, 'r^')
elif df.PB.values[i] < sell_pb and df.MFI.values[i] < sell_mfi:
ax2.plot(df.index.values[i], 0, 'gv')
#ax2.grid(True)
ax2.legend(loc='best')
df[['return','strategy']].cumsum().apply(np.exp).plot(ax=ax3)
return strategy, mdd
'STOCK > 비트코인' 카테고리의 다른 글
볼린저밴드-찾기 (0) | 2022.01.16 |
---|---|
볼린저밴드-추세추종-문제점 (0) | 2022.01.16 |
볼린저밴드-추세추종 (실전 테스트) (0) | 2022.01.11 |
볼린저밴드-추세추종 (0) | 2022.01.10 |
백테스트 - MLP 일봉 4년데이터 (0) | 2022.01.03 |