본문 바로가기

파이썬으로 퀀트 프로그램 만들기 project/백테스팅

[백테스팅]MACD와 RSI를 이용한 선물 매매하기

728x90

지금까지 제가 포스팅한 글에서는 상승 배팅만 가능한 백테스팅 코드였습니다. 

그러나 이번에는 하락에도 배틱이 가능하면 레버리지 또한 조절 가능한 코드를 소개 해드리겠습니다.

 

이번 백테스팅에 적용할 매매 아이디어는 다음과 같습니다.

 

매수조건

1. MACD 와 MACD signal 골든크로스

2. 조건1 발생 5봉 이내에 rsi 과매도 발생

3. 조건 2 발생 시 ema 정배열일 경우 롱

 

매도 조건 (매수조건의 반대 경우)

1. MACD 와 MACD signal 데드크로스

2. 조건1 발생 5봉 이내에 rsi 과매수 발생

3. 조건 2 발생 시 ema 역배열일 경우 숏

 

그리고 기본적으로 손익비 5퍼센트, 수수료 0.05퍼센트, 레버리지 3배로 설정하겠습니다.

차트 데이터는 한국 비트코인 5분봉 4만개 데이터를 사용하겠습니다.

 

#코드

본격적으로 코드 소개해 드리겠습니다.

 

import pyupbit

access = "123abc"          # 본인 값으로 변경
secret = "123abc"        # 본인 값으로 변경
upbit = pyupbit.Upbit(access, secret)

print(upbit.get_balance("KRW-BTC"))     # KRW-BTC 조회
print(upbit.get_balance("KRW"))         # 보유 현금 조회

pyupbit 라이브러리를 이용하기 위한 access 값과 secret값을 입력해 줍니다. 

get_balance를 통해 잘 로그인 되었는지 확인합니다.

 

import talib
import matplotlib.pyplot as plt
import datetime
import numpy as np

ticker = "KRW-BTC"
count = 40000
df = pyupbit.get_ohlcv(ticker, interval='minute5',count = count)

df = df[["open","high","low","close"]]

df['RSI'] = talib.RSI(df['close'], timeperiod=14)
df['macd'],df['macd_signal'], macd_hist = talib.MACD(df["close"], fastperiod=12, slowperiod=26, signalperiod=9)
df['ema_120'] = talib.EMA(df['close'],120)
df['ema_20'] = talib.EMA(df['close'],20)

#trading fee
tf = 0.0005

# 레버리지 크기 
# +10 -> 10배 롱 
# -5 -> 5배 숏
# 0 -> 포지션 없음
df['buy_condition'] = 0 
df['hpr'] = 1. # 누적 수익률
df['ror'] = 1. 
df['buy_price'] = 0. # 평단가
df['dr'] = 1. # 금일 수익률
df["trading_number"] = 0
df["win"] = 0
df["lose"] = 0

비트코인 5분봉 데이터 4만개를 받아줍니다.

그리고 talib 라이브러리를 통해 rsi, macd, macd_signal, ema120, ema20 열들을 만들어줍니다.

그리고 매매 수수료를 0.05%로 설정해주고, 필요한 열들을 추가해줍니다.

'buy_condition' 열을 통해 레버리지에 따른 수익률을 계산해 줄 것입니다.

 

### islong 함수, isshort 함수

# macd 골든 크로스, 5일 이내 과매도, ema 정배열 -> 롱
def islong(i):
    over_sell = 35

    #macd
    macd_idx = np.where(df.columns == 'macd')[0][0]
    #macd_signal
    macd_signal_idx = np.where(df.columns == 'macd_signal')[0][0]
    #rsi
    rsi_idx = np.where(df.columns == 'RSI')[0][0]
    #ema120
    ema120_idx = np.where(df.columns == 'ema_120')[0][0]
    #ema20
    ema20_idx = np.where(df.columns == 'ema_20')[0][0]

    #i의 인덱스
    i = np.where(df.index == i)[0][0]


    if df.iloc[i-1,macd_idx] < df.iloc[i-1,macd_signal_idx] and df.iloc[i, macd_idx] > df.iloc[i, macd_signal_idx]:
        if (df.iloc[i,rsi_idx] < over_sell or df.iloc[i-1,rsi_idx] < over_sell or df.iloc[i-2,rsi_idx] < over_sell or df.iloc[i-3,rsi_idx] < over_sell or df.iloc[i-4,rsi_idx] < over_sell):
            if df.iloc[i,ema20_idx]>df.iloc[i,ema120_idx]:
                return True
    else:
        return False

