python 파이썬으로 주식 매매 전략 만들고 백테스팅하기 : backtesting.py

반응형

  이번엔 파이썬을 이용해 매매 전략을 만들고 백테스팅하기 위한 패키지를 소개한다. 아마 백테스팅으로 가장 유명한 건 zipline이 아닐까 하는데, zipline을 만든 Quantopian이 망하면서 업데이트가 안된지 꽤 되어 현재 사용하기에는 좀 무리가 있다. zipline이나 backtesting.py 외에도 백테스팅 툴은 꽤 많으나 그중에 쓰기 간단하고 앵간한 기능은 다 지원하고 이후 성과분석 시트까지 제공하는 패키지 중에는 backtesting.py가 가장 괜찮지 않나 한다.

https://github.com/kernc/backtesting.py

 

GitHub - kernc/backtesting.py: :mag_right: :snake: Backtest trading strategies in Python.

:mag_right: :chart_with_upwards_trend: :snake: :moneybag: Backtest trading strategies in Python. - GitHub - kernc/backtesting.py: :mag_right: :snake: Backtest trading strategies in Python.

github.com

 

  설치부터 진행한다.

pip install backtesting

  간단하게 이동평균선 골든 크로스 / 데드 크로스에 따라 매매를 하는 전략을 만들고 성과평가까지 진행해보겠다. 이동평균선을 만들기 위해 pandas-ta를 사용하고, 데이터는 Finance-Datareader로 가져오겠다. 각각의 패키지의 대한 설명은 이전 포스팅을 참조한다.

2024.03.29 - [주가 예측 모델/관련 package] - python 파이썬으로 주식 차트 분석 / 기술적 지표 사용하기 : pandas-ta

 

python 파이썬으로 주식 차트 분석 / 기술적 지표 사용하기 : pandas-ta

2024.03.29 - [주가 예측 모델/관련 package] - python 파이썬으로 주식 차트 분석 / 기술적 지표 사용하기 : TA-Lib python 파이썬으로 주식 차트 분석 / 기술적 지표 사용하기 : TA-Lib 이번엔 앞서 yfinance, financ

antsinvest.tistory.com

2024.03.28 - [주가 예측 모델/관련 package] - python 파이썬으로 주식/주가 정보 가져오기 : Finance Data Reader(Finance-DataReader)

 

python 파이썬으로 주식/주가 정보 가져오기 : Finance Data Reader(Finance-DataReader)

yfinance에 이어 파인썬으로 주식 주가 정보를 가져오는 패키지 중 많이 사용되는 패키지이다. 국내 주식은 KRX에서 가져오는 듯 하고 이 외에도 naver, investing.com, yahoo! finance 등에서 데이터를 가져

antsinvest.tistory.com

 

 

from backtesting import Backtest, Strategy
from backtesting.lib import crossover
import pandas_ta as ta
import FinanceDataReader as fdr

class SmaCross(Strategy):
    def init(self):
        close = self.data.Close

    def next(self):
        if crossover(self.data.sma10, self.data.sma20):
            self.buy()
        elif crossover(self.data.sma20, self.data.sma10):
            self.close()
           
data = fdr.DataReader('005930')
data['sma10'] = ta.sma(data['Close'], 10)
data['sma20'] = ta.sma(data['Close'], 20)
data.dropna(inplace=True)

bt = Backtest(data, SmaCross,
              cash=1000000, commission=.002,
              exclusive_orders=True)

output = bt.run()
print(output)
bt.plot(filename=f'./tearsheet.html', plot_drawdown=True, open_browser=False)

  class로 만들어준 부분은 전략이다. class의 next를 이용해 매매를 진행한다. 우리는 앞쪽에서 crossover를 import 해줬는데 backtesting에서 제공하는 펑션은 crossover, cross, barssince 3가지다. cross는 위로 올라갔는지 아래로 올라갔는지에 상관없이 교차가 발생하는 경우이고, barssince는 특정 조건 후 몇 개 봉이 지났는지를 리턴한다. 예를 들어, 어떤 패턴이 발생한 이후 3개 봉 이후에 주문을 넣는 경우 이 펑션을 사용할 수 있다. 저 3개 펑션을 안 쓰고도 일반적인 파이썬 로직을 이용해 처리를 해도 무관하다. crossover를 사용하지 않고 구현을 할 때는 아래처럼 구현할 수 있다.

