In the post Bitcoin trading with Python — Bollinger Bands strategy analysis the author reported 34% returns over the initial investment on back test. Does it work all the time? Is it the best strategy?
Let us use backtrader platform and compare several strategies just for buy signal. Backtrader is a feature-rich Python framework for backtesting and trading. Backtrader allows you to focus on writing reusable trading strategies, indicators and analyzers instead of having to spend time building infrastructure.
We will use the following strategies:
Crossover – based on two simple moving averages (SMA) with periods 10 and 30 days. Buy if fast SMA line crosses slow to the upside.
Consecutive 2 prices (Simple1) – Buy if two consecutive prices are decreasing – if current price is less than previous price and previous price is also less than previous price
if self.dataclose[0] < self.dataclose[-1]:
if self.dataclose[-1] < self.dataclose[-2]:
self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
self.order = self.buy()
Consecutive 4 prices (Simple2) – Buy if within 4 price windows we have decrease more than 5% of original price between any two prices from this window:
if tr_str == "simple2":
# since there is no order pending, are we in the market?
if not self.position: # not in the market
if (self.dataclose[0] - self.dataclose[-1]) < -0.05*self.dataclose[0] or (self.dataclose[0] - self.dataclose[-2]) < -0.05*self.dataclose[0] or (self.dataclose[0] - self.dataclose[-3]) < -0.05*self.dataclose[0] or (self.dataclose[0] - self.dataclose[-4]) < -0.05*self.dataclose[0]:
#if self.dataclose[-1] < self.dataclose[-2]:
self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
self.order = self.buy()
Bollinger Bands – Buy when the price cross the bottom band.
if self.data.close < self.boll.lines.bot:
self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
self.order = self.buy()
Our starting asset value is 10000. We use daily prices.
After running above strategies we get results like below:

Final Vaues for Strategies
cross 9999.06 simple1 9999.31 simple2 9999.91 BB 10011.099999999999
Thus we see that Bollinger Band looks more promising comparing with other strategies that we tried. However we did not do any optimization – using different parameters to improve performance of strategy. We learned how to set different strategies with backtrader and got understanding how to issue buy signal. Below you can find full source code.
# -*- coding: utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
from datetime import datetime
import backtrader as bt
matplotlib.use('Qt5Agg')
plt.switch_backend('Qt5Agg')
# Create a subclass of Strategy to define the indicators and logic
class SmaCross(bt.Strategy):
# parameters which are configurable for the strategy
params = dict(
pfast=10, # period for the fast moving average
pslow=30, # period for the slow moving average
)
params['tr_strategy'] = None
def __init__(self):
self.boll = bt.indicators.BollingerBands(period=50, devfactor=2)
self.dataclose= self.datas[0].close # Keep a reference to
self.sma1 = bt.ind.SMA(period=self.p.pfast) # fast moving average
self.sma2 = bt.ind.SMA(period=self.p.pslow) # slow moving average
self.crossover = bt.ind.CrossOver(self.sma1, self.sma2) # crossover signal
self.tr_strategy = self.params.tr_strategy
def next(self, strategy_type=""):
tr_str = self.tr_strategy
print (self.tr_strategy)
# Log the closing prices of the series
self.log("Close, {0:8.2f} ".format(self.dataclose[0]))
self.log('sma1, {0:8.2f}'.format(self.sma1[0]))
if tr_str == "cross":
if not self.position: # not in the market
if self.crossover > 0: # if fast crosses slow to the upside
self.buy() # enter long
if tr_str == "simple1":
if not self.position: # not in the market
if self.dataclose[0] < self.dataclose[-1]:
if self.dataclose[-1] < self.dataclose[-2]:
self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
self.order = self.buy()
if tr_str == "simple2":
if not self.position: # not in the market
if (self.dataclose[0] - self.dataclose[-1]) < -0.05*self.dataclose[0] or (self.dataclose[0] - self.dataclose[-2]) < -0.05*self.dataclose[0] or (self.dataclose[0] - self.dataclose[-3]) < -0.05*self.dataclose[0] or (self.dataclose[0] - self.dataclose[-4]) < -0.05*self.dataclose[0]:
self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
self.order = self.buy()
if tr_str == "BB":
#if self.data.close > self.boll.lines.top:
#self.sell(exectype=bt.Order.Stop, price=self.boll.lines.top[0], size=self.p.size)
if self.data.close < self.boll.lines.bot:
self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
self.order = self.buy()
print('Current Portfolio Value: %.2f' % cerebro.broker.getvalue())
def log(self, txt, dt=None):
# Logging function for the strategy. 'txt' is the statement and 'dt' can be used to specify a specific datetime
dt = dt or self.datas[0].datetime.date(0)
print('{0},{1}'.format(dt.isoformat(),txt))
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))
strategy_final_values=[0,0,0,0]
strategies = ["cross", "simple1", "simple2", "BB"]
for tr_strategy in strategies:
cerebro = bt.Cerebro() # create a "Cerebro" engine instance
data = bt.feeds.GenericCSVData(
dataname='GE.csv',
fromdate=datetime(2019, 1, 1),
todate=datetime(2019, 9, 13),
nullvalue=0.0,
dtformat=('%Y-%m-%d'),
datetime=0,
high=2,
low=3,
open=1,
close=4,
adjclose=5,
volume=6,
openinterest=-1
)
print ("data")
print (data )
cerebro.adddata(data) # Add the data feed
# Print out the starting conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.addstrategy(SmaCross, tr_strategy=tr_strategy) # Add the trading strategy
result=cerebro.run() # run it all
figure=cerebro.plot(iplot=False)[0][0]
figure.savefig('example.png')
# Print out the final result
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
ind=strategies.index(tr_strategy)
strategy_final_values[ind] = cerebro.broker.getvalue()
print ("Final Vaues for Strategies")
for tr_strategy in strategies:
ind=strategies.index(tr_strategy)
print ("{} {} ". format(tr_strategy, strategy_final_values[ind]))
References
1. Backtrader
2. BackTrader Documentation – Quickstart
3. Backtrader: Bollinger Mean Reversion Strategy
4. Bitcoin trading with Python — Bollinger Bands strategy analysis

















You must be logged in to post a comment.