Developing a Recursive Indicator (with a seed)
One of the initial goals of backtrader was:
- Being able to quickly prototype indicators to test new ideas
It doesn’t have to be a perfect indicator, but being able to quickly and easily develop them does help. To confirm the design was righ, one of the first indicators to be in the standard arsenal of backtrader was an Exponential Moving Average (aka EMA) which by definition is: recursive.
Note
Trivia: as you may imagine the 1st ever indicator was a SimpleMovingAverage
Since the question of how to develop a recursive indicator has been posted in
the backtrader community
let’s develop a quick ExponentialMovingAverage
indicators.
A recursive indicator like the
- It uses the previous value to calculate the current value
You can see the mathematics for example in Wikipedia - Exponential Moving Average
If you have been brave enough to read it all, you’ll have seen that the period is used to calculate the Exponential Smoothing. We’ll use it.
To solve the conundrum for the calculation of the first value the industry
settled on using a simple average of the previous period
values.
As a leverage we are going to use bt.indicators.PeriodN
which:
-
Already defines a
period
parameter -
Informs the framework about the actual
period
used by the end user
See its definition at: Docs - Indicator Reference
Let’s then develop our EMA
import backtrader as bt class EMA(bt.indicators.PeriodN): params = {'period': 30} # even if defined, we can redefine the default value lines = ('ema',) # our output line def __init__(self): self.alpha = 2.0 / (1.0 + self.p.period) # period -> exp smoothing factor def nextstart(self): # calculate here the seed value self.lines.ema[0] = sum(self.data.get(size=self.p.period)) / self.p.period def next(self): ema1 = self.lines.ema[-1] # previous EMA value self.lines.ema[0] = ema1 * (1.0 - self.alpha) + self.data[0] * self.alpha
Almost easier done than said. The key being the provision of the seed value in
nextstart
, which
-
Will be called once, when the minimum warmp up period of the indicator has been met.
As opposed to
next
which will be then called for each new data value that is delivered into the system
The default implementation of nextstart
simply delegates the job to
next
which for most indicators (for example a Simple Moving Average) is
the right thing to do. But in this case overriding and providing the seed value
is the key.
Plotting it along the data
Being a moving average, it would be nice if the indicator plotted on the same
axis as the data for which it calculates the average. Because we have inherited
from PeriodN
the default value for plotting is (see it in the docs):
subplot=True
which of course means that a subplot
(another axis on the chart) will be
created for our indicator. This can be easily overridden.
import backtrader as bt class EMA(bt.indicators.PeriodN): plot = dict(subplot=False)
And done. If you want to control more plotting options check Docs - Plotting
Good Luck!