This weeks post is not directly related to Backtrader but will definitely come in useful as our journey into backtesting develops. The more we develop, the more tools end up in our tool box. Indicators, strategies, reports, analyzers, you name it. However, it is no good having so many tools if we can never find them. Worse still is buying another wrench when we already have 10 of them! What I am blabbering on about? Well, our project folder is our tool box and the files inside it are it’s compartments. If we don’t organize things efficiently it becomes hard to find that nice piece of code you developed 6 months ago. Then if we do manage to find it, should we just copy and paste it into a new script, we will end up with multiple wrenches in our tool box. The problem with this is not so much the waste of having lots of versions of the same code, it is when you notice there is problem and you then need to apply the same fix to multiple different scripts. It can be a real pain.
There must be a better wayAnd there is! Modular code allows you to organized and re-use the code you have already developed in a much neater way. Better still, when you need to make a fix, you only need to do it once. In Python, we have been using modules from day one. Especially in these tutorials. Any time we are using the
importstatement, we are using a module. Fortunately, creating a module in Python is very simple and is not limited to packages installed via pip and then stored in a part of your computers file-system that you rarely step foot into.
Go ModularTo create a module simply:
- Create a folder in your working directory and give it a name. This will be the name of your module. The example below uses “extensions” as the module / folder name.
- Create an empty file in the folder with the name
__init__.py. This signals to Python that the directory is a module.
my-module. If it does, you will received a
SyntaxErrorwhen trying to import it.
__pycache__If you have followed along and imported the module, you may notice that a
__pycache__folder has appeared in the directory. When you run a python program, the interpreter must convert your script to something called bytecode before it is executed. The pycache stores a cached version of the module in bytecode. This makes it a little quicker to run in future because it already converted and cached. You can largely ignore this folder. If you delete it, do not worry, it will be remade next time you run the module.
Back to our modulesSo now we have a module, it is time to do something useful with it. Lets place an indicator inside the extensions folder so we can re-use the same indicator in multiple scripts. For this example I will take the pivot/swing indicator posted in the code snippet: Swing Indicator. When adding this code to the module we have a couple of options (which all boil down to personal preference):
- Create an indicators.py file which contains all our indicators
- Create an indicators sub-module which then contains a python file for each indicator
import backtrader as bt class SwingInd(bt.Indicator): ''' A Simple swing indicator that measures swings (the lowest/highest value) within a given time period. ''' lines = ('swings', 'signal') params = (('period',7),) def __init__(self): #Set the swing range - The number of bars before and after the swing #needed to identify a swing self.swing_range = (self.p.period * 2) + 1 self.addminperiod(self.swing_range) def next(self): #Get the highs/lows for the period highs = self.data.high.get(size=self.swing_range) lows = self.data.low.get(size=self.swing_range) #check the bar in the middle of the range and check if greater than rest if highs.pop(self.p.period) > max(highs): self.lines.swings[-self.p.period] = 1 #add new swing self.lines.signal = 1 #give a signal elif lows.pop(self.p.period) < min(lows): self.lines.swings[-self.p.period] = -1 #add new swing self.lines.signal = -1 #give a signal else: self.lines.swings[-self.p.period] = 0 self.lines.signal = 0Notice that we still need to make an import call of another module inside our new module (
import backtrader as bt). This is because the code inside it requires the Backtrader module. It does not matter if you have imported Backtrader in the script which imports the indicator. It still needs to be imported here. You should now have a directory that looks like this:
Import the IndicatorFinally, we should make a simple script that shall import the indicator.
import backtrader as bt from datetime import datetime from extensions.indicators import SwingInd class simpleStrategy(bt.Strategy): def __init__(self): self.piv = SwingInd(period=7) #Create an instance of cerebro cerebro = bt.Cerebro(stdstats=False) #Add our strategy cerebro.addstrategy(simpleStrategy) #Get Apple data from Yahoo Finance. data = bt.feeds.YahooFinanceData( dataname='AAPL', fromdate = datetime(2016,1,1), todate = datetime(2017,1,1), buffered= True ) #Add the data to Cerebro cerebro.adddata(data) # Run over everything cerebro.run() #Finally plot the end results cerebro.plot(style='candlestick')You can see the line
from extensions.indicators import SwingIndgives us access to the swing indicator without needing to add all the indicators code inside our script. Running the script will give you some output as follows. In it you can see the swing indicator has been added to the bottom of the chart. For further reading on creating modules and importing, take a look at these references:
- The official documentation: https://docs.python.org/3/tutorial/modules.html
- Another, more detailed tutorial on modules: https://www.digitalocean.com/community/tutorials/how-to-write-modules-in-python-3