Chaikin Money Flow (CMF)
References
- fidelity.com: Chaikin Money Flow (CMF)
- stockcharts.com: chaikin_money_flow_cmf
- github.com: pandas-ta cmf
Definition
Chaikin Money Flow (CMF) developed by Marc Chaikin is a volume-weighted average of accumulation and distribution over a specified period. The standard CMF period is 21 days. The principle behind the Chaikin Money Flow is the nearer the closing price is to the high, the more accumulation has taken place. Conversely, the nearer the closing price is to the low, the more distribution has taken place. If the price action consistently closes above the bar’s midpoint on increasing volume, the Chaikin Money Flow will be positive. Conversely, if the price action consistently closes below the bar’s midpoint on increasing volume, the Chaikin Money Flow will be a negative value.
- A CMF value above the zero line is a sign of strength in the market, and a value below the zero line is a sign of weakness in the market.
- Wait for the CMF to confirm the breakout direction of price action through trend lines or through support and resistance lines. For example, if a price breaks upward through resistance, wait for the CMF to have a positive value to confirm the breakout direction.
- A CMF sell signal occurs when price action develops a higher high into overbought zones, with the CMF diverging with a lower high and beginning to fall.
- A CMF buy signal occurs when price action develops a lower low into oversold zones, with the CMF diverging with a higher low and beginning to rise.
Calculation
CMF = n-day Sum of [(((C - L) - (H - C)) / (H - L)) x Vol] / n-day Sum of Vol
Where: n = number of periods, typically 21 H = high L = low C = close Vol = volume
Load basic packages
import pandas as pd
import numpy as np
import os
import gc
import copy
from pathlib import Path
from datetime import datetime, timedelta, time, date
#this package is to download equity price data from yahoo finance
#the source code of this package can be found here: https://github.com/ranaroussi/yfinance/blob/main
import yfinance as yf
pd.options.display.max_rows = 100
pd.options.display.max_columns = 100
import warnings
warnings.filterwarnings("ignore")
import pytorch_lightning as pl
random_seed=1234
pl.seed_everything(random_seed)
Global seed set to 1234
1234
#S&P 500 (^GSPC), Dow Jones Industrial Average (^DJI), NASDAQ Composite (^IXIC)
#Russell 2000 (^RUT), Crude Oil Nov 21 (CL=F), Gold Dec 21 (GC=F)
#Treasury Yield 10 Years (^TNX)
#benchmark_tickers = ['^GSPC', '^DJI', '^IXIC', '^RUT', 'CL=F', 'GC=F', '^TNX']
benchmark_tickers = ['^GSPC']
tickers = benchmark_tickers + ['GSK', 'NVO', 'PFE']
#https://github.com/ranaroussi/yfinance/blob/main/yfinance/base.py
# def history(self, period="1mo", interval="1d",
# start=None, end=None, prepost=False, actions=True,
# auto_adjust=True, back_adjust=False,
# proxy=None, rounding=False, tz=None, timeout=None, **kwargs):
dfs = {}
for ticker in tickers:
cur_data = yf.Ticker(ticker)
hist = cur_data.history(period="max", start='2000-01-01')
print(datetime.now(), ticker, hist.shape, hist.index.min(), hist.index.max())
dfs[ticker] = hist
2022-09-06 23:59:36.399435 ^GSPC (5707, 7) 1999-12-31 00:00:00 2022-09-06 00:00:00
2022-09-06 23:59:36.774461 GSK (5707, 7) 1999-12-31 00:00:00 2022-09-06 00:00:00
2022-09-06 23:59:37.120235 NVO (5707, 7) 1999-12-31 00:00:00 2022-09-06 00:00:00
2022-09-06 23:59:37.516756 PFE (5707, 7) 1999-12-31 00:00:00 2022-09-06 00:00:00
ticker = 'GSK'
dfs[ticker].tail(5)
| Open | High | Low | Close | Volume | Dividends | Stock Splits | |
|---|---|---|---|---|---|---|---|
| Date | |||||||
| 2022-08-30 | 33.230000 | 33.290001 | 32.919998 | 32.959999 | 3994500 | 0.0 | 0.0 |
| 2022-08-31 | 32.790001 | 32.880001 | 32.459999 | 32.480000 | 4291800 | 0.0 | 0.0 |
| 2022-09-01 | 31.830000 | 31.990000 | 31.610001 | 31.690001 | 12390900 | 0.0 | 0.0 |
| 2022-09-02 | 31.600000 | 31.969999 | 31.469999 | 31.850000 | 8152600 | 0.0 | 0.0 |
| 2022-09-06 | 31.650000 | 31.760000 | 31.450001 | 31.565001 | 2282742 | 0.0 | 0.0 |
Define CMF calculation function
def cal_cmf(ohlcv: pd.DataFrame, period: int = 10) -> pd.Series:
"""
Chaikin Money Flow (CMF) developed by Marc Chaikin is a volume-weighted average of accumulation and distribution over a specified period. The standard CMF period is 21 days. The principle behind the Chaikin Money Flow is the nearer the closing price is to the high, the more accumulation has taken place. Conversely, the nearer the closing price is to the low, the more distribution has taken place. If the price action consistently closes above the bar's midpoint on increasing volume, the Chaikin Money Flow will be positive. Conversely, if the price action consistently closes below the bar's midpoint on increasing volume, the Chaikin Money Flow will be a negative value.
"""
ohlcv = ohlcv.copy()
ohlcv.columns = [c.lower() for c in ohlcv.columns]
high = ohlcv["high"]
low = ohlcv["low"]
close = ohlcv["close"]
volume = ohlcv["volume"]
ad = (2*close - (high + low))*volume/(high - low)
cmf = (ad.rolling(period, min_periods=period).sum())/(volume.rolling(period, min_periods=period).sum())
return pd.Series(cmf, name='CMF')
Calculate CMF
df = dfs[ticker][['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.round(2)
help(cal_cmf)
Help on function cal_cmf in module __main__:
cal_cmf(ohlcv: pandas.core.frame.DataFrame, period: int = 10) -> pandas.core.series.Series
Chaikin Money Flow (CMF) developed by Marc Chaikin is a volume-weighted average of accumulation and distribution over a specified period. The standard CMF period is 21 days. The principle behind the Chaikin Money Flow is the nearer the closing price is to the high, the more accumulation has taken place. Conversely, the nearer the closing price is to the low, the more distribution has taken place. If the price action consistently closes above the bar's midpoint on increasing volume, the Chaikin Money Flow will be positive. Conversely, if the price action consistently closes below the bar's midpoint on increasing volume, the Chaikin Money Flow will be a negative value.
df_ta = cal_cmf(df, period = 21)
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )
del df_ta
gc.collect()
19950
from core.finta import TA
help(TA.BBANDS)
Help on function BBANDS in module core.finta:
BBANDS(ohlc: pandas.core.frame.DataFrame, period: int = 20, MA: pandas.core.series.Series = None, column: str = 'close', std_multiplier: float = 2) -> pandas.core.frame.DataFrame
Developed by John Bollinger, Bollinger Bands® are volatility bands placed above and below a moving average.
Volatility is based on the standard deviation, which changes as volatility increases and decreases.
The bands automatically widen when volatility increases and narrow when volatility decreases.
This method allows input of some other form of moving average like EMA or KAMA around which BBAND will be formed.
Pass desired moving average as <MA> argument. For example BBANDS(MA=TA.KAMA(20)).
df_ta = TA.BBANDS(df, period = 20, column="close", std_multiplier=1.95)
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )
del df_ta
gc.collect()
42
display(df.head(5))
display(df.tail(5))
| Open | High | Low | Close | Volume | CMF | BB_UPPER | BB_MIDDLE | BB_LOWER | |
|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||
| 1999-12-31 | 19.60 | 19.67 | 19.52 | 19.56 | 139400 | NaN | NaN | NaN | NaN |
| 2000-01-03 | 19.58 | 19.71 | 19.25 | 19.45 | 556100 | NaN | NaN | NaN | NaN |
| 2000-01-04 | 19.45 | 19.45 | 18.90 | 18.95 | 367200 | NaN | NaN | NaN | NaN |
| 2000-01-05 | 19.21 | 19.58 | 19.08 | 19.58 | 481700 | NaN | NaN | NaN | NaN |
| 2000-01-06 | 19.38 | 19.43 | 18.90 | 19.30 | 853800 | NaN | NaN | NaN | NaN |
| Open | High | Low | Close | Volume | CMF | BB_UPPER | BB_MIDDLE | BB_LOWER | |
|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||
| 2022-08-30 | 33.23 | 33.29 | 32.92 | 32.96 | 3994500 | 0.132024 | 41.099946 | 35.7605 | 30.421054 |
| 2022-08-31 | 32.79 | 32.88 | 32.46 | 32.48 | 4291800 | 0.130608 | 40.446679 | 35.3665 | 30.286321 |
| 2022-09-01 | 31.83 | 31.99 | 31.61 | 31.69 | 12390900 | 0.097057 | 39.764640 | 34.9440 | 30.123360 |
| 2022-09-02 | 31.60 | 31.97 | 31.47 | 31.85 | 8152600 | 0.125980 | 38.904860 | 34.5310 | 30.157140 |
| 2022-09-06 | 31.65 | 31.76 | 31.45 | 31.57 | 2282742 | 0.109165 | 37.926255 | 34.1165 | 30.306745 |
df[['CMF']].hist(bins=50)
array([[<AxesSubplot:title={'center':'CMF'}>]], dtype=object)

