Portfolio construction and risk · Intermediate
Volatility targeting
Sizing positions by forecast risk to keep the risk carried roughly constant, and where the rule stops protecting.
Educational material only. Not investment advice.
Contents
Abstract
Volatility targeting sizes each position as target volatility divided by forecast volatility. The rule stabilises risk in each asset class studied, but raises the Sharpe ratio mainly where expected returns do not rise with volatility, as in equities and credit. Judged against a constant exposure with the same volatility, nearly a century of US data shows a modest gain on the market, too small to be statistically distinguished from zero, and a large, clear one on the momentum factor.
Key takeaways
- Returns are hard to forecast but volatility is not, because it clusters; volatility targeting uses that to size positions.
- Choose one way to state the estimator’s memory (span, half-life or decay factor) and convert the others to it; an EWMA with a half-life of a few weeks is a sensible default.
- The rule reliably stabilises risk and reduces extreme losses. It improves the Sharpe ratio mainly where expected returns do not rise with volatility, notably equities and credit.
- Judge it against a constant exposure with the same volatility, not against the unscaled asset.
- The rule reacts after the event, cannot see jumps or gradual declines, and is most exposed after long calm periods.
Before you start
- Daily returns, standard deviation and annualisation by the square root of time
- Leverage, and how it scales both return and risk
- Indicators and the 200-day moving average, for the backtest conventions and the realised-volatility function
Volatility targeting sizes a position in inverse proportion to its forecast volatility, so that the risk carried stays roughly constant: less exposure when an asset is turbulent, more when it is calm. It works because volatility clusters and is far easier to forecast than returns. The risk control it delivers is dependable. The improvement in risk-adjusted return is not, and depends on the asset. There are also conditions, such as sudden jumps and slow, quiet declines, in which the rule offers little protection at all.
The rule
Choose a target volatility, forecast the asset’s volatility, and size the position as their ratio:
Here σtarget is the annualised volatility the strategy aims to run at, σt is the annualised forecast made at the close of day t − 1 from returns up to that close, and L is a cap on leverage. With a 10% target and an asset running at 20%, the rule holds half a unit of exposure; if the asset calms to 8%, it holds one and a quarter, subject to the cap.
The rule relies on an asymmetry in what can be forecast. Tomorrow’s return is very hard to predict; tomorrow’s volatility is substantially more predictable, because volatility clusters: large moves tend to follow large moves and quiet days tend to follow quiet days. Mandelbrot documented the pattern in commodity prices in the 1960s, and it is among the best-established regularities in financial returns.1,2 Volatility targeting uses that forecasting power for the one decision it can inform: the size of the position.
Scaling by 1/σ holds volatility constant. Scaling by 1/σ2, the weight a mean-variance investor would choose if expected returns did not change, varies exposure much more aggressively; that is the form studied by Moreira and Muir, discussed below.3
Forecasting volatility
Most of the design choices concern the forecast in the denominator. Every estimator trades responsiveness against noise: one that reacts quickly to a new regime also reacts to random fluctuations, and each reaction is a trade that must be paid for.
Rolling window
The root mean square of the last n daily returns (a standard deviation measured about zero), annualised by multiplying by the square root of the number of trading days in a year: 252 today, and about 294 before 1952, when US markets also opened on Saturdays (the real-data code counts them from the calendar). It is simple to explain and to audit. (On real data the laboratory uses the ordinary standard deviation about the mean, the realised_vol function listed in Indicators and the 200-day moving average. The two differ little except for the shortest windows, where subtracting the mean of a handful of returns adds noise: a 5-day window misses the next month’s volatility by a typical factor of 1.74 about the mean and 1.65 about zero, on the measure of Figure 3.) Its weakness is that every day in the window carries equal weight and then drops out at once, so the estimate jumps when an old shock leaves the window, weeks after anything actually happened.
Exponentially weighted moving average (EWMA)
Each day the daily variance estimate vt moves a fixed fraction of the way towards the latest squared return:
Recent days count most and old days fade smoothly, so there is no cliff when a shock ages out. The memory can be described in three equivalent ways, and this note uses them consistently: the decay factor λ; the span s, with λ = 1 − 2/(s + 1); and the half-life, the number of days after which a return’s weight has halved, ln 0.5 / ln λ. J.P. Morgan’s RiskMetrics document popularised λ = 0.94 for daily data,4 a half-life of about 11 trading days. That value was calibrated for one-day value-at-risk. For sizing positions a longer memory is usually preferable, because it trades less: a half-life of roughly two to six weeks (about 10 to 30 trading days) is a reasonable range, and the laboratory below defaults to a span of 60 days, a half-life of 21.
def sessions_per_year(index: pd.DatetimeIndex) -> pd.Series:
"""Trading sessions in the year up to each date: the number that turns a daily volatility
into an annual one AT THAT TIME.
252 is right only for the modern calendar. Before 1952 US markets also traded on Saturdays
(about 294 sessions a year), and a forecast annualised with the square root of 252 then
understates annual volatility. Counted over the trailing 365.25 days, which uses only dates
already seen; before a full year has passed, the sessions seen so far are scaled up to a year
(counting the rest of the first year would use dates not yet seen).
"""
days = index.values.astype("datetime64[D]").astype(np.int64)
lo = np.searchsorted(days, days - 365.25, side="right")
n = (np.arange(len(days)) - lo + 1).astype(float)
elapsed = days - days[0] + 1
n = np.where(elapsed <= 365.25, n * 365.25 / elapsed, n)
return pd.Series(n, index=index)
def ewma_vol(ret: pd.Series, span: int = 60, periods=None, seed_days: int = 20) -> pd.Series:
"""Annualised EWMA volatility with decay lambda = 1 - 2 / (span + 1).
v_t = lambda * v_{t-1} + (1 - lambda) * r_t^2, seeded with the mean square of the first
`seed_days` returns. The mean is taken as zero, which costs little at daily frequency.
`periods` defaults to the sessions per year at each date (`sessions_per_year`), the same
calendar basis `backtest.stats` reports realised volatility on.
"""
periods = sessions_per_year(ret.index).to_numpy() if periods is None else periods
lam = 1.0 - 2.0 / (span + 1.0)
r2 = ret.pow(2).to_numpy()
v = np.empty_like(r2)
v[: seed_days] = np.nan
acc = r2[:seed_days].mean()
for t in range(seed_days, len(r2)):
acc = lam * acc + (1.0 - lam) * r2[t]
v[t] = acc
return pd.Series(np.sqrt(v * periods), index=ret.index)GARCH and related models
Engle’s ARCH model and Bollerslev’s generalisation, GARCH, make volatility clustering an explicit statistical model in which forecasts revert towards a long-run level.5,6 The GJR variant allows negative returns to raise volatility more than positive returns of the same size.7 An EWMA is the special case with no reversion. GARCH can therefore produce better multi-day forecasts; for a one-day-ahead sizing decision the two often differ little, and GARCH’s parameters must be estimated and can drift.
Range-based and intraday estimators
Closing prices discard most of what happens during the day. Estimators built from the daily open, high, low and close use more of it. Under idealised assumptions Parkinson’s high-low estimator is about five times, and Garman and Klass’s about seven times, as efficient as the close-to-close estimator;8,9 both are biased by discrete sampling and by overnight moves, which Yang and Zhang’s estimator is designed to handle.10 With intraday data, squared five-minute returns can be summed into a realised variance, and Andersen, Bollerslev, Diebold and Labys showed that realised volatility can be modelled and forecast directly.11 The five-minute interval is a compromise against market-microstructure noise. Corsi’s HAR model, a regression on daily, weekly and monthly realised volatility, is a simple and robust way to forecast it.12 Better inputs give a less noisy forecast for the same responsiveness, which reduces turnover.
| Estimator | Response to a new regime | Noise in the estimate | Data required |
|---|---|---|---|
| Rolling window | Slow, then drops shocks abruptly | High for short windows | Daily closes |
| EWMA | Smooth; speed set by the half-life | Moderate | Daily closes |
| GARCH family | Smooth, with mean reversion | Lower, if the model fits | Parameter estimation |
| Range-based | As for its window | Lower than close-to-close | Open, high, low, close |
| Realised (intraday) | Fast | Lowest, if microstructure noise is handled | Clean intraday data |
What it achieves, and where
Holding risk constant is, first, a risk-management device, and for most assets that is most of what it achieves. For some assets it also improves return per unit of risk. The evidence repays careful reading, because the headline results and the details differ.
Stable risk and a thinner left tail
Because volatility persists, a position scaled by a recent estimate carries much more stable risk than a fixed position: the realised volatility of a targeted strategy stays far closer to its target than the asset’s own volatility stays to its average. In the broadest study of the subject, Harvey and co-authors found that volatility targeting reduced the likelihood of extreme returns in every asset class they examined.13 This benefit is largely mechanical. It is also conditional: it depends on turbulence building up over several days, and it does not apply to a sudden jump that arrives while the position is large (see Failure modes).
When the Sharpe ratio improves
Whether volatility targeting also raises the Sharpe ratio depends on how an asset’s expected return moves with its volatility. If expected returns rose in proportion to variance, the square of volatility, a constant exposure would already be the best choice, and cutting exposure in turbulent periods would lower the Sharpe ratio. If they rose in proportion to volatility itself, the reward per unit of risk would be the same every day; holding risk constant captures it exactly and still beats a constant exposure. If expected returns do not rise with volatility, or fall, exposure is being cut precisely when the reward for bearing risk is lowest, and the Sharpe ratio improves by more. Even with a constant expected return part of the gain is mechanical, because the average of 1/σ exceeds 1/√(average of σ2).
In equities the evidence points to the favourable case. Black and Christie documented that stock prices and volatility tend to move in opposite directions, the so-called leverage effect;14,15 the name is partly a misnomer, since Christie found that financial leverage explains only part of it, and a competing explanation, volatility feedback, runs from higher volatility to lower prices through a higher required return.16 (The leverage effect is unrelated to the leverage cap in the rule above.) Moreira and Muir found that portfolios scaled by the inverse of recent variance earned positive and mostly significant alphas relative to the unmanaged versions of the market and several equity factors, because expected returns did not rise in step with variance.3 Earlier, Fleming, Kirby and Ostdiek showed that investors would pay a meaningful fee to time volatility in an asset-allocation setting,17 and Barroso and Santa-Clara found that scaling equity momentum by its own recent volatility largely removed its crashes and roughly doubled its Sharpe ratio in their sample.18
Harvey and co-authors, working across more than sixty assets, found Sharpe-ratio improvements for equities and credit, which they attributed to the leverage effect, and little change for government bonds, currencies and commodities.13 Their results are a reasonable baseline expectation.
The sceptical evidence
Two cautions apply to the stronger claims. Moreira and Muir’s scaling constant, and the combination of managed and unmanaged portfolios implied by their alphas, are estimated over the full sample. Cederburg, O’Doherty, Wang and Yan tested variance-managed versions of 103 equity strategies and found that, once those choices had to be made in real time, the managed versions did not systematically outperform.19 On balance, the evidence supports volatility targeting as a reliable way to stabilise risk and reduce the frequency of extreme losses, and as a conditional, asset-dependent way to improve the Sharpe ratio.
A worked example
A single day of arithmetic shows the mechanism, and why the rule reacts after the event. Take a 12% target and an EWMA forecast with λ = 0.94. The current daily volatility estimate is 1.00%, which annualises to 15.9%. The position is therefore 12% / 15.9% = 0.76 units of exposure.
The asset then falls 3% in a day. The position loses 0.76 × 3% = 2.27% of capital: the rule could not anticipate the move, so the first day of turbulence is always taken at the previous size. At the close the estimate updates:
That is a daily volatility of 1.22%, or 19.3% annualised, so the next day’s position becomes 12% / 19.3% = 0.62. One large day reduced exposure by about 18%, and exposure recovers only as subsequent calm days dilute the shock.
| Before the move | After the move | |
|---|---|---|
| Daily volatility estimate | 1.00% | 1.22% |
| Annualised (× √252) | 15.9% | 19.3% |
| Position for a 12% target | 0.76× | 0.62× |
Values computed from the formulas above.
A century of US data
The rule is easy to test on public data. The Kenneth R. French Data Library publishes the daily total return of the US stock market (the CRSP value-weighted index) and of the momentum factor back to 1926, with the one-month Treasury bill return. Applying the rule with a 12% target, an EWMA with a 60-day span, a 2× cap, a ±10% no-trade band and 5 bp per unit traded, from 5 May 1927 to 31 August 2026 (momentum from 3 September 1927), gives the following. Unused capital earns T-bills; the momentum factor is self-financing and holds no cash leg. The results are historical and hypothetical: a textbook rule applied to an index, not the returns of any account.20 The momentum factor is itself a paper portfolio: its own rebalancing costs and short-borrow fees are not charged here (only the 5 bp on each change in the overlay’s weight), and a weight of 1× in it means one unit long and one unit short, so the 2× cap corresponds to about 4× of gross stock positions. Its returns are therefore higher than an investor could have earned. The comparison with the matched exposure carries over better, although the targeted version, at an average weight of 1.34×, would have borne more of those costs.
| Market 1 × | Market, matched | Market, targeted | Momentum 1 × | Momentum, matched | Momentum, targeted | |
|---|---|---|---|---|---|---|
| Annual return | 10.2% | 8.4% | 9.8% | 5.7% | 5.2% | 11.9% |
| Annual volatility | 17.5% | 12.3% | 12.3% | 12.9% | 11.6% | 11.6% |
| Sharpe ratio | 0.46 | 0.46 | 0.57 | 0.49 | 0.49 | 1.04 |
| Worst drawdown | −84.1% | −70.9% | −52.5% | −72.3% | −67.8% | −38.4% |
| Worst day | −17.4% | −12.2% | −10.4% | −18.5% | −16.6% | −11.3% |
| Turnover a year | — | — | 1.8× | — | — | 1.7× |
Kenneth R. French Data Library, daily: the market 5 May 1927 to 31 August 2026, momentum 3 September 1927 to 31 August 2026. Market Sharpe ratios are of returns in excess of T-bills; momentum returns are long-short returns earning no interest on collateral, so their annual returns and drawdowns are not comparable with the market’s (their Sharpe ratios are, since both are returns in excess of cash). “Matched” is a constant exposure scaled after the fact to the targeted path’s volatility (0.70× for the market, 0.90× for momentum); a constant scaling leaves the Sharpe ratio unchanged, so the matched column’s equals the unscaled column’s by construction, and what it changes is return, volatility and drawdown. Annualised from the calendar (see Forecasting volatility).
Two results stand out, and both agree with the literature. On the market, the targeted rule beat the volatility-matched constant exposure on Sharpe ratio (0.57 against 0.46) and on drawdown. The gain, about 0.10, is modest relative to its uncertainty: resampling whole calendar years gives it a standard error of about 0.06, so a 95% interval runs from roughly −0.02 to 0.22. It includes zero: on its own the gain is suggestive, not significant at the 5% level. It is not concentrated in the famous crashes: summed by decade, the targeted rule’s excess return over the matched exposure was largest in the 1940s and 1950s and was negative in the 1930s, the 1970s and the 2020s, when volatility spiked and then fell before the rule had rebuilt its exposure. The improvement is a long-run average.
On momentum the effect is much larger and much clearer: against the matched exposure the Sharpe ratio roughly doubled (0.49 to 1.04, standard error of the gain about 0.08), close to the doubling Barroso and Santa-Clara reported (0.53 to 0.97) with a six-month realised-variance estimator and monthly rebalancing,18 and the worst drawdown fell from 68% to 38%, because momentum’s crashes arrive in high-volatility rebounds.21 (The unscaled factor’s worst drawdown lasted a decade, from 1932 to 1942; the momentum series in Market anomalies shows it.)
These are full-sample, single-path results with parameters chosen in advance rather than tuned; the laboratory below lets the reader vary them and see how much the conclusions depend on the choices.
The market’s worst drawdown in the table, and the targeted rule’s, both came in the decline that began in 1929.
- Step 1 of 4
The raw series
On 3 September 1929 the US market closed at its high. Held at exposure one, with dividends reinvested, it then lost 84.1% by 8 July 1932: the deepest drawdown in the record, and the one in the table’s first column.
- Step 2 of 4
The forecast
The rule’s forecast of annual volatility stood at 16% on the day of the high. The crash of October 1929 drove it to 78% by 15 November 1929, 6.5 times the 12% target.
- Step 3 of 4
The scaled series
The exposure held is set from the previous close’s forecast, and re-traded only outside the ±10% band: 0.69× at the high, and 0.16× by 15 November 1929, so much of the fall that followed was taken with a small position. When volatility subsided in the spring of 1930 the rule rebuilt its exposure to 0.73× by 21 April 1930, shortly before the next leg of the decline, and it shared in that leg.
- Step 4 of 4
The drawdowns compared
From the 1929 high the market at exposure one fell 84.1%. A constant 0.70× exposure, matched to the targeted strategy’s volatility over the whole century, fell 70.9%, and the targeted strategy 52.5%. Holding less on average accounts for 13.2 percentage points of the improvement; holding less when the forecast was high accounts for the other 18.4.
US market total return and one-month T-bills, Kenneth R. French Data Library, daily, January 1929 to December 1933. The rule of the table above: a 12% target, an EWMA forecast with a 60-day span, a 2× cap, a ±10% no-trade band and 5 bp per unit traded, each day’s exposure decided at the previous close. Every path is rebased to 1 on the market’s 1929 high. Without script, or with reduced motion, the figure shows its final state.
The whole backtest is two functions: the sizing rule with its band, and a loop that lags the weight, charges costs and holds cash.
def target_weights(forecast: pd.Series, target: float = 0.12, cap: float = 2.0,
band: float = 0.0) -> pd.Series:
"""w = min(cap, target / forecast), traded only when it moves more than `band` (a fraction of
the current weight) away from the weight held."""
raw = (target / forecast).clip(upper=cap)
held = np.full(len(raw), np.nan)
cur = np.nan
for i, x in enumerate(raw.to_numpy()):
if np.isnan(x):
continue
if np.isnan(cur) or abs(x - cur) > band * cur:
cur = x
held[i] = cur
return pd.Series(held, index=raw.index).fillna(0.0)def run(ret: pd.Series, weight: pd.Series, cash: pd.Series | None = None,
cost: float = 0.0, lag: int = 1) -> pd.DataFrame:
"""Daily P&L of holding `weight` in `ret`, the rest in `cash`.
lag=1: the weight decided at close t earns the return of t+1 (trade at the signal's close).
lag=2: trade one period later. lag=0: LOOK-AHEAD (the signal earns its own day's return).
cost: a fraction of capital charged on every unit of exposure traded (0.001 = 10 bp).
"""
w = weight.shift(lag).fillna(0.0)
cash = pd.Series(0.0, index=ret.index) if cash is None else cash.reindex(ret.index).fillna(0.0)
traded = w.diff().abs().fillna(w.abs())
pnl = w * ret + (1.0 - w) * cash - cost * traded
return pd.DataFrame({"ret": pnl, "weight": w, "traded": traded})def years_spanned(index: pd.DatetimeIndex) -> float:
"""Calendar years covered by a daily index, counting the last day in full."""
return ((index[-1] - index[0]).days + 1) / 365.25
def stats(pnl: pd.Series, cash: pd.Series, periods: float | None = None,
weight: pd.Series | None = None) -> dict:
"""The summary every table in the notes reports.
periods=None annualises from the calendar: observations per year are counted from the
dates, which matters for US data before 1952, when markets also traded on Saturdays.
"""
wealth = (1.0 + pnl).cumprod()
years = years_spanned(pnl.index) if periods is None else len(pnl) / periods
periods = len(pnl) / years
excess = pnl - cash.reindex(pnl.index).fillna(0.0)
vol = pnl.std(ddof=1) * np.sqrt(periods)
out = {
"cagr": wealth.iloc[-1] ** (1.0 / years) - 1.0,
"vol": vol,
"sharpe": excess.mean() * periods / (excess.std(ddof=1) * np.sqrt(periods)),
"max_dd": (wealth / wealth.cummax().clip(lower=1.0) - 1.0).min(),
"worst_day": pnl.min(),
}
if weight is not None:
out["time_in_market"] = weight.mean()
out["switches"] = int((weight.diff().abs() > 1e-12).sum())
out["turnover"] = weight.diff().abs().sum() / years
return outAn interactive laboratory
The laboratory runs the same computation in the browser, ported line for line from the Python above (the site’s build checks that the port reproduces the table). Choose the US market, the momentum factor, or twelve years (eleven after warm-up) of synthetic returns from a volatility-clustering process with fat tails, a stronger volatility response to falls than to rises and two one-day jumps that no forecast could anticipate. The synthetic series holds the expected return constant, so it isolates the mechanical effect of volatility clustering. In every case the weight for a day is set from returns up to the previous close.
The fair comparison is a constant exposure scaled to the same realised volatility as the targeted strategy (scaled after the fact, so it is a yardstick rather than a strategy anyone could have run). The asset at exposure one, at about 18% volatility (the US market here; the synthetic series is built around 18%), is simply a riskier position than a 12% target. The difference between the targeted strategy and the matched exposure is the value of timing the exposure. Across 40 synthetic histories with a 12% target, a 60-day span, a 2× cap and no band, the targeted strategy’s Sharpe ratio exceeded the matched constant exposure’s by +0.03 on average, with a standard deviation of 0.11, and was higher in 26 of the 40. Over eleven years, a difference of that size cannot be distinguished from zero on any single history, which is why the real-data results above, a century long, carry more weight. The synthetic history shown first is a median draw.
5 May 1927 to 31 August 2026 (99 years): targeting 12%, realised volatility came in at 12.3% and 63-day volatility stayed within a quarter of the target 83% of the time. Against a constant exposure with the same volatility, the Sharpe ratio was 0.57 versus 0.46 and the worst drawdown −52.5% versus −70.9%, for 1.8× turnover a year.
| 5 May 1927 to 31 August 2026 (99 years) | Exposure 1× | Constant, matched vol | Vol-targeted |
|---|---|---|---|
| Annual return compound | 10.2% | 8.4% | 9.8% |
| Annual volatility realised | 17.5% | 12.3% | 12.3% |
| Sharpe ratio excess over T-bills | 0.46 | 0.46 | 0.57 |
| Worst drawdown peak to trough | −84.1% | −70.9% | −52.5% |
| Worst day | −17.4% | −12.2% | −10.4% |
| Average exposure | 1.00× | 0.70× | 0.97× |
| Turnover a year one-way | — | — | 1.8× |
Kenneth R. French Data Library (Dartmouth): daily Fama–French factors and momentum factor, CRSP-based. mkt = Mkt-RF + RF (US market total return); rf = one-month T-bill; mom = momentum factor. Weights are decided at each close from returns up to that close and applied from the next day. The matched column is a constant exposure scaled after the fact to the vol-targeted path’s volatility: a yardstick, not a strategy anyone could have run. A constant scaling leaves the Sharpe ratio unchanged, so the matched column’s equals exposure 1×’s by construction.
Two properties recur. On the synthetic histories and on the market, realised volatility runs somewhat above the target: on the synthetic histories by about 5% of the target on average (a 12% target realising about 12.6%), and on the market 12.3% against 12%, because estimation noise in the denominator, fat tails and jumps all push in the same direction; a practitioner may set the target slightly below the level actually wanted. Both the forecast and the realised figure count the trading days in each year from the calendar; annualising with 252 throughout would understate the pre-1952 years and add roughly another quarter of a percentage point of overshoot. On momentum it lands slightly below (11.6%), because the 2× cap binds in its calmest stretches. And the stability of risk, visible in the middle panel, is far more reliable than any improvement in return.
Costs, turnover and leverage caps
Every change in the forecast is a trade, so the estimator’s noise becomes a cost. In the laboratory’s synthetic history with no band, an EWMA with a 10-day span turns over about 22 times capital a year (one-way), which costs roughly 112 bp of return a year at 5 bp per unit traded; a 120-day span turns over about 1.9 times.
A century of the US market shows the same trade-off, and how little accuracy a longer memory gives up. Holding the rule of the table fixed and changing only the estimator’s memory, the EWMA forecast the next month’s volatility best at a span of 40 days, off by a typical factor of 1.436. Between spans of 20 and 120 days its typical miss barely moved, from 1.447 to 1.457, while the rule’s turnover fell from 7.8 to 0.8 times a year, and from a span of 20 days upwards its Sharpe ratio after costs stayed between 0.55 and 0.58. The rolling window forecast less accurately than the EWMA at every memory tested. The best memory lengthens with how far ahead the forecast looks (20 days for the next week, 120 for the next quarter), and part of every miss is the noise in a single month’s realised volatility, which no forecast can remove.
The EWMA with a 60-day span (half-life 21 days) missed the next month’s volatility by a typical factor of 1.440 and made the rule turn over 1.8× a year. The most accurate span was 40 days (×1.436); short memories chase every bump and trade constantly, long ones remember a crisis for months after it has passed.
def forecast_error(forecast: pd.Series, ret: pd.Series, days_ahead: int = 21, periods=None,
start: int = 0) -> dict:
"""How well a volatility forecast predicted the volatility that followed.
The forecast in row t (made at the close of t) is compared with the realised volatility of
the next `days_ahead` returns, r_(t+1) .. r_(t+days_ahead): the root mean square about zero,
annualised with the sessions per year at t. The error of one forecast is ln(forecast /
realised), so a forecast twice too high and one half too low are equally wrong. Returns the
root mean square of that error (exp of it is the typical factor a forecast was off by) and
its mean (positive: forecasts ran above what followed). Rows before `start`, and the last
`days_ahead` rows, which have no complete future, are left out.
"""
periods = sessions_per_year(ret.index).to_numpy() if periods is None else np.broadcast_to(periods, len(ret))
r2 = pd.Series(ret.pow(2).to_numpy())
ahead = r2.rolling(days_ahead).mean().shift(-days_ahead).to_numpy() # mean of r_(t+1..t+days_ahead)^2
realised = np.sqrt(ahead * periods)
f = forecast.to_numpy()
ok = np.isfinite(f) & np.isfinite(realised) & (realised > 0) & (f > 0) & (np.arange(len(f)) >= start)
e = np.log(f[ok] / realised[ok])
return {"rmse": float(np.sqrt(np.mean(e ** 2))), "bias": float(np.mean(e)), "n": int(ok.sum())}Three practices keep turnover under control.
- Use a longer memory than intuition suggests. A fast estimator reacts sooner to a crash but also to every false signal. In the synthetic tests here, half-lives of a few weeks (the best was about two weeks) outperformed half-lives of a few days after costs.
- Trade only when the difference is material. A no-trade band, for example rebalancing only when the target exposure differs from the current one by more than 10% of the current position, removes most of the small adjustments that pay the spread for little benefit.
- Cap leverage, and consider a floor. In very calm markets the rule calls for high leverage precisely when volatility is most likely to rise from a low base. A cap limits this, and a minimum exposure can prevent a strategy from remaining almost flat for months after a single shock. The backtests here finance leverage at the T-bill rate; a real account pays a spread over it, and rebalancing the drift of prices back to the target weight is a further cost these tables leave out (small at these turnovers).
Failure modes
Volatility targeting reduces risk on average. It is not insurance, and its failures are predictable enough to plan for.
- It reacts after the event. The forecast is built from past returns, so the first day of a volatility spike is taken at the previous size, as the worked example shows. The rule protects against the second week of a crisis, not its first day.
- It cannot see jumps. A price gap after an announcement, or while the market is closed, occurs between two forecasts, and no stop order can execute inside it. Across 40 synthetic histories, the targeted strategy’s single worst day was worse than the matched constant exposure’s in 27, because a jump can arrive while exposure is elevated after a calm spell (in the median synthetic history the two were almost identical: −5.9% against −6.0%).
- Exposure is highest after calm periods. Low realised volatility produces the largest positions, and long calm periods can end abruptly. This is the case for a leverage cap and for scepticism about very low forecasts.
- Gradual declines pass unnoticed. A market that falls steadily in small steps can keep realised volatility low while prices fall a long way. The rule reads this as calm and remains fully invested.
- Similar rules may trade together. Many investors use related rules. After a shock they may all reduce exposure at once, which can add to the move they are responding to, and execution is likely to be worst when the rule is most active.
- Backtests can use information not yet available. Sizing a position with a volatility estimate that includes the same day’s return is look-ahead and flatters every result. Size on information available before the trade, and charge the cost of every adjustment.
Implementation checklist
- Choose one estimator and a moderate memory (an EWMA with a half-life of a few weeks is a sensible start), and do not tune it to the backtest.
- Compute the forecast from data up to the previous close, state when the trade executes, and record both.
- Set a leverage cap before looking at results, and a no-trade band sized to trading costs.
- Judge the rule first on realised volatility against target, drawdowns and the left tail, compared with a constant exposure of the same volatility, and only then on the Sharpe ratio.
- Stress-test it with jumps and gradual declines, the two situations it cannot detect.
- When running several strategies, decide whether to target each one, the portfolio, or both, and record why.
References
- Mandelbrot, B. (1963). The variation of certain speculative prices. Journal of Business, 36(4), 394–419. https://doi.org/10.1086/294632
- Cont, R. (2001). Empirical properties of asset returns: Stylized facts and statistical issues. Quantitative Finance, 1(2), 223–236. https://doi.org/10.1080/713665670
- Moreira, A., & Muir, T. (2017). Volatility-managed portfolios. Journal of Finance, 72(4), 1611–1644. https://doi.org/10.1111/jofi.12513
- J.P. Morgan / Reuters (1996). RiskMetrics: Technical document (4th ed.). Morgan Guaranty Trust Company, New York.
- Engle, R. F. (1982). Autoregressive conditional heteroscedasticity with estimates of the variance of United Kingdom inflation. Econometrica, 50(4), 987–1007. https://doi.org/10.2307/1912773
- Bollerslev, T. (1986). Generalized autoregressive conditional heteroskedasticity. Journal of Econometrics, 31(3), 307–327. https://doi.org/10.1016/0304-4076(86)90063-1
- Glosten, L. R., Jagannathan, R., & Runkle, D. E. (1993). On the relation between the expected value and the volatility of the nominal excess return on stocks. Journal of Finance, 48(5), 1779–1801. https://doi.org/10.1111/j.1540-6261.1993.tb05128.x
- Parkinson, M. (1980). The extreme value method for estimating the variance of the rate of return. Journal of Business, 53(1), 61–65. https://doi.org/10.1086/296071
- Garman, M. B., & Klass, M. J. (1980). On the estimation of security price volatilities from historical data. Journal of Business, 53(1), 67–78. https://doi.org/10.1086/296072
- Yang, D., & Zhang, Q. (2000). Drift-independent volatility estimation based on high, low, open, and close prices. Journal of Business, 73(3), 477–492. https://doi.org/10.1086/209650
- Andersen, T. G., Bollerslev, T., Diebold, F. X., & Labys, P. (2003). Modeling and forecasting realized volatility. Econometrica, 71(2), 579–625. https://doi.org/10.1111/1468-0262.00418
- Corsi, F. (2009). A simple approximate long-memory model of realized volatility. Journal of Financial Econometrics, 7(2), 174–196. https://doi.org/10.1093/jjfinec/nbp001
- Harvey, C. R., Hoyle, E., Korgaonkar, R., Rattray, S., Sargaison, M., & Van Hemert, O. (2018). The impact of volatility targeting. Journal of Portfolio Management, 45(1), 14–33. https://doi.org/10.3905/jpm.2018.45.1.014
- Black, F. (1976). Studies of stock price volatility changes. Proceedings of the 1976 Meetings of the American Statistical Association, Business and Economic Statistics Section, 177–181.
- Christie, A. A. (1982). The stochastic behavior of common stock variances: Value, leverage and interest rate effects. Journal of Financial Economics, 10(4), 407–432. https://doi.org/10.1016/0304-405x(82)90018-6
- Campbell, J. Y., & Hentschel, L. (1992). No news is good news: An asymmetric model of changing volatility in stock returns. Journal of Financial Economics, 31(3), 281–318. https://doi.org/10.1016/0304-405x(92)90037-x
- Fleming, J., Kirby, C., & Ostdiek, B. (2001). The economic value of volatility timing. Journal of Finance, 56(1), 329–352. https://doi.org/10.1111/0022-1082.00327
- Barroso, P., & Santa-Clara, P. (2015). Momentum has its moments. Journal of Financial Economics, 116(1), 111–120. https://doi.org/10.1016/j.jfineco.2014.11.010
- Cederburg, S., O’Doherty, M. S., Wang, F., & Yan, X. (2020). On the performance of volatility-managed portfolios. Journal of Financial Economics, 138(1), 95–117. https://doi.org/10.1016/j.jfineco.2020.04.015
- French, K. R. (n.d.). Data Library: Fama/French factors (daily) and momentum factor (daily). Tuck School of Business, Dartmouth College. mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
- Daniel, K., & Moskowitz, T. J. (2016). Momentum crashes. Journal of Financial Economics, 122(2), 221–247. https://doi.org/10.1016/j.jfineco.2015.12.002