# macd 데드 크로스, 5일 이내 과매수, ema 역배열 -> 숏
def isshort(i):
    over_buy = 65

    #macd
    macd_idx = np.where(df.columns == 'macd')[0][0]
    #macd_signal
    macd_signal_idx = np.where(df.columns == 'macd_signal')[0][0]
    #rsi
    rsi_idx = np.where(df.columns == 'RSI')[0][0]
    #ema120
    ema120_idx = np.where(df.columns == 'ema_120')[0][0]
    #ema20
    ema20_idx = np.where(df.columns == 'ema_20')[0][0]

    #i의 인덱스
    i = np.where(df.index == i)[0][0]
    
    if df.iloc[i-1,macd_idx] > df.iloc[i-1,macd_signal_idx] and df.iloc[i, macd_idx] < df.iloc[i, macd_signal_idx]:
        if (df.iloc[i,rsi_idx] > over_buy or df.iloc[i-1,rsi_idx] > over_buy or df.iloc[i-2,rsi_idx] > over_buy or df.iloc[i-3,rsi_idx] > over_buy or df.iloc[i-4,rsi_idx] > over_buy):
            if df.iloc[i,ema20_idx]<df.iloc[i,ema120_idx]:
                return True
    else:
        return False

islong함수와 isshort함수를 통해 롱에 배팅할 것인지, 숏에 배팅할 것인지 판단해 줄 것입니다.

 

 

이제 본격적인 백테스팅 코드 입니다.

#backtest

#시간 간격
delta = 5
delta = datetime.timedelta(minutes=delta)

#손절, 익절 기준
p = 5
stop_loss = 1 - p*0.01
stop_profit = 1+ p*0.01

#레버리지
leverage = 3
tf = tf*leverage

