When comparing investable assets people often use the compound annual growth rate (CAGR) as a key metric. I was curious so I wanted to know what CAGR a HODLer could expect at various points over the last decade or so and how that changed depending on how long they held it.
I put together a basic python script in a jupyter notebook to show the rolling CAGR for someone who held for various lengths of time. Each point on the graph represents the CAGR on that date for the prior time period. For example on the 4 year chart each point shows the CAGR for the prior 4 year period ending on that date.
Underneath each chart you'll see what percentage of time the CAGR was above and below 25%. Think of this as "pick any N-year period to HODL and the odds that your purchasing power compounded faster than 25% is this."
In case I messed up the calculations, I provided code to generate it yourself at the bottom of this post. I used weekly aggregates to make things simple but you could use arbitrary aggregates.

Bitcoin CAGR

You can see that for any 4 year period the CAGR was greater than 25%.

Comparison to S&P 500

Below is the same 4 year chart for SPY. You can see that at no point did the S&P 500 compound faster than 25%.

Code

Python isn't my native language so please forgive me.
import numpy as np import matplotlib.pyplot as plt def calculate_cagr(start_value, end_value, periods_in_years): return (end_value / start_value) ** (1 / periods_in_years) - 1 def plot_rolling_cagr(num_years, df): """ Plots the rolling CAGR of an asset over a specified number of years. The dataframe should contain a 'vwap' column with the asset's value. This assumes that the data is weekly. Adjust the 'points_per_period' variable if the data is not weekly. """ points_per_period = num_years * 52 # Adjust based on your data's frequency rolling_cagr = [] for i in range(len(df) - points_per_period): start_value = df['vwap'].iloc[i] end_value = df['vwap'].iloc[i + points_per_period] cagr = calculate_cagr(start_value, end_value, num_years) # num years rolling_cagr.append(cagr * 100) # convert to percentage df['rolling_cagr'] = np.nan start_index = df.index[points_per_period] end_index = df.index[len(rolling_cagr) + points_per_period - 1] df.loc[start_index:end_index, 'rolling_cagr'] = rolling_cagr plt.figure(figsize=(14, 7)) plt.plot(df.index, df['rolling_cagr'], label=f"{num_years}-Year Rolling CAGR", color='red', linewidth=1, alpha=0.7) plt.axhline(y=0, color='black', linestyle='-', linewidth=0.8) plt.axhline(y=25, color='blue', linestyle='--', linewidth=0.8) plt.title(f"{num_years}-Year Rolling CAGR") plt.xlabel('Date') plt.ylabel('CAGR (%)') plt.legend() plt.show()
Very cool
At some point that will have to mellow out, but probably not until after Bitcoin has eaten everything else's lunch.
reply
reply
Thanks for sharing those!
reply
It just so happens I was calculating CAGR of real estate in my area today.
To put these numbers into perspective my house has an 8% CAGR over the last 15 years.
reply
Nice post.
reply
Amazing! Thanks for sharing.
reply