Edit page

Financial returns often alternate between calm periods and bursts of large swings, which constant-variance Gaussian noise cannot capture. Stochastic volatility models instead treat volatility as a latent process: each return is Gaussian, but its scale depends on an unobserved log-volatility that evolves over time (Taylor 1986). This page infers a short AR(1) log-volatility chain from synthetic returns containing a brief volatility shock.

The latent log-volatility follows a Gaussian AR(1) process:

h[0] ~ Gaussian(0, sigma0)
h[t] ~ Gaussian(phi * h[t-1], sigmaEta)   for t = 1 .. T-1

and each observed return is conditioned on the corresponding volatility level:

y[t] ~ Gaussian(0, exp(h[t] / 2))

so a higher h[t] means a wider, higher-variance return distribution at that time step. The autoregressive coefficient phi controls how persistent volatility is; phi close to 1 makes volatility mean-revert slowly, matching the empirical clustering of calm and turbulent periods.

Inference uses Hamiltonian Monte Carlo because every latent variable is continuous and the joint log-density is differentiable. The short 15-step series keeps the example within a browser-friendly runtime; production analysis would still require multiple chains and convergence diagnostics.

var T = 15;
var phi = 0.9;
var sigmaEta = 0.3;
var sigma0 = 0.5;

// Synthetic returns: mostly small day-to-day moves, with a burst of two
// large-magnitude returns at t = 7, 8 simulating a volatility shock.
var returns = [0.05, -0.03, 0.02, -0.04, 0.01, 0.03, -0.02,
                2.10, -1.80,
                0.05, -0.02, 0.03, -0.01, 0.02, -0.03];

var model = function() {
  var h0 = gaussian(0, sigma0);
  var loop = function(t, hPrev, acc) {
    if (t === T) { return acc; }
    var h = t === 0 ? hPrev : gaussian(phi * hPrev, sigmaEta);
    observe(Gaussian({mu: 0, sigma: Math.exp(h / 2)}), returns[t]);
    return loop(t + 1, h, acc.concat([h]));
  };
  return loop(0, h0, []);
};

var post = Infer({method: 'MCMC', kernel: {HMC: {steps: 5, stepSize: 0.15}}, samples: 1500, burn: 500}, model);

var meanVol = map(function(t) {
  return expectation(post, function(sample) { return Math.exp(sample[t] / 2); });
}, _.range(T));

display('inferred volatility (posterior mean of exp(h/2)) per time step:');
display(map(function(x) { return x.toFixed(2); }, meanVol));

viz.line(_.range(T), returns);
viz.line(_.range(T), meanVol);

var burstVol = listMean(meanVol.slice(7, 9));
var otherVol = listMean(meanVol.slice(0, 7).concat(meanVol.slice(9)));
display('mean inferred volatility during the return burst (t = 7, 8): ' + burstVol.toFixed(3));
display('mean inferred volatility elsewhere: ' + otherVol.toFixed(3));
display('volatility rises around the large returns: ' + (burstVol > otherVol));

The inferred volatility trace stays low while returns are small, rises around the burst at t = 7, 8, and then declines as phi pulls the latent process toward its long-run mean. The final comparison checks that this run assigns higher posterior mean volatility to the burst than to the surrounding period. This is a qualitative demonstration, not a substitute for convergence diagnostics on a real time series.

Richer versions of this model add fat-tailed observation noise, leverage effects, or multivariate volatility, and are estimated from real asset returns with particle MCMC or variational methods (Kim, Shephard & Chib 1998); this page keeps the state space small enough to run comfortably in the browser.

References: