Chapter 12 Laboratory — Module 12: The Learning Machine¶

Mathematical Foundations of Modern Finance · Part III · Week 12

Four panels: the Gaussian update, the Kalman filter, the de-smoothing panel, and the drift panel. Reproduces the $151.5M ± $3.3M nowcast and recovers θ = 0.8 from a smoothed series.

Seeds: 20261201–20261204.

In [1]:
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams.update({"figure.figsize": (7, 4.2), "axes.grid": True,
                     "grid.alpha": 0.3, "font.size": 11})
BRASS, INK = "#B4884A", "#0F1E3D"
import mfmf_engine_ch12 as eng

E1 — The Gaussian update (supports LOS 12.2)¶

A prior $N(\$153M, \$4M^2)$ and an independent noisier signal $N(\$148M, \$6M^2)$ fuse into a posterior nowcast of $151.5M with a ±$3.3M band — weight 0.69 on the prior, and the weights are not opinions but precisions.

In [2]:
r1 = eng.E1_nowcast()
print(f"Nowcast mean     : ${r1['nowcast_mean']:.2f}M   (book: $151.5M)")
print(f"Nowcast sd (1-sd): ${r1['nowcast_sd']:.2f}M    (book: $3.3M)")
print(f"Weight on prior  : {r1['weight_on_prior']:.3f}   (book: 0.69)")
print(f"Weight on signal : {r1['weight_on_signal']:.3f}")
Nowcast mean     : $151.46M   (book: $151.5M)
Nowcast sd (1-sd): $3.33M    (book: $3.3M)
Weight on prior  : 0.692   (book: 0.69)
Weight on signal : 0.308

E2 — The Kalman filter and innovation whiteness (supports LOS 12.3)¶

Run the filter on a simulated state. Correctly specified, the innovations are white (no autocorrelation). Mis-set the observation noise by a factor of four and the autocorrelogram betrays it.

In [3]:
r2 = eng.E2_kalman()
print(f"Filter RMSE               : {r2['rmse']:.4f}")
print(f"Innovation autocorr (correct)  : {r2['innov_autocorr_correct']:+.4f}")
print(f"Innovation autocorr (misspec)  : {r2['innov_autocorr_misspec']:+.4f}")
print(f"Whiteness OK              : {r2['whiteness_ok']}")
Filter RMSE               : 0.1340
Innovation autocorr (correct)  : -0.0436
Innovation autocorr (misspec)  : +0.4016
Whiteness OK              : True

E3 — De-smoothing (supports LOS 12.4–12.5)¶

Reported marks smooth the true returns: $r_t = (1-\theta)r^\*_t + \theta r_{t-1}$. An AR(1) regression recovers $\theta \approx 0.8$; unsmoothing restores the true volatility the reported series understated.

In [4]:
r3 = eng.E3_desmooth()
print(f"theta (true / recovered): {r3['theta_true']} / {r3['theta_hat']:.3f}")
print(f"Reported vol            : {r3['reported_vol']:.3f}")
print(f"Unsmoothed vol          : {r3['unsmoothed_vol']:.3f}")
print(f"True vol                : {r3['true_vol']}")
theta (true / recovered): 0.8 / 0.815
Reported vol            : 0.062
Unsmoothed vol          : 0.195
True vol                : 0.18

E4 — Learning the drift (supports LOS 12.6)¶

With a prior standard deviation on the drift, the weight the posterior places on the data grows with the horizon. Even twenty years leaves substantial prior weight — the drift is hard to learn.

In [5]:
r4 = eng.E4_drift()
print(f"Prior precision tau0    : {r4['tau0']:.1f}")
print(f"Data weight (20 years)  : {r4['data_weight_20y']:.3f}")
print(f"Prior weight (20 years) : {r4['prior_weight_20y']:.3f}")
Prior precision tau0    : 2500.0
Data weight (20 years)  : 0.207
Prior weight (20 years) : 0.793

Validation checks¶

Every laboratory report must reproduce these; a report whose checks do not pass is returned ungraded.

In [6]:
checks = eng.validation_checks()
for k, v in checks.items():
    if k.startswith("_"): continue
    print(f"[{'PASS' if v else 'FAIL'}] {k}")
assert checks["ALL_PASS"], "Validation failed — do not submit."
print("\nAll Module validation checks pass.")
[PASS] V1_nowcast_151.5
[PASS] V2_nowcast_sd_3.3
[PASS] V3_weight_on_prior_0.69
[PASS] V4_kalman_innovation_white
[PASS] V5_desmooth_recovers_theta
[PASS] ALL_PASS

All Module validation checks pass.