How to do it...

Execute the following steps to backtest a strategy based on the Bollinger Bands.

  1. Import the libraries:
import backtrader as bt
import datetime
import pandas as pd
  1. The template of the strategy is presented:
class BBand_Strategy(bt.Strategy):
params = (('period', 20),
('devfactor', 2.0),)

def __init__(self):
# some code

def log(self, txt):
# some code

def notify_order(self, order):
# some code

def notify_trade(self, trade):
# some code

def next_open(self):
# some code

The __init__ block is defined as:

    def __init__(self):
        # keep track of close price in the series
        self.data_close = self.datas[0].close
        self.data_open = self.datas[0].open

        # keep track of pending orders/buy price/buy commission
        self.order = None
        self.price = None
        self.comm = None

        # add Bollinger Bands indicator and track the buy/sell signals
        self.b_band = bt.ind.BollingerBands(self.datas[0], 
                                            period=self.p.period, 
                                            devfactor=self.p.devfactor)
        self.buy_signal = bt.ind.CrossOver(self.datas[0], 
                                           self.b_band.lines.bot)
        self.sell_signal = bt.ind.CrossOver(self.datas[0], 
                                            self.b_band.lines.top)

The log block is defined as:

def log(self, txt):
dt = self.datas[0].datetime.date(0).isoformat()
print(f'{dt}, {txt}')

The notify_order block is defined as:

 def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return

if order.status in [order.Completed]:
if order.isbuy():
self.log(
f'BUY EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}'
)
self.price = order.executed.price
self.comm = order.executed.comm
else:
self.log(
f'SELL EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}'
)

elif order.status in [order.Canceled, order.Margin,
order.Rejected]:
self.log('Order Failed')

self.order = None

The notify_trade block is defined as:

def notify_trade(self, trade):
if not trade.isclosed:
return

self.log(f'OPERATION RESULT --- Gross: {trade.pnl:.2f}, Net: {trade.pnlcomm:.2f}')

The next_open block is defined as:

def next_open(self):
if not self.position:
if self.buy_signal > 0:
size = int(self.broker.getcash() / self.datas[0].open)
self.log(f'BUY CREATED --- Size: {size}, Cash: {self.broker.getcash():.2f}, Open: {self.data_open[0]}, Close: {self.data_close[0]}')
self.buy(size=size)
else:
if self.sell_signal < 0:
self.log(f'SELL CREATED --- Size: {self.position.size}')
self.sell(size=self.position.size)

  1. Download the data:
data = bt.feeds.YahooFinanceData(
dataname='MSFT', fromdate=datetime.datetime(2018, 1, 1), todate=datetime.datetime(2018, 12, 31)
)
  1. Set up the backtest:
cerebro = bt.Cerebro(stdstats = False, cheat_on_open=True)

cerebro.addstrategy(BBand_Strategy)
cerebro.adddata(data)
cerebro.broker.setcash(10000.0)
cerebro.broker.setcommission(commission=0.001)
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='time_return')
  1. Run the backtest:
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
backtest_result = cerebro.run()
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
  1. Plot the results:
cerebro.plot(iplot=True, volume=False)

The resulting graph is presented below:

The log is presented below:

We can see that the strategy managed to make money, even after accounting for commission costs. We now turn to an inspection of the analyzers.

  1. Run the following code to investigate different returns metrics:
print(backtest_result[0].analyzers.returns.get_analysis())

The output of the preceding line is as follows:

OrderedDict([('rtot', 0.06155731237239935), 
('ravg', 0.00024622924948959743),
('rnorm', 0.06401530037885826),
('rnorm100', 6.401530037885826)])
  1. Create a plot of daily portfolio returns:
returns_dict = backtest_result[0].analyzers.time_return.get_analysis()
returns_df = pd.DataFrame(list(returns_dict.items()),
columns = ['report_date', 'return'])
.set_index('report_date')
returns_df.plot(title='Portfolio returns')

Running the code results in the following plot:

The flat lines represent periods when we have no open positions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.1.97