퀀트

21. 백테스팅 기본 예제

만 기 2023. 1. 10. 20:09

 

백테스팅 기본 예제

 

docs : https://kernc.github.io/backtesting.py/doc/backtesting/backtesting.html#backtesting.backtesting.Backtest

 

backtesting.backtesting API documentation

Module backtesting.backtesting Core framework data structures. Objects from this module can also be imported from the top-level module directly, e.g. from backtesting import Backtest, Strategy class Backtest (data, strategy, *, cash=10000, commission=0.0,

kernc.github.io

 

 

backtesting 라이브러리 설치

pip install backtesting

 

 

backtesting 라이브러리 임포트

from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA, GOOG
  • backtesting : 핵심 모듈
    • import class : Backtest, Strategy, Order, Position, Trade
    • Backtest(data, strategy, cash=10000, commission=0.0, margin=1.0, trade_on_close=False, hedging=False, exclusive_orders=False)
      • (데이터프레임, Starategy class, 초기현금, 수수료, 증거금(레버리지), 종가거래, 헷징, 단일거래)
      • .run() : 백테스팅 실행 메서드. 결과(통계)를 시리즈로 반환.
      • .plot() : 백테스트 실행 플로팅
    • Strategy : 투자 전략 기본 클래스. Strategy.init() , Strategy.next()에 자신의 전략을 정의하면 된다.
      • def init(): 함수 안에는 indicator(지표)를 선언한다. indicator들을 self.I 로 감싼다.
      • def next(): 함수 안에는 지표를 이용한 전략과 포지션 등을 설정한다.
      • I : self.I(지표, 종가, 파라미터(기간))
      • 이외에도 buy, sell 등
  • backtesting.lib : 전략 클래스, 보조 기능 모음집
  • backtesting.test : 테스트용 데이터 및 유틸리티
    • 전역변수
      • EURUSD : 2017년 4월부터 2018년 2월까지의 시간별 EUR/USD 외환 데이터 DataFrame.
      • GOOG : 2004년부터 2013년까지 일간 NASDAQ:GOOG(Google) 주가 데이터의 DataFrame.
    • fuction
      • def SMA(arr, n) : n배열의 기간 단순 이동 평균을 반환 arr한다.

 

 

backtesting 전략 클래스 생성

# 골든 크로스 매수 - 데드 크로스 매도
class SmaCross(Strategy):                   # 내 전략 (Strategy 상속)
    def init(self):
        price = self.data.Close             # 데이터의 종가
        self.ma1 = self.I(SMA, price, 10)   # 지표 : 단기이평선
        self.ma2 = self.I(SMA, price, 20)   # 지표 : 장기이평선

    def next(self):                         # 포지션 잡을 시점
        if crossover(self.ma1, self.ma2):   # crossover 조건 : 골든
            self.buy()                      # 매수
        elif crossover(self.ma2, self.ma1): # crossover 조건 : 데드
            self.sell()                     # 매도
  • 이동평균선 골든크로스 매수, 데드크로스 매도 전략
  • init 함수에 10일 이평선, 20일 이평선 정의
  • next 함수에는 crossover시에 포지션 잡기
  • .sell() 메서드가 실행되면, 매수했던 주식을 청산(매도) 하면서 동시에 숏(공매도) 포지션을 잡게된다.

 

 

backtesting 실행

bt = Backtest(GOOG, 
              SmaCross, 
              commission=.002,
              exclusive_orders=True)

stats = bt.run()
print(stats)
bt.plot()
  • (구글데이터프레임, SmaCross 전략 Class, 수수료0.2%, 단일거래)
  • .run()으로 백테스팅 실행 후 결과 반환
  • .plot()으로 그래프 나타내기

 


fdr에서 크롤링한 데이터로 백테스팅하기

import FinanceDataReader as fdr

# 골든 크로스 매수 - 데드 크로스 매도
class SmaCross(Strategy):
    def init(self):
        price = self.data.Close
        self.ma1 = self.I(SMA, price, 10)
        self.ma2 = self.I(SMA, price, 20)

    def next(self):
        if crossover(self.ma1, self.ma2):
            self.buy()
        elif crossover(self.ma2, self.ma1):
            self.sell()

# 005930 : 삼성전자
data = fdr.DataReader('005930')

bt = Backtest(data,
              SmaCross,
              cash=1000000,
              commission=.002,
              exclusive_orders=True)
stats = bt.run()
print(stats)
bt.plot()
  • cash = 최초 자산

⇒ 단기 이평선과 장기 이평선의 날짜를 변경해보면서 테스트하기

 

 

숏 포지션 없이 매도만 사용하기

# 골든 크로스 매수 - 데드 크로스 매도
class SmaCross(Strategy):
    def init(self):
        price = self.data.Close
        self.ma1 = self.I(SMA, price, 10)
        self.ma2 = self.I(SMA, price, 20)

        # buy 포지션으로 어떤 시점에 매수할지 시그널.
        self.signal = self.I(lambda: np.repeat(np.nan, len(self.data.Close)), scatter=True)

    def next(self):
        if crossover(self.ma1, self.ma2):
            self.position.close()    # 포지션 청산
            self.buy()
        elif crossover(self.ma2, self.ma1):
            self.position.close()    # buy포지션 청산
    
    # buy시점을 그려주는데 필요한 함수
    def buy(self, *args, **kwargs):
        self.signal[-1] = 1  # On buy, input 1 instead of existing NaN
        super().buy(*args, **kwargs)

# 005930 : 삼성전자
data = fdr.DataReader('005930')

bt = Backtest(data,
              SmaCross,
              cash=1000000,
              commission=.002,
              exclusive_orders=True)
stats = bt.run()
print(stats)
bt.plot()
  • lambda(매개변수, 표현식) : 함수를 한줄로 만들 수 있는 명령어. 매개변수 생략 가능
  • np.repeat(복사할 값, 반복 횟수, 축방향) : 배열 반복 복사
  • .position.close() : Position클래스의 close메소드. 활성된 거래 포지션 청산
  • def buy : 매수 함수 실행
  • super(). : 부모 클래스의 속성을 사용하고싶을 경우. 자식 클래스가 기존 메서드를 확장하되, 원래 구현을 재정의에 포함하고 싶을 때.

=> 골든크로스 신호 조건일때 buy함수 호출하고 data.Close길이만큼의 NaN배열에서 현재 마지막 NaN을 1로 바꿈으로 시그널을 표시한다.

 

 

비중 조절

# 골든 크로스 매수 - 데드 크로스 매도
class SmaCross(Strategy):
    def init(self):
        price = self.data.Close
        self.ma1 = self.I(SMA, price, 10)
        self.ma2 = self.I(SMA, price, 20)

        self.signal = self.I(lambda: np.repeat(np.nan, len(self.data.Close)), scatter=True)

        self.portion = 0.5    # 디폴트 : 0.9999

    def next(self):
        if crossover(self.ma1, self.ma2):
            self.position.close()
            self.buy(size=self.portion)       # buy 호출 시에 portion적용
        elif crossover(self.ma2, self.ma1):
            self.position.close()
    
    def buy(self, *args, **kwargs):
        self.signal[-1] = 1  # On buy, input 1 instead of existing NaN
        super().buy(*args, **kwargs)

# 005930 : 삼성전자
data = fdr.DataReader('005930')

bt = Backtest(data,
              SmaCross,
              cash=1000000,
              commission=.002,
              exclusive_orders=True)
stats = bt.run()
print(stats)
bt.plot()
  • portion : 투자 비중. buy 호출 시에 인자로 들어간다.
  • 수익률은 줄지만 MDD도 하락한다.