5.4 — Dynamic Bayesian Models: Rolling Updates and Adaptive Risk

Chapter 5 · Rethinking Statistics with Bayesian Methods

Prof. Xuhu Wan

Section 5.4 · Chapter 5 · Learning Statistics with Python

Dynamic Bayesian Models: Rolling Updates and Adaptive Risk

Rethinking Statistics with Bayesian Methods

Prof. Xuhu Wan

ISOM, HKUST Business School · 2026 Edition

Dynamic Bayesian Models: Rolling Updates and Adaptive Risk

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.

Yesterday’s posterior is today’s prior

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\)?

  • \(\sqrt{1/(1/0.002^2 + 253/0.02^2)} \approx 0.00106\)
  • \(0.002 / 253 \approx 0.0000079\)
  • \(0.02 / \sqrt{253} \approx 0.00126\)
  • It depends on the order of the returns

Update AAPL’s mean through 2020, one day at a time

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)\).

Update AAPL’s mean through 2020, one day at a time

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.

Watch the belief tighten

During the March 2020 crash the posterior sd of μ…

  • Widened, because the returns were extreme
  • Stayed constant
  • Kept shrinking — the update only counts observations
  • Reset to the prior

A static parameter cannot adapt

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?

  • It still shrinks to zero, just more slowly
  • It settles at a positive steady state
  • It grows without bound
  • It oscillates

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.

Let the data choose q: the local-level model

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.

Adaptive risk: a local level for |r|

\(\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.

The bridge to the Kalman filter

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)?

round((-q + np.sqrt(q**2 + 4*q)) / 2, 3)

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.

Your turn: adaptive risk with rolling block updates

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.

What you discovered

  • Precision adds: 253 sequential updates reproduced the batch posterior 0.00199 ± 0.00106 exactly — the posterior is a sufficient summary of the past.
  • A static parameter’s variance only ever shrinks; the March 2020 crash made the band narrower.
  • Adding process noise \(q\) each day gives a steady-state gain — the local-level model, whose MLE said AAPL’s mean is constant but its volatility wanders.
  • Five lines of hand-rolled recursion matched 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.

Mistakes Library: “Clinton 99 %” — a posterior that forgot the states move together (November 2016)

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?

Decision Memo — Allocate to the GOOG Single-Name Sleeve?

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.

Working with an AI Copilot

Three prompts that work with this chapter’s tools — and the pitfalls each one hides.

  1. “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.

  2. “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.

  3. “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.

Chapter Summary

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.

Discussion Questions

  1. 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?

  2. 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?

  3. 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?

  4. 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?