We use essential cookies to run the site (session, security, and your theme/language preferences). With your permission we also load embedded third-party content, such as YouTube videos. Cookie Policy
The math behind markets, rates, and anything that drifts but always returns
Author(s):Elier Rodríguez García
Index
Introduction
Some quantities wander freely — stock prices climb or plunge with no ceiling pulling them back. Others seem anchored: they drift, sometimes far, but then snap back toward a familiar level. Interest rates do this. The spread between two correlated stocks does this. The velocity of a particle in a fluid does this.
The Ornstein-Uhlenbeck (OU) process is the simplest mathematical model of that anchored wandering. Proposed by Leonard Ornstein and George Uhlenbeck in 1930 to describe Brownian motion with friction, it turns out to describe an enormous range of phenomena.
The core idea is a restoring force: wherever the process is right now, it is being nudged — proportionally — back toward a long-run mean μ. Add random noise on top of that nudge, and you get a process that can stray but never wanders to infinity.
Unlike a pure random walk, the OU process has a well-defined stationary distribution. Leave it running long enough and it settles into a bell curve centered at μ. That property — stationarity — is what makes it useful anywhere you expect equilibrium.
Try It
Adjust the three parameters and watch the simulated path evolve in real time. The dashed line is the long-run mean μ; the shaded band is one standard deviation of the stationary distribution (±σ/2θ).
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// OU parameters: theta=reversion speed, sigma=volatility, mu=long-run mean
const DT = 0.05; // time step (Euler-Maruyama discretisation)
const N_STEPS = 200; // total steps in the simulated path
const PAD = { top: 20, right: 16, bottom: 28, left: 36 }; // canvas padding in pixels
let theta = 0.5, sigma = 0.5, mu = 0;
let path = []; // simulated path values
let animId = null; // requestAnimationFrame handle
let step = 0; // current step index
// Box-Muller transform: generates a standard normal sample
function randn() {
let u = 0, v = 0;
while (u === 0) u = Math.random();
while (v === 0) v = Math.random();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
// Euler-Maruyama step: dX = theta*(mu-X)*dt + sigma*sqrt(dt)*dW
function ouStep(x) {
return x + theta * (mu - x) * DT + sigma * Math.sqrt(DT) * randn();
}
function initPath() {
path = [mu]; // start the path at the long-run mean
step = 0;
}
// Draws the canvas: band, mean line, path, axes, y-labels
function draw() {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const innerW = W - PAD.left - PAD.right;
const innerH = H - PAD.top - PAD.bottom;
const statSd = sigma / Math.sqrt(2 * theta); // stationary standard deviation = sigma / sqrt(2*theta)
const yMin = mu - 3 * statSd;
const yMax = mu + 3 * statSd;
const yRange = yMax - yMin || 1;
function xPx(i) { return PAD.left + (i / (N_STEPS - 1)) * innerW; }
function yPx(y) { return PAD.top + (1 - (y - yMin) / yRange) * innerH; }
ctx.clearRect(0, 0, W, H);
// shaded band shows the stationary ±1 sd region ±1 stationary sd
ctx.fillStyle = 'rgba(29,53,87,0.07)';
ctx.fillRect(PAD.left, yPx(mu + statSd), innerW, yPx(mu - statSd) - yPx(mu + statSd));
// dashed horizontal line at the long-run mean mean line
ctx.setLineDash([6, 4]);
ctx.strokeStyle = '#1d3557';
ctx.lineWidth = 1.2;
ctx.beginPath();
ctx.moveTo(PAD.left, yPx(mu));
ctx.lineTo(PAD.left + innerW, yPx(mu));
ctx.stroke();
ctx.setLineDash([]);
// the simulated OU path in red OU path
if (path.length >= 2) {
ctx.strokeStyle = '#e63946';
ctx.lineWidth = 1.6;
ctx.beginPath();
ctx.moveTo(xPx(0), yPx(path[0]));
for (let i = 1; i < path.length; i++) ctx.lineTo(xPx(i), yPx(path[i]));
ctx.stroke();
}
// x and y axis lines
ctx.strokeStyle = '#adb1b8';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(PAD.left, PAD.top);
ctx.lineTo(PAD.left, H - PAD.bottom);
ctx.lineTo(W - PAD.right, H - PAD.bottom);
ctx.stroke();
// y-axis tick labels at mean ± 1 sd
ctx.fillStyle = '#666';
ctx.font = '10px system-ui';
ctx.textAlign = 'right';
for (const v of [mu - statSd, mu, mu + statSd]) {
ctx.fillText(v.toFixed(2), PAD.left - 4, yPx(v) + 4);
}
}
function animate() {
if (step >= N_STEPS - 1) {
const halfLife = Math.log(2) / theta;
document.getElementById('status').textContent =
i18nFmt('Done! Half-life of reversion: %hl% time units.', { hl: halfLife.toFixed(2) });
animId = null;
return;
}
path.push(ouStep(path[path.length - 1]));
step++;
draw();
animId = requestAnimationFrame(animate);
}
function runSim() {
if (animId) { cancelAnimationFrame(animId); animId = null; }
initPath();
draw();
animate();
}
function reset() {
if (animId) { cancelAnimationFrame(animId); animId = null; }
initPath();
draw();
document.getElementById('status').textContent = '';
}
// wire each slider to update the parameter and reset the chart wire sliders
function wireSlider(id, valId, setter, dec) {
const sl = document.getElementById(id);
const sp = document.getElementById(valId);
sl.addEventListener('input', () => {
const v = parseFloat(sl.value);
setter(v);
sp.textContent = v.toFixed(dec);
reset();
});
}
wireSlider('theta', 'theta-val', v => { theta = v; }, 2);
wireSlider('sigma', 'sigma-val', v => { sigma = v; }, 2);
wireSlider('mu', 'mu-val', v => { mu = v; }, 2);
document.getElementById('btn-run').addEventListener('click', runSim);
document.getElementById('btn-reset').addEventListener('click', reset);
initPath();
draw();
document.getElementById('status').textContent = 'Click "Run simulation" to start.';
Result
Console
Notice how increasing θ (the reversion speed) makes the path cling more tightly to the mean — large excursions are damped quickly. Increasing σ (volatility) widens the band and makes the path choppier, but the pull back is still there. Set θ near zero and the path becomes almost a pure random walk — drifting with no restoring force.
The Math
The OU process Xt satisfies the stochastic differential equation (SDE):
dXt=θ(μ−Xt)dt+σdWt
where θ>0 is the speed of reversion, μ is the long-run mean, σ>0 is the volatility, and Wt is standard Brownian motion.
Key results (all follow from solving the SDE exactly):
Exact solution: Xt=μ+(X0−μ)e−θt+σ∫0te−θ(t−s)dWs. The deterministic part decays exponentially toward μ; the stochastic part adds Gaussian noise.
Conditional mean: E[Xt∣X0]=μ+(X0−μ)e−θt. The process "remembers" its starting point, but that memory fades at rate θ.
Conditional variance: Var(Xt∣X0)=2θσ2(1−e−2θt), growing from 0 to σ2/(2θ).
Stationary distribution: as t→∞, Xt∼N(μ,2θσ2). The process is ergodic — time averages equal ensemble averages.
Half-life of reversion: the time for half the gap between X0 and μ to close on average is t1/2=ln(2)/θ.
The OU process is the only time-homogeneous process that is simultaneously Gaussian, Markov, and stationary. That uniqueness theorem (proved by Doob in 1942) explains why it appears in so many different fields under so many different names — the Vasicek model in interest rates, the Langevin equation in physics, the AR(1) process in discrete-time statistics.
Where It Matters
The restoring-force idea surfaces wherever a system has an equilibrium it keeps returning to:
Interest-rate modeling: the Vasicek model (1977) is the OU process applied to short-term rates — calibrated to market data, it prices bonds and interest-rate derivatives. Rates can rise or fall, but monetary policy acts as the restoring force.
Pairs trading / statistical arbitrage: two correlated stocks (say, two airlines) often move together. The spread between them is approximately OU. Traders buy the spread when it is wide (expecting it to narrow) and sell when it is tight — the entire strategy rests on mean reversion.
Physics — Brownian motion with friction: Ornstein and Uhlenbeck's original 1930 application. A particle in a fluid is pushed by random molecular collisions (noise) and slowed by viscous drag (restoring force). The velocity is exactly OU.
Biology and neuroscience: membrane potentials of neurons between spikes, population sizes around a carrying capacity, and hormone levels throughout the day all exhibit mean-reverting dynamics modeled by OU-type equations.
Commodities and energy: natural gas prices, electricity spot prices, and agricultural commodities are mean-reverting because high prices attract supply and depress demand, pulling prices back down.
Understanding the OU process is the entry point to a whole family of models — Hull-White rates, the Heston volatility model, and Bayesian inference on time series all rely on the same underlying math.
Conclusion
The Ornstein-Uhlenbeck process answers a deceptively simple question: what happens when randomness is paired with a restoring force? The answer — a Gaussian, ergodic, stationary process — turns out to be exactly what you need to model interest rates, particle velocities, market spreads, and biological rhythms.
Its three parameters are transparent: θ controls how fast the system "forgets" deviations from μ; σ controls how noisy the path is; and μ is the level everything gravitates toward. Calibrate those from data and you have a working model of almost any mean-reverting phenomenon.
The next time you see a spread, a rate, or a price that wanders but always returns — that is the OU process at work, quietly pulling everything back toward equilibrium. See also Bayesian inference for how such models are fitted to real data.
Comments
Loading comments...