top of page

CPI, Gold, and SP500: Building an Inflation Hedge Strategy in Python

  • Writer: Nikhil Adithyan
    Nikhil Adithyan
  • 4 days ago
  • 7 min read

A step-by-step guide


ree

The Consumer Price Index (CPI) is an indicator used to measure the increase in the average price of goods and services, which affects our daily expenses. Yes! Even traders should eat and sleep (well, not so sure about the second one…). An increase in our cost of leaving usually pushes down equities, such as shares. So, we are doomed from both sides? No.


Gold is the counterweight to this challenge, and historically, when inflation increases, investors find their piece, investing in Gold, which moves opposite to equities. The exact “why” is entirely theoretical and a bit controversial since gold is nothing more than a metal whose price is set based on what people are willing to pay for it and has no other use. But this is not in the scope of our article to examine…


The scope of this article is to actually examine this correlation, and find some investing strategy that can transform this knowledge into an edge that will give us what else? Profits.


With the use of Python and the API suite of FinancialModelingPrep, we will:


  • Get the prices of CPI and Gold

  • Compute and discuss the correlation between those two

  • Design a strategy that will invest in Gold during high inflation periods and in the SP500 in all other cases, and

  • Discuss the results of the backtesting


Let’s Code

First, we will define the packages that we will use in this article. We will set a period of 50 years from 1975.



import requests
import matplotlib.pyplot as plt
import pandas as pd

token = 'YOUR FMP API KEY'
fromdate = '1975-01-01'
todate = '2025-10-02'

The first thing will be to get started with FMP’s Economics Indicators API API for economic indicators, specifically the CPI.



url = f'https://financialmodelingprep.com/stable/economic-indicators'
name = 'CPI'
querystring = {"apikey":token, "name":name, "from":fromdate, "to":todate}

resp = requests.get(url, querystring).json()

df_cpi = pd.DataFrame(resp)
df_cpi.drop(columns=["name"], inplace=True)
df_cpi.tail(5)

ree

As you will see, the data we receive is monthly. CPI is not an index calculated daily; it is published monthly by the Bureau of Labor Statistics (BLS) for the US, which is within our scope for this article.


Now, we are going to download using FMP’s Light Chart API, the one with historical EOD prices for assets and other assets, such as gold.



url = f'https://financialmodelingprep.com/stable/historical-price-eod/light'
symbol = 'GCUSD'

querystring = {"apikey":token, "symbol":symbol, "from":fromdate, "to":todate}
resp = requests.get(url, querystring).json()

df_gold = pd.DataFrame(resp)
df_gold.drop(columns=["symbol","volume"], inplace=True)
df_gold.tail(5)

ree

As you can see, for gold, we have a daily price, so the first thing we need to do is to merge those two dataframes, but keep the gold price only for the beginning of the month. This will be a bit tricky, since when the first of the month (when we have CPI prices) is on a weekend, we will not have the gold price. We will fix that with the use of merge_asof and direction='backward'



df_cpi['date'] = pd.to_datetime(df_cpi['date']).dt.normalize()
df_gold['date'] = pd.to_datetime(df_gold['date']).dt.normalize()

df_cpi_sorted = df_cpi.sort_values('date')
df_gold_sorted = df_gold.sort_values('date')

df_cpi_gold = pd.merge_asof(
    df_cpi_sorted,
    df_gold_sorted,
    on='date',
    direction='backward'
).reset_index(drop=True)

df_cpi_gold = df_cpi_gold.rename(columns={'value': 'cpi', 'price': 'gold'})
df_cpi_gold.tail(5)

ree

As you can see, we now have a perfect monthly dataframe with the prices of CPI and Gold aligned.


Now, let’s calculate the correlation during those 50 years.



subset = df_cpi_gold[['cpi', 'gold']].dropna()
value_price_corr = subset['cpi'].corr(subset['gold'], method='pearson')
pd.DataFrame({'cpi_gold_pearson_correlation': [value_price_corr]})

ree

The correlation is 0.85. This is a very high number that shows that historically, the CPI and gold tend to move in the same direction. When CPI rises, gold rises, and the opposite occurs when it falls.


But we don’t have to wait 50 years to prove that. In fact, this is incorrect because things might have changed, or there could be periods when the direction is opposite. We will investigate this using rolling correlation. We will reduce the period to 10 years, and let’s see the results.



years = 10
window = years*12
df_cpi_gold['rolling_corr'] = df_cpi_gold['cpi'].rolling(window=window, min_periods=window).corr(df_cpi_gold['gold'])

plt.figure(figsize=(12, 4))
plot_data = df_cpi_gold[['date', 'rolling_corr']].dropna()
plt.plot(plot_data['date'], plot_data['rolling_corr'])
plt.title(f'Rolling {years}-Years Correlation: CPI Value vs Gold Price')
plt.xlabel('Date')
plt.ylabel('Correlation')
plt.ylim(-1, 1)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Output:


