Bitcoin Seasonality: Fooled by Randomness

Autumn is my favorite time of year. Football (and more importantly as a Bills fan, fantasy football) is back, everything tastes like pumpkin, and I don’t get sweaty walking around outside. It’s also the start of the cryptocurrency bull season! Or is it? Let’s find out.

It’s already started: Twitter gurus posting that the coming months are the most bullish for Bitcoin. Many still remember the frenzied bull market of 2017 and are hoping for a repeat. Charts like the one above paint such a clear picture: prices go up in October and November, so buy!  Certainly it can’t just be a coincidence, right? In this post, we’ll try to determine whether being long Bitcoin in the fall months is a valid investment thesis.

Monthly Performance

First, we’ll look at how Bitcoin has performed in each given month based on the Sharpe Ratio of daily returns. You can read about my preference for this metric in this post. The following snippet bins returns by month and calculates the Sharpe for each.

# Create Sharpe Ratio
def sharpe_ratio(x):
	return np.mean(x) / np.std(x) * np.sqrt(365)
	
# Load some data
xbt_1d = pd.read_csv('XBT_1d.csv', parse_dates=True, index_col=0)
# Returns for spot Bitcoin
xbt_1d['returns'] = xbt_1d['Open'].pct_change().shift(-2)
# Returns for inverse products like Bitmex futures/swaps
xbt_1d['returns_inv'] = 1 - xbt_1d['Open'].shift(-1) / xbt_1d['Open'].shift(-2)
# Get performance by month
grouped_by_month = (
	xbt_1d['returns']
	.groupby(xbt_1d.index.month)
	.apply(sharpe_ratio)
)
months = {k:v for k,v in enumerate(calendar.month_abbr)}
grouped_by_month.index = map(months.get, grouped_by_month.index)

# Plot the results
fig, ax = plt.subplots(figsize=(10, 7))
grouped_by_month.plot(kind='bar', color='tab:blue', edgecolor='k', ax=ax)
ax.set_title('Bitcoin Returns by Month', fontsize=16)
ax.set_xlabel('Month')
ax.set_ylabel('Sharpe Ratio of Daily Returns')
ax.set_xticklabels(grouped_by_month.index, rotation=0)

At first glance, it looks like the theory holds up. October and November appear to be the best months to buy, with November showing strong out-performance. The question we have to answer is whether this is a significant market anomaly or just an random artifact of the data.

Randomness Testing

To determine whether there is a statistical difference between the fall months and the rest of the year, we’ll do the following:

  • Randomly shuffle the order of returns
  • Bin these shuffled returns by month
  • Calculate the greatest Sharpe Ratio obtained using these shuffled returns and record this number
  • Repeat this process thousands of times to generate a distribution of the greatest Sharpe Ratios attained
  • Compare the observed performance from real market data to some threshold from the random distribution 

It’s important to use the greatest value as a benchmark, because the only reason we’ve selected a given month is by looking at all of them and selecting the best. We had no pre-existing thesis for why prices should behave a certain way at a certain time.

This process can be implemented as follows:

# Generate random distribution
random_sharpes = pd.Series()
for i in range(5000):
	permuted = np.random.permutation(xbt_1d['returns'])
	random_returns = pd.Series(permuted, index=xbt_1d.index)
	random_by_month = random_returns.groupby(random_returns.index.month).apply(sharpe_ratio)
	max_sharpe = random_by_month.max()
	random_sharpes.loc[i] = max_sharpe
	
# Plot the results
perc_95 = np.percentile(random_sharpes, 95)
max_observed = grouped_by_month.max()
fig, ax = plt.subplots()
random_sharpes.hist(bins=25, edgecolor='k', ax=ax)
ax.axvline(perc_95, color='r')
ax.set_title('Distribution of Greatest Sharpe Ratio, Random Returns', fontsize=14)
ax.set_xlabel('Sharpe Ratio')

print('95th percentile: {}'.format(np.round(perc_95, 2)))
print('Maximum Observed Sharpe Ratio: {}'.format(np.round(max_observed, 2)))
95th percentile: 5.12
Maximum Observed Sharpe Ratio: 5.10

The black line above represents our real historical returns for the month of November, while the red is the 95th percentile of random returns. This test reveals that by chance alone, we would expect one month to have a Sharpe Ratio greater than 5.12 about 5% of the time. The maximum observed Sharpe, for the month of November, was 5.10. While we can’t technically reject the null with 95% confidence, (1) it’s not a magical hard limit, and (2) we’re extremely close. There may be some merit to buying in November, after all!

We can visualize this on our original chart for comparison:

Conclusion / Closing Thoughts

Through randomness testing, we discovered that the chances of one month out-performing to the extent that November has historically are actually pretty low. However, there are some caveats:

  • We’re dealing with a limited sample size here. The price series used for this testing dates back only to 2012
  • The sample is heavily influenced by two parabolic bull runs in 2013 and 2017
  • Maybe there’s a flaw in my testing methodology (please let me know in the comments below!)

With that said, it’s important as traders to be aware of any possible market anomaly we can exploit to our advantage. Keep your ear to the ground and be ready to take advantage.

If you enjoyed this article, please leave a comment below! Also feel free to follow on twitter, connect on Linkedin, and join the Quant Talk Telegram chat here:  https://t.me/joinchat/GrWzrxH7Z0X_65JD3NLGMw