La variation en pourcentage
Les prix sont difficiles à comparer : un mouvement de 5 $ n'a pas le même sens sur une action à 20 $ que sur une à 500 $. Un rendement simple supprime l'échelle. C'est la variation de prix rapportée au prix précédent, si bien que +2 % signifie la même chose sur n'importe quel actif.
Avec pandas, prices.shift(1) décale chaque valeur d'un cran vers le bas, si bien que chaque ligne voit le prix précédent. Diviser par ce résultat calcule tous les rendements d'un coup, et le premier est NaN faute de prix précédent. prices.pct_change() fait la même chose en un appel.
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]Les rendements se composent, ils ne s'additionnent pas
Une journée à +2 % suivie d'une journée à -1 % ne donne pas +1 % sur deux jours. Chaque rendement s'applique à un prix de départ différent, donc les facteurs de croissance 1 + r se multiplient : le rendement total est (1 + r1) * (1 + r2) * ... - 1. Ignorez le NaN de la première ligne (prod l'ignore) et le résultat est égal au prix final divisé par le prix initial, moins un. Additionner les rendements s'en approche mais c'est faux, et l'écart grandit avec la volatilité.
# 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 actifs et entre années
Les rendements simples s'additionnent entre actifs : le rendement d'un portefeuille sur une période est la somme de poids * rendement de ses lignes. Avec des Series, weights * returns associe les entrées par étiquette, donc l'ordre des symboles importe peu (avec .values, vous perdez cette protection). Dans le temps, ils ne s'additionnent pas mais se composent : pour annoncer un taux annuel à partir d'une période plus courte, on élève le facteur de croissance à la puissance jours_de_bourse / jours.
# 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 rendement simple vaut
prix / prix précédent - 1:prices / prices.shift(1) - 1ouprices.pct_change(); la première entrée estNaN. - Dans le temps, les rendements se composent :
(1 + returns).prod() - 1, jamaisreturns.sum(). - Entre actifs, ils s'additionnent avec des poids :
(weights * returns).sum(), associés par étiquette.
À suivre : les rendements logarithmiques, une alternative qui s'additionne dans le temps et qui est le choix habituel en statistique.
Lab : ce qui est noté
- Transformer des prix en rendements par période avec
simple_returns25 pts - Composer les rendements en un total avec
cumulative_return25 pts - Pondérer les rendements des actifs en un seul nombre avec
portfolio_return25 pts - Exprimer un rendement en taux annuel avec
annualized_return25 pts