A return is not a price
Worked examples
Read the claim, then run it and check the machine agrees. One at a time — nothing here is taken on trust.
import numpy as np
prices = [100.0, 110.0, 99.0]
simple = [prices[i] / prices[i - 1] - 1 for i in range(1, 3)]
logs = [np.log(prices[i] / prices[i - 1]) for i in range(1, 3)]
print("simple:", [f"{r:+.4%}" for r in simple], f"sum {sum(simple):+.4%}")
print("logs :", [f"{l:+.4%}" for l in logs], f"sum {sum(logs):+.4%}")
print(f"truth : {prices[-1] / prices[0] - 1:+.4%}")
print(f"compound simples: {(1 + simple[0]) * (1 + simple[1]) - 1:+.4%}")
print(f"exp(sum of logs): {np.exp(sum(logs)) - 1:+.4%}")
The slide's board work, executed: the simple returns sum to +0.0000% while the account is down −1.0000%; the log returns sum to −1.0050% and exponentiate back to exactly −1.0000%. Two correct routes to the truth (compound the simples, or add the logs and exp), one seductive wrong one (add the simples). Every returns bug you will ever write is one of these three lines wearing a disguise.
import numpy as np
import qadata
prices = qadata.trending_prices() # 756 seeded daily closes
simple = prices.pct_change().dropna()
logs = np.log(prices).diff().dropna()
true_growth = prices.iloc[-1] / prices.iloc[0]
print(f"true growth : {true_growth:.12f} ({true_growth - 1:+.4%})")
print(f"prod(1 + simple) : {(1 + simple).prod():.12f} err {abs((1 + simple).prod() - true_growth):.1e}")
print(f"exp(sum of logs) : {np.exp(logs.sum()):.12f} err {abs(np.exp(logs.sum()) - true_growth):.1e}")
print(f"1 + sum of simple: {1 + simple.sum():.12f} <- the adding error, {simple.sum() - (true_growth - 1):+.4%} too rich")
Both correct routes hit 1.303606995782 — the compounded product is off by 7×10⁻¹⁵ and the exp-of-summed-logs by 2×10⁻¹⁶, pure floating point residue. Adding the simples instead reports +30.4137% against a true +30.3607% — half a percent conjured out of arithmetic on a calm 1%-vol market. Reconciling to ~1e-15 like this is the cheapest pipeline test in quant finance: it catches wrong-verb bugs, dropped days, and silent NaNs all at once.
import numpy as np
import qadata
prices = qadata.trending_prices()
simple = prices.pct_change().dropna()
logs = np.log(prices).diff().dropna()
drag = simple.mean() - logs.mean()
taylor = 0.5 * (simple ** 2).mean()
print(f"mean simple : {simple.mean() * 1e4:.4f} bp/day")
print(f"mean log : {logs.mean() * 1e4:.4f} bp/day")
print(f"drag : {drag * 1e4:.4f} bp/day")
print(f"half mean r²: {taylor * 1e4:.4f} bp/day")
print(f"daily vol : {simple.std():.4%}")
The measured drag is 0.5166bp/day and the Taylor prediction ½·mean(r²) says 0.5173bp/day — the approximation earns its keep to a hundredth of a basis point. Read the two means as different questions: 4.0283bp is the arithmetic average day (what a marketer quotes), 3.5117bp is the compound rate your wealth actually grows at. On a 1%-vol asset they differ by 13%; on a volatile one the gap can eat the whole edge — that reckoning is m07-3.
Check the concept
One question at a time. Unsure? Revisit the lecture, then answer.
The challenge
Pass the quiz to unlock the challenge — your code will still be waiting here.