데이터 공부를 기록하는 공간

볼린저밴드-추세추종-문제점 본문

STOCK/비트코인

볼린저밴드-추세추종-문제점

BOTTLE6 2022. 1. 16. 00:00

백테스트에서 슬리피지와 수수료를 고려하지 않았었다. 

수수료 0.05%는 그리 크지 않을 것이라 생각했다.

10만원 기준, 1회 매수,매도 세트로 수행시 0.1%로 100원의 수수료가 들어간다. 

1000만원 기준, 1회 매수, 매도 세트로 수행 시 0.1%로 1만원의 수수료가 들어간다. 

 

슬리피지는 내가 백테스트를 했을 때 원하는 가격과 실제로 내가 매수/매도하는 가격의 차이인데 이것이 꽤나 크다. 

 

지금부터 보인다. 

 

다음은 백테스트를 위한 함수

# window_b : 볼린저밴드 윈도우 
# window_f : 현금흐름지표 윈도우
# buy_pb : 매수조건 퍼센트볼린저 하한
# buy_mfi : 매수조건 현금흐름지표 하한
# sell_pb : 매도조건 퍼센트볼린저 상한
# sell_mfi : 매도조건 현금흐름지표 상한
# 0 < pb < 1
# 0 < mfi < 100

def 볼린저밴드_추세추종_찾기2_반전2(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'])
    
    
    """ return 변경 """
    df['return'] = np.log(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']
    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)
# window_b : 볼린저밴드 윈도우 
# window_f : 현금흐름지표 윈도우
# buy_pb : 매수조건 퍼센트볼린저 하한
# buy_mfi : 매수조건 현금흐름지표 하한
# sell_pb : 매도조건 퍼센트볼린저 상한
# sell_mfi : 매도조건 현금흐름지표 상한
# 0 < pb < 1
# 0 < mfi < 100

def 볼린저밴드_추세추종2_반전2(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['return'] = np.log(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)
    
    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)

 

최적의 파라미터를 찾아보기 >>

results = pd.DataFrame(columns = ['window_b', 'window_f','buy_pb','buy_mfi','sell_pb','sell_mfi','strategy', 'mdd'])

#window_b = np.arange(13,26,6)
window_b = [13,19,25]
#window_f = np.arange(3,16,3)
window_f = [3,6,9]
#buy_mfi = np.arange(1, 32, 10)

buy_pb = [0.2, 0.4, 0.6]
sell_pb = [0.65, 0.75, 0.85]

buy_mfi = [60,80]
sell_mfi = [30,50]

i=0
for b in window_b:
    for f in window_f:
        for b_mfi in buy_mfi:
            for s_mfi in sell_mfi:
                for b_pb in buy_pb:
                    for s_pb in sell_pb:
                        try:
                            strategy, mdd = 볼린저밴드_추세추종_찾기2_반전2(data=data, window_b = b, window_f = f,  buy_pb = b_pb, buy_mfi = b_mfi, sell_pb = s_pb, sell_mfi=s_mfi)
                            results.loc[i, 'window_b'] = b
                            results.loc[i, 'window_f'] = f
                            results.loc[i, 'buy_pb'] = b_pb
                            results.loc[i, 'buy_mfi'] = b_mfi
                            results.loc[i, 'sell_pb'] = s_pb
                            results.loc[i, 'sell_mfi'] = s_mfi
                            results.loc[i, 'strategy'] = strategy
                            results.loc[i, 'mdd'] = mdd 
                            i+=1   
                        except:
                            print('error')
                            pass

window_b = 25, window_f=3, buy_pb=0.6, buy_mfi=60, sell_pb=0.65, sell_mfi=50 일 때, 

백테스트를 했을 때는 

1달간, 11% 최종수익 수익, MDD 12% 였었음 

좋은 시나리오는 아니었으나, 백테스트와 실전과 차이를 알아보기 위해 이 알고리즘으로 테스트 

 

○ 실전 과 백테스트 간 차이

도지코인 

2022.01.15 00 ~ 24시 약 24시간

매수 시에는 보통 백테스트를 하는 종가보다 비싼가격으로 매수 

매도 시에는 보통 백테스트를 하는 종가보다 저렴한 가격으로 매도 

 

> 200원의 도지코인에서 1원의 오차가 발생하는 것은 0.5% 수수료 발생하는 것과 같다. 

매수 매도시 모두 발생한다면 1%의 오차가 발생하는 것이다. 

 

> 백테스트와 비교하여 거래마다 슬리피지와 수수료가 1.1%씩 발생하므로, 이를 극복하기 위해서는 

1.1% 이상의 차이가 나는 거래만을 수행해야 한다. 

 

(해결책?)

- 백테스트에서 수수료를 넣고 본다.

- 완벽하게 수익률이 나는 거래만을 찾아본다.

- 시장가 매수/매도가 아닌 지정가 매수/매도를 한다 ? 

- 익절, 손절 조건을 정해놓는다? 

 

 

 

 

 

 

Comments