Extending an Indicator
In Object Oriented Programming, and of course in Python itself, extension of an existing class can be achieved in two ways.
-
Inheritance (or subclassing)
-
Composition (or embedding)
In Developing an Indicator, the indicator Trix
was developed in just
a couple lines of code. The ChartSchool - Trix
reference literature has a Trix
with a signal line showing the similarities
with MACD.
Let’s “compose” MyTrixSignal
using the already developed Trix
class MyTrixSignalComposed(bt.Indicator):
lines = ('trix', 'signal')
params = (('period', 15), ('sigperiod', 9))
def __init__(self):
self.lines.trix = MyTrix(self.data, period=self.p.period)
self.lines.signal = btind.EMA(self.lines.trix, period=self.p.sigperiod)
Some things had to be repeated in the definition such as the name of the
trix
line and the period
to use for calculation. A new line signal
and the corresponding sigperiod
parameter have been defined.
The 2-liner is a good result.
Now let’s go for inheritance, but first recalling how Trix
looks like:
class MyTrix(bt.Indicator):
lines = ('trix',)
params = (('period', 15),)
def __init__(self):
ema1 = btind.EMA(self.data, period=self.p.period)
ema2 = btind.EMA(ema1, period=self.p.period)
ema3 = btind.EMA(ema2, period=self.p.period)
self.lines.trix = 100.0 * (ema3 - ema3(-1)) / ema3(-1)
Using Trix
as the base class, this is the aspect of TrixSignal
class MyTrixSignalInherited(MyTrix):
lines = ('signal',)
params = (('sigperiod', 9),)
def __init__(self):
super(MyTrixSignalInherited, self).__init__()
self.lines.signal = btind.EMA(self.lines.trix, period=self.p.sigperiod)
The inherited indicator ends up also being a 2-liner but:
-
No redefinition of the
trix
line is needed -
No redefinition of the
period
parameter is needed
Both are inherited from the base class Trix
. And the calculation of the
trix
line is done in the base class __init__
method:
- super(MyTrixSignalInherited, self).init()
The choice of composition vs inheritance is a classic. This example is not meant to clarify which is better but more to show that:
Note
Inheritance works even in the presence of the metadefinitions of lines and params, which also inherit from the metadefinitions of the base class
And finally the code and charts for both versions when put in action.
- The first one shows the inherited version
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
import backtrader.feeds as btfeeds
from mytrix import MyTrixSignalInherited
class NoStrategy(bt.Strategy):
params = (('trixperiod', 15),
('analyzer', False),)
def __init__(self):
MyTrixSignalInherited(self.data, period=self.p.trixperiod)
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(NoStrategy, trixperiod=15)
# Create a Data Feed
datapath = ('../datas/2006-day-001.txt')
data = bt.feeds.BacktraderCSVData(dataname=datapath)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Run over everything
cerebro.run()
# Plot the result
cerebro.plot()
- The first one shows the composed version
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
import backtrader.feeds as btfeeds
from mytrix import MyTrixSignalComposed
class NoStrategy(bt.Strategy):
params = (('trixperiod', 15),
('analyzer', False),)
def __init__(self):
MyTrixSignalComposed(self.data, period=self.p.trixperiod)
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(NoStrategy, trixperiod=15)
# Create a Data Feed
datapath = ('../datas/2006-day-001.txt')
data = bt.feeds.BacktraderCSVData(dataname=datapath)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Run over everything
cerebro.run()
# Plot the result
cerebro.plot()