The Chaikin Money Flow (CMF) is a popular volume based indicator. It was developed by Marc Chaikin who has been an analyst, trader and entrepreneur in the industry since 1965. Further, he has been developing trading indicators since the 1980’s during which time he worked on some well-known indicators (with this being one of them). More recently he has founded the company Chaikin Analytics which provides analytics tools and software.
Source: https://en.wikipedia.org/wiki/Marc_Chaikin
Marc’s CMF Indicator is available on Tradingview as an open source indicator. This means we can take a look at that code and use it to make a port for Backtrader. However, before we get into the code, let’s take a quick look at the theory behind the indicator and some of the common ways to interpret it.
Chaikin Money Flow
Designed to measure the flow of money over a given period of time, the Chaikin Money Flow indicator will be positive when money is flowing into an asset and negative when money is flowing out of an asset. The way in which the indicator is calculated means that the value fluctuates between -1 and 1. As such, this indicator can be considered an oscillator.
The indicator has 3 main components:
- Multiplier: This is the key component that allows the indicator to oscillate. A calculation is made which results in a negative or positive value that can then be used as multiplier. Essentially, the calculation will be positive if the
close
value is closer to the high of the day. Conversely, if theclose
value is closer to the low of the day, it will be negative. - Adjusted Volume: Then we adjust the volume to a positive or negative value (
volume
* the multiplier) - CMF Value: Finally we take a
x
period sum of the adjusted volume and divide it by the same period of total volume.
The actual formula and calculation will be seen in the code later but for those of you that prefer to put your math hat on, here is a link to the mathematical formula: https://en.wikipedia.org/wiki/Chaikin_Analytics#Chaikin_Money_Flow
Using Chaikin Money Flow
Given that this is an oscillating indicator, we have the usual suspects when it comes to interpretation. These being:
- Zero Line: If the indicator is above zero, it suggests there is buying bias in the market. If it is below, we have a selling Bias.
- Extremes: If the CMF value gets to an extreme level it could indicate a trend reversal. However, we must be careful in defining extreme. Due to the way the indicator is calculated, the longer the look-back period you use, the less likely it is to ever get to an extreme level. For example, with a look back period of 20, you would need 20 consecutive closes where the high is equal to the close value in order to reach 1.
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
''' Author: www.backtest-rookies.com MIT License Copyright (c) 2018 backtest-rookies.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //////////////////////////////////////////////////////////////////////////////// BACKTRADER CHAIKIN MONEY FLOW INDICATOR PORT //////////////////////////////////////////////////////////////////////////////// //@version=3 study(title="Chaikin Money Flow", shorttitle="CMF") length = input(20, minval=1) ad = close==high and close==low or high==low ? 0 : ((2*close-low-high)/(high-low))*volume mf = sum(ad, length) / sum(volume, length) plot(mf, color=green, title="MF") hline(0, color=gray, title="Zero", linestyle=dashed) ''' import backtrader as bt from datetime import datetime class MoneyFlow(bt.Indicator): lines = ('money_flow',) params = ( ('len', 20), ) plotlines = dict( money_flow=dict( _name='CMF', color='green', alpha=0.50 ) ) def __init__(self): # Let the indicator get enough data self.addminperiod(self.p.len) # Plot horizontal Line self.plotinfo.plotyhlines = [0] # Aliases to avoid long lines c = self.data.close h = self.data.high l = self.data.low v = self.data.volume self.data.ad = bt.If(bt.Or(bt.And(c == h, c == l), h == l), 0, ((2*c-l-h)/(h-l))*v) self.lines.money_flow = bt.indicators.SumN(self.data.ad, period=self.p.len) / bt.indicators.SumN(self.data.volume, period=self.p.len) class testStrategy(bt.Strategy): def __init__(self): self.rsi = MoneyFlow(self.data) #Create an instance of cerebro cerebro = bt.Cerebro() #Add our strategy cerebro.addstrategy(testStrategy) #Get Apple data from Yahoo Finance. data = bt.feeds.Quandl( 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') |
Code Commentary
All of the action for this indicator is contained within the __init__()
method. For anyone who has a basic knowledge of Backtrader and Python, the majority of the code within __init()__
should be self-explanatory. However, if you are a complete beginner, fear not! We have some getting started tutorials here:
https://backtest-rookies.com/getting-started/
The two areas of which have not been covered before and are worth commenting on are the plotlines
dictionary and the self.data.ad
line.
Plotlines
The plotlines dictionary allows us to specify the look and feel of our plotted line. Each line has it’s own dictionary within it. This is how we can specify different parameters for different lines. Backtrader uses matplotlib
for plotting and as such we occasionally need to reference the matplotlib
documentation to see what options/parameters are available to us. The official documentation makes a special mention to point this out.
Most of options specified in
plotlines
are meant to be directly passed over tomatplotlib
when plotting.
So for example, in the official Backtrader docs we have the code snippet:
1 2 |
lines = ('histo',) plotlines = dict(histo=dict(_method='bar', alpha=0.50, width=1.0)) |
However, if you see this and try to set a width parameter on a line chart rather than a bar chart (by changing the _method
), you will encounter issues. So it is always a good idea to check the matplot lib docs.
Backtrader Docs: https://www.backtrader.com/docu/plotting/plotting.html#line-specific-plotting-options
Matplotlib Docs: https://matplotlib.org/api/pyplot_summary.html
Matplotlib Bar Options: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.bar.html#matplotlib.pyplot.bar
bt.If(), bt.And() and bt.Or()
Next, we go to the long self.data.ad
line. Although it appears quite complex at first, we can break it down in plain English:
self.data.ad = bt.If(bt.Or(bt.And(c == h, c == l), h == l), 0, ((2*c-l-h)/(h-l))*v)
IF the Close == High
AND the Close == Low
OR High == Low
THEN the value = 0 OTHERWISE, perform the multiplier calculation.
The reason it can look a little daunting is because we need to nest bt.And()
/bt.Or()
inside the bt.If()
function. We do this as we cannot use pythons own if
,and
andor
operators when creating new data lines during __init__
. This is a technical limitation and therefore, bt.If()
and a series of other operators were created as a workaround. For more information, see the documentation here:
https://www.backtrader.com/docu/concepts.html#some-non-overriden-operators-functions
On The Chart
Finally, let’s run the indicator on some data to see how it looks!
Thanks for your article,
However, when i trying to check same with excel calculation is coming wrong.
Let me know how can i send you my excel.
I’m using this formula for calculation :
1. Money Flow Multiplier = [(Close – Low) – (High – Close)] /(High – Low)
2. Money Flow Volume = Money Flow Multiplier x Volume for the Period
3. 20-period CMF = 20-period Sum of Money Flow Volume / 20 period Sum of Volume
Hi Anil,
Thanks for taking a deep look at the code. It is always good to have people highlight potential issues.
However, I have double checked the results and can confirm that it is a correct. When I say correct, I mean it is a faithful port of the Tradingview indicator.
Please see this image. I have added some debug prints and compared the indicator values with Tradingview.
https://imgur.com/a/ceXRzlj
Hi, thanks for the very helpfult code.
It’s worked with your default dataset, but when I tried to apply to my own data, i got this error:
Traceback (most recent call last):
File “D:/Python/advisor-bitcoin/test/test.py”, line 133, in
cerebro.run()
File “C:\Users\duyvi\AppData\Local\Programs\Python\Python37\lib\site-packages\backtrader\cerebro.py”, line 1127, in run
runstrat = self.runstrategies(iterstrat)
File “C:\Users\duyvi\AppData\Local\Programs\Python\Python37\lib\site-packages\backtrader\cerebro.py”, line 1293, in runstrategies
self._runonce(runstrats)
File “C:\Users\duyvi\AppData\Local\Programs\Python\Python37\lib\site-packages\backtrader\cerebro.py”, line 1652, in _runonce
strat._once()
File “C:\Users\duyvi\AppData\Local\Programs\Python\Python37\lib\site-packages\backtrader\lineiterator.py”, line 297, in _once
indicator._once()
File “C:\Users\duyvi\AppData\Local\Programs\Python\Python37\lib\site-packages\backtrader\lineiterator.py”, line 297, in _once
indicator._once()
File “C:\Users\duyvi\AppData\Local\Programs\Python\Python37\lib\site-packages\backtrader\linebuffer.py”, line 631, in _once
self.once(self._minperiod, self.buflen())
File “C:\Users\duyvi\AppData\Local\Programs\Python\Python37\lib\site-packages\backtrader\linebuffer.py”, line 755, in once
self._once_op(start, end)
File “C:\Users\duyvi\AppData\Local\Programs\Python\Python37\lib\site-packages\backtrader\linebuffer.py”, line 772, in _once_op
dst[i] = op(srca[i], srcb[i])
ZeroDivisionError: float division by zero
Can you take a look at this pls, thank you very much