σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademySkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
Skills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python skill›Module 2 · NumPy & pandas for market data›Topic 2.10·~35 minIntermediate

Simple returns

Skill positionModule 2 of 11 · Topic 10 of 20
2.10 Simple returns
—Lab score— / 100Total XP—Streak—

The percentage change

Prices are hard to compare: a $5 move means one thing on a $20 stock and another on a $500 one. A simple return removes the scale. It is the change in price as a fraction of the previous price, so +2% means the same on any asset.

r_t = (P_t - P_{t-1}) / P_{t-1} = P_t / P_{t-1} - 1

In pandas, prices.shift(1) moves every value one step down, so each row sees the previous price. Dividing by it computes all the returns at once, and the first one is NaN because there is no previous price. prices.pct_change() does the same in one call.

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]

Returns compound, they do not add

A +2% day followed by a -1% day is not a +1% two-day return. Each return applies to a different starting price, so the growth factors 1 + r multiply: the total return is (1 + r1) * (1 + r2) * ... - 1. Skip the NaN of the first row (prod ignores it) and the result matches the end price divided by the start price, minus one. Summing the returns gets close but is wrong, and the gap grows with volatility.

# 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.0

Across assets and across years

Simple returns add up across assets: the return of a portfolio over one period is the sum of weight * return of its holdings. With Series, weights * returns matches the entries by label, so the order of the symbols does not matter (use .values and you lose that protection). Over time they do not add but compound, so to quote a yearly rate from a shorter period you raise the growth factor to the power trading_days / days.

# 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.2274
A return is relative to the starting priceAlways say the period (daily, monthly, yearly) next to a return, and keep returns of different periods apart: a daily 1% and a yearly 1% are very different numbers. Put the period in the variable name, for example daily_returns.
  • A simple return is price / previous price - 1: prices / prices.shift(1) - 1 or prices.pct_change(); the first entry is NaN.
  • Over time returns compound: (1 + returns).prod() - 1, never returns.sum().
  • Across assets they add with weights: (weights * returns).sum(), matched by label.

Next up: log returns, an alternative that adds up over time and is the usual choice for statistics.

Lab · what's graded

  • Turn prices into period returns with simple_returns25 pts
  • Compound the returns into a total with cumulative_return25 pts
  • Weight the asset returns into one number with portfolio_return25 pts
  • Express a return as a yearly rate with annualized_return25 pts
Explorer · lab-43-1
▾ lab-43-1
…
bash · in-browser
learner@quantcraft:~/lab-43-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-43-1 goes back to the starter files and the terminal is cleared. Your current solution will be lost — this can't be undone.

Will be reset
  • main.py → starter code
  • Terminal history and test output
  • Unsubmitted changes
Will be kept
  • Best lab score
  • Your XP and streak
  • Lecture progress and notes

Log in to run your code.

Every lecture is free to read. Create an account or log in to run code, run tests and submit this lab.

  • In-browser IDE with a bash terminal — nothing to install
  • Instant grading, XP and your place on the skill
Create an accountLog in
OR
Continue with GitHub