2022년 1월 6일 목요일

backtesting 이란? backtesting.py 이해와 분석

backtesting 이란? backtesting.py 이해와 분석

backtesting.py custom data 적용과  buy() 메소드 사용과 구매시점 이해

backtesting.py 나만의 전략 my Strategy 만들어보기

1. backtesting 이란?

데이터에서 예측 모델을 테스트하는것을 말합니다. 

다시 좀 더 풀어서 설명해보겠습니다. 주식, 코인 등의 예전 데이터를 가지고 예측하는 모델 또는 사고 파는 전략을 만들었을때 이것을 시뮬레이션 해보는 것입니다. 

위에 말한 데이터의 경우 테스트의 결과는 이익률로 나타날 것입니다.


2. backtesting.py 분석하는 이유

backtesting을 하면서 예측 모델이나 사고 파는 전략을 만들때 기존 library가 마음에 들지 않거나 기능추가를 해야 하는 경우가 있을 수 있는데 이때 소스가 존재한다면 좋기 때문에 opensource쪽으로 알아보았습니다.

github에서 검색시 아래 내용들이 검색되었는데, 복잡하지 않고 소스 코드가 단순한 backtesting.py 을 분석해보기로 결정하였습니다.

https://github.com/mementum/backtrader

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


3. 설치 방법

pip install backtesting


4. sample code 분석

아래가 github에서 제공하는 예제 코드입니다.

from backtesting import Backtest, Strategy
from backtesting.lib import crossover

from backtesting.test import SMA, GOOG


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()


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

print(stats)


4.1 Backtest

backtest class의 생성자에 넘어가는 인자를 살펴보겠습니다. 예제에서는 아래와 같이 설정하고 있습니다.

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

GOOG 는 주식 일별 데이터입니다.

자료는 아래 링크를 눌러보면 주식의 데이터의 일반적인 형태(날짜,open 시초가,high 최고가,low 최저가,close 종가,volume 거래량)를 취하고 있습니다.

https://github.com/kernc/backtesting.py/blob/master/backtesting/test/GOOG.csv


만약 다른 데이터를 backtesting 하려면 GOOG말고 다른 자료를 넣어야 합니다. 이 부분은 나중에 다른 예제로 만들어 보겠습니다.

SmaCross 는 사고 파는 전략을 구현한 클래스입니다.

여기에서 SmaCross 클래스는 Strategy 클래스를 상속받아 init(), next()를 만들어 주고 next()메소드 안에서 사야한다면 buy(), 팔아야한다면 sell()을 호출해주면 됩니다.

commission 인자가 있는데 이것은 중계 수수료입니다. order가 일어날때 수수료 만큼 %더가져간다는 의미입니다. 0.1은 10%, 위의 예제에서는 0.2%가 됩니다.

exclusive_orders True는 새 주문이 발생하면 이전 주문했던것은 자동으로 닫히게 됩니다.

실행은 제일 아래쪽 stats = bt.run() 에 의해서 실행이 됩니다.


4.2 Strategy

다음은 SmaCross 클래스 설명입니다. Strategy 클래스를 상속 받아서 init next를 구현합니다.

	def init(self):
		price = self.data.Close
		self.ma1 = self.I(SMA, price, 10)
		self.ma2 = self.I(SMA, price, 20)

여기에서의 init 코드는 위와 같습니다.

self.data.Close는 GOOG(입력데이터) csv값의 Close(종가)를 의미합니다. 그런데 self.data가 array type이기 때문에 price에 값이 하나가 들어가는게 아니라 종가 배열이 들어가게 됩니다.

self.ma1, self.ma2 이런 처리는 이동 평균값입니다. 

self.ma1 = self.I(SMA, price, 10)

여기코드에 의해서 price 값 10개를 이용하여 평균값으로 만들어서 ma1에 기록합니다. 이것을 이동하면서 ma1배열에 만들게 됩니다.

self.ma2 = self.I(SMA, price, 20)

위 코드는 데이터가 하루씩 들어있기 때문에 20일 평균값이 들어갑니다.

