Skip to content

Brownian Motion

Visualize stochastic processes with Matplotlib.


Brownian Motion Class

import matplotlib.pyplot as plt
import numpy as np

class BrownianMotion:

    def __init__(self, T=1):
        self.T = T

    def run_MC(self, num_paths=1, num_steps=None, seed=None):
        """
        Generate Brownian motion sample paths.

        Parameters
        ----------
        num_paths : int
            Number of paths to generate
        num_steps : int
            Number of time steps
        seed : int
            Random seed for reproducibility

        Returns
        -------
        t : array
            Time points
        dt : float
            Time step size
        sqrt_dt : float
            Square root of time step
        B : array
            Brownian motion paths (num_paths x num_steps+1)
        dB : array
            Increments (num_paths x num_steps)
        """
        if num_steps is None:
            num_steps = int(self.T * 12 * 21)  # Daily steps for 1 year
        if seed is not None:
            np.random.seed(seed)

        t = np.linspace(0, self.T, num_steps + 1)
        dt = t[1] - t[0]
        sqrt_dt = np.sqrt(dt)

        Z = np.random.standard_normal((num_paths, num_steps))
        if num_paths > 1:
            Z = (Z - Z.mean(axis=0)) / Z.std(axis=0)

        dB = Z * sqrt_dt
        B = np.concatenate([np.zeros((num_paths, 1)), dB.cumsum(axis=1)], axis=1)

        return t, dt, sqrt_dt, B, dB

Plotting Sample Paths

import matplotlib.pyplot as plt
import numpy as np

def main():
    num_paths = 10
    num_steps = 1000

    bm = BrownianMotion()
    t, _, _, B, _ = bm.run_MC(num_paths, num_steps, seed=0)

    fig, ax = plt.subplots(figsize=(12, 4))
    ax.set_title('Ten Sample Paths of Brownian Motion $B_t$')

    for i in range(num_paths):
        ax.plot(t, B[i], alpha=0.7)

    ax.set_xlabel('Time $t$')
    ax.set_ylabel('$B_t$')
    ax.axhline(0, color='black', linewidth=0.5, linestyle='--')
    ax.grid(True, alpha=0.3)

    plt.show()

if __name__ == "__main__":
    main()

Distribution at Terminal Time

import matplotlib.pyplot as plt
import numpy as np
from scipy import stats

def main():
    num_paths = 10000
    num_steps = 1000
    T = 1

    bm = BrownianMotion(T=T)
    t, _, _, B, _ = bm.run_MC(num_paths, num_steps, seed=42)

    # Terminal values
    terminal_values = B[:, -1]

    fig, ax = plt.subplots(figsize=(10, 4))

    # Histogram
    ax.hist(terminal_values, bins=50, density=True, alpha=0.7, label='Simulated')

    # Theoretical distribution: N(0, T)
    x = np.linspace(-4, 4, 100)
    ax.plot(x, stats.norm(0, np.sqrt(T)).pdf(x), 'r-', lw=2, label=f'$N(0, {T})$')

    ax.set_xlabel('$B_T$')
    ax.set_ylabel('Density')
    ax.set_title(f'Distribution of $B_T$ at $T={T}$')
    ax.legend()

    plt.show()

if __name__ == "__main__":
    main()

Geometric Brownian Motion

Stock price model:

import matplotlib.pyplot as plt
import numpy as np

def simulate_gbm(S0, mu, sigma, T, num_paths, num_steps, seed=None):
    """
    Simulate Geometric Brownian Motion.

    S_t = S_0 * exp((mu - sigma^2/2)*t + sigma*B_t)
    """
    if seed is not None:
        np.random.seed(seed)

    dt = T / num_steps
    t = np.linspace(0, T, num_steps + 1)

    # Generate standard normal increments
    dW = np.random.randn(num_paths, num_steps) * np.sqrt(dt)
    W = np.concatenate([np.zeros((num_paths, 1)), dW.cumsum(axis=1)], axis=1)

    # GBM formula
    drift = (mu - 0.5 * sigma**2) * t
    diffusion = sigma * W
    S = S0 * np.exp(drift + diffusion)

    return t, S

def main():
    S0 = 100       # Initial price
    mu = 0.10      # Drift (10% annual return)
    sigma = 0.20   # Volatility (20%)
    T = 1          # Time horizon (1 year)
    num_paths = 20
    num_steps = 252  # Trading days

    t, S = simulate_gbm(S0, mu, sigma, T, num_paths, num_steps, seed=42)

    fig, ax = plt.subplots(figsize=(12, 5))

    for i in range(num_paths):
        ax.plot(t * 252, S[i], alpha=0.6)

    ax.axhline(S0, color='black', linestyle='--', label=f'$S_0={S0}$')
    ax.axhline(S0 * np.exp(mu * T), color='red', linestyle='--', 
               label=f'Expected: ${S0 * np.exp(mu * T):.1f}$')

    ax.set_xlabel('Trading Days')
    ax.set_ylabel('Stock Price')
    ax.set_title('Geometric Brownian Motion - Stock Price Simulation')
    ax.legend()
    ax.grid(True, alpha=0.3)

    plt.show()

if __name__ == "__main__":
    main()

Confidence Bands

import matplotlib.pyplot as plt
import numpy as np

def main():
    num_paths = 1000
    num_steps = 252
    T = 1

    bm = BrownianMotion(T=T)
    t, _, _, B, _ = bm.run_MC(num_paths, num_steps, seed=42)

    # Calculate statistics
    mean = B.mean(axis=0)
    std = B.std(axis=0)

    # Theoretical standard deviation
    theoretical_std = np.sqrt(t)

    fig, ax = plt.subplots(figsize=(12, 5))

    # Plot a few sample paths
    for i in range(5):
        ax.plot(t, B[i], alpha=0.3, color='blue')

    # Confidence bands
    ax.fill_between(t, mean - 2*std, mean + 2*std, 
                    alpha=0.2, color='blue', label='±2σ (empirical)')
    ax.plot(t, 2*theoretical_std, 'r--', label='±2σ (theoretical)')
    ax.plot(t, -2*theoretical_std, 'r--')

    ax.set_xlabel('Time $t$')
    ax.set_ylabel('$B_t$')
    ax.set_title('Brownian Motion with Confidence Bands')
    ax.legend()
    ax.grid(True, alpha=0.3)

    plt.show()

if __name__ == "__main__":
    main()

Key Takeaways

  • Brownian motion is the foundation of stochastic calculus
  • Use NumPy for efficient path generation
  • Plot multiple paths to show distribution of outcomes
  • GBM models stock prices with log-normal distribution
  • Confidence bands show expected variation over time