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.
Go Modular
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.
Created an extensions folderCreate __init__.py
And that is it. We have a module. If you now try and import it from a script, you will not receive an error.
Opening python and importing the moduleNote: Your module name must not contain a dash - e.g. my-module. If it does, you will received a SyntaxError when 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 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
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[0] = 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[0] = -1 #give a signal
else:
self.lines.swings[-self.p.period] = 0
self.lines.signal[0] = 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:
After adding the indicators module
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
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 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:
I have followed this example and created a SwingInd class under extensions/indicators. The catch, though, is that I want to apply this swing indicator to a custom indicator rather than to price, i.e. I want to detect swings of my custom indicator.
I have not been able to figure out how to do this. Is there a way to pass a different line into SwingInd, or does it need to be rewritten to support this ability? If the latter, would you mind providing a simple example?
Phenomenal post. Really helped clear up the thinking on how to make bt modular / OO.
Having said that, when i try and do the above with a strategy (instead of an indicator) as shown above, it doesn’t work. Have you tried that out ? Is it even possible?
I’m very new to Backtrader.
I have followed this example and created a SwingInd class under extensions/indicators. The catch, though, is that I want to apply this swing indicator to a custom indicator rather than to price, i.e. I want to detect swings of my custom indicator.
I have not been able to figure out how to do this. Is there a way to pass a different line into SwingInd, or does it need to be rewritten to support this ability? If the latter, would you mind providing a simple example?
Thank you for your great blog!
Hi Lee,
You can create a custom data feed for backtrader and access it directly in the indicator.
Take a look at this bit of backtrader documentation for extending data feeds with custom info.
https://www.backtrader.com/docu/extending-a-datafeed.html
Phenomenal post. Really helped clear up the thinking on how to make bt modular / OO.
Having said that, when i try and do the above with a strategy (instead of an indicator) as shown above, it doesn’t work. Have you tried that out ? Is it even possible?