이동 평균 관련해서는 WIKI를 참고 하시기 바랍니다.

https://ko.wikipedia.org/wiki/이동평균

여기에서 만들려고 하는 전략은 10일 이동선과 20일 이동선이 cross가 일어나면 사고 팔고 하는 전략입니다.

입력을 들어가는 ma1,ma2의 값 형태를 알아보기 위해서 코드를 약간 변형하여 값을 출력해보았습니다. (SmaCross는 자신의 사고 파는 전략이니 이름을 바꾸어도 됩니다.)

class SmaCross(Strategy):
	def init(self):
		price = self.data.Close
		print(type(price))
		print(price)
		self.ma1 = self.I(SMA, price, 2)
		print(type(self.ma1))
		print(self.ma1)
		self.ma2 = self.I(SMA, price, 3)
		print(type(self.ma2))
		print(self.ma2)

실행 시키면 아래와 같은 형태가 되는데 이부분을 위의 csv 값과 비교해보면 이해가 될겁니다.

<class 'backtesting._util._Array'>
[100.34 108.31 109.4  ... 799.78 801.2  806.19]
<class 'backtesting._util._Indicator'>
[    nan 104.325 108.855 ... 794.955 800.49  803.695]
<class 'backtesting._util._Indicator'>
[         nan          nan 106.01666667 ... 793.56       797.03666667
 802.39      ]
이동 평균을 눈에 띄기 쉽게 2, 3으로 변형해보았고, 2인 경우 어떻게 계산되는지 위 그림에서 표시하였습니다. nan는 값이 없음을 의미합니다. (2일 이동 평균이면 nan이 하나 나옵니다. 그림에서 보시면 제일 아래쪽에 값이 없기 때문입니다.)
마찬가지로 3일 이동 평균이면 nan nan 이 두개 나오고 (100.34+108.31+109.4)/3이 첫번째가 될겁니다. 값은 106.0166666666667 이 계산됩니다.
출력값 확인해보면 아래와 같은 형태이고 

[         nan          nan 106.01666667 ... 793.56       797.03666667

 802.39      ]

106.01666667 로 출력되었음을 알 수 있습니다.

여기까지는 이동 평균에 대한 설명이고, 이동 평균이 값이 Cross되면 buy, sell은

다음 코드로 구현이 되어있습니다.

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

여기에서 crossover는 미리 준비가 되어있는 라이브러리입니다. 두개의 배열을 가지고 -2 ,-1 의 인덱스의 들어있는 값을 비교하게 됩니다. 관련 코드는 라이브러리 쪽에 있으며, 아래 내용을 참고 하시면 됩니다.

https://github.com/kernc/backtesting.py/blob/master/backtesting/lib.py

def crossover(series1: Sequence, series2: Sequence) -> bool:
    """
    Return `True` if `series1` just crossed over (above)
    `series2`.
        >>> crossover(self.data.Close, self.sma)
        True
    """
    series1 = (
        series1.values if isinstance(series1, pd.Series) else
        (series1, series1) if isinstance(series1, Number) else
        series1)
    series2 = (
        series2.values if isinstance(series2, pd.Series) else
        (series2, series2) if isinstance(series2, Number) else
        series2)
    try:
        return series1[-2] < series2[-2] and series1[-1] > series2[-1]
    except IndexError:
        return False


5. 그래프

샘플을 실행시키면 브라우저에 그래프가 뜨게 되면서 많은 정보가 나오게 됩니다.

가장 중요한 항목은 중간에 있는 이익을/손실로 나는 비율이 나오게 됩니다. 또한 한가지 알고 있어야하는점은 buy, sell을 호출하면 곧장 매매가 일어나는게 아니라 다음날 매매가 일어나게 됩니다.



6. 끝마지며

간단하게 샘플에 대해서 알아봤습니다.
다음에는 다른 데이터를 이용해서 backtesting을 하거나, 전략을 바꿔 본다던가 단순 전략이 아닌 예측 결과값을 통한 결과의 backtesting을 시도해보록 하겠습니다.



댓글 없음:

댓글 쓰기