Reporting annual cumulative returns on multiple assets in Python

Learn how to process multiple financial assets and include them in a report highlighting each asset's annual performance.

When comparing the performance of financial assets like Bitcoin, ETFs, and stocks, which was the most profitable asset since 2010?

Annual performance comparison of various financial assets since 2010
F1. Annual performance of financial assets since 2010

In this tutorial, we explain how to download, process, and report on the performance of financial assets using Python.

The report is inspired by Charlie Bilello’s analyses.


We define the list of tickers for the assets to analyze.

tickers = ['BTC-USD', 'QQQ', 'IWF', 'SPY', ...]

We download daily historical price movements using the yfinance library, which downloads data from Yahoo Finance.

import yfinance as yf
df =
Daily historical price chart for Bitcoin, ETFs, and stocks
F2. Daily historical prices of financial assets


  1. How to download historical price data for multiple financial assets?
  2. How to calculate the annual cumulative return of each asset?
  3. Why is it necessary to group data for cumulative calculations?
  4. How to select the last day of cumulative return in each year?
  5. How to identify the maximum and minimum return values in each year?
  6. How to calculate the percentage of positive returns for each asset?


Annual cumulative return

We select the adjusted closing prices Adj Close from 2010 and calculate the annual cumulative return, choosing the last business day of each year.

 .loc['2010':, 'Adj Close']

BTC-USD (Bitcoin) shows missing values in the early years due to its regulation and adoption (see the full story on Wikipedia).

Annual cumulative returns for ETFs, showing missing early years for Bitcoin
F3. Annual cumulative returns of ETFs

Column summary

How did the assets perform over the years?

We calculate the average annual return and the annual cumulative return for each asset.

For more details on the calculation of returns, visit this tutorial.

t_avg = df.mean(axis=1).mul(100)
t_cum = df.add(1).cumprod(axis=1).sub(1).mul(100).iloc[:,[-1]]

pd.DataFrame({'AVG': t_avg, 'CAGR': t_cum})
Summary of average annual return and cumulative return for various financial assets
F4. Return summary of financial assets

Row summary

What were the maximum and minimum values each year? What was the percentage of positive returns?

positive_pct = lambda x: (x > 0).mean() * 100
dfr.agg(['max', 'min', positive_pct])

All assets showed positive cumulative returns at the end of the period, with BTC-USD being the most profitable asset.

Annual performance summary showing maximum, minimum values, and percentage of positive returns for financial assets
F5. Annual performance summary of financial assets

Combining and styling

Finally, we combine all tables and apply styling to highlight maximum and minimum values.

This tutorial explains how to apply styles to pandas tables to create a heat matrix this tutorial.

Annual performance comparison of various financial assets since 2010
F1. Annual performance of financial assets since 2010

Why did almost all assets show negative returns in 2022?

Looking forward to your comments.


  1. Downloading historical data: to get data for multiple financial assets from Yahoo Finance.
  2. Calculating annual cumulative return: .pct_change().add(1).cumprod().sub(1) to determine an asset’s performance over time.
  3. Grouping data for cumulative calculations: groupby.cumprod to reset cumulative calculations at the start of each year.
  4. Selecting the last day of cumulative return: .resample('Y').last() to find the final value of each year, useful for annual analysis.
  5. Identifying maximum and minimum values: .agg(['max', 'min']) to discover the extremes in annual asset performance.
  6. Calculating the percentage of positive returns: lambda x: (x > 0).mean() * 100 to evaluate the frequency of an asset’s gains.

In addition to this tutorial, you can learn more with our live courses.

I’ll teach you the best practices for writing more efficient code in fewer lines and address your questions, all while working on practical cases with data that interests you.

For more info, visit datons

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to datons.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.