σμ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 8 · Stochastic models & machine learning›Topic 8.1·~50 minAdvanced

Brownian motion

Skill positionModule 8 of 11 · Topic 1 of 10
8.1 Brownian motion
—Lab score— / 100Total XP—Streak—

The random walk with the steps shrunk

Imagine a price that moves up or down each second by a random amount. Make the interval shorter and shorter, and shrink each step so the total spread stays reasonable: the limit is Brownian motion W(t), introduced to describe pollen grains in water and adopted by Bachelier in 1900 to model prices. It is defined by three properties: W(0) = 0; increments over non-overlapping intervals are independent; and W(t) - W(s) is normal with mean 0 and variance t - s.

import numpy as np

rng = np.random.default_rng(1)
dt = 0.01
z = rng.standard_normal(5)                         # standard normal draws
increments = z * np.sqrt(dt)                       # each step over dt has standard deviation sqrt(dt)
print(np.round(increments, 4))

A path is a running sum

To simulate a path on a grid of times 0, dt, 2 dt, ..., draw one standard normal z per step, scale it by sqrt(dt), and add up: W(k * dt) is the sum of the first k increments, np.cumsum. Put a 0 in front because the path starts at 0. The variance of W(t) is t, so the spread of the position grows like the square root of time, the same square-root law as in volatility scaling and VaR.

import numpy as np

def brownian_path(z, dt):
    return np.concatenate([[0.0], np.cumsum(z * np.sqrt(dt))])

rng = np.random.default_rng(1)
n, dt = 252, 1 / 252
ends = np.array([brownian_path(rng.standard_normal(n), dt)[-1] for _ in range(4000)])
print(round(float(ends.mean()), 3), round(float(ends.var()), 3))      # about 0 and 1: W(1) is a standard normal

A path that is infinitely rough

A Brownian path is continuous but nowhere smooth, and its length is infinite. Its roughness has a precise measure, the quadratic variation, the sum of squared steps. For a smooth curve this sum shrinks to 0 as the grid gets finer; for Brownian motion it stays at the elapsed time t, whatever the grid, because each squared step is about dt and there are t / dt of them. This fact, (dW)^2 = dt, is what makes stochastic calculus different from ordinary calculus, and is the key to the next topics.

import numpy as np

def quadratic_variation(path):
    return float(np.sum(np.diff(path) ** 2))

rng = np.random.default_rng(2)
t = 2.0
for steps in (10, 100, 10000):
    dt = t / steps
    path = np.concatenate([[0.0], np.cumsum(rng.standard_normal(steps) * np.sqrt(dt))])
    print(steps, round(quadratic_variation(path), 3))              # settles near 2.0 as the grid gets finer

smooth = np.linspace(0, 1, 10001) * 2
print(round(quadratic_variation(smooth), 5))                       # a straight line: close to 0

Drift and volatility

A price has a trend and a scale. Arithmetic Brownian motion with drift is X(t) = mu * t + sigma * W(t): mu is the drift (the trend per unit of time), sigma the volatility (the scale of the random part). It is the simplest model of a price change, and it can go negative, which is why prices themselves are modelled as geometric Brownian motion in the next topic. It is still the building block for rates and spreads.

import numpy as np

def drifted_path(z, dt, mu, sigma):
    t = np.arange(len(z) + 1) * dt
    w = np.concatenate([[0.0], np.cumsum(z * np.sqrt(dt))])
    return mu * t + sigma * w

rng = np.random.default_rng(3)
n, dt = 252, 1 / 252
path = drifted_path(rng.standard_normal(n), dt, 0.08, 0.2)             # 8% a year drift, 20% volatility
print(round(float(path[-1]), 4), "after one year; the drift alone would give 0.08")
print(np.round(drifted_path(np.zeros(4), 0.25, 0.08, 0.2), 4))         # no noise: just the trend line
Simulated paths are only as good as their drawsIn this lab the draws are given, so results are exact and testable. In real simulations they come from a seeded generator (np.random.default_rng(seed)), which makes a run reproducible. A single path proves nothing: statistics such as the mean or the variance of W(1) only show up over thousands of paths, and the error of a Monte Carlo estimate shrinks like 1 / sqrt(paths).
  • Brownian motion starts at 0 and has independent normal increments of variance dt: simulate with z * sqrt(dt) and a cumulative sum.
  • The variance of W(t) is t, and the quadratic variation of a path stays at the elapsed time: (dW)^2 = dt.
  • Add trend and scale with X(t) = mu * t + sigma * W(t); one path proves nothing, statistics need many paths.

Next up: geometric Brownian motion, the model in which prices cannot go negative.

Lab · what's graded

  • Scale the shocks with brownian_increments25 pts
  • Build the path with brownian_path25 pts
  • Measure the roughness with quadratic_variation25 pts
  • Add a trend with drifted_path25 pts
Explorer · lab-102-1
▾ lab-102-1
…
bash · in-browser
learner@quantcraft:~/lab-102-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

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