1. 사용자 데이터 사용하기
1.1 무작정 넣어보기
이전에는 기본으로 들어있는 sample data를 사용하였는데, 일반적인 데이터를 넣어보겠습니다.
아래와 같은 형태의 csv 파일을 넣어봤습니다.
backtest1.csv 입니다. 이 자료는 2021-03-02일자 이더리움 분단위 자료를 가져왔습니다.
실제 예제는 더 많은데 여기에서는 몇줄만 표기하였습니다.
indexdatetime,open,high,low,close,volume 2021-03-02 14:19:00,1787500.0,1790000.0,1787500.0,1790000.0,53.28931438 2021-03-02 14:20:00,1790000.0,1791000.0,1789000.0,1790500.0,181.15638692 2021-03-02 14:21:00,1790500.0,1791000.0,1787000.0,1787500.0,91.69244693 2021-03-02 14:22:00,1787500.0,1787500.0,1786500.0,1787000.0,178.84881752
...이하 생략...
csv 읽는 함수는 github 에 있는 코드를 약간 변형하였습니다.
https://github.com/kernc/backtesting.py/blob/master/backtesting/test/__init__.py
import pandas as pd def _read_file(filename): from os.path import dirname, join return pd.read_csv(filename, index_col=0, parse_dates=True, infer_datetime_format=True)
...일부 코드 생략...
df = _read_file('../testdata/backtest1.csv') print(df) bt = Backtest(df, SmaCross, commission=.002, exclusive_orders=True) stats = bt.run() bt.plot() print(stats)
테스트해보면 아래와 같은 오류가 발생합니다.
오류를 살펴보면 columns 'Open', 'High', 'Low', 'Close', and (optionally) 'Volume' 이런식으로 되어야 한다고 되어있습니다. 순서는 관계가 없고 label 명이 대소 문자 일치하지 않으면 오류가 발생합니다.
Traceback (most recent call last): File "bt_sample.py", line 32, in <module> bt = Backtest(data, SmaCross, commission=.002, File "C:\Users\USER\AppData\Local\Programs\Python\Python38\lib\site-packages\backtesting\backtesting.py", line 1067, in __init__ raise ValueError("`data` must be a pandas.DataFrame with columns " ValueError: `data` must be a pandas.DataFrame with columns 'Open', 'High', 'Low', 'Close', and (optionally) 'Volume'
1.2 컬럼명 변경하기
pandas 컬럼의 label 변경 방법 pandas에 rename() 메소드를 이용합니다.
df = _read_file('../testdata/backtest1.csv') print(df) df = df.rename({'open': 'Open', 'high': 'High', 'low': 'Low', 'close': 'Close', 'volume': 'Volume'}, axis='columns') print(df)
전과 후를 출력해 봤습니다.
잘 변환 된것을 알 수 있습니다.
open high low close volume indexdatetime 2021-03-02 14:19:00 1787500.0 1790000.0 1787500.0 1790000.0 53.289314 2021-03-02 14:20:00 1790000.0 1791000.0 1789000.0 1790500.0 181.156387 2021-03-02 14:21:00 1790500.0 1791000.0 1787000.0 1787500.0 91.692447 ... 생략 ... Open High Low Close Volume indexdatetime 2021-03-02 14:19:00 1787500.0 1790000.0 1787500.0 1790000.0 53.289314 2021-03-02 14:20:00 1790000.0 1791000.0 1789000.0 1790500.0 181.156387 2021-03-02 14:21:00 1790500.0 1791000.0 1787000.0 1787500.0 91.692447 ... 생략 ...
2. 좀 더 깊게 분석 해보기
2.1 캔들 차트
주식에서 흔희 볼 수 있는 캔들 차트입니다. 그런데 일반 주식의 색상이 다른 경우가 있어서 확인해 봤습니다.
여기 제공되는 캔들 차트는 빨간색이 Open가격보다 Close가격이 내려가는 것입니다. 녹색은 올랐다는 표시입니다.
2.2 한번만 구매/판매 해보기
앞에서 필요시 buy(), sell()을 호출하면 된다고 설명하였는데 막상 해보면 이상하게 동작합니다.
기존 사용하던 예제에서 self.i 를 추가하여 처음 한번만 구매하고 다음날 판매하도록 구현하였습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from backtesting import Backtest, Strategy from backtesting.lib import crossover from backtesting.test import SMA, GOOG class SmaCross(Strategy): def init(self): self.i = 0; def next(self): print(len(self.data), self.data, self.data.Open) if self.i==0 : self.buy() elif self.i==1 : self.sell() self.i+=1 bt = Backtest(GOOG, SmaCross, commission=0.0, exclusive_orders=True) stats = bt.run() bt.plot() print(stats) print(stats['_trades']) |
트레이드 되는 내역은 라인 24: print(stats['_trades']) 에 의해서 확인이 가능합니다.
이번에는 sell() 메소드를 사용하지 않고 self.position.close() 을 사용하였습니다. 이 메소드는 거래를 닫는 함수입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from backtesting import Backtest, Strategy from backtesting.lib import crossover from backtesting.test import SMA, GOOG class SmaCross(Strategy): def init(self): self.i = 0; def next(self): print(len(self.data), self.data, self.data.Open) if self.i==0 : self.buy() elif self.i==1 : self.position.close() self.i+=1 bt = Backtest(GOOG, SmaCross, commission=0.0, exclusive_orders=True) stats = bt.run() bt.plot() print(stats) print(stats['_trades']) |
결과를 보면 최종 100.441% 에 한번 거래시 0.004424 리턴되는 수익률이 적절히 표시되고 있습니다.
다시 한번 정리해보자면 구매시는 self.buy(), 판매시에는 self.position.close() 함수를 이용합니다.
2.3 구매/판매 시점 이해하기
next() 메소드 내에서 buy나 sell을 하게 되면 차트에서는 어떻게 표기될지와 언제 구매하도록 구현 되었는지 살펴보겠습니다.
다음 링크에서 설명을 발견할 수 있습니다.
https://kernc.github.io/backtesting.py/doc/examples/Quick%20Start%20User%20Guide.html
Note, backtesting.py cannot make decisions / trades within candlesticks — any new orders are executed on the next candle's open (or the current candle's close if trade_on_close=True). If you find yourself wishing to trade within candlesticks (e.g. daytrading), you instead need to begin with more fine-grained (e.g. hourly) data.
위와 같은 문구가 있습니다. 즉 다음번 캔들 오픈될때 거래가 실행되는데 trade_on_close=True 설정을 하면 close 될때 거래가 실행된다고 합니다.
each next() call so that array[-1] (e.g. self.data.Close[-1] or self.sma1[-1]) always contains the most recent value, array[-2] the previous value, etc. (ordinary Python indexing of ascending-sorted 1D arrays).
또한 자세히 읽어보면 array[-1] (e.g. self.data.Close[-1] or self.sma1[-1]) 이런값으로 최신값을 접근가능하며, 그 전 데이터는 array[-2] 이런식으로 접근이 가능하다고 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from backtesting import Backtest, Strategy from backtesting.lib import crossover from backtesting.test import SMA, GOOG import time class SmaCross(Strategy): def init(self): self.i = 0; def next(self): print(len(self.data), self.data, self.data.Open) time.sleep(1) if self.i==0 : self.buy() elif self.i==1 : self.position.close() self.i+=1 bt = Backtest(GOOG, SmaCross, commission=0.0, exclusive_orders=True) stats = bt.run() bt.plot() print(stats) print(stats['_trades']) |
C:\Users\USER\Documents\GitHub\dota2trader\code>python bt_sample.py 2 <Data i=2 (2004-08-23 00:00:00) Open=110.75, High=113.48, Low=109.05, Close=109.4, Volume=9137200.0> [100. 101.01] 3 <Data i=3 (2004-08-24 00:00:00) Open=111.24, High=111.6, Low=103.57, Close=104.87, Volume=7631300.0> [100. 101.01 110.75] 4 <Data i=4 (2004-08-25 00:00:00) Open=104.96, High=108.0, Low=103.88, Close=106.0, Volume=4598900.0> [100. 101.01 110.75 111.24] 5 <Data i=5 (2004-08-26 00:00:00) Open=104.95, High=107.95, Low=104.66, Close=107.91, Volume=3551000.0> [100. 101.01 110.75 111.24 104.96] 6 <Data i=6 (2004-08-27 00:00:00) Open=108.1, High=108.62, Low=105.69, Close=106.15, Volume=3109000.0> [100. 101.01 110.75 111.24 104.96 104.95] 7 <Data i=7 (2004-08-30 00:00:00) Open=105.28, High=105.49, Low=102.01, Close=102.01, Volume=2601000.0> [100. 101.01 110.75 111.24 104.96 104.95 108.1 ] 8 <Data i=8 (2004-08-31 00:00:00) Open=102.3, High=103.71, Low=102.16, Close=102.37, Volume=2461400.0> [100. 101.01 110.75 111.24 104.96 104.95 108.1 105.28] 9 <Data i=9 (2004-09-01 00:00:00) Open=102.7, High=102.97, Low=99.67, Close=100.25, Volume=4573700.0> [100. 101.01 110.75 111.24 104.96 104.95 108.1 105.28 102.3 ]
준비된 csv 데이터는 2004-08-19일 부터입니다. 그리고 제일 처음 데이터가 self.data.Open [100. 101.01] 임에 주목해주세요. 앞쪽 self.data 의 출력은 <Data i=2 (2004-08-23 00:00:00) Open=110.75, High=113.48, Low=109.05, Close=109.4, Volume=9137200.0> 이런식으로 나오는 부분은 index라서 해당 날짜는 아닌것으로 확인 됩니다. open 값만을 가지고 확인했을때 101.01은 2004-08-20임을 알 수 있습니다.
[100. 101.01] 이런식으로 나온다는것은 self.data.Open[-1] = 101.01 이 되고 self.data.Open[-2] = 100. 이 됩니다.
위 사실은 위에서 언급한 아래 내용과 일치합니다.
또한 자세히 읽어보면 array[-1] (e.g. self.data.Close[-1] or self.sma1[-1]) 이런값으로 최신값을 접근가능하며, 그 전 데이터는 array[-2] 이런식으로 접근이 가능하다고 합니다.
그렇다고 하더라도 왜 두번째 캔들부터 들어오게 되는지는 소스의 확인 이 필요합니다.
앞쪽 몇개 데이터는 안들어올 수 있는데 warming up 때문에 그렇습니다. 위 소스에서 start가 0이 아니라 1+어떤 값이 되기 때문에 index가 skip되기 때문입니다.정리, next() 메소드 안에서 오늘 날짜의 데이터는 self.data.Open[-1], self.data.High[-1], self.data.Low[-1], self.data.Close[-1] 로 접근이 가능합니다. 그리고 그이전 날짜의 데이터는 self.data.Open[-2], self.data.High[-2], self.data.Low[-2], self.data.Close[-2] 로 접근이 가능합니다.
2.3 trade_on_close=True 사용해보기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from backtesting import Backtest, Strategy from backtesting.lib import crossover from backtesting.test import SMA, GOOG import time class SmaCross(Strategy): def init(self): self.i = 0; def next(self): if self.i==0 : self.buy() elif self.i==1 : self.position.close() self.i+=1 bt = Backtest(GOOG, SmaCross, commission=0.0, exclusive_orders=True, trade_on_close=True) stats = bt.run() bt.plot() print(stats) print(stats['_trades']) |
좌측이 기존(다음날 open 기준), 우측이 trade_on_close=True 사용한 예제 입니다. 당일 close 기준으로 변경되었습니다.
댓글 없음:
댓글 쓰기