[파이썬] 코인 자동매매 프로그램 만들기 #2 - 백테스팅
개발/Python

[파이썬] 코인 자동매매 프로그램 만들기 #2 - 백테스팅

반응형


백테스팅 준비

전략 설정하기

먼저 실제 백테스팅 코드를 작성하기전 자동매매에 사용할 투자 전략을 설정해야 합니다.

이번 프로젝트에서 사용할 전략은 래리 윌리엄스(Larry R. Williams)의 변동성 돌파 전략입니다.

변동성 돌파 전략을 간단하게 알아보자면 다음과 같습니다

1. 전일 고가 - 전일 고가를 계산하여 변동폭을 구한다 ( range = 전일 고가 - 전일 저가 )
2. 당일 시가 + 변동성 x 돌파계수(k) 를 매수목표가로 잡고 장중가격이 목표가를 넘으면 매수
3. 다음날 시가에 매도

 

라이브러리

업비트에서 제공하는 API를 더 쉽게 사용할 수 있게 해주는 라이브러리인 pyupbit를 이용하여 코드를 작성해 보겠습니다

sharebook-kr/pyupbit: python wrapper for upbit API (github.com)

 

GitHub - sharebook-kr/pyupbit: python wrapper for upbit API

python wrapper for upbit API . Contribute to sharebook-kr/pyupbit development by creating an account on GitHub.

github.com

 

Visual Studio Code에서 새 cmd 터미널을 열어 pyupbit 깃허브 페이지에 나와있는대로

pip install pyupbit

를 입력하여 pyupbit 모듈을 설치합니다

 

설치가 완료되었으니 모듈을 한번 테스트 해보도록 하겠습니다

test.py라는 새 파이썬 파일을 만들고 모듈을 import 합니다

get_ohlcv()라는 함수를 이용하면 비트코인의 차트 데이터를 dataFrame의 형태로 가져올 수 있습니다

파라미터가 없는경우 기본적으로 비트코인에 대한 일봉 데이터를 200개 가져옵니다

깃허브 페이지의 파라미터 정보를 참고하여 비트코인에 대한 1주일 간의 차트데이터를 불러와보겠습니다

import pyupbit

df = pyupbit.get_ohlcv("KRW-BTC", count=7)
print(df)

 

python test.py 명령어를 통해 파일을 실행해보면 다음과 같이 날짜, 시가, 고가, 저가, 종가등의 데이터를 확인할 수 있습니다


백테스팅 코드 작성

변수 선언

백테스팅 코드를 작성할 준비가 완료되었으니 본격적으로 코드를 작성해보겠습니다

우선 테스트에 필요한 변수들을 선언해 줍니다

import pyupbit
import numpy as np

class backTesting :
    def __init__(self, daily_data, start_cash) :
        self.daily_data = daily_data # 일봉 데이터
        self.fee = 0 # 수수료
        self.buy_signal = False # 매수 신호
        
        self.start_cash = start_cash # 시작 자산
        self.current_cash = start_cash # 현재 자산
        self.highest_cash = start_cash # 자산 최고점
        self.lowest_cash = start_cash # 자산 최저점

        self.ror = 1 # 수익률
        self.accumulated_ror = 1 # 누적 수익률
        self.mdd = 0 # 최대 낙폭

        self.trade_count = 0 # 거래횟수
        self.win_count = 0 # 승리횟수

 

수수료

조금 더 정확한 결과값을 얻기위해서 엑셀을 통해 실제 거래시 발생하는 수수료를 계산해보겠습니다

업비트의 수수료 0.05%를 기준으로 만약 100만원의 자금을 가지고 60만원에 매수를 해서 65만원에 매도를 하였을 때 발생하는 수익률은 다음과 같습니다

수수료(%) 0.05  
투자원금 1,000,000  
매수체결가 600,000  
매도체결가 650,000  
매수체결수량 1.666666667 투자원금 / 매수체결가
매수체결수량(수수료제외) 1.665833333 매수체결수량 * (1 - 수수료 / 100)
매도체결수량 1.665833333  
거래금액 1,082,792 매도체결수량 * 매도체결가
거래금액(수수료제외) 1,082,250 거래금액 * (1 - 수수료 / 100)
체결가 기준 수익률 1.083333333 매도체결가 / 매수체결가
실제 수익률 1.082250271 거래금액(수수료제외) / 투자원금
수익률 차이 0.001083062 체결가 기준 수익률 - 실제 수익률

대략 0.0011의 수익률 차이가 발생합니다

따라서 0대신 0.0011의 수수료 값을 넣어 테스트를 진행해보겠습니다

 

테스트 실행

다음으로 실제 테스트를 진행할 코드를 작성해보겠습니다

먼저 전략에 따라 변동폭 ( 고가 - 저가 )를 구하고 목표매수가( 당일 시가 + 변동폭 * K )를 계산합니다

돌파계수 K값은 우선 0.5로 고정하겠습니다

def execute(self) :
    K = 0.5

    # 변동폭 ( 고가 - 저가 )
    self.daily_data['range'] = self.daily_data['high'] - self.daily_data['low']
    # 목표매수가 ( 시가 + 변동폭 * K )
    self.daily_data['targetPrice'] = self.daily_data['open'] + self.daily_data['range'].shift(1) * K

 

그 후 데이터를 출력해보면 다음과 같이 각 거래일 마지막에 변동폭과 목표매수가가 추가된 것을 볼 수 있습니다

 

이제 해당 데이터를 각 날짜별로 탐색하면서 각 변수들의 변화를 계산해 줍니다

