Stock Market: Friday the 13th

The stock market is the apex of legalized gambling. There are countless get rich quick schemes, penny stocks, Ponzi schemes, as well as legitimate and stable ways to make money. The market is studied by some of the brightest minds and any arbitrary, risk free profit making scheme (called, cunningly enough, an arbitrage) is supposed to be non-existent. Despite Dire Straits' infinite wisdom, there's not supposed to be money for nothing

But can we play on people's irrationality? Are people superstitious enough that the market behaves differently on unlucky days?

I thought this would be easy enough to google, but frankly, I just didn't trust their math. So here is mine, as well as some simple code I used to pull it together.


Where do we start?

I choose the S&P 500 as a representation of the stock market. There are a few advantages:

  • Yahoo Finance has the data, for free, and in CSV format
  • I don't have to aggregate stocks to look at the whole market
  • Outliers and oddities should be minimized.


The dataset has the opening and closing prices by day. If you don't care about the coding, you can skip to the graphs. The steps I had to follow, and are mirrored in the comments in the code (bottom of page) are:

  • Import data
  • Parse data into day of week and day of month
  • Filter data to 2000 and later (though, this doesn't change the answer)
  • Calculate daily change
  • Aggregate data
  • Graph

The red line represents the average daily change, which is essentially 0%. The two dotted lines are the 95% confidence intervals, so any change within this range would be considered "normal" (assuming normal distribution yadda yadda yadda).

When we look by the day of month, we can see there is more volatility, but still pretty close to 0%. The 13th is between the blue lines, and we can see it is line with the other days.


But what about Friday AND 13th?

Look at this table that averages the change for every day and day of week combination:

Looking back up to the initial graphs, this -0.01% for Friday the 13th falls easily within our confidence intervals of roughly ±2.5%.

Final Thoughts on Friday the 13th

Examining our table, all of the days fall within our confidence interval as well. From this I would conclude an investing strategy built around the time of month and day of week will not work consistently. Maybe this is a duh statement. But it also shows, at least in this singular regard, people are behaving rationally and not selling all their stocks on unlucky days. That is slightly comforting to me.

Code:

###Friday the 13th#import librariesimport matplotlib.pyplot as pltfrom matplotlib.pyplot import figureimport mathimport pandas as pdimport osimport numpy as npfrom pandas.plotting import table
#Import dataos.chdir('/Users/erikolson/Desktop/Writing Projects/Data Sci Cat Blog/Culture Society/')dfm = pd.read_csv("Yahoo_S&P.csv")
#parse date to day of weekdfm['date_time'] = pd.to_datetime(dfm['Date'])dfm['weekday'] = dfm['date_time'].dt.day_name()dfm['year'] = dfm['date_time'].dt.yeardfm['day'] = dfm['date_time'].dt.day
#filter the datadfm = dfm[(dfm.year >=2000)]
#calculate changedfm['change_perc']=(dfm['Open']-dfm['Close'])/dfm['Open']dfm['change_mm_perc']=(dfm['High']-dfm['Low'])/dfm['Open']
#used later for naming columns and selecting columns cols = ['Open','Close','High','Low','change_perc','change_mm_perc']stdcols =[]for x in cols: stdcols=stdcols+[x+'_std']
#groupby and get mean and standard deviation dfmWday = dfm.groupby(['weekday'])[cols].mean()dfmWdayS = dfm.groupby(['weekday'])[cols].agg(np.std)dfmWdayS.columns = stdcolsdfmWday = pd.concat([dfmWday, dfmWdayS[['change_perc_std','change_mm_perc_std']]], axis=1, sort=False)del dfmWdayS#calculate confidence intervalsdfmWday['change_perc_CI_L'] = dfmWday['change_perc']-1.96*dfmWday['change_perc_std']dfmWday['change_perc_CI_H'] = dfmWday['change_perc']+1.96*dfmWday['change_perc_std']dfmWdaySum = dfmWday[['change_perc','change_perc_CI_L','change_perc_CI_H']]dfmWdaySum.style.format({'change_perc': '{:,.2%}'.format, 'change_perc_CI_L': '{:,.2%}'.format, 'change_perc_CI_H': '{:,.2%}'.format})dfmWdaySum = dfmWdaySum.reindex(index = ['Monday','Tuesday','Wednesday','Thursday','Friday'])
#groupby and get mean and standard deviation, but now for day of month dfmDay = dfm.groupby(['day'])[cols].mean()dfmDay['day_change_perc'] = (dfmDay['Open']-dfmDay['Close'])/dfmDay['Open']dfmDayS = dfm.groupby(['day'])[cols].agg(np.std)dfmDayS.columns = stdcolsdfmDay = pd.concat([dfmDay, dfmDayS[['change_perc_std','change_mm_perc_std']]], axis=1, sort=False)del dfmDaySdfmDay['change_perc_CI_L'] = dfmDay['change_perc']-1.96*dfmDay['change_perc_std']dfmDay['change_perc_CI_H'] = dfmDay['change_perc']+1.96*dfmDay['change_perc_std']dfmDaySum = dfmDay[['change_perc','change_perc_CI_L','change_perc_CI_H']]dfmDaySum.style.format({'change_perc': '{:,.2%}'.format, 'change_perc_CI_L': '{:,.2%}'.format, 'change_perc_CI_H': '{:,.2%}'.format})
#make table that displays day and month WdaybyDay = pd.pivot_table(dfm, values='change_perc', index=['day'],columns=['weekday'], aggfunc=np.mean)WdaybyDay = WdaybyDay[['Monday','Tuesday','Wednesday','Thursday','Friday']]WdaybyDay.style.format({'Monday': '{:,.2%}'.format, 'Tuesday': '{:,.2%}'.format, 'Wednesday': '{:,.2%}'.format, 'Thursday': '{:,.2%}'.format,'Friday': '{:,.2%}'.format})
os.chdir('/Users/erikolson/Desktop/Writing Projects/Data Sci Cat Blog/Blog Graphs/') #WdaybyDay.to_csv('Friday13WDay.csv')



#graph by day of weekfig= plt.figure(figsize=(12, 6)) plt.ylim(-.03, .03) plt.xlim(0, 4) plt.text(2, .03, "S&P Daily Change 2000-2019", fontsize=18, ha="center") plt.xticks(fontsize=14)
# Remove the plot frame lines. Ensure that the axis ticks only show up on the bottom and left of the plot. ax = plt.subplot(111)fig.patch.set_facecolor((239/255,238/255,236/255))ax.set_facecolor((239/255,238/255,236/255))ax.spines["top"].set_visible(False) ax.spines["bottom"].set_visible(True) ax.spines["right"].set_visible(False) ax.spines["left"].set_visible(True)ax.yaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:,.0%}'.format(y)))plt.xlabel('Day', fontsize=18)plt.ylabel('Average Percentage Change', fontsize=18)ax.get_xaxis().tick_bottom() ax.get_yaxis().tick_left() plt.tick_params(axis="both",which="both",bottom=False,top=False,labelbottom=True,left=False,right=False,\ labelleft=True,labelsize = 12)

plt.plot(dfmWdaySum['change_perc'],"-",color="red")#,label="Change")plt.plot(dfmWdaySum['change_perc_CI_L'],":",color="red")#,label="Change")plt.plot(dfmWdaySum['change_perc_CI_H'],":",color="red")#,label="Change")
plt.plot((0, 32),(-0.02,-0.02), "--", lw=0.5, color="black", alpha=0.3) plt.plot((0, 32),(-0.01,-0.01), "--", lw=0.5, color="black", alpha=0.3) plt.plot((0, 32),(0,0), "--", lw=0.5, color="black", alpha=0.3) plt.plot((0, 32),(0.01,0.01), "--", lw=0.5, color="black", alpha=0.3) plt.plot((0, 32),(0.02,0.02), "--", lw=0.5, color="black", alpha=0.3)
os.chdir('/Users/erikolson/Desktop/Writing Projects/Data Sci Cat Blog/Blog Graphs/') plt.savefig('Friday13WDay.png',bbox_inches = 'tight',facecolor=(239/255,238/255,236/255))




#graph by day lof monthfig= plt.figure(figsize=(12, 6)) plt.ylim(-.03, .03) plt.xlim(1, 31) plt.text(16, .03, "S&P Daily Change 2000-2019", fontsize=18, ha="center")
# Make sure your axis ticks are large enough to be easily read. From 0 to 91, count by 10 plt.xticks(fontsize=14)
# Remove the plot frame lines. Ensure that the axis ticks only show up on the bottom and left of the plot. ax = plt.subplot(111)fig.patch.set_facecolor((239/255,238/255,236/255))ax.set_facecolor((239/255,238/255,236/255))ax.spines["top"].set_visible(False) ax.spines["bottom"].set_visible(True) ax.spines["right"].set_visible(False) ax.spines["left"].set_visible(True)ax.yaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:,.0%}'.format(y)))plt.xlabel('Day', fontsize=18)plt.ylabel('Average Percentage Change', fontsize=18)ax.get_xaxis().tick_bottom() ax.get_yaxis().tick_left() plt.tick_params(axis="both",which="both",bottom=False,top=False,labelbottom=True,left=False,right=False,\ labelleft=True,labelsize = 12)

plt.plot(dfmDaySum['change_perc'],"-",color="red")#,label="Change")plt.plot(dfmDaySum['change_perc_CI_L'],":",color="red")#,label="Change")plt.plot(dfmDaySum['change_perc_CI_H'],":",color="red")#,label="Change")
plt.plot((0, 32),(-0.02,-0.02), "--", lw=0.5, color="black", alpha=0.3) plt.plot((0, 32),(-0.01,-0.01), "--", lw=0.5, color="black", alpha=0.3) plt.plot((0, 32),(0,0), "--", lw=0.5, color="black", alpha=0.3) plt.plot((0, 32),(0.01,0.01), "--", lw=0.5, color="black", alpha=0.3) plt.plot((0, 32),(0.02,0.02), "--", lw=0.5, color="black", alpha=0.3) plt.plot((12.5,12.5),(-0.03,0.03), ":", lw=1, color="blue", alpha=0.3) plt.plot((13.5,13.5),(-0.03,0.03), ":", lw=1, color="blue", alpha=0.3)
os.chdir('/Users/erikolson/Desktop/Writing Projects/Data Sci Cat Blog/Blog Graphs/') plt.savefig('Friday13Day.png',bbox_inches = 'tight',facecolor=(239/255,238/255,236/255))