def next(self):
    if self.data.sma10 > self.data.sma20 and and self.position.is_long == False:
        self.buy()
    elif self.data.sma10 < self.data.sma20 and and self.position.is_long == True:
        self.close()

    order는 buy, sell, close 등이 있는데, sell의 경우는 short 포지션을 취하는 주문이다. 선물, 옵션이 아닌 경우에는 close를 이용해 주문을 청산해주면 간단하고, 현재 매수 포지션이 있는지 체크하고 동일한 수량으로 sell 주문을 내면 close와 동일하게 사용할 수도 있다. 단, 나중에 Backtest를 호출할 때, exclusive_orders에 True를 체크했을 때만. 오더의 buy, sell 등을 호출할 때, 주문수량, 주문단가, 스탑로스, 슬리피지 등을 넣어줄 수 있다. 지금처럼 아무것도 넣지 않으면 최대수량에 현재가로 주문을 넣는다고 보면 된다.

  Backtest를 이용해 데이터와 전략을 가지고 백테스팅을 수행하는데, 이때는 초기자본금과 커미션 등을 설정해 줄 수 있다. 현재는 0.2%를 거래수수료로 설정해줬다. 수수료에 따라 이후 실적이 천차만별이 되니 0으로 넣고 테스트 결과가 잘 나온다고 좋아하지 말자. 앞에서 잠깐 이야기한 exclusive_orders는 '매수 or 매도 포지션 중 한 가지만 가질 수 있다'에 대한 True/False 값이다. False를 넣는 경우는 주로 선물이나 옵션의 경우에 소위 양매수를 하는 경우이다. 아니면 바이러니 옵션으로 매수와 매도의 단가를 다르게 해서 캡이니 플로어니 하는 투자를 하는 경우 사용하는 파라메터. 이 외에 현물 매수 후 공매도라든지 다양하게 사용할 수 있지만 일단은 한 개 포지션만 가능하게 해서 테스트해보자. 추가로 현재 봉 종가 기준으로 주문을 넣는 trade_on_close라는 파라메터 그리고 레버리지를 설정할 수 있는 margin이라는 파라메터도 있다. 각자의 투자 대상과 전략에 따라 세팅한다.

  마지막에 백테스팅 후 return을 output에 저장했다. 밑에서 보겠지만 여기에는 거래 요약이 담겨 있다. 이후에 plot으로 거래 내역을 저장한다. 저장하지 않고 수행 완료 후에 자동으로 뜨게 할 수도 있으나 일단 나는 저장하겠다. 파일 명을 지정하지 않을 경우에는 전략이름으로 파일이 생성된다.

  이 상태에서 실행을 하면 거래 실적과 티어시트 파일이 생긴다. 혹시 이 과정에서 아래처럼 에러가 나는 경우에는, bokeh를 2.4.3 버전으로 재설치 해주자.

BokehDeprecationWarning: Passing lists of formats for DatetimeTickFormatter scales was deprecated in Bokeh 3.0. Configure a single string format for each scale
/home/anaconda3/envs/xxxx/lib/python3.10/site-packages/backtesting/_plotting.py:250: UserWarning: DatetimeFormatter scales now only accept a single format. Using the first provided: '%d %b'
  formatter=DatetimeTickFormatter(days=['%d %b', '%a %d'],
BokehDeprecationWarning: Passing lists of formats for DatetimeTickFormatter scales was deprecated in Bokeh 3.0. Configure a single string format for each scale
/home/anaconda3/envs/xxxx/lib/python3.10/site-packages/backtesting/_plotting.py:250: UserWarning: DatetimeFormatter scales now only accept a single format. Using the first provided: '%m/%Y'
  formatter=DatetimeTickFormatter(days=['%d %b', '%a %d'],
pip install bokeh==2.4.3

 

  실행한 결과를 보자. 

backtesting.py로 수행한 백테스팅 전략 performance 결과

  'Exposure Time'은 보유 시간이다. 전체 기간 중에 57% 동안 포지션이 열려 있었다. 'Equity Final'은 마지막 보유 자산, 'Return'은 전체 기간의 수익률, 'Return (Ann.)'은 연평균 수익률이다. 수익률은 사서 계속 들고 있었던 'Buy & Hold Return'과 비교해봐야 한다. 그냥 들고 있었던 것에 비해 엄청 낮은 걸 알 수 있다. 밑으로 내려가서, 'Win Rate'는 승률이다. 각 거래가 수익을 내고 끝났는지의 비율이다. 'Avg. Trade'는 평균 거래 수익이다. 이런 항목들은 수수료가 큰 영향을 끼친다. '# Trades'는 총 거래 횟수.

 

 

  이제 tearsheet.html을 열어보자.

backtesting.py로 백테스팅한 결과를 시각화

  맨 위에는 자산 변동, 두 번째에는 Drawdown이 표시된다. Profit / Loss에는 각 거래의 손익이 표시되는데, 삼각형의 색은 수익을 냈을 경우 초록, 손실을 냈을 경우 빨강으로 표현된다. 위쪽 삼각형은 long(매수) 포지션이라는 뜻인데, 우리는 매수 주문만 냈기 때문에 위쪽 삼각형만 있다. 삼각형의 크기는 주문수량의 크기, 그리고 높낮이는 수익과 손실률을 나타낸다. 아래는 가격차트이고 이중에 꼬리가 길게 내려간 건 원천 데이터에 오류나 유실이 있기 때문이다. 혹시 이런 튀는 데이터가 나오면 이런 건 제거한 후 백테스팅을 돌리자. 이 차트는 html이다 보니 확대 축소가 가능하며 각 마커에 마우스를 가져다 대면 상세 데이터를 확인 가능하다.

 

  해당 패키지는 python을 이용해 전략을 만들고 테스트하기에 거의 모든 기능을 지원한다. 과거에는 zipline(전략/백테스트)과 pyfolio(성과평가) 모듈을 이용하던 것을 하나의 패키지로 손쉽게 뽑아볼 수 있다. 물론 zipline과 pyfolio가 제공하던 기능들이 backtesting.py보다 많긴 하지만 Quantopian이 무너진 후로 이만한 대안이 없다. 그리고 패키지 기능보다 중요한 것은 전략을 어떻게 가져가느냐에 대한 고민이 아닐까 싶다.

반응형