σμ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 1 · Python foundations for quants›Topic 1.33·~45 minBeginner

Random variables and expectation

Skill positionModule 1 of 11 · Topic 33 of 33
1.33 Random variables and expectation
—Lab score— / 100Total XP—Streak—

A number whose value is not yet known

A random variable is a quantity whose value depends on chance: the roll of a die, tomorrow's return, the price of a stock at expiry. A discrete random variable takes a list of possible values, each with a probability. Together the values and probabilities are its distribution. The probabilities are never negative and add up to 1. In Python this is just two lists of the same length, values and probs. A fair die is values = [1, 2, 3, 4, 5, 6] with probs = [1/6] * 6.

import math

values = [1, 2, 3, 4, 5, 6]
probs = [1 / 6] * 6

# A valid distribution: no negatives, and the probabilities add up to 1
print(all(p >= 0 for p in probs))             # True
print(math.isclose(sum(probs), 1.0))          # True (compare floats with isclose)

# A stock tomorrow: down 5%, up 2% or up 8%
returns = [-0.05, 0.02, 0.08]
chances = [0.2, 0.5, 0.3]
print(math.isclose(sum(chances), 1.0))        # True

Expectation: the probability-weighted average

The expected value E[X] is the average you would get over many repetitions: multiply each value by its probability and add up, sum(v * p). A die has E[X] = 3.5, a number it can never show, which is fine: it is a long-run average, not a prediction of one outcome. Expectation is linear: E[a*X + b] = a*E[X] + b. The expectation of a function E[g(X)] applies g to each value first: sum(g(v) * p). Careful: in general E[g(X)] is not g(E[X]). An option's payoff max(S - K, 0) is the classic case.

def expected_value(values, probs):
    return sum(v * p for v, p in zip(values, probs))

die = [1, 2, 3, 4, 5, 6]
fair = [1 / 6] * 6
print(round(expected_value(die, fair), 4))            # 3.5

# E[g(X)]: apply g to each value first. Call payoff with strike 100:
prices = [90, 100, 110, 120]
chances = [0.1, 0.4, 0.3, 0.2]
mean_price = expected_value(prices, chances)
print(mean_price)                                      # 106.0
print(expected_value([max(s - 100, 0) for s in prices], chances))  # 7.0  = E[max(S - K, 0)]
print(max(mean_price - 100, 0))                        # 6.0  = max(E[S] - K, 0), not the same

Variance: how spread out is it?

Two investments can have the same expected return and very different risk. The variance measures the spread around the mean: Var(X) = E[(X - mu)^2], which can be computed as E[X^2] - E[X]^2. The standard deviation is its square root, in the same units as X; for returns this is the volatility. For the die, Var = 35/12 = 2.9167. Both are built from expectations of functions of X, so one well-written expected_of(g, values, probs) gives you the mean, the second moment and everything that follows.

def expected_value(values, probs):
    return sum(v * p for v, p in zip(values, probs))

def expected_of(g, values, probs):
    return expected_value([g(v) for v in values], probs)

def variance(values, probs):
    mean = expected_of(lambda v: v, values, probs)
    return expected_of(lambda v: v * v, values, probs) - mean ** 2

die, fair = [1, 2, 3, 4, 5, 6], [1 / 6] * 6
print(round(variance(die, fair), 4))                   # 2.9167

returns, chances = [-0.05, 0.02, 0.08], [0.2, 0.5, 0.3]
print(round(expected_value(returns, chances), 4))      # 0.024
print(round(variance(returns, chances), 6))            # 0.002044

Simulation and the law of large numbers

You can also estimate an expectation by drawing samples. rng.choices(values, weights=probs, k=n) returns n draws, and the average of the draws approaches E[X] as n grows: the law of large numbers. This is the heart of Monte Carlo pricing. Use your own random.Random(seed) so that the run is repeatable and does not disturb other code. With few draws the average is noisy; the error shrinks only like one over the square root of n, so 100 times more draws buy 10 times more accuracy.

import random

die, fair = [1, 2, 3, 4, 5, 6], [1 / 6] * 6

def sample_mean(values, probs, n, seed):
    rng = random.Random(seed)                    # private, repeatable generator
    draws = rng.choices(values, weights=probs, k=n)
    return sum(draws) / n

for n in (10, 1000, 100000):
    print(n, round(sample_mean(die, fair, n, 1), 4))
# 10 3.1
# 1000 3.598
# 100000 3.5017       (closing in on 3.5)

print(sample_mean(die, fair, 1000, 1) == sample_mean(die, fair, 1000, 1))  # True: same seed, same answer
Check the distribution before you use itA distribution whose probabilities do not add up to 1, contain a negative number or do not match the values in length gives a meaningless expectation without any error. Validate first: equal lengths, no negatives, math.isclose(sum(probs), 1.0). Raise ValueError for anything else. Remember too that an expectation is a long-run average: a strategy with a positive expected value can still lose money on any single day.
  • A distribution is values plus probs (no negatives, sum 1); E[X] = sum(v * p).
  • E[g(X)] = sum(g(v) * p), which is not g(E[X]); Var = E[X^2] - E[X]^2.
  • Sample means converge to E[X] (law of large numbers); use your own random.Random(seed).

Next up: NumPy arrays and dtypes, the fast, typed arrays that the rest of the course is built on.

Lab · what's graded

  • Average outcomes by their probabilities with expected_value25 pts
  • Take the expectation of a payoff with expected_of25 pts
  • Measure the spread with variance25 pts
  • Estimate the expectation by simulation with sample_mean25 pts
Explorer · lab-33-1
▾ lab-33-1
…
bash · in-browser
learner@quantcraft:~/lab-33-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-33-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