본문 바로가기
파이썬/주식

Python,Backtrader 다중 데이터 백테스팅 Python sqlite3 to backtrader / Mutliple Data Feeds / Pandas DataFrame to Backtrader

by 행복론자 2020. 1. 7.

그동안 Pandas Dataframe으로 생성하고 to_excel로 엑셀형태로 주가 데이터를 저장해왔습니다.

이번에는 sqlite3에 저장한 주가 데이터를 가져와 여러개의 데이터를 추가한 Backtesting을 다뤄보겠습니다.

이미 준비된 db가 있다고 가정하여 데이터를 가져와 db를 생성하는 과정은 생략하겠습니다. 

 

 

1.준비물

- Python,backtrader,sqlite3,pandas 설치

- 주가 데이터를 넣은 sqlite3 db

 

 

2.BacktraderMutliple Data Feeds

먼저 db에 들어 있는 데이터는 다음과 같은 형태입니다.

FinanceDataReader를 통해 가져온 layout 그대로입니다.

 

 

다음은 위와 같은 데이터를 저장하고 있는 db에 있는 데이터를 cerebro 객체에 넣는 코드입니다. 

import sqlite3
import pandas as pd
import backtrader as bt

#Variable for our starting cash
startcash = 1000000

#Create an instance of cerebro
cerebro = bt.Cerebro()

#DB connection
con = sqlite3.connect('datas/stock_price.db', timeout=10)
cursor = con.cursor()

dataframe_collection =[]
dataframe_collection=[]
for idx, ticker in enumerate(['005930', '017670']):
    dataframe_collection.append(pd.read_sql("SELECT * FROM '{0}'".format(ticker), con, index_col='Date', parse_dates=['Date'] ))    
    data = bt.feeds.PandasData(dataname=dataframe_collection[idx])
    cerebro.adddata(data, name=ticker)

 

 

005930,삼성전자

017670,SKT를 DataFrame 형태로 읽어와 bt.feeds.PandasData를 통해 cerebro에 전달됩니다.

여기서 index가 datetime형태인 것처럼 저장했으나 실제로는 str이라 type을 바꿔주는 과정이 필요합니다.

dataframe_collection.append(pd.read_sql("SELECT * FROM '{0}'".format(ticker), con, index_col='Date', parse_dates=['Date'] ))    

 

 

read_sql 함수의 param으로 parse_date를 전달하기 싫다 하시면 아래 코드를 추가 하시면 됩니다.

dataframe_collection[idx].index = pd.to_datetime(dataframe_collection[idx].index)

 

 

이 과정을 생략하고 str 형태인 index로 date를 전달할 경우 이와 같은 에러가 발생합니다.

AttributeError: 'str' object has no attribute 'to_pydatetime'

 

 

데이터를 전달할 때 name을 설정해줍니다. 이렇게 설정된 name을 통해 strategy 안에서 구별됩니다.

 cerebro.adddata(data, name=ticker)

 

 

아래는 Backtrader Rookies 홈페이지에서 코드를 가져와 Data를 Feed하는 부분한 구현한 코드입니다. 전략은 크로스 전략이고 next 함수를 통해 한 ticker, ticker 별로 수행됩니다.

 

다중백테스팅.py

import sqlite3
import pandas as pd
import backtrader as bt

class maCross(bt.Strategy):
    '''
    For an official backtrader blog on this topic please take a look at:
    https://www.backtrader.com/blog/posts/2017-04-09-multi-example/multi-example.html
    oneplot = Force all datas to plot on the same master.
    '''
    params = (
    ('sma1', 20),
    ('sma2', 60),
    ('oneplot', True)
    )

    def __init__(self):
        '''
        Create an dictionary of indicators so that we can dynamically add the
        indicators to the strategy using a loop. This mean the strategy will
        work with any numner of data feeds.
        '''
        self.inds = dict()
        for i, d in enumerate(self.datas):
            self.inds[d] = dict()
            self.inds[d]['sma1'] = bt.indicators.SimpleMovingAverage(
                d.close, period=self.params.sma1)
            self.inds[d]['sma2'] = bt.indicators.SimpleMovingAverage(
                d.close, period=self.params.sma2)
            self.inds[d]['cross'] = bt.indicators.CrossOver(self.inds[d]['sma1'],self.inds[d]['sma2'])

            if i > 0: #Check we are not on the first loop of data feed:
                if self.p.oneplot == True:
                    d.plotinfo.plotmaster = self.datas[0]

    def next(self):
        for i, d in enumerate(self.datas):            
            dt, dn = self.datetime.date(), d._name
            pos = self.getposition(d).size
            if not pos:  # no market / no orders
                if self.inds[d]['cross'][0] == 1:
                    self.buy(data=d, size=10)
                elif self.inds[d]['cross'][0] == -1:
                    self.sell(data=d, size=10)
            else:
                if self.inds[d]['cross'][0] == 1:
                    self.close(data=d)
                    self.buy(data=d, size=10)
                elif self.inds[d]['cross'][0] == -1:
                    self.close(data=d)
                    self.sell(data=d, size=10)

    def notify_trade(self, trade):
        dt = self.data.datetime.date()
        if trade.isclosed:
            print('{} {} Closed: PnL Gross {}, Net {}'.format(
                                                dt,
                                                trade.data._name,
                                                round(trade.pnl,2),
                                                round(trade.pnlcomm,2)))

#Variable for our starting cash
startcash = 1000000

#Create an instance of cerebro
cerebro = bt.Cerebro()

#Add our strategy
cerebro.addstrategy(maCross, oneplot=False)

#DB connection
con = sqlite3.connect('datas/stock_price.db', timeout=10)
cursor = con.cursor()

dataframe_collection =[]

# Pass it to the backtrader datafeed and add it to the cerebro
for idx, ticker in enumerate(['005930', '017670']):
    dataframe_collection.append(pd.read_sql("SELECT * FROM '{0}'".format(value), con, index_col='Date'))
    dataframe_collection[idx].index = pd.to_datetime( dataframe_collection[idx].index)
    data = bt.feeds.PandasData(dataname=dataframe_collection[idx])
    cerebro.adddata(data, name=ticker)

# Set our desired cash start
cerebro.broker.setcash(startcash)

# Run over everything
cerebro.run()

#Get final portfolio Value
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

#Print out the final result
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))

#Finally plot the end results
cerebro.plot(style='candlestick')

 

 

next 함수부를 보시면 기존에는 데이터를 하나만 추가할 경우 포지션을 self.position을 통해 접근했으나 

다중 데이터를 가지고 있을 경우, pos = self.getposition(d).size로 접근해야 합니다. 

여기서 변수 d에는 우리가 넣은 데이터를 기준으로 생성된 object이며 d._name은 아래 과정을 통해 만들어졌기 때문에 d._name은 각각 005930(삼성전자), 01760(SKT)입니다. 

cerebro.adddata(data, name=ticker)

 

 

위 전략을 수행했을 때 결국 돈을 잃었지만, 다중 데이터를 넣어서 백테스팅을 해본데 의미가 있을 것 같습니다. 

 

3.참고(코드 원본)

https://backtest-rookies.com/2017/08/22/backtrader-multiple-data-feeds-indicators/


같이 읽어보면 좋은 글

2022.12.27 - [파이썬/가상화폐] - [전자책] 바이낸스 코인선물자동매매 시스템 개발 방법을 담은 책이 출시되었습니다.

 

[전자책] 바이낸스 코인선물자동매매 시스템 개발 방법을 담은 책이 출시되었습니다.

🎁 바이낸스 자동매매 시스템 개발 방법을 담은 책이 출시되었습니다. "나 대신 일해주는 코인선물자동매매 프로그램 개발, 노하우 및 소스를 모두 공개합니다" ✔️ Q: 무슨 내용인가요? Python

jsp-dev.tistory.com

 

2022.11.05 - [파이썬/가상화폐] - [공지] 코인거래소별 프리미엄 체크봇 개발 가이드와 풀소스 전자책 | binance bybit | 업비트 김치프리미엄

 

[공지] 코인거래소별 프리미엄 체크봇 개발 가이드와 풀소스 전자책 | binance bybit | 업비트 김치프

https://kmong.com/gig/417785 거래소별 코인 프리미엄 알림봇 개발 가이드를 드립니다 | 36000원부터 시작 가능한 총 평점 5점의 3개 총 작업 개수 완료한 총 평점 5점인 JSDEV의 전자책, 투잡·재테크 전자

jsp-dev.tistory.com

 

 

2019/12/08 - [파이썬/주식] - Python, Backtrader로 전략검증, RSI 이용한 매매 전략 백테스팅(BackTesting)

 

Python, Backtrader로 전략검증, RSI 이용한 매매 전략 백테스팅(BackTesting)

Backtesting 백테스팅(Backtesting)이란 과거 데이터를 바탕으로 개발된 알고리즘을 검증하는 것을 의미합니다. 이를 쉽게 할 수 있도록 해주는 Zipline, TA-lib, Backtrader 라이브러리가 있습니다. 이번 포스

jsp-dev.tistory.com

 

 

반응형
이 포스팅은 쿠팡파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

댓글