In the recent Canonical vs
Non-Canonical post, the
MoneyFlowIndicator) was developed.
Although it is developed in the canonical way, it does still offer some room for improvement and becoming generic.
Let's focus of the 1st lines of the implementation, the ones which create the typical price
class MFI_Canonical(bt.Indicator): lines = ('mfi',) params = dict(period=14) def __init__(self): tprice = (self.data.close + self.data.low + self.data.high) / 3.0 mfraw = tprice * self.data.volume ...
A typical instantiation would look like this
class MyMFIStrategy(bt.Strategy): def __init__(self): mfi = bt.MFI_Canonical(self.data)
The problem here should be obvious: "One needs an input for the indicator
volume components (aka *lines in
the backtrader ecosystem)"*
It may, of course, be the case that one wishes to create a
using components from different data sources (lines from data feeds or lines
from other indicators) As simple as wanting to give the
close a lot more
weight, without having to develop a specific indicator. Considering the
OHLCV field ordering, a multiple inputs, extra weight for
close, instantiation could look like this
class MyMFIStrategy2(bt.Strategy): def __init__(self): wclose = self.data.close * 5.0 mfi = bt.MFI_Canonical(self.data.high, self.data.low, wclose, self.data.volume)
Or because the user previously worked with
ta-lib and fancies the multiple
Supporting multiple inputs
backtrader tries to be as pythonic as possible and the
containing the list of data feeds in the system (and which is auto-magically
provided to your strategy) can be queried for its length. Let's use this to
discriminate what the caller wants and properly calculate
class MFI_MultipleInputs(bt.Indicator): lines = ('mfi',) params = dict(period=14) def __init__(self): if len(self.datas) == 1: # 1 data feed passed, must have components tprice = (self.data.close + self.data.low + self.data.high) / 3.0 mfraw = tprice * self.data.volume else: # if more than 1 data feed, individual components in OHLCV order tprice = (self.data0 + self.data1 + self.data2) / 3.0 mfraw = tprice * self.data3 # No changes with regards to previous implementation flowpos = bt.ind.SumN(mfraw * (tprice > tprice(-1)), period=self.p.period) flowneg = bt.ind.SumN(mfraw * (tprice < tprice(-1)), period=self.p.period) mfiratio = bt.ind.DivByZero(flowpos, flowneg, zero=100.0) self.l.mfi = 100.0 - 100.0 / (1.0 + mfiratio)
Notice how the individual components are referenced as
self.dataX (such as
This is the same as using
self.datas[x], as in
Let's see graphically that this indicator produces the same results as the canonical one, and the same results when the multiple inputs correspond to the original components of the data feed. To do so, it will be run in a strategy as this
class MyMFIStrategy2(bt.Strategy): def __init__(self): MFI_Canonical(self.data) MFI_MultipleInputs(self.data, plotname='MFI Single Input') MFI_MultipleInputs(self.data.high, self.data.low, self.data.close, self.data.volume, plotname='MFI Multiple Inputs')
Without having to resort to check each value, it should be obvious from the picture that the results are the same for the three.
Let's finally see what happens if put a lot more weight on to the
close. Let's run like this.
class MyMFIStrategy2(bt.Strategy): def __init__(self): MFI_MultipleInputs(self.data) MFI_MultipleInputs(self.data.high, self.data.low, self.data.close * 5.0, self.data.volume, plotname='MFI Close * 5.0')
Whether this makes sense or not is left to the reader, but one can clearly see
that adding weight to the
close has altered the pattern.
By simple using the pythonic
len, one can transform an indicator which uses
a data feed with multiple components (and fixed names) into an indicator which
accepts multiple generic inputs.