Markets: A Rigorous Course

Exercise · m02_returns_stats.py

The statistics of returns

Prices are not the object of study. Returns are. Implement each function, then:

cd exercises && .venv/bin/python -m pytest modules/M02-statistics-of-returns/tests

Use numpy/pandas. Every function takes a pandas Series of prices or returns (index = dates) unless stated otherwise.

Tests (7)

  • test_simple_and_log_returns
  • test_annualized_return_and_vol
  • test_max_drawdown
  • test_kurtosis_normal_vs_fat
  • test_autocorr_and_clustering
  • test_stylized_fact_classifier
  • test_tail_ratio

Theory: M02 notes. Run with Ctrl/⌘+Enter.

not run yet

Not saved: sign up to keep your code
"""Exercise 2.1: the statistics of returns (notes.md §2.1–2.7).

Prices are not the object of study. Returns are. Implement each function, then:

    cd exercises && .venv/bin/python -m pytest modules/M02-statistics-of-returns/tests

Use numpy/pandas. Every function takes a pandas Series of prices or returns
(index = dates) unless stated otherwise.
"""

import numpy as np
import pandas as pd


def simple_returns(prices: pd.Series) -> pd.Series:
    """R_t = P_t / P_{t-1} - 1. Drop the first (NaN) row."""
    raise NotImplementedError


def log_returns(prices: pd.Series) -> pd.Series:
    """r_t = ln(P_t / P_{t-1}). Drop the first (NaN) row.

    Why bother: log returns add across time (r_{1..T} = sum r_t), simple
    returns add across assets in a portfolio. You will use both.
    """
    raise NotImplementedError


def annualized_return(log_r: pd.Series, periods: int = 252) -> float:
    """Geometric annual return (CAGR) from daily log returns.

    CAGR = exp(mean(r) * periods) - 1
    """
    raise NotImplementedError


def annualized_vol(r: pd.Series, periods: int = 252) -> float:
    """Sample std (ddof=1) scaled by sqrt(periods). Assumes iid returns (it's an approximation)."""
    raise NotImplementedError


def max_drawdown(prices: pd.Series) -> tuple[float, pd.Timestamp, pd.Timestamp]:
    """Largest peak-to-trough fall, as a negative fraction (e.g. -0.38).

    Return (mdd, peak_date, trough_date) where peak_date is the date of the
    running high that preceded the worst trough.
    """
    raise NotImplementedError


def excess_kurtosis(r: pd.Series) -> float:
    """Sample excess kurtosis: E[(r-mu)^4]/sigma^4 - 3, using population moments (ddof=0).

    A normal distribution scores 0. Daily equity index returns usually score 5-20.
    """
    raise NotImplementedError


def autocorr(r: pd.Series, lag: int = 1) -> float:
    """Pearson correlation between r_t and r_{t-lag}."""
    raise NotImplementedError


def vol_clustering(r: pd.Series, lags: int = 5) -> float:
    """Average autocorrelation of |r_t| over lags 1..lags.

    Returns themselves are nearly uncorrelated, but their SIZE is strongly
    autocorrelated: big moves follow big moves. This is the stylized fact that
    GARCH (week 8) and every options desk (weeks 5-6) are built on.
    """
    raise NotImplementedError


def tail_ratio(r: pd.Series, k: float = 3.0) -> float:
    """Observed frequency of |r - mean| > k*std divided by what a normal distribution predicts.

    Normal two-sided P(|Z| > k) = 2 * (1 - Phi(k)). Use scipy.stats.norm.
    A value of 5 means k-sigma days happen 5x more often than the bell curve says.
    """
    raise NotImplementedError


def looks_like_real_market(prices: pd.Series) -> bool:
    """Classify a price path as market-like (True) or iid-Gaussian GBM (False).

    The test hands you 40 paths: 20 from a GARCH-t-with-jumps process
    (toolkit.data.synthetic_prices) and 20 from GBM with varying volatility.
    Build a decision rule from the stylized facts above and justify the
    thresholds statistically (what are the sampling distributions of excess
    kurtosis and of the |r| autocorrelation under the iid-Gaussian null for
    n = 1500? See notes §2.6). 90% accuracy required.
    """
    raise NotImplementedError

No runs yet

The stub's functions raise NotImplementedError until you write them. Fill in one, press Run tests, and watch its tests turn green.

Reference solution

Available after your first run, with an account approved for code runs.

Create a free account

Reading is open to everyone. An account keeps your progress, answers, notes and code drafts, and gives you the study plan, the journals and the simulator. Running code needs an account approved for it.