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 way
And 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.
To 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.
And that is it. We have a module. If you now try and import it from a script, you will not receive an error.
Note: Your module name must not contain a dash
my-module. If it does, you will received a
SyntaxError when trying to import it.
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 modules
So 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
First lets take the swing indicator code and paste it into a file called indicators.py:
import backtrader as bt
A Simple swing indicator that measures swings (the lowest/highest value)
within a given time period.
lines = ('swings', 'signal')
params = (('period',7),)
#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
#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
self.lines.swings[-self.p.period] = 0
self.lines.signal = 0
Notice 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 Indicator
Finally, we should make a simple script that shall import the indicator.
import backtrader as bt
from datetime import datetime
from extensions.indicators import SwingInd
self.piv = SwingInd(period=7)
#Create an instance of cerebro
cerebro = bt.Cerebro(stdstats=False)
#Add our strategy
#Get Apple data from Yahoo Finance.
data = bt.feeds.YahooFinanceData(
fromdate = datetime(2016,1,1),
todate = datetime(2017,1,1),
#Add the data to Cerebro
# Run over everything
#Finally plot the end results
You can see the line
from extensions.indicators import SwingInd gives 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