Chapter 5 · Rethinking Statistics with Bayesian Methods
Section 5.1 · Chapter 5 · Learning Statistics with Python
Rethinking Statistics with Bayesian Methods
Prof. Xuhu Wan
ISOM, HKUST Business School · 2026 Edition
You will build the posterior distribution of GOOG’s Sharpe ratio three ways — on a grid, then with a sampler you write yourself — and discover why the classical estimate is too small. Predict every number before you run it.
\[\underbrace{p(\theta \mid X)}_{\text{posterior}} \;\propto\; \underbrace{p(X \mid \theta)}_{\text{likelihood}}\;\underbrace{p(\theta)}_{\text{prior}}\]
Classical statistics fixes \(\theta\) and asks how the data would vary. Bayes fixes the data and asks how \(\theta\) varies.
After seeing 1 258 daily returns, which object lets you say “P(Sharpe > 1) = 0.99”?
GOOG daily returns, October 2015 to October 2020 — the notebook’s data. The annualised Sharpe ratio is \(\sqrt{252}\,\bar r / s\).
Mean daily return ≈ 0.00080, daily sd ≈ 0.01644. What does the classical Sharpe ratio print (3 dp)?
0.771
Sharpe 0.771, excess kurtosis 6.72. A normal has kurtosis 0 — these returns have far fatter tails. The model must say so.
\[r_t \sim \text{StudentT}(\nu,\ \mu,\ \sigma)\]
Three parameters: location \(\mu\), scale \(\sigma\), and degrees of freedom \(\nu\) that set the tail weight.
Which choice of \(\nu\) produces the fattest tails?
On a log scale the normal’s tail is a parabola falling off a cliff; the t(3) tail is almost a straight line. A 4-sigma event: 6.3e-05 under the normal, 0.028 under t(3) — about 440 times more likely.
The notebook’s choices, one per parameter:
| Parameter | Prior | Why |
|---|---|---|
| \(\mu\) | \(N(\bar r,\ s)\) | centred on the sample mean, very wide (sd = one daily sd) |
| \(\sigma\) | \(\text{Uniform}(s/1000,\ 1000\,s)\) | positive, essentially flat |
| \(\nu\) | \(2 + \text{Exponential}(\lambda = 1/29)\) | mean 31, but mass near 2 keeps fat tails possible; \(\nu > 2\) guarantees a finite variance |
What does the prior \(\nu = 2 + \text{Exp}(1/29)\) force?
Check it: expon.cdf(8, scale=29) = 0.241 — the prior gives only 24 % to \(\nu < 10\). It allows fat tails without insisting on them.
In Colab
PyMC is not available in the browser. This is the notebook’s model; the next slides reproduce its posterior with numpy and scipy.
with pm.Model() as sr_model:
mean = pm.Normal("mean", mu=rmean, sigma=rstd)
std = pm.Uniform("std", lower=rstd/1000, upper=rstd*1000)
df = pm.Exponential("df", 1/29, initval=5) + 2.0
returns = pm.StudentT("returns", nu=df, mu=mean, sigma=std,
observed=google["return"])
pm.Deterministic("sharpe", np.sqrt(252) * mean / std)
trace = pm.sample(tune=1500, draws=1000, chains=2, random_seed=8888)Four lines of priors, one likelihood, one derived quantity. Everything else is the sampler — which we now build by hand.
With one or two parameters you do not need a sampler at all: evaluate prior × likelihood on a grid and normalise. This is Bayes’ rule made literal.
Same five years, S&P 500. Treat \(\sigma\) as known (the sample sd). Prior \(\mu \sim N(0,\ 0.001^2)\). For a normal likelihood the posterior has a closed form — predict what the grid should reproduce.
Posterior precision = prior precision + \(n/\sigma^2\). With \(n = 1258\), \(\sigma = 0.01186\), prior sd 0.001, what is the posterior sd (6 dp)?
0.000317
Grid 0.000497 0.000317, closed form 0.000497 0.000317 — identical to six decimals.
log_post - log_post.max() before exp is not cosmetic: 1 258 log-densities sum to about 3 800, and exp of that overflows.0.000497 sits between the prior mean 0 and the sample mean 0.00055: a precision-weighted average. The data’s precision (\(n/\sigma^2 \approx 8.9\) million) dwarfs the prior’s (1 million), so the data win — mostly.Two parameters? A 20 × 20 grid is still only 400 points. Let’s do \(\mu\) and \(\sigma\) for GOOG under a Student-t likelihood with \(\nu = 4\).
Posterior mean σ = 0.0108 — well below the sample sd 0.0164. The t-scale is not the standard deviation; fat tails inflate the sd. And Sharpe 2.035 with P(Sharpe > 1) = 0.972. Hold that thought.
A 400-point grid per parameter and a 3-parameter model (\(\mu, \sigma, \nu\)). How many likelihood evaluations?
The curse of dimensionality is why we need Markov chain Monte Carlo: instead of visiting every point, wander through parameter space so that time spent in a region is proportional to its posterior probability.
Propose a random step, accept it with probability \(\min(1, p^\star / p_t)\), otherwise stay. That is the whole algorithm — and it never needs the normalising constant.
At \(\theta_t\) the posterior density is \(p_t\); you propose \(\theta^\star\) with density \(p^\star\).
\(p^\star = 0.3\,p_t\). What does Metropolis do?
In code: if log(u) < log_post(prop) - log_post(current). Work in logs; the difference of two logs is a ratio of densities, so the unknown normaliser \(p(X)\) cancels.
Sample \(\theta = (\mu, \log\sigma, \log(\nu - 2))\) so the chain can never leave the allowed region. The extra ls and le terms are the Jacobians of those transforms.
3438.4 at the sample sd, 3443.2 at half of it. The likelihood already prefers a scale smaller than the sample sd — the tails are being handled by \(\nu\), not by \(\sigma\).
Acceptance 0.47 — inside the healthy 0.2–0.5 band. \(\nu \approx 2.7\): GOOG’s tails are heavier than a t(3) (the notebook’s PyMC run found 2.79). \(\sigma = 0.0097\), six-tenths of the sample sd.
What does a healthy trace look like?
Posterior mean 2.38, 95 % credible interval [1.30, 3.59], P(Sharpe > 1) = 0.99. The notebook’s PyMC run gave 2.35 and [1.31, 3.42] — the same answer from a twelve-line sampler. The classical 0.77 lies outside the credible interval. “The Sharpe ratio is under-estimated by classical statistics.”
The posterior mean Sharpe is 2.38 against a classical 0.77. The main reason?
Be honest about what σ means
The t-distribution’s standard deviation is \(\sigma\sqrt{\nu/(\nu-2)}\), which for \(\nu \approx 2.7\) is almost twice \(\sigma\). Re-computing Sharpe on that basis gives a posterior mean of about 1.18. The “under-estimation” is a statement about ordinary-day risk. Decide which risk you are pricing before you quote a Sharpe.
Lo (2002): the classical standard error of an annualised Sharpe is \(\sqrt{252\,(1 + \tfrac12 \widehat{SR}_d^2)/n}\).
Which interval lets you say “there is a 95 % probability the true Sharpe lies in here”?
[-0.11, 1.65][1.30, 3.59]Note the classical interval includes zero: a t-test cannot reject “GOOG earned nothing”. The posterior says P(Sharpe > 0.5) = 0.999.
The notebook’s practice. The starter runs the sampler on AAPL (2 515 returns). Switch r_p to NVDA from nv (2023–2024, 500 returns) and compare posterior mean and classical Sharpe for each.
Next: §5.2 — regression as a normal distribution, and how to check a model before fitting it.
Prof. Xuhu Wan · HKUST ISOM · Learning Statistics with Python