Overview of backtrader (Intermediate)
The previous posts went through a quick overview of the backtrader platform so that one could see the speed and simplicity of using backtrader to execute your trading strategies.
This post goes through the structure of the backtrader platform
- Data Feeds. The data for the trading strategy needs to be loaded into the backtester needs to be loaded in.
- Trading strategy. The trading strategy is the most complex and involved part of the programming process since this is where decisions are made to buy/sell the asset.
-
Backtester settings.
Cerebro
is the name of the backtester. Cerebro requires several settings such as (i) Trading capital (ii) Broker Comissions (iii) Datafeed (iv) Trading strategy (v) Size of each trading position. -
Running backtest. Run the
Cerebro
backtester and print out all trades executed. - Evaluating performance. Graphically evaluate the performance of the backtested trading strategy.
Import modules¶
import datetime
import os.path
import sys
import backtrader as bt
import matplotlib.pyplot as plt
Data¶
homepath = os.getenv('HOME')
datapath = os.path.join(homepath, 'github/backtrader/datas/orcl-1995-2014.txt')
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
fromdate=datetime.datetime(2000,1,1),
todate = datetime.datetime(2000,12,31),
reverse=False)
Strategy Class¶
The __init__
function that initialies the strategy class will contain all the attributes (e.g., indicators - SMA, RSI, SMA, price data - volume, open price, close price) and the variabl
The next
function is where the trading strategy is written based on the attributes
class Strat2_BGTMA_SLSMA(bt.Strategy):
params = (
('maperiod',15), # Tuple of tuples containing any variable settings required by the strategy.
('printlog',False), # Stop printing the log of the trading strategy
)
def __init__(self):
self.dataclose= self.datas[0].close # Keep a reference to the "close" line in the data[0] dataseries
self.order = None # Property to keep track of pending orders. There are no orders when the strategy is initialized.
self.buyprice = None
self.buycomm = None
# Add SimpleMovingAverage indicator for use in the trading strategy
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
def log(self, txt, dt=None, doprint=False):
if self.params.printlog or doprint: # Add if statement to only log of printlog or doprint is True
dt = dt or self.datas[0].datetime.date(0)
print('{0},{1}'.format(dt.isoformat(),txt))
def notify_order(self, order):
# 1. If order is submitted/accepted, do nothing
if order.status in [order.Submitted, order.Accepted]:
return
# 2. If order is buy/sell executed, report price executed
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, Price: {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm: {3:8.2f}'.format(
order.executed.price,
order.executed.size,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else:
self.log('SELL EXECUTED, {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm{3:8.2f}'.format(
order.executed.price,
order.executed.size,
order.executed.value,
order.executed.comm))
self.bar_executed = len(self) #when was trade executed
# 3. If order is canceled/margin/rejected, report order canceled
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 {0:8.2f}, NET {1:8.2f}'.format(
trade.pnl, trade.pnlcomm))
def next(self):
# Log the closing prices of the series from the reference
self.log('Close, {0:8.2f}'.format(self.dataclose[0]))
if self.order: # check if order is pending, if so, then break out
return
# since there is no order pending, are we in the market?
if not self.position: # not in the market
if self.dataclose[0] > self.sma[0]:
self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
self.order = self.buy()
else: # in the market
if self.dataclose[0] < self.sma[0]:
self.log('SELL CREATE, {0:8.2f}'.format(self.dataclose[0]))
self.order = self.sell()
def stop(self):
self.log('MA Period: {0:8.2f} Ending Value: {1:8.2f}'.format(
self.params.maperiod,
self.broker.getvalue()),
doprint=True)
Backtest settings¶
Rather than using cerebro.addstrategy()
, we now use cerebro.optstrategy()
as we are optimizing the strategy over a variety of parameters in our SMA indicator. In this case we are testing all the maperiod
from 10, 11, 12, ..., 30, 31 bars.
cerebro = bt.Cerebro()
cerebro.adddata(data)
strats = cerebro.optstrategy(
Strat2_BGTMA_SLSMA,
maperiod=range(10,31),
printlog=False)
cerebro.addsizer(bt.sizers.FixedSize,stake=10)
cerebro.broker.setcash(1000.0)
cerebro.broker.setcommission(commission=0.0)
Run backtest¶
The maxcpus
allows us to trigger how many cpus to run in parallel when performing the optimization. We find that the SMA strategy is profitable when we use values between 18 to 25 days.
cerebro.run(maxcpus=1)
Conclusion¶
In this post, we learnt how to optimize parameters in our trading strategy.
- We learnt how to use
cerebro.optstrategy
where we indicate which parameter we want to optimize and the range of parameters to evaluate the strategy's performance. - We altered the log function of the Strategy class to only print the values of the parameter being optimized and its corresponding portfolio value.
Comments
Comments powered by Disqus