@run-out Cool. Took your advice! Here's what I ended up with.

First, I wrote a simple CAGR analyzer that anyone can use:

class CAGRAnalyzer(bt.analyzers.Analyzer):
"""
Analyzer returning CAGR of the portfolio
"""
def nextstart(self):
self.rets = AutoOrderedDict()
self.rets.start_value = self.strategy.broker.getvalue()
self.rets.start_date = self.strategy.datetime.datetime().date()
def stop(self):
self.rets.end_value = self.strategy.broker.getvalue()
self.rets.end_date = self.strategy.datetime.datetime().date()
self.rets.num_years = (self.rets.end_date - self.rets.start_date).days / 365.25
self.rets.cagr = self.calculate_cagr(self.rets.start_value, self.rets.end_value, self.rets.num_years)
def calculate_cagr(self, start_value, end_value, num_years):
"""
The CAGR formula is:
EV / BV ^ (1/n) – 1.
EV and BV are the ending and beginning values, while n is the number of time periods (usually months or years)
for which you are calculating an average. The ^ character means “to the power of”; we take the ratio of EV / BV
and raise it to the power of 1/n. Subtracting one (one = 100%)
"""
return ((end_value / start_value) ** (1 / num_years)) - 1

Next, I derived a real CAGR analyzer from it which pulls in a csv file of CPI data and calculates the annualized inflation rate and finally the real CAGR:

class RealCAGRAnalyzer(CAGRAnalyzer):
params = (('cpi_data_file_path', None),)
"""
Analyzer returning the real CAGR (adjusted for inflation using values from a csv passed in with
a "date" column and a "us_cpi" column
"""
def __init__(self):
self.cpi_data_file_path = self.params.cpi_data_file_path
self.cpi_data = pd.read_csv(self.cpi_data_file_path, parse_dates=['date'], index_col=0)
def nextstart(self):
super(RealCAGRAnalyzer, self).nextstart()
self.rets.start_cpi = self.cpi_data.loc[pd.to_datetime(self.rets.start_date)]['us_cpi']
def stop(self):
super(RealCAGRAnalyzer, self).stop()
self.rets.end_cpi = self.cpi_data.loc[pd.to_datetime(self.rets.end_date)]['us_cpi']
self.rets.cagr = self.calculate_cagr(self.rets.start_value, self.rets.end_value, self.rets.num_years)
self.rets.annualized_inflation_rate = self.calculate_cagr(self.rets.start_cpi, self.rets.end_cpi, self.rets.num_years)
self.rets.real_cagr = self.annualized_real_return(self.rets.cagr, self.rets.annualized_inflation_rate)
def annualized_real_return(self, nominal_cagr, annualized_inflation_rate):
"""
Annualized Real Return = ((1 + CAGR / 1 + annualized inflation rate) – 1) X 100
"""
return (1 + nominal_cagr) / (1 + annualized_inflation_rate) - 1

Though im not an expert in finance, everything looks reasonable:

start_date: 1913-01-31
end_date: 2020-11-30
start_value: $100,000.00
end_value: $38,158,746.88
num_years: 107.83
start_cpi: $9.8
end_cpi: $260.23
cagr: 5.67%
annualized_inflation_rate: 3.09%
real_cagr: 2.50%

Thanks for the help!