for i in df.index:
    if i == df.index[0]:
        continue

    isindex = df.index == i
    isindex_delta = df.index == i - delta
    if not(isindex.sum() and isindex_delta.sum()):
        temp = np.where(df.index == i)[0][0]
        trading_number_idx = np.where(df.columns == 'trading_number')[0][0]
        win_idx = np.where(df.columns == 'win')[0][0]
        lose_idx = np.where(df.columns == 'lose')[0][0]
       
        df.iloc[temp,trading_number_idx] = df.iloc[temp-1,trading_number_idx]
        df.iloc[temp,win_idx] = df.iloc[temp-1,win_idx]
        df.iloc[temp,lose_idx] = df.iloc[temp-1,lose_idx]
        continue


    if df.loc[i-delta,'buy_condition'] == 0:      
        # 과매도, 골든크로스 -> 롱 배팅
        if islong(i):   
            df.loc[i,'buy_condition'] = leverage # buy condition
            df.loc[i,'buy_price'] = df['close'][i].copy() # buy price
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() + 1 # trading_number + 1
        
        # 과매수, 데드크로스 -> 숏 배팅
        elif isshort(i):   
            df.loc[i,'buy_condition'] = -leverage # buy condition
            df.loc[i,'buy_price'] = df['close'][i].copy() # buy price
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() + 1 # trading_number + 1

        else:
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() # trading_number
            
        df.loc[i,'win'] = df['win'][i-delta].copy()  
        df.loc[i,'lose'] = df['lose'][i-delta].copy() 
            
    #롱
    elif df.loc[i-delta,'buy_condition'] > 0: 
        # 손절 or 익절
        if df.loc[i-delta,'ror'] >= stop_profit or df.loc[i-delta,'ror'] <= stop_loss :
            df.loc[i,'buy_condition'] = 0 # buy condition
            df.loc[i,'dr'] = (df['open'][i].copy()/df['close'][i-delta].copy()) 
            df.loc[i,'dr'] = (df.loc[i,'dr']-1)*leverage+1 # dr
            df.loc[i,'ror'] = df['ror'][i-delta].copy()*df.loc[i,'dr'].copy() # ror   
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() # trading_number
            if  df['ror'][i] > 1:
                df.loc[i,'win'] = df.loc[i-delta,'win'] + 1
                df.loc[i,'lose'] = df['lose'][i-delta].copy() 
            
            else:
                df.loc[i,'lose'] = df.loc[i-delta,'lose'] + 1
                df.loc[i,'win'] = df['win'][i-delta].copy()  


        # 과매수, 데드크로스 -> 숏 으로 바꿈
        elif isshort(i):   
            df.loc[i,'buy_condition'] = -leverage # buy condition
            df.loc[i,'dr'] = (df['close'][i].copy()/df['close'][i-delta].copy()) #dr
            df.loc[i,'buy_price'] = df['close'][i].copy()
            df.loc[i,'dr'] = (df.loc[i,'dr']-1)*leverage+1 # dr
            df.loc[i,'ror'] = df['ror'][i-delta].copy()*df.loc[i,'dr'].copy() # ror
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() + 1 # trading_number + 1
            if  df['ror'][i] > 1:
                df.loc[i,'win'] = df.loc[i-delta,'win'] + 1
                df.loc[i,'lose'] = df['lose'][i-delta].copy() 
            
            else:
                df.loc[i,'lose'] = df.loc[i-delta,'lose'] + 1  
                df.loc[i,'win'] = df['win'][i-delta].copy()        

        else:
            df.loc[i,'buy_condition'] = df["buy_condition"][i-delta]
            df.loc[i,'buy_price'] = df['buy_price'][i-delta].copy() # buy price
            df.loc[i,'dr'] = (df['close'][i].copy()/df['close'][i-delta].copy())# dr
            df.loc[i,'dr'] = (df.loc[i,'dr']-1)*leverage+1 # dr
            df.loc[i,'ror'] = df['ror'][i-delta].copy()*df.loc[i,'dr'].copy()# ror
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() # trading_number
            df.loc[i,'win'] = df['win'][i-delta].copy()  
            df.loc[i,'lose'] = df['lose'][i-delta].copy()         

    #숏
    else: #df.loc[i-delta,'buy_condition'] > 0
        # 손절 or 익절
        if df.loc[i-delta,'ror'] >= stop_profit or df.loc[i-delta,'ror'] <= stop_loss :
            df.loc[i,'buy_condition'] = 0 # buy condition
            df.loc[i,'dr'] = (df['open'][i].copy()/df['close'][i-delta].copy())#dr
            df.loc[i,'dr'] = (df.loc[i,'dr']-1)*-leverage+1 # dr
            df.loc[i,'ror'] = df['ror'][i-delta].copy()*df.loc[i,'dr'].copy() # ror   
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() # trading_number
            if  df['ror'][i] > 1:
                df.loc[i,'win'] = df.loc[i-delta,'win'] + 1
                df.loc[i,'lose'] = df['lose'][i-delta].copy() 
            
            else:
                df.loc[i,'lose'] = df.loc[i-delta,'lose'] + 1 
                df.loc[i,'win'] = df['win'][i-delta].copy()  

        # 과매도, 골든크로스 -> 롱으로 바꿈
        elif islong(i):   
            df.loc[i,'buy_condition'] = leverage # buy condition
            df.loc[i,'dr'] = (df['close'][i].copy()/df['close'][i-delta].copy())#dr
            df.loc[i,'buy_price'] = df['close'][i].copy()
            df.loc[i,'dr'] = (df.loc[i,'dr']-1)*-leverage+1 # dr
            df.loc[i,'dr'] = (df.loc[i,'dr']-1)*leverage+1 # dr
            df.loc[i,'ror'] = df['ror'][i-delta].copy()*df.loc[i,'dr'].copy() # ror
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() + 1 # trading_number + 1
            if  df['ror'][i] > 1:
                df.loc[i,'win'] = df.loc[i-delta,'win'] + 1
                df.loc[i,'lose'] = df['lose'][i-delta].copy() 
            
            else:
                df.loc[i,'lose'] = df.loc[i-delta,'lose'] + 1
                df.loc[i,'win'] = df['win'][i-delta].copy()  

        else:
            df.loc[i,'buy_condition'] = df["buy_condition"][i-delta]
            df.loc[i,'buy_price'] = df['buy_price'][i-delta].copy() # buy price
            df.loc[i,'dr'] = (df['close'][i].copy()/df['close'][i-delta].copy())# dr
            df.loc[i,'dr'] = (df.loc[i,'dr']-1)*-leverage+1 # dr
            df.loc[i,'ror'] = df['ror'][i-delta].copy()*df.loc[i,'dr'].copy() # ror     
            df.loc[i,'trading_number'] = df['trading_number'][i-delta].copy() # trading_number  
            df.loc[i,'win'] = df['win'][i-delta].copy()  
            df.loc[i,'lose'] = df['lose'][i-delta].copy() 