#https://github.com/matplotlib/mplfinance
#this package help visualize financial data
import mplfinance as mpf
import matplotlib.colors as mcolors
# all_colors = list(mcolors.CSS4_COLORS.keys())#"CSS Colors"
# all_colors = list(mcolors.TABLEAU_COLORS.keys()) # "Tableau Palette",
# all_colors = list(mcolors.BASE_COLORS.keys()) #"Base Colors",
all_colors = ['dodgerblue', 'firebrick','limegreen','skyblue','lightgreen', 'navy','yellow','plum', 'yellowgreen']
#https://github.com/matplotlib/mplfinance/issues/181#issuecomment-667252575
#list of colors: https://matplotlib.org/stable/gallery/color/named_colors.html
#https://github.com/matplotlib/mplfinance/blob/master/examples/styles.ipynb
def make_3panels2(main_data, add_data, mid_panel=None, chart_type='candle', names=None, figratio=(14,9)):
"""
main chart type: default is candle. alternatives: ohlc, line
example:
start = 200
names = {'main_title': 'MAMA: MESA Adaptive Moving Average',
'sub_tile': 'S&P 500 (^GSPC)', 'y_tiles': ['price', 'Volume [$10^{6}$]']}
make_candle(df.iloc[-start:, :5], df.iloc[-start:][['MAMA', 'FAMA']], names = names)
"""
style = mpf.make_mpf_style(base_mpf_style='yahoo', #charles
base_mpl_style = 'seaborn-whitegrid',
# marketcolors=mpf.make_marketcolors(up="r", down="#0000CC",inherit=True),
gridcolor="whitesmoke",
gridstyle="--", #or None, or - for solid
gridaxis="both",
edgecolor = 'whitesmoke',
facecolor = 'white', #background color within the graph edge
figcolor = 'white', #background color outside of the graph edge
y_on_right = False,
rc = {'legend.fontsize': 'small',#or number
#'figure.figsize': (14, 9),
'axes.labelsize': 'small',
'axes.titlesize':'small',
'xtick.labelsize':'small',#'x-small', 'small','medium','large'
'ytick.labelsize':'small'
},
)
if (chart_type is None) or (chart_type not in ['ohlc', 'line', 'candle', 'hollow_and_filled']):
chart_type = 'candle'
len_dict = {'candle':2, 'ohlc':3, 'line':1, 'hollow_and_filled':2}
kwargs = dict(type=chart_type, figratio=figratio, volume=False, volume_panel=1,
panel_ratios=(4,2), tight_layout=True, style=style, returnfig=True)
if names is None:
names = {'main_title': '', 'sub_tile': ''}
added_plots = { }
for name_, data_ in add_data.iteritems():
added_plots[name_] = mpf.make_addplot(data_, panel=0, width=1, secondary_y=False)
fb_bbands_ = dict(y1=add_data.iloc[:, 0].values,
y2=add_data.iloc[:, 1].values,color="lightskyblue",alpha=0.1,interpolate=True)
fb_bbands_['panel'] = 0
fb_bbands= [fb_bbands_]
if mid_panel is not None:
i = 0
for name_, data_ in mid_panel.iteritems():
added_plots[name_] = mpf.make_addplot(data_, panel=1, color=all_colors[i])
i = i + 1
fb_bbands2_ = dict(y1=-0.1*np.ones(mid_panel.shape[0]),
y2=0.1*np.ones(mid_panel.shape[0]),color="lightskyblue",alpha=0.1,interpolate=True)
fb_bbands2_['panel'] = 1
fb_bbands.append(fb_bbands2_)
fig, axes = mpf.plot(main_data, **kwargs,
addplot=list(added_plots.values()),
fill_between=fb_bbands)
# add a new suptitle
fig.suptitle(names['main_title'], y=1.05, fontsize=12, x=0.1285)
# axes[0].legend([None]*5)
# handles = axes[0].get_legend().legendHandles
# axes[0].legend(handles=handles[2:],labels=list(added_plots.keys()))
axes[0].set_title(names['sub_tile'], fontsize=10, style='italic', loc='left')
# axes[0].set_ylabel(names['y_tiles'][0])
# axes[2].set_ylabel(names['y_tiles'][1])
return fig, axes
start = -100
end = df.shape[0]
names = {'main_title': f'{ticker}',
'sub_tile': 'CMF'}
aa_, bb_ = make_3panels2(df.iloc[start:end][['Open', 'High', 'Low', 'Close', 'Volume']],
df.iloc[start:end][['BB_UPPER', 'BB_LOWER' ]],
df.iloc[start:end][['CMF',]],
chart_type='hollow_and_filled',names = names)
