Exercise · m13a_breaks.py
Structural breaks: Chow test, sup-F and CUSUM; size and power
not run yet
Not saved: sign up to keep your code"""M13A · Structural breaks: Chow test, sup-F and CUSUM; size and power. Read notes §13A.10 first.
Regression y_t = z_t' beta + u_t with z_t = [1, x_t'] (a constant is always added).
Chow test at a known split s (first regime t < s, second t >= s), k = number of coefficients:
F = [(SSR_R - SSR_1 - SSR_2) / k] / [(SSR_1 + SSR_2) / (n - 2k)] ~ F(k, n - 2k) under H0.
Run: python -m pytest modules/M13A-cointegration-stat-arb/tests/test_m13a_breaks.py --import-mode=importlib
"""
from __future__ import annotations
import numpy as np
from scipy import stats # noqa: F401
def chow_test(y: np.ndarray, X: np.ndarray, split: int):
"""Chow F statistic and p-value for a break at index `split` (eq. 13A.37). Returns (F, p).
X may be 1-D (one regressor) or 2-D; add the constant yourself. p = stats.f.sf(F, k, n - 2k)."""
raise NotImplementedError
def sup_f(y: np.ndarray, X: np.ndarray, trim: float = 0.15):
"""Quandt–Andrews sup-F: the maximum Chow F over splits s = floor(trim n), ..., ceil((1 - trim) n).
Returns (sup_F, argmax_split, splits, F_values)."""
raise NotImplementedError
def simulate_break(T: int, shift: float, seed: int, frac: float = 0.5):
"""y_t = 1 + 0.5 x_t + shift * 1{t >= frac T} + u_t, x_t, u_t iid N(0,1). (Given.)"""
rng = np.random.default_rng(seed)
x = rng.standard_normal(T)
u = rng.standard_normal(T)
d = (np.arange(T) >= int(frac * T)).astype(float)
return 1.0 + 0.5 * x + shift * d + u, x
def chow_rejection_rate(shift: float, T: int, nsim: int, seed: int = 0, alpha: float = 0.05) -> float:
"""Share of simulations in which the Chow test at the TRUE split T // 2 rejects at level alpha.
Draw per-simulation seeds with np.random.default_rng(seed).integers(0, 2**31 - 1, nsim) and call
simulate_break(T, shift, int(s)) for each. shift = 0 estimates size; shift != 0 estimates power.
"""
raise NotImplementedError
def snooped_chow_rejection_rate(T: int, nsim: int, seed: int = 0, alpha: float = 0.05, trim: float = 0.15):
"""Data-snooping experiment with NO break (shift = 0): for each simulated sample pick the split
that maximises F (sup_f), then use the ordinary Chow p-value at that split.
Returns (rejection_rate, sup_F_values). Seeds as in chow_rejection_rate."""
raise NotImplementedError
def recursive_residuals(y: np.ndarray, X: np.ndarray) -> np.ndarray:
"""Brown–Durbin–Evans recursive residuals w_t, t = k+1..n (eq. 13A.38):
w_t = (y_t - z_t' b_{t-1}) / sqrt(1 + z_t' (Z_{t-1}' Z_{t-1})^{-1} z_t),
b_{t-1} = OLS on the first t-1 observations. Returns an array of length n - k."""
raise NotImplementedError
def cusum_test(y: np.ndarray, X: np.ndarray, a: float = 0.948):
"""CUSUM test (eq. 13A.39). W_r = sum_{t<=r} w_t / sigma_hat with sigma_hat^2 = sum w^2 / (n-k);
5% boundaries +-a [sqrt(n-k) + 2 j / sqrt(n-k)], j = 1..n-k. Returns (W, bound, reject)."""
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.