Chapter 5 · Rethinking Statistics with Bayesian Methods
Section 5.4 · Chapter 5 · Learning Statistics with Python
Rethinking Statistics with Bayesian Methods
Prof. Xuhu Wan
ISOM, HKUST Business School · 2026 Edition
Every posterior you computed so far used all the data at once. Markets arrive one day at a time. You will update a belief day by day, discover that a static parameter cannot adapt, add one line to fix it — and find you have written the Kalman filter.
Normal mean, known \(\sigma\). One observation \(r_t\) updates \((m, v)\) — the posterior mean and variance of \(\mu\):
\[\frac{1}{v_t} = \frac{1}{v_{t-1}} + \frac{1}{\sigma^2}, \qquad m_t = v_t\left(\frac{m_{t-1}}{v_{t-1}} + \frac{r_t}{\sigma^2}\right)\]
After 253 daily updates with \(\sigma = 0.02\) and prior sd 0.002, what is the posterior sd of \(\mu\)?
253 days, mean 0.00277, sd 0.02939. We fix \(\sigma = 0.02\) (known variance is the price of a closed form) and start from a prior \(\mu \sim N(0, 0.002^2)\).
Day 1 sd 0.00199, day 253 mean 0.00199, sd 0.00106 — and the batch formula gives exactly 0.00199, 0.00106. Sequential = batch. The posterior is a sufficient summary of the past.
During the March 2020 crash the posterior sd of μ…
The fix is one line: before each update, admit that \(\mu\) may have moved overnight.
\[\mu_t = \mu_{t-1} + \eta_t, \quad \eta_t \sim N(0, q) \qquad\Longrightarrow\qquad v_{t-1} \leftarrow v_{t-1} + q\]
With \(q > 0\) added every day, what happens to the posterior variance \(v_t\) in the long run?
Observation equation \(r_t = \mu_t + \varepsilon_t\), state equation \(\mu_t = \mu_{t-1} + \eta_t\): this is the local-level model, and the recursion you just wrote is the Kalman filter for it.
UnobservedComponents estimates both variances by maximum likelihood. First on the returns themselves.
Level variance 8.8e-13 — effectively zero. The data say the mean of AAPL’s daily return does not wander: a constant is as good as it gets. That is a finding, not a failure. Risk is a different story.
\(\sqrt{\pi/2}\,|r_t|\) is an unbiased daily-volatility proxy under normality. Give it a wandering level.
Filtered vol 1.2 % on 2 Jan 2020, 8.8 % on 16 Mar, back to 1.7 % by year-end. Signal-to-noise q = 0.025: the level moves, but each day’s |r| is mostly noise.
In steady state the filter’s update is \(m_t = m_{t-1} + K\,(y_t - m_{t-1})\) with gain \(K = \tfrac{-q + \sqrt{q^2 + 4q}}{2}\).
With \(q = 0.0251\), what is the steady-state gain \(K\) (3 dp)?
0.146
Your five-line recursion reproduces filtered_state to 5e-07. \(K = 0.146\) is an EWMA with a 12.7-day span — RiskMetrics’ \(\lambda = 0.94\) (span 32) is the same filter with a smaller \(q\). Chapter 6 adds trends, seasons and regressors to this state.
The notebook’s practice: 20 days set the prior, then update every 10 days with the posterior of the previous block as the new prior. The starter resets the prior every block. Move the reset out of the loop so beliefs carry forward. sd_final should then equal \(\sigma/\sqrt{253}\) = 0.00126: a prior built from 20 days carries exactly 20 observations of information, so block updates end where a one-shot update would.
0.00199 ± 0.00106 exactly — the posterior is a sufficient summary of the past.UnobservedComponents to better than \(10^{-6}\); the gain \(K = 0.146\) is an EWMA with a 12.7-day span.Next: Chapter 6 — the same state-space idea with trends, ARIMA and the full Kalman filter.
Warning
On the eve of the 8 November 2016 US presidential election the Princeton Election Consortium gave Clinton a 99 % chance of winning and the HuffPost model 98 %; FiveThirtyEight said 71 %. National polls had her about 3 points ahead and she won the popular vote by 2.1 — an ordinary polling miss. But Wisconsin, Michigan and Pennsylvania, polling at Clinton +2 to +6, went to Trump by under a point, and the Electoral College 306–232.
The 99 % models treated the fifty state polling errors as independent, so a 2-point lead became near-certainty as the errors averaged away. FiveThirtyEight’s likelihood gave the errors a common component — one demographic miss shared across the Rust Belt — and its posterior stayed wide.
Lesson: a posterior’s width is only as honest as the dependence structure in the likelihood. Errors modelled as independent when they are really one error shrink the credible interval by \(\sqrt{n}\). As with Student-t tails, run the predictive check before you print 99 %: could this model have generated the miss you are about to see?
The posterior of the Sharpe ratio is not the deliverable. A recommendation with a probability attached is.
To: CIO, Multi-Strategy Fund From: <Your name>, quantitative analyst Subject: Allocation to the GOOG single-name sleeve Date: 2026-09-15
Recommendation: Allocate 5 % of risk budget; review after 250 trading days.
Evidence (Student-t model, 1 258 daily returns 2015–2020, Metropolis posterior): - Posterior mean annualised Sharpe 2.38; 95 % credible interval [1.30, 3.59]; classical CI [−0.11, 1.65] includes zero. - P(Sharpe > 1) = 0.99; P(Sharpe > 0.5) = 1.00. - Tail index ν ≈ 2.7: a 4σ day is 440 × more likely than a normal model says.
Caveats: - Sharpe uses the t-scale σ; on the standard-deviation basis the posterior mean is ≈ 1.18. - The local-level filter (§5.4, AAPL) finds a constant mean but a volatility that moved from 1.2 % to 8.8 % in Q1 2020 — size positions on filtered vol, not the sample sd. - Single name; no cross-sectional shrinkage applied (Chapter 5 of the book).
Next step: re-run the posterior every 10 days with the previous posterior as prior; trigger a review if P(Sharpe > 0.5) falls below 0.8.
Three prompts that work with this chapter’s tools — and the pitfalls each one hides.
“Write a Metropolis sampler for this log-posterior and report the acceptance rate.” Good. Then check the trace yourself: an LLM will happily call a 0.98 acceptance rate “excellent” when it means the step size is far too small and the chain has explored nothing.
“Which prior should I use for ν?” It will suggest Exponential(1/29) + 2 or Gamma(2, 0.1) — both fine — but ask it to draw prior predictive samples and show you P(ν < 5). The prior you accept should be one you have seen, not one you were told.
“Is the Bayesian Sharpe of 2.38 better than the classical 0.77?” The copilot will say yes. It is answering a different question. Ask instead: “which σ does each number divide by, and which one matches my risk limit?” — the answer in this chapter was “they measure different risks”, and no model chooses your risk definition for you.
| Concept | Tool |
|---|---|
| Posterior ∝ likelihood × prior | np.exp(log_post - log_post.max()) on a grid |
| Student-t return model, Sharpe posterior | scipy.stats.t.logpdf, Metropolis loop with rng.uniform() |
| Credible interval | np.percentile(draws, [2.5, 97.5]) |
| Prior / posterior predictive check | rng.normal(...) on parameter draws; Bayesian p-value |
| Conjugate normal-linear posterior | Sn = inv(inv(S0) + X.T @ X / σ²), rng.multivariate_normal |
| Robust regression | minimize(nll_t) with student_t.logpdf; weights \((\nu+1)/(\nu+r^2)\) |
| Sequential update / local level | precision recursion; sm.tsa.UnobservedComponents(y, level="local level") |
In Colab the same models are four lines of PyMC each — now you know what pm.sample does.
Next: Chapter 6 — Time Series Models for the Mean.
Your fund reports Sharpe ratios on a standard-deviation basis to investors but sizes positions on the t-scale σ. Which posterior should go in the marketing deck, and what has to be disclosed alongside it?
The prior predictive check rejected Prior I as “absurd”, yet with 200 points Prior I and Prior II give nearly identical posteriors. When does the choice of prior actually change a decision — and how would you demonstrate that to a sceptical PM?
The Student-t fit gave the March 2020 days weights near 0.02. A risk manager objects: “those are exactly the days I care about”. Is robust regression the wrong tool for risk, the right tool for alpha, or both?
The local-level MLE said AAPL’s mean return does not wander but its volatility does. What would you expect for a fund’s strategy returns, and how would you set \(q\) if you had only 60 days of live track record?
Prof. Xuhu Wan · HKUST ISOM · Learning Statistics with Python