Several weeks back, I posted some work I had done on ETF fund flows and what they could tell us about how investors, on average, fare with respect to timing their entries and exits.
TL;DR: Most investors are terrible at timing inflows and outflows to the market. They badly trail benchmarks because they tend to pile in near the top and exit the markets near the bottom.
The consistently correct strategy is to do the exact opposite of the bulk of investors. In other words, buy when investors are pulling money out of the market, sell when they're piling in.
Since the first 11 days of October have been rather active, I thought I'd take the occasion to build on the prior article with an update of fund flows month-to-date. Contrarians may find some cause to "go against the flow".
I won't go into much detail on the nuts and bolts of fund flows. For that, please see the aforementioned prior post.
import pandas as pd import numpy as np import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns import datetime ### Proprietary libraries import sys sys.path.append('/anaconda2/') # either place your config.py in this path, or add another sys.path.append() pointing to your path for config.py import config ## TODO: place file called config.py in the /anaconda2/ folder sys.path.append(config.REPO_ROOT+'data/') #Chad: Home from prices.eod import iqfeed from prices.eod.read import get_adjustment_factors ## Function to load prices and adjust dividends def make_data(symbols): price = iqfeed.read_etf_daily(symbols) so = iqfeed.read_etf_so(symbols) #shares outstanding df = price[['close']].join(so) df.loc[df.so==0,'so'] = None df.so = df.so.ffill() # handle days of unreported shares out df = df[df.so>0] adj_factors = get_adjustment_factors(symbols).stack() adj_factors.name = 'adj_factors' df['close_adj'] = df.close * adj_factors df['daily_ret_adj'] = df.close_adj.groupby(level='symbol').pct_change() df['log_ret_adj'] = np.log(df.close_adj).groupby(level='symbol').diff() df['flow'] = np.log(df.so).groupby(level='symbol').diff() df['mkt_cap_adj'] = df.so*df.close_adj # catch outliers #df = df[df.daily_ret_adj.abs()<0.25] # daily price changes of >25% #df = df[df.so.pct_change().abs()<0.50] # shares outstanding change of >50% # standardize time frame df = df.xs(slice('2017-12-01',None),level='date',drop_level=False) return df.copy()
Here's a quick peek at what the data looks like, using SPY as an example.
## Example: SPY # close_adj: closing price, adjusted for divs/splits (if applicable) # so: shares outstanding # daily_ret_adj: daily return (0.01 is 1%) on adjusted prices # flow: log percent change to shares outstanding. In other words, amount invested (redeemed) as a (log) percent of market cap df = make_data(['SPY']) df=df.xs('SPY',level='symbol') df['2018':].so.plot(title='SPY: Shares Outstanding',figsize=(8,4)) print(df[['close_adj','so','daily_ret_adj','flow']].tail())
close_adj so daily_ret_adj flow date 2018-10-05 287.82 965032128.0 -0.005597 -0.001657 2018-10-08 287.82 974532096.0 0.000000 0.009796 2018-10-09 287.40 967732096.0 -0.001459 -0.007002 2018-10-10 278.30 972782144.0 -0.031663 0.005205 2018-10-11 272.17 976032128.0 -0.022027 0.003335
(df.flow*df.mkt_cap_adj).rolling(21).sum().plot(title='2018 YTD Fund Flows ($)',figsize=(8,4)) plt.axhline(y=0.0,color='grey',ls='--')
<matplotlib.lines.Line2D at 0x11563f28>
I'll start with the major indices - S&P 500, Russell 2K, Nasdaq 100, and Vanguard Total World index. And yes, I intentionally ignored the Dow. It's not a real index. If we all ignore it, perhaps it'll go away.
symbols = ['SPY','IWM','QQQ','VT'] df = make_data(symbols).sort_index().loc['2018-10'] df.flow.unstack().cumsum().plot(title="Major Equity Indices\nOctober Month-to-date")
<matplotlib.axes._subplots.AxesSubplot at 0xc249d30>
QQQ has had major outflows but the other three have seen measurable inflows over the month to date. Surprising...
Next, we'll look at sectors like Consumer Discretionary, Technology, Materials, Energy, Healthcare, Utilities, Industrials, and Consumer Staples.
symbols = ['XLY','XLK','XLB','XLE','XLV','XLU','XLI','XLP'] df = make_data(symbols).sort_index().loc['2018-10'] df.flow.unstack().cumsum().plot(title="Sector Indices\nOctober Month-to-date")
<matplotlib.axes._subplots.AxesSubplot at 0x1136d358>
Industrials have had positive flows in October.
Materials have had >6% outflows each.
Third, we'll look at countries and regions including
symbols = ['EWA','EWG','EWJ','EWC','FXI','EEM'] df = make_data(symbols).sort_index().loc['2018-10'] df.flow.unstack().cumsum().plot(title="Country/Regional Indices\nOctober Month-to-date")
<matplotlib.axes._subplots.AxesSubplot at 0x1148b6a0>
Germany had negative flows, but rather limited.
Japan has gained substantial assets.
Finally, we'll look at major fixed income funds including
20+ year Treasury,
10 year Treasury,
Corporate (investment grade).
symbols = ['AGG','TLT','IEF','MUB','JNK','LQD'] df = make_data(symbols).sort_index().loc['2018-10'] df.flow.unstack().cumsum().plot(title="Fixed Income Indices\nOctober Month-to-date")
<matplotlib.axes._subplots.AxesSubplot at 0x12b50fd0>
Ding, ding, ding! Both long treasurys (TLT) and Junk bonds (JNK) have shed almost 15% of assets this month alone. Junk has seen outflows of almost 20% from the high water mark one week ago (Oct 4th), while long bonds seemed to have greatest redemptions in the first few days of the month.
To put this in context, below is a chart of these ETFs during all of 2018 YTD - I'll throw in JNK's close cousin HYG for comparison.
symbols = ['JNK','HYG','IEF','TLT'] df = make_data(symbols).sort_index().loc['2018'] df.flow.unstack().cumsum().plot(title="Fixed Income Indices\nOctober Month-to-date")
<matplotlib.axes._subplots.AxesSubplot at 0x129a0f98>
This month's redemption appears to be an acceleration of a longer term trend for junk, while it appears to be a short term reversal for treasurys.
Quick and dirty, we see that:
- Equities have not had outflows this week, despite what you might have expected.
- Sector funds have, by and large, seen outflows - especially materials and industrials
- Japan has seen inflows
- Junk bonds and long treasurys have each seen double-digit outflows thus far in October.
Contrarians may see these flows and consider loading up on materials, industrials, junk bonds and long treasurys. This is not investment advice...
One last thing...¶
If you've found this post useful or enlightening, please consider subscribing to the email list to be notified of future posts (email addresses will only be used for this purpose...).
You can also follow me on twitter and forward to a friend or colleague who may find this topic interesting.