FRED API#

The Federal Reserve Economic Data (FRED) is maintained by the Federal Reserve Bank of St. Louis. It contains over 800,000 economic time series from dozens of sources, including:

  • GDP and economic growth indicators

  • Inflation measures (CPI, PCE)

  • Interest rates and yield curves

  • Employment and labor market data

  • International economic data

  • Cryptocurrency prices

Getting Started with FRED#

To use the FRED API, you need to:

  1. Create a FRED account

  2. Request an API key

  3. Store your key securely (never hardcode it!)

Install the package:

pip install fredapi

Set-up#

# Core packages
from fredapi import Fred
import os
from dotenv import load_dotenv

import numpy as np
import pandas as pd
import datetime as dt

import matplotlib as mpl 
import matplotlib.pyplot as plt

# Include this to have plots show up in your Jupyter notebook.
%matplotlib inline 
# Load API keys from .env file
load_dotenv()

# Retrieve API keys
FRED_API_KEY = os.getenv('FRED_API_KEY')

# Initialize FRED API
fred = Fred(api_key=FRED_API_KEY)

Secure API Key Storage#

Here’s the wrong way to use your API key:

# DON'T DO THIS!
fred = Fred(api_key='your_api_key_here')

If you push this code to GitHub, your key is exposed to the world!

Troubleshooting GitHub Secrets#

If your code can’t find your API key (e.g., you get an error like ValueError or None when you try to use it), work through this checklist:

1. Check your secret name for typos. The name you use in os.getenv('FRED_API_KEY') must exactly match the name you entered on GitHub. Secret names are not case-sensitive on GitHub’s side, but it’s good practice to keep them consistent. Only letters, numbers, and underscores are allowed – no spaces or special characters.

2. Check the API key value itself. When you paste your key into the Value field on GitHub, make sure you haven’t accidentally copied extra spaces or characters before or after the key. A trailing space or newline character will silently break things.

3. Make sure you granted repository access. When you created the secret, you needed to select which repositories can use it. If you forgot to add your lab repo, the secret won’t be available in that Codespace. Go back to Settings > Codespaces > Codespaces secrets, click on the secret name, and make sure your repo is listed under repository access.

4. Stop and restart your Codespace. Secrets are loaded when a Codespace starts up. If you added or changed a secret while your Codespace was already running, it won’t pick up the change automatically. You need to stop the Codespace and then reopen it. Here’s how:

  1. Save your work first. Do a git add, git commit, and git push (or use the Source Control panel in VS Code to sync) so your files are backed up to GitHub.

  2. Close the Codespace browser tab (or VS Code window). Note that closing the tab alone does not stop the Codespace – it keeps running in the background.

  3. Go to github.com/codespaces. This page lists all of your Codespaces.

  4. Find the Codespace you want to restart. Click the three dots (...) to the right of it.

  5. Click Stop codespace.

  6. Once it has stopped, go back to your lab repository on GitHub and click the green Code button, then Codespaces, and open your existing Codespace. It will restart with the updated secrets.

5. Verify the secret is loaded. You can add a quick check in your notebook to make sure the key was found (without printing the actual key!):

FRED_API_KEY = os.getenv('FRED_API_KEY')
print(type(FRED_API_KEY))  # Should print <class 'str'>, not <class 'NoneType'>

If it prints <class 'NoneType'>, the secret isn’t being found. Go back through the steps above.

FRED Example: Bitcoin Data#

Let’s pull Bitcoin price data from FRED. You can find the series here.

When you find data on FRED, note the series code - that’s how you’ll request it via the API.

btc = fred.get_series('CBBTCUSD')
btc.tail()
2026-03-06    68350.00
2026-03-07    67193.87
2026-03-08    66055.14
2026-03-09    68480.54
2026-03-10    69847.95
dtype: float64

That’s just a plain Series, not a DataFrame. Let’s convert it and clean it up.

btc = btc.to_frame(name='btc')
btc = btc.rename_axis('date')
btc
btc
date
2014-12-01 370.00
2014-12-02 378.00
2014-12-03 378.00
2014-12-04 377.10
2014-12-05 NaN
... ...
2026-03-06 68350.00
2026-03-07 67193.87
2026-03-08 66055.14
2026-03-09 68480.54
2026-03-10 69847.95

4118 rows × 1 columns

# Drop missing values
btc = btc.dropna()
# Calculate returns
btc['ret'] = btc['btc'].pct_change()
/var/folders/kx/y8vj3n6n5kq_d74vj24jsnh40000gn/T/ipykernel_31063/1503532820.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  btc['ret'] = btc['btc'].pct_change()
btc = btc.loc['2015-01-01':, ['btc', 'ret']]
btc.plot()
<Axes: xlabel='date'>
../_images/3249351b93f9bb09190caac7be0b0dbca0984aa6c314f03f2864808365132cec.png

That’s not a very good graph - the returns and price levels are in different units. Let’s format the average return nicely:

print(f'Average return: {100 * btc.ret.mean():.2f}%')
Average return: 0.20%

Visualizing Cumulative Returns#

Let’s create a cumulative return chart and daily return chart, stacked on top of each other.

btc['ret_g'] = btc.ret.add(1)  # gross return
btc['ret_c'] = btc.ret_g.cumprod().sub(1)  # cumulative return
btc
btc ret ret_g ret_c
date
2015-01-08 288.99 -0.150029 0.849971 -0.150029
2015-01-13 260.00 -0.100315 0.899685 -0.235294
2015-01-14 120.00 -0.538462 0.461538 -0.647059
2015-01-15 204.22 0.701833 1.701833 -0.399353
2015-01-16 199.46 -0.023308 0.976692 -0.413353
... ... ... ... ...
2026-03-06 68350.00 -0.036720 0.963280 200.029412
2026-03-07 67193.87 -0.016915 0.983085 196.629029
2026-03-08 66055.14 -0.016947 0.983053 193.279824
2026-03-09 68480.54 0.036718 1.036718 200.413353
2026-03-10 69847.95 0.019968 1.019968 204.435147

4074 rows × 4 columns

fig, axs = plt.subplots(2, 1, sharex=True, sharey=False, figsize=(10, 6))

axs[0].plot(btc.ret_c, 'g', label='BTC Cumulative Return')
axs[1].plot(btc.ret, 'b', label='BTC Daily Return')
            
axs[0].set_title('BTC Cumulative Returns')
axs[1].set_title('BTC Daily Returns')

axs[0].legend()
axs[1].legend();
../_images/8c4fa194effb29ce2a5ce20bd92630c26fdffc02c26a2e9d525d658796556307.png

A Bitcoin Simulation #

Let’s put together some ideas, write a function, and run a simulation using Geometric Brownian Motion (GBM).

What is GBM?#

GBM is a stochastic differential equation commonly used to model asset prices:

\[dS = \mu S dt + \sigma S dW_t\]

This says the change in stock price has two components:

  • A drift (\(\mu\)): the average increase over time

  • A shock (\(\sigma dW_t\)): random noise scaled by volatility

The solution gives us the price at any time \(t\):

\[S(t) = S(0) \exp \left(\left(\mu - \frac{1}{2}\sigma^2\right)t + \sigma W(t)\right)\]

Note

We’re not predicting here. We’re capturing basic dynamics of how an asset moves and seeing what’s possible in the future.

# Simulation parameters
T = 30        # Time horizon (days)
N = 30        # Number of time steps
S_0 = btc.btc.iloc[-1]  # Initial BTC price
N_SIM = 100   # Number of simulations
mu = btc.ret.mean()
sigma = btc.ret.std()
def simulate_gbm(s_0, mu, sigma, n_sims, T, N):
    """Simulate asset prices using Geometric Brownian Motion."""
    dt = T / N  # One day
    dW = np.random.normal(scale=np.sqrt(dt), size=(n_sims, N))  # Random shocks
    W = np.cumsum(dW, axis=1)  # Cumulative sum of shocks
    time_step = np.linspace(dt, T, N)
    time_steps = np.broadcast_to(time_step, (n_sims, N))
    S_t = s_0 * np.exp((mu - 0.5 * sigma ** 2) * time_steps + sigma * np.sqrt(time_steps) * W)
    S_t = np.insert(S_t, 0, s_0, axis=1)
    return S_t
# Run the simulation
gbm_simulations = simulate_gbm(S_0, mu, sigma, N_SIM, T, N)
# Plot all simulations
gbm_simulations_df = pd.DataFrame(np.transpose(gbm_simulations))

ax = gbm_simulations_df.plot(alpha=0.2, legend=False)
ax.set_title('BTC Price Simulations (30 days)', fontsize=16)
ax.set_xlabel('Days')
ax.set_ylabel('Price ($)');
../_images/72ad673fb2b85191d665fe640c60907c2308877a9d0931767f79254e65beca1e.png

The y-axis has a wide range because some extreme values are possible given Bitcoin’s high volatility.

FRED Example: GDP Data#

Let’s pull another common series - U.S. GDP. The series code is GDP.

gdp = fred.get_series('GDP', observation_start='2010-01-01', observation_end='2023-01-27')
gdp = gdp.to_frame(name='GDP')
gdp.head()
GDP
2010-01-01 14764.610
2010-04-01 14980.193
2010-07-01 15141.607
2010-10-01 15309.474
2011-01-01 15351.448

You can browse available series on the FRED website and use their series codes with fred.get_series(). Check the fredapi documentation for more options, like searching for series by keyword.