ree

That is a very interesting graph. It looks like during the 90s and beginning of the 2000s, as well as in 2020, the correlation was non-existent, and at times even turned negative, meaning CPI and Gold moved in opposite directions. The possible reasoning is the following:


  • During the 90s and the beginning of the 2000s, we had various events that can explain that. We had a disciplined monetary policy that resulted in low CPI, and the introduction of inflation-linked bonds provided institutional investors with an alternative to Gold.

  • And then the COVID-19 period arrived… Apparently, during a black swan event, not much can be done. CPI could not be forecasted back then, and gold was mostly driven by global fear and liquidity injections.


The exceptions observed during the 1990s and 2020, rather than challenging the premise of correlation, actually tested it and thus proved the overall relationship between CPI and gold, showing that correlation exists under typical economic conditions.


Let’s Backtest

But is it possible to gain an edge with this information? Is it possible to determine when to invest in equities and when in gold?


Therefore, we will backtest a simple theory. When the CPI is in the top 20% of the previous monthly changes, we will invest in gold. In all other cases, we will invest in the S&P 500.


First, we need to get into our dataframe the prices of SP500. This will be done with the following Python code and merge it into the initial dataframe as we did previously, and copy them to a new dataframe called df_backtest.



ticker = '^GSPC'
url = f'https://financialmodelingprep.com/api/v3/historical-price-full/{ticker}'
querystring = {"apikey":token, "from":fromdate, "to":todate}
data = requests.get(url, querystring)

if data.status_code != 200:
    print(f"Error: {data.status_code}")
    print(data.text)
data = data.json()

df_sp500 = pd.DataFrame(data['historical'])

df_cpi_gold['date'] = pd.to_datetime(df_cpi_gold['date']).dt.normalize()

sp = df_sp500[['date', 'adjClose']].copy()
sp['date'] = pd.to_datetime(sp['date']).dt.normalize()
sp = sp.sort_values('date').rename(columns={'adjClose': 'sp500'})

df_backtest = pd.merge_asof(
    df_cpi_gold.sort_values('date'),
    sp,
    on='date',
    direction='backward'
).reset_index(drop=True)

df_backtest = df_backtest[['date', 'cpi', 'gold', 'sp500']]
df_backtest.tail(5)

ree

To test our strategy, we will also need to calculate the percentage changes for CPI, gold, and SP500.



df_backtest['cpi_pct_change'] = df_cpi_gold['cpi'].pct_change()
df_backtest['gold_pct_change'] = df_cpi_gold['gold'].pct_change()
df_backtest['sp500_pct_change'] = df_backtest['sp500'].pct_change()
df_backtest

ree

Also, we will need to do the following:


  • Calculate the threshold of 80% of the CPI, to understand if the inflation is above it or not.

  • We will calculate a column signal, which, if true, means that we are above 80%, so we are in an inflation period.

  • Finally, based on the signal we will use for this month, the return of gold or the S&P 500, respectively


Note: We will shift the signal by 3 periods. The reason is that even if we have the information that the inflation of the first of the month is a specific value, this is not published until after mid-month. Therefore, we are going to penalise our strategy slightly using the information after 1 or 2 weeks, depending on when the CPI was published.



df_backtest.dropna(inplace=True)
df_backtest['quantile_threshold'] = df_backtest['cpi_pct_change'].rolling(window=1000, min_periods=1).quantile(0.8)
df_backtest['signal'] = (df_backtest['cpi_pct_change'] > df_backtest['quantile_threshold']).shift(3)

df_backtest['strategy_return'] = df_backtest['gold_pct_change'].where(df_backtest['signal'],
                                                                      df_backtest['sp500_pct_change'])
df_backtest

ree

Finally, based on the percentage change each month, we will calculate the equity as if we had invested 1 dollar in 1975 and determine what our returns would be today.



df_backtest['gold_equity'] = (1 + df_backtest['gold_pct_change']).cumprod()
df_backtest['sp500_equity'] = (1 + df_backtest['sp500_pct_change']).cumprod()
df_backtest['strategy_equity'] = (1 + df_backtest['strategy_return']).cumprod()

df_backtest[["gold_equity", "sp500_equity", "strategy_equity"]].tail(1)

ree

It is very promising. In our scenario, if you invested 1 dollar in gold for 50 years, you would have 19.1 dollars today, and if you invested in the S&P 500, you would have 81 dollars. With the strategy we are backtesting, you would have 141.1 dollars! This is an extraordinary result.


Let’s plot it!



required_cols = {'date', 'gold_equity', 'sp500_equity', 'strategy_equity'}
missing = required_cols - set(df_backtest.columns)
if missing:
    raise ValueError(f"Missing required columns in df_backtest: {missing}")

