El cambio porcentual
Los precios son difíciles de comparar: un movimiento de 5 $ significa una cosa en una acción de 20 $ y otra en una de 500 $. Un rendimiento simple elimina la escala. Es el cambio de precio como fracción del precio anterior, de modo que +2 % significa lo mismo en cualquier activo.
En pandas, prices.shift(1) desplaza cada valor un paso hacia abajo, de modo que cada fila ve el precio anterior. Dividir por él calcula todos los rendimientos a la vez, y el primero es NaN porque no hay precio anterior. prices.pct_change() hace lo mismo en una sola llamada.
import pandas as pd
prices = pd.Series([100.0, 102.0, 100.98, 103.0], index=["Mon", "Tue", "Wed", "Thu"])
returns = prices / prices.shift(1) - 1
print(returns.round(4))
# Mon NaN
# Tue 0.02
# Wed -0.01
# Thu 0.02
# dtype: float64
print(prices.pct_change().round(4).tolist()) # [nan, 0.02, -0.01, 0.02]Los rendimientos se componen, no se suman
Un día de +2 % seguido de uno de -1 % no es un +1 % en dos días. Cada rendimiento se aplica a un precio inicial distinto, así que los factores de crecimiento 1 + r se multiplican: el rendimiento total es (1 + r1) * (1 + r2) * ... - 1. Omite el NaN de la primera fila (prod lo ignora) y el resultado coincide con el precio final dividido por el inicial, menos uno. Sumar los rendimientos se acerca pero es incorrecto, y la diferencia crece con la volatilidad.
# Compounding the returns gives the same total as the end price over the start price
returns = prices.pct_change().dropna()
print(round((1 + returns).prod() - 1, 6)) # 0.03
print(round(prices.iloc[-1] / prices.iloc[0] - 1, 6)) # 0.03
print(round(returns.sum(), 6)) # 0.030004 close, but not the same
print((1 + returns).cumprod().round(4).tolist()) # [1.02, 1.0098, 1.03] the growth of 1 dollar# Gains and losses are not symmetric: +10% then -10% does not get you back
up, down = 0.10, -0.10
print(round((1 + up) * (1 + down) - 1, 4)) # -0.01
# After a -50% loss you need +100% to recover
print(1 / (1 - 0.5) - 1) # 1.0Entre activos y entre años
Los rendimientos simples se suman entre activos: el rendimiento de una cartera en un periodo es la suma de peso * rendimiento de sus posiciones. Con Series, weights * returns empareja las entradas por etiqueta, así que el orden de los símbolos no importa (con .values pierdes esa protección). En el tiempo no se suman sino que se componen: para dar una tasa anual a partir de un periodo más corto se eleva el factor de crecimiento a la potencia días_bursátiles / días.
# Weights times returns, matched by label (the order of the symbols differs on purpose)
weights = pd.Series({"AAPL": 0.5, "MSFT": 0.3, "TSLA": 0.2})
day = pd.Series({"TSLA": -0.03, "AAPL": 0.012, "MSFT": 0.004})
print(round((weights * day).sum(), 4)) # 0.0012 correct: AAPL with AAPL, TSLA with TSLA
print(round((weights.values * day.values).sum(), 4)) # -0.0106 wrong: position by position, TSLA met AAPL's weight# Annualising: 5% in 60 trading days, if it kept compounding at that pace for a year of 252 days
total, days = 0.05, 60
print(round((1 + total) ** (252 / days) - 1, 4)) # 0.2274daily_returns.- Un rendimiento simple es
precio / precio anterior - 1:prices / prices.shift(1) - 1oprices.pct_change(); la primera entrada esNaN. - En el tiempo los rendimientos se componen:
(1 + returns).prod() - 1, nuncareturns.sum(). - Entre activos se suman con pesos:
(weights * returns).sum(), emparejados por etiqueta.
A continuación: los rendimientos logarítmicos, una alternativa que se suma en el tiempo y es la elección habitual en estadística.
Lab: qué se califica
- Convertir precios en rendimientos por periodo con
simple_returns25 pts - Componer los rendimientos en un total con
cumulative_return25 pts - Ponderar los rendimientos de los activos en un número con
portfolio_return25 pts - Expresar un rendimiento como tasa anual con
annualized_return25 pts