Exercise · m02_returns_stats.py
The statistics of returns
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.