df_plot_eq = df_backtest.copy()
df_plot_eq['date'] = pd.to_datetime(df_plot_eq['date'])
df_plot_eq = df_plot_eq.sort_values('date')

plt.figure(figsize=(11, 6))
plt.plot(df_plot_eq['date'], df_plot_eq['gold_equity'], label='Gold Equity', linewidth=1.5)
plt.plot(df_plot_eq['date'], df_plot_eq['sp500_equity'], label='S&P 500 Equity', linewidth=1.5)
plt.plot(df_plot_eq['date'], df_plot_eq['strategy_equity'], label='Strategy Equity', linewidth=2.0)

plt.title('Equity Curves: Gold vs S&P 500 vs Strategy')
plt.xlabel('Date')
plt.ylabel('Equity (Cumulative, base=1.0)')
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()

Output:


ree

The plot is very convincing. The graph is mainly moving like the SP500, giving the push to go higher when we are invested in gold.


Let’s see some risk metrics on the strategies, and first, we will start with the maximum drawdown.



def calculate_max_drawdown(returns):
    wealth_index = (1 + returns).cumprod()
    running_max = wealth_index.cummax()
    drawdown = (wealth_index - running_max) / running_max
    max_drawdown = drawdown.min()
    return max_drawdown

print(f'GOLD has maximum drawdown of {calculate_max_drawdown(df_backtest["gold_pct_change"])}')
print(f'SP500 has maximum drawdown of {calculate_max_drawdown(df_backtest["sp500_pct_change"])}')
print(f'Strategy has maximum drawdown of {calculate_max_drawdown(df_backtest["strategy_return"])}')

ree

Apparently, in a time span of 50 years, the maximum drawdown is not the “dream” of any investor. However, what we should keep from the results is that the drawdown of our strategy is less than what gold or the SP500 experienced during this period.


Let’s now try the Sharpe Ratio.



def calculate_sharpe_ratio(returns, risk_free_rate, periods_per_year):
    excess_return = returns - risk_free_rate / periods_per_year
    mean_excess_return = excess_return.mean() * periods_per_year
    vol = excess_return.std() * (periods_per_year ** 0.5)
    sharpe_ratio = mean_excess_return / vol
    return sharpe_ratio

print(f'GOLD has Sharpe Ratio of {calculate_sharpe_ratio(df_backtest["gold_pct_change"], risk_free_rate=0.02, periods_per_year=12)}')
print(f'SP500 has Sharpe Ratio of {calculate_sharpe_ratio(df_backtest["sp500_pct_change"], risk_free_rate=0.02, periods_per_year=12)}')
print(f'Strategy has Sharpe Ratio of {calculate_sharpe_ratio(df_backtest["strategy_return"], risk_free_rate=0.02, periods_per_year=12)}')

ree

Even with the Sharpe Ratio, the comments can be the same. The strategy practically has the same (slightly better) Sharpe Ratio as the SP500, proving that with this strategy, we don’t increase our risk in favour of our returns.


That is not correct

The results are very promising, so it makes sense to discuss potential flaws in this method. However, we should first acknowledge that although a backtesting method warrants scepticism, we should not dismiss the results entirely and should keep our investigation ongoing. Clearly, in the context of a simple blog post, this isn’t always practical, and you will need to carefully consider your next steps on your journey to profit. That said, potential drawbacks of this method include the following:


  • Having 50 years of data doesn’t necessarily mean it’s better than 10 or 20 years. Investment environments change over time. For example, investing in gold or the S&P 500 was different in the 70s (maybe even with those old phones?), whereas today you can do this from your mobile.

  • The signal of 20% is quite simplistic to use for the entire period and can be considered an oversimplification or even overfitting.

  • We ignore other asset classes like bonds which are another safe haven against inflation spikes and recessions.

  • Even though the strategy’s drawdown is better than the others, a 47% drawdown is still not something you should be aiming for in your investment strategy.

  • No fees, slippage, or any other factors are included in the backtesting that might significantly alter the results.


Conclusions

Finally, let’s put in bullets our findings:


  • CPI and gold prices demonstrate a significant long-term positive correlation, indicating that gold serves as an inflation hedge during normal economic conditions.

  • The correlation varies over time, with notable exceptions during the 1990s and the COVID-19 pandemic, highlighting macroeconomic influences.

  • A backtested strategy switching between gold and the S&P 500 based on high inflation signals showed higher returns and slightly better risk-adjusted performance than buy-and-hold.

  • Despite promising results, caution is needed as the strategy oversimplifies market dynamics, ignores transaction costs, other asset classes, and potential data biases.


Remember, not everything that shines is gold. Sometimes it’s just the market laughing at us. So invest smart, but keep your sense of humour polished and your expectations grounded!


Thank you for reading, and I hope you enjoyed it!


Bring information-rich articles and research works straight to your inbox (it's not that hard). 

Thanks for subscribing!

© 2023 by InsightBig. Powered and secured by Wix

bottom of page