To make the code more readable, we first present the general outline of the class (trading strategy) and then define separate pieces in the following code blocks.
- The template of the strategy is presented below:
class SmaStrategy(bt.Strategy):
params = (('ma_period', 20), )
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(self):
# some code
The __init__ block is defined as:
def __init__(self):
self.data_close = self.datas[0].close
self.order = None
self.price = None
self.comm = None
self.sma = bt.ind.SMA(self.datas[0],
period=self.params.ma_period)
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}')
self.bar_executed = len(self)
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 block is defined as:
def next(self):
if self.order:
return
if not self.position:
if self.data_close[0] > self.sma[0]:
self.log(f'BUY CREATED --- Price: {self.data_close[0]:.2f}')
self.order = self.buy()
else:
if self.data_close[0] < self.sma[0]:
self.log(f'SELL CREATED --- Price: {self.data_close[0]:.2f}')
self.order = self.sell()
The code for data is the same as in the signal strategy, so it is not included here, to avoid repetition.
- Set up the backtest:
cerebro = bt.Cerebro(stdstats = False)
cerebro.adddata(data)
cerebro.broker.setcash(1000.0)
cerebro.addstrategy(SmaStrategy)
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)
- Run the backtest:
print(f'Starting Portfolio Value: {cerebro.broker.getvalue():.2f}')
cerebro.run()
print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}')
- Plot the results:
cerebro.plot(iplot=True, volume=False)
The resulting graph is presented below:
From the preceding graph, we see that the strategy managed to make $11.56 over the year. Additionally, we present a piece of the log:
The log contains information about all the created and executed trades, as well as the operation results, in case it was a sell.