df['hpr'] = df['dr'].cumprod()

 

 

이제 결과를 띄워서 확인해 보겠습니다.

import matplotlib.pyplot as plt
from matplotlib import gridspec
import datetime 


#과매도
over_sell = 35
#과매수
over_buy = 65

fig = plt.subplots(figsize=(18,15), sharex = True)#sharex를 통해 두그림의 x축을 공유함
gs = gridspec.GridSpec(nrows=3, ncols=1, height_ratios=[3,1,1])#gridspec을 통해 여러 축들을 원하는 대로 배치 가능

#주가, 수익률 나타내기
ax1 = plt.subplot(gs[0])
ax1.plot(df.index, df['hpr']*df.iloc[0,0], color='black', label="trading",alpha=0.7) # 수익률
ax1 = df['close'].plot(label="index", alpha = 0.5) # 주가

for i in df.index:

    ##index 예외처리##
    if i == df.index[0]:
        continue
    isindex = df.index == i
    isindex_delta = df.index == i - delta
    if not(isindex.sum() and isindex_delta.sum()):
        continue

    # 롱
    if df.loc[i-delta,'buy_condition'] <=0  and df.loc[i,'buy_condition'] > 0:
        ax1.plot(i,df.loc[i,'hpr']*df.loc[df.index[0],'open'], color="red",marker="^", markersize=5)

    # 숏
    elif df.loc[i-delta,'buy_condition'] >=0 and df.loc[i,'buy_condition'] < 0:
        ax1.plot(i,df.loc[i,'hpr']*df.loc[df.index[0],'open'], color="blue",marker="v", markersize=5)
    
plt.legend()

#RSI 나타내기
ax2 = plt.subplot(gs[1])
ax2 = df['RSI'].plot(color='brown')
ax2.hlines(over_buy,df.index[0],df.index[-1], color= 'black')
ax2.hlines(over_sell,df.index[0],df.index[-1], color= 'black')
plt.title("RSI",position=(0.03,0),fontsize=13)

#MACD
ax3 = plt.subplot(gs[2])
ax3 = df['macd'].plot(color='red')
ax3 = df['macd_signal'].plot(color='blue')
ax3.hlines(0,df.index[0],df.index[-1], color= 'black')
plt.title("MACD",position=(0.03,0),fontsize=13)
plt.subplots_adjust(wspace=0, hspace=0)

 

맨윗칸에서 하늘색 그래프는 비트코인 차트이고, 검은 색 선은 누적 수익률입니다. 결과가 좋지 못하네요

중간중간에 빨간색 화살표와 파란색 화살표를 통해 어디서 롱, 숏을 배팅했는지를 보여줍니다.

두번째 칸은 rsi를 뜻하고, 세번째 칸은 macd와 macd signal을 보여줍니다.

 

구체적인 승률을 확인하고 buy&hold 와 누적수익률을 비교해봅시다.

win_ratio=df.loc[df.index[-1],'win']/df.loc[df.index[-1],'trading_number']
buy_and_hold = df.iloc[-1,3]/df.iloc[0,0]
hpr = df.loc[df.index[-1],'hpr']

print("투자기간: ",df.index[0],"~",df.index[-1])
print("전적:",df.loc[df.index[-1],'trading_number']," 승:",df.loc[df.index[-1],'win'], " 패:",df.loc[df.index[-1],'lose'])
print("승률: ",win_ratio)
print("buy and hold: ", buy_and_hold)
print("hpr: ",hpr)

투자기간:  2023-09-19 19:55:00 ~ 2024-02-06 03:20:00
전적: 38  승: 19  패: 18
승률:  0.5
buy and hold:  1.6126234906695938
hpr:  1.3996523339585758

 

아쉬운 결과네요.

 

# 느낀점

백테스팅 코드에서 데이터프레임에 접근할 때 loc 메소드를 썼는데, 그 과정에서 datetime인 인덱스를 사용하는 과정이 있었어요. 그 과정이 조금 난잡해 보이네요. 다음에는 iloc 메스도를 쓰도록 해야겠습니다.

백테스팅 결과도 아쉬웠어요. 4달 이상의 기간동안 38번의 적은 매매횟수도 아쉽고, 승률과 누적수익률 또한 아쉬웠습니다. 자동화 매매로 돈 버는게 역시 쉽지 않다는게 계속 느껴지네요