Markets: A Rigorous Course

Exercise · m13a_breaks.py

Structural breaks: Chow test, sup-F and CUSUM; size and power

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

Tests (7)

  • test_chow_equals_dummy_interaction_f_test
  • test_chow_size_close_to_nominal
  • test_chow_power_increases_with_shift
  • test_snooping_inflates_size_and_supf_critical_value
  • test_sup_f_locates_break
  • test_recursive_residuals_sum_of_squares_equals_ssr
  • test_cusum_detects_large_mean_shift

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

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.

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.