for idx, row in df.iterrows() :
    # 매수 신호 확인
    self.buy_signal = np.where(row['low'] <= row['targetPrice'] <= row['high'], True, False) # 목표가에 달성한 경우 목표가에 매수해 다음날 시가에 매도한 것으로 판단

    # 거래횟수 계산
    self.trade_count += 1 if self.buy_signal else 0

    # 수익률 계산
    self.ror = row['close'] / row['targetPrice'] - self.fee if self.buy_signal else 1 # 다음날 시가와 당일 종가의 시간차이가 거의 없으므로 종가로 계산

    # 승리 횟수 계산
    self.win_count += 1 if self.ror > 1 else 0

    # 누적 수익률 계산
    self.accumulated_ror *= self.ror

    # 현재 자산 갱신
    self.current_cash *= self.ror

    # 자산 최고점 갱신
    self.highest_cash = max(self.highest_cash, self.current_cash)

    # 자산 최저점 갱신
    self.lowest_cash = min(self.lowest_cash, self.current_cash)

    # 최대 낙폭 계산
    dd = (self.highest_cash - self.current_cash) / self.highest_cash * 100
    self.mdd = max(self.mdd, dd)

 

결과 출력

테스트가 종료된 후 결과를 간단하게 출력해 주도록 하겠습니다

결과를 보여주는 함수를 작성하고 execute 함수 마지막에 호출해 줍니다

def result(self) :
    print('='*40)
    print('테스트 결과')
    print('-'*40)
    print('총 거래 횟수 : %s' %self.trade_count)
    print('승리 횟수 : %s' %self.win_count)
    print('승률 : %s' %(self.win_count / self.trade_count * 100))
    print('누적 수익률 : %s' %self.accumulated_ror)
    print('현재 잔액 : %s' % self.current_cash)
    print('최고 잔액 : %s' % self.highest_cash)
    print('최저 잔액 : %s' % self.lowest_cash)
    print('최대 낙폭 (MDD) : %s' % self.mdd)
    print('='*40)

 

 

마지막으로 테스트에 사용될 차트 데이터를 불러와 backTesting 클래스의 인스턴스를 생성하고 execute 함수를 실행합니다

시작자금 100만원으로 비트코인에 대한 약 6개월간의 백테스팅을 진행해 보겠습니다

df = pyupbit.get_ohlcv("KRW-BTC", count=180) # 일봉 데이터
backtest = backTesting(df, 1000000)
backtest.execute()

 

백테스팅 파일을 실행해보면 다음과 같이 테스트 결과를 확인할 수 있습니다

 

최종 코드

import pyupbit
import numpy as np

class backTesting :
    def __init__(self, daily_data, start_cash) :
        self.daily_data = daily_data # 일봉 데이터
        self.fee = 0.0011 # 수수료 ( calculate_fee.xlsx 참고 )
        self.buy_signal = False # 매수 신호
        
        self.start_cash = start_cash # 시작 자산
        self.current_cash = start_cash # 현재 자산
        self.highest_cash = start_cash # 자산 최고점
        self.lowest_cash = start_cash # 자산 최저점

        self.ror = 1 # 수익률
        self.accumulated_ror = 1 # 누적 수익률
        self.mdd = 0 # 최대 낙폭

        self.trade_count = 0 # 거래횟수
        self.win_count = 0 # 승리횟수

    def execute(self) :
        K = 0.5
        
        # 변동폭 ( 고가 - 저가 )
        self.daily_data['range'] = self.daily_data['high'] - self.daily_data['low']
        # 목표매수가 ( 시가 + 변동폭 * K )
        self.daily_data['targetPrice'] = self.daily_data['open'] + self.daily_data['range'].shift(1) * K

        for idx, row in df.iterrows() :
            # 매수 신호 확인
            self.buy_signal = np.where(row['low'] <= row['targetPrice'] <= row['high'], True, False) # 목표가에 달성한 경우 목표가에 매수해 다음날 시가에 매도한 것으로 판단

            # 거래횟수 계산
            self.trade_count += 1 if self.buy_signal else 0

            # 수익률 계산
            self.ror = row['close'] / row['targetPrice'] - self.fee if self.buy_signal else 1 # 다음날 시가와 당일 종가의 시간차이가 거의 없으므로 종가로 계산

            # 승리 횟수 계산
            self.win_count += 1 if self.ror > 1 else 0

            # 누적 수익률 계산
            self.accumulated_ror *= self.ror
            
            # 현재 자산 갱신
            self.current_cash *= self.ror

            # 자산 최고점 갱신
            self.highest_cash = max(self.highest_cash, self.current_cash)

            # 자산 최저점 갱신
            self.lowest_cash = min(self.lowest_cash, self.current_cash)

            # 최대 낙폭 계산
            dd = (self.highest_cash - self.current_cash) / self.highest_cash * 100
            self.mdd = max(self.mdd, dd)

        self.result()

    def result(self) :
        print('='*40)
        print('테스트 결과')
        print('-'*40)
        print('총 거래 횟수 : %s' %self.trade_count)
        print('승리 횟수 : %s' %self.win_count)
        print('승률 : %s' %(self.win_count / self.trade_count * 100))
        print('누적 수익률 : %s' %self.accumulated_ror)
        print('현재 잔액 : %s' % self.current_cash)
        print('최고 잔액 : %s' % self.highest_cash)
        print('최저 잔액 : %s' % self.lowest_cash)
        print('최대 낙폭 (MDD) : %s' % self.mdd)
        print('='*40)

df = pyupbit.get_ohlcv("KRW-BTC", count=180) # 일봉 데이터
backtest = backTesting(df, 1000000)
backtest.execute()

 

* 백테스팅에 대한 소스코드와 수수료를 계산하는 엑셀파일은 Github에서도 확인 가능합니다 - Github