https://www.backtrader.com/docu/
자주 보게 될 것 같아 공식 페이지를 쭉 따라가면서 읽어 보았다.
우선 backtrader는 다음을 지원한다.
핵심 사용법은 다음과 같다.
1. 거래 전략을 만든다.
1) 잠재적으로 조정할 파라미터 결정
2) 사용할 indicator 초기화
3) 시장의 진입/나가는 논리 결정
2. Cerebro Engine을 만든다.
1) 전략을 inject 하는게 먼저
2) cerebro.adddata를 이용해서 데이터 inject
3) cerebro.run()시행
4) cerebro.plot()를 통해 바로 결과 확인
사용하는 용어들은 다음이 있다.
Lines : 데이터, indicator, 전략 모두 line들로 이루어진다.
line은 point들이 연속으로 모여 생성된다. 예를 들어 시장에 대하 말하면, DataFeed들은 다음의 점이 매일 모여 만들어진다.(Open, High, Low, Close, Volume, OpenInterest)
여기서 시간에 따른 Open들의 연속이 Line이다. 따라서 DataFeed는 주로 6개의 line(시간까지 고려하면 7개) 로 이루어진다.
Index 0: 언제나 'current'는 0번 인덱스다. 그리고 가장 마지막 output은 -1번에 있다. 보통 iterable의 마지막 원소를 -1로 쓰므로, 이와 일관된다. 다시 강조하지만 'output'의 마지막이다.
그냥 buy를 하면, 명시 안된 한 datas[0]가 기본 자산이다. 주식의 매매 단위수 또한 정해주지 않는한 1주가 기본이다. 또한 정해주지 않는다면, 다음날 open가격에 매매가 이루어진다.
거래를 다룰 때, '바'의 개수를 이용한 거래 전략을 세울 수 있다. timeframe과 무관하게 바를 이용한 거래가 가능하다는 점.
포지션의 최종가치를 출려할 때, 아직 포지션이 남아있는 것은 시가로 계산한 가치를 출력함. 따라서 그동안의 이익+남은 포지션의 가격이 최종 가치일 것.
전략을 만들 때, 다양한 파라미터 값을 만들고, 이 값들을 수정하면서 최적의 전략을 찾고 싶을 것. 그래서 여기서도 아래처럼 파라미터를 정의
params = (
('myparam' , 27),
('exitbars', 5),
)
(마지막거 뒤에 쉼표를 붙여야 된다!!)
그래서, addstrategy를 할 때
cerebro.addstrategy(TestStrategy, myparam=20,exitbars=7) 처럼 파라미터 값을 수정해줄 수 있음.
sizer는 아래 처럼 추가
cerebro.addsize(by.sizers.FixedSize, stake=10)
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
# Import the backtrader platform
import backtrader as bt
# Create a Stratey
class TestStrategy(bt.Strategy):
#파라미터용.
params= (('exitbars', 5),)
def log(self, txt, dt=None):
#종가를 출력해주는 함수. 지정해주지 않으면 self.datas[0]이 거래에 사용할 데이터이다.
#dataclose에는 종가가 들어간다.
#next로 넘어갈 때, log을 호출하는데, 이때 오늘의 종가가 넘어간다.
#date(0)은 오늘 날짜를 의미
''' Logging function for this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close
#이전 거래들 추적, 가격 및 수수료도
self.order= None
self.buyprice=None
self.buycomm=None
def notify_order(self,order):
if order.status in [order.Submitted, order.Accepted]:
#아직 제출 혹은 accept만 된 상태면 끝.
return
#주문을 받았지만, 돈이 부족하면 실행은 안될수도 있음.
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm))
else:
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %( order.executed.price, order.executed.value, order.executed.comm))
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Oreder Canceled/Margin/Rejected')
#order 초기화
self.order=None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])
#아직 주문이 처리되는 중
if self.order:
return
#아직 시장에서 포지션이 없다면
if not self.position:
if self.dataclose[0] < self.dataclose[-1]:
if self.dataclose[-1] < self.dataclose[-2]:
#주가가 2일 연속 하락시
self.log('BUY CREATE, %.2f' % self.dataclose[0])
self.order=self.buy()
else:
#포지션을 만든뒤(지금은 산것) 5개의 봉이 지나면 판매
if len(self) >= (self.bar_executed+5):
self.log('SELL CREATE, %.2f' % self.dataclose[0])
self.order=self.sell()
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
#자체 브로커 instance
# Add a strategy
cerebro.addstrategy(TestStrategy)
# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')
# Create a Data Feed
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
#yahoo는 데이터를 역순으로 제공하므로 reversed를 이용해 준다.
reverse=False)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
cerebro.addsizer(bt.sizers.FixedSize, stake=10)
# Set our desired cash start
#default는 10k이다.
cerebro.broker.setcash(100000.0)
#set commision 0.1%
cerebro.broker.setcommision(commission=0.001)
# Print out the starting conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# Run over everything
cerebro.run()
# Print out the final result
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
이미 이걸로도 충분히 이용할 가치가 있지만, 이 모듈에서도 indicator를 구현해 두었다. 아래 전략으로 거래를 한다고 가정하자. close가 평균보다 커질 때 AtMarket로 사고, 이미 샀다면 close가 평균보다 작아질 때 판다.
한번에 하나의 거래만 실행할 수 있다.
from __future__ import(absolute_import, division, print_function,
unicode_literals)
import datetime
import os.path
import sys
import backtrader as bt
class TestStrategy(bt.Strategy):
params = (('maperiod',15),)
def log(self,txt,dt=None):
dt= dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(),txt))
def __init__(self):
self.dataclose=self.datas[0].close
self.order=None
self.buyprice=None
self.buycomm = None
#sma indicator
self.sma=bt.indicators.SimpleMovingAverage(self.datas[0],period=self.params.maperiod)
def notify_order(self,order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price, order.executed.value, order.executed.comm))
self.bar_executed=len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
self.order= None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, net %.2f' % (trade.pnl, trade.pnlcomm))
def next(self):
self.log('Close, %.2f' % self.dataclose[0])
if self.order:
return
if not self.position:
if self.dataclose[0]>self.sma[0]:
self.log('BUY CREATE, %.2f' % self.dataclose[0])
self.order=self.buy()
else:
if self.dataclose[0] < self.sma[0]:
self.log('SELL CREATE, %.2f' % self.dataclose[0])
self.order=self.sell()
if __name__== '__main__':
cerebro=bt.Cerebro()
cerebro.addstrategt(TestStrategy)
modpath=os.path.dirname(os.path.abspath(sys.argv[0]))
datapath=os.path.join(modpath, '../../datas/orc1-1995-2014.txt')
data=bt.feeds.YahooFinanceCSVData(
dataname=datapath,
fromdate=datetime.datetime(2000,1,1,),
todate=datetime.datetime.(2000,12,31),
reverse=False
)
cerebro.adddata(data)
cerebro.broker.setcash(1000.0)
cerebro.addsizer(bt.sizer.FixedSize, stake = 10)
cerebro.setcommission(commission=0.0)
print('Start porfolio value: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('Final portfolio value: %.2f' % cerebro.broker.getvalue())
위 코드를 사용할 경우, 로그에 등장하는 첫 거래일은, SMA를 사용하므로 1월 24일 부터 나타남.(15번째 바)
백트레이더는, 입력된 모든 indicator를 사용하므로, 그 이전일들은 결정과정에 포함하지 않음.
백트레이더에서도 plot 제공. 일단 지원되는 indicator들에는 아래것들이 있음
bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
bt.indicators.WeightedMovingAverage(self.datas[0], period=25).subplot = True
bt.indicators.StochasticSlow(self.datas[0])
bt.indicators.MACDHisto(self.datas[0])
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
bt.indicators.ATR(self.datas[0]).plot = False
이 값들을 TestStratgey의 init안에 넣고,
main에서 최종 포트폴리오 값을 출력한다음
cerebro.plot()를 마지막에 불러주면 된다. 그러면 싹다 한번에 출력된다.
파라미터의 최적화도 꽤 간단하다. 쭉 정리해 보자.
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
import backtrader as bt
class TestStrategy(bt.Strategy):
params = (
('maperiod' , 15),
('printlog', False),
)
def log(self,txt, dt=None, doprint=False):
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__:
~~
def notify_order:
~~
def notify_trade:
~~
def next:
~
#optimize 시 필요. 또한, 포트폴리오의 가치 출력을 여기에 넣음.
def stop(self):
self.log('(MA Period %2d) Ending Value %.2f' % (self.param.maperiod, self.broker.getvalue()), doprint=True)
if __name__ == '__main__':
cerebro = bt.Cerebro()
#최적화의 경우 addstrategy가 아니고 optstrategy
strats = cerebro.optstrategy(TestStrategy, maperiod=range(10,31))
~~
#세팅들
~~
cerebro.run(maxcpus=1)
여기까지가 기본이다.