Quant500 Academy

2. Price versus total return: two different series, two different conclusions

Level 3: Adjustments and corporate actions

The previous module treated the adjustment as a technical correction. This one deals with the decision behind it, because there is no single correct series: there are two, they measure different things and they answer different questions. Choosing badly does not produce a 0.1% error; it produces differences that over twenty years are counted in multiples.

Two questions, two series

A price series answers this: what is the security quoted at? It is the series you see on the screen, the one you use to know what price you would get in at today and the one you need in order to compute a commission, a slippage figure or the size of an order.

A total return series answers something else: by how much has the money of somebody who bought and reinvested everything they were paid multiplied? Its levels are not tradable prices. They are the value of an imaginary portfolio that started with one security and kept buying additional fractions every time a dividend came in.

The confusion arises because both arrive in a column called price and looking exactly alike. But only the second is a measure of performance.

import pandas as pd

px = pd.Series(
    [100.0, 101.0, 99.5, 100.5, 103.0],
    index=pd.date_range("2024-01-02", periods=5, freq="B"),
)
div = pd.Series(0.0, index=px.index)
div.iloc[2] = 1.20   # dividend with ex date on the third day

r_precio = px.pct_change()
r_total = (px + div) / px.shift(1) - 1

print((1 + r_precio).prod() - 1)   # 0.0300  -> +3.00%
print((1 + r_total).prod() - 1)    # 0.0424  -> +4.24%

Five sessions and there are already 124 basis points of difference. The price series is not miscalculated: it is measuring something else. What is wrong is calling what it returns a rate of return.

How far apart they drift over the long run

The arithmetic is merciless because dividend yield does not add, it compounds. Suppose a price index that advances 8.0% a year and its total return version that advances 10.0%: two points of difference, which sounds like very little.

anios = 30
print(1.08 ** anios)                   # 10.06 times
print(1.10 ** anios)                   # 17.45 times
print(1.10 ** anios / 1.08 ** anios)   # 1.734

After thirty years, the version with dividends is worth 73% more. Not 2% more nor 60% more: 73%, and it grows the longer the horizon. In markets with a high dividend yield (the UK market, much of the European one, utilities, oil majors, listed property funds) the gap is larger still, because a very big part of the shareholder's return never shows up in the price: it leaves the company as cash and the price, by construction, falls to reflect it.

That is the point to internalize. A dividend is not an extra added on top of the price rise: it is a part of the return that has been subtracted from the price. The price series of a company that pays out all its earnings and does not grow can spend decades flat while its shareholder doubles their money. The series is not lying. It simply is not measuring what you think.

Rigged comparisons

This is where it stops being a debate about definitions and becomes a backtest that passes a bad strategy or fails a good one.

The rule is easy to state and easy to skip: numerator and denominator have to be built on the same convention. If you do not know which one your benchmark index uses, find out before drawing conclusions, not afterwards.

def comparar(estrategia, referencia, tipo_estrategia, tipo_referencia):
    # Abort if conventions get mixed: that is a data fault, not a strategy fault.
    if tipo_estrategia != tipo_referencia:
        raise ValueError(
            "Convenciones distintas: " + tipo_estrategia + " vs " + tipo_referencia
        )
    return (1 + estrategia).prod() - (1 + referencia).prod()

A real case: the DAX and its invisible brother

Germany's DAX is the textbook example because it works the opposite way round from almost every other index. The one that gets published and appears in the headlines is a total return index: it incorporates reinvested dividends. There is also a less widely circulated price version, the DAX Kursindex, which does not. In everyday conversation both go by the same name, and they have been drifting apart for decades.

The practical consequence is direct. If you backtest a strategy on the closing prices of German stocks and compare it with the DAX you downloaded without looking, you are competing against a rival that collects dividends while you do not. With an average yield on the order of 3% a year, thirty years of that advantage is a factor of 1.03 to the power of 30, around two and a half times. No reasonable strategy overcomes that kind of handicap, and not because it is a bad one.

The reverse case exists too and is more common: the S&P 500 and the Nikkei 225 you see in the headlines are price indices. Comparing yourself against them using dividend-adjusted prices hands you an advantage you have not earned.

To go deeper into this lesson

The rest of Quant500

What you are learning, applied to the index every day:

The model portfolio, weekly in your inbox

Every Saturday, its week in one minute: how it went, the best and the worst, and which insiders bought with their own money. Plus, the day the model is rebuilt, which stocks come in and which go out. A simulation, not advice. At most one email a week, no ads, no account; one-click unsubscribe. Made by one person: Pol Gracia.

A model portfolio with simulated results, not a personalized recommendation. Your email is used for this only: privacy.

Behind the Academy and the rest of Quant500 there is one person: Pol Gracia Serrahima.