Volatility Estimators
All functions · nseries package
GARCHVariance
func (s Series) GARCHVariance(omega, alpha, beta float64) Series
GARCHVariance returns the GARCH(1,1) conditional variance of Bollerslev (1986) with fixed parameters and a zero mean. The receiver is a series of returns, normally log returns c.Log().Delta(1) (or c.LogRatio(c.Shift(1))), with index 0 the oldest bar. Bar 0 is not a return (Delta(1) writes 0 there), so s[0] is not read. The model has no mean term: pass demeaned returns for a constant-mean model. The parameters are the caller’s, for example from an offline fit; the library does no estimation.
With M = len(s), out the newly allocated result and h the conditional variance state, h is seeded with the unconditional variance omega / (1 - alpha - beta), evaluated as omega / ((1 - alpha) - beta); this is the forecast for bar 1 before any return is seen. Then, for each bar t = 1 .. M-1, with r = s[t]:
h = (omega + alpha r^2) + beta h
out[t] = h
Every product is rounded on its own and the additions run left to right, so no platform fuses a multiply and an add and every platform gives the same bits.
Storage convention: out[t] is the variance forecast for bar t+1, made at the close of bar t from the returns up to and including s[t]. Python’s arch (arch_model(r, mean=“Zero”, vol=“GARCH”, p=1, q=1)) and R’s rugarch store the variance for bar t at bar t, so out[t] corresponds to their value for the next bar, and out[M-1] to arch’s one-step forecast at the end of the sample. arch_model(…).fix(params) always computes its own backcast, so for a close match use arch’s GARCH().compute_variance on the returns from bar 1 (r[1:]) with backcast set to the seed (and variance bounds, which clamp only in extreme cases); arch’s sigma2[t] then matches out[t] to rounding (about 1e-15 relative), not bit for bit.
The default backcast of arch is data-driven (it reads the first 75 squared returns, a look-ahead), so values agree with arch only once the seed difference has decayed (by the factor beta per bar), unless arch is given the returns from bar 1 (r[1:]) and the seed as its backcast (through compute_variance; fix() accepts no backcast). The method is recursive, so a slice of the history that starts later differs from the full series by a seed difference that decays by the factor beta per bar.
The warm-up reads 0, not NaN: bar 0 is 0 and the first valid index is 1. Invalid parameters give aligned zeros, M values of 0. The parameters are valid when omega, alpha and beta are finite, omega is positive, alpha and beta are not negative, (1 - alpha) - beta is positive and the seed is finite. The positivity of (1 - alpha) - beta is the stationarity condition, which makes the unconditional variance exist; it is computed exactly so, not as alpha + beta < 1, which differs in the last bit for some values. The recursion does not recover from non-finite values: if s[t] is NaN or infinite at a bar t >= 1, or the state is not finite at bar t (an overflow), bars t to M-1 are NaN. A NaN at s[0] changes nothing.
The variance is in squared return units, and for t >= 1 a finite out[t] is at least omega. With percent returns (as arch recommends) omega scales by a factor of 1e4. Annualise a daily variance by multiplying it by 252 (or the caller’s count of bars per year), and the volatility by its square root.
The case alpha + beta = 1 with omega = 0 (IGARCH, the RiskMetrics exponentially weighted variance) is outside the domain, because the unconditional variance does not exist. For RiskMetrics in this same storage convention use the zero-mean recipe r.Mul(r).XAverageAlpha(0.06), and XVarianceAlpha for the demeaned form, with r from c.Log().Delta(1): both read bar 0 (a 0 seed whose weight decays by 0.94 per bar), so a NaN there, as from c.LogRatio(c.Shift(1)), makes every value NaN. With beta = 0 the model is the ARCH(1) model of Engle (1982).
Recipe for Chan’s signal: with v := r.GARCHVariance(omega, alpha, beta), the sign of v.Sub(r.Squared()) forecasts whether the next bar’s squared return is above today’s.
Sources: Bollerslev, “Generalized Autoregressive Conditional Heteroskedasticity”, Journal of Econometrics 31 (1986); Engle, “Autoregressive Conditional Heteroscedasticity with Estimates of the Variance of United Kingdom Inflation”, Econometrica 50 (1982); Engle and Mezrich, “GARCH for Groups”, Risk 9 (1996), for targeting; Kissell, Algorithmic Trading Methods (2nd ed., 2020), ch. 11; Chan, Machine Trading (2017), for the signal; Python arch.
GARCHVarianceTargeted
func (s Series) GARCHVarianceTargeted(alpha, beta, longVar float64) Series
GARCHVarianceTargeted returns the GARCH(1,1) conditional variance of Bollerslev (1986) with fixed alpha and beta, a zero mean and variance targeting (Engle and Mezrich 1996): omega = longVar (1 - alpha - beta), so the unconditional variance is longVar. The receiver is a series of returns, normally log returns c.Log().Delta(1) (or c.LogRatio(c.Shift(1))); bar 0 is not a return (Delta(1) writes 0 there), so s[0] is not read. The model has no mean term: pass demeaned returns for a constant-mean model.
longVar is the caller’s choice. To keep the result causal, compute it from bars before the first output you use, for example the sample variance of an earlier window; a variance of the whole sample, or of any window that ends later, looks ahead. The recursion is that of GARCHVariance, with omega evaluated as longVar times ((1 - alpha) - beta), rounded once, and the state h seeded with longVar, the forecast for bar 1 before any return is seen. With M = len(s), out the newly allocated result and r = s[t], for each bar t = 1 .. M-1:
h = (omega + alpha r^2) + beta h
out[t] = h
So out[t] is the variance forecast for bar t+1, made at the close of bar t from the returns up to and including s[t]. Python’s arch and R’s rugarch store the variance for bar t at bar t, so out[t] corresponds to their value for the next bar. The default backcast of arch is data-driven (it reads the first 75 squared returns, a look-ahead), so values agree with arch only once the seed difference has decayed (by the factor beta per bar), unless arch is given the returns from bar 1 (r[1:]) and longVar as its backcast (through compute_variance; fix() accepts no backcast). The method is recursive, so a slice of the history that starts later differs from the full series by a seed difference that decays by the factor beta per bar.
The warm-up reads 0, not NaN: bar 0 is 0 and the first valid index is 1. Invalid parameters give aligned zeros, M values of 0: alpha, beta and longVar must be finite, longVar positive, alpha and beta not negative, and (1 - alpha) - beta positive (stationarity). A subnormal longVar can make omega underflow to 0. The recursion does not recover from non-finite values: if s[t] is NaN or infinite at a bar t >= 1, or the state is not finite at bar t, bars t to M-1 are NaN. A NaN at s[0] changes nothing.
The variance is in squared return units. With percent returns (as arch recommends) longVar, and so omega, scales by a factor of 1e4. Annualise a daily variance by multiplying it by 252 (or the caller’s count of bars per year), and the volatility by its square root. See GARCHVariance for the RiskMetrics recipe, Chan’s signal and the sources.
GARCHVolatility
func (s Series) GARCHVolatility(omega, alpha, beta float64) Series
GARCHVolatility returns the forecast conditional standard deviation of the GARCH(1,1) model of Bollerslev (1986) with fixed parameters and a zero mean: math.Sqrt of GARCHVariance(omega, alpha, beta), bar by bar, in the units of the returns. The receiver is a series of returns, normally log returns c.Log().Delta(1) (or c.LogRatio(c.Shift(1))); bar 0 is not a return (Delta(1) writes 0 there), so s[0] is not read. The model has no mean term: pass demeaned returns for a constant-mean model.
As in GARCHVariance, the state is seeded with the unconditional variance omega / ((1 - alpha) - beta), and out[t] is the forecast for bar t+1, made at the close of bar t from the returns up to and including s[t]. Python’s arch and R’s rugarch store the variance for bar t at bar t, so out[t] corresponds to their value for the next bar. The default backcast of arch is data-driven (it reads the first 75 squared returns, a look-ahead), so values agree with arch only once the seed difference has decayed (by the factor beta per bar), unless arch is given the returns from bar 1 (r[1:]) and the seed as its backcast (through compute_variance; fix() accepts no backcast). The method is recursive, so a slice of the history that starts later differs from the full series by a seed difference that decays by the factor beta per bar.
The warm-up reads 0, not NaN: bar 0 is 0 and the first valid index is 1. Invalid parameters give aligned zeros, M values of 0: omega, alpha and beta must be finite, omega positive, alpha and beta not negative, and (1 - alpha) - beta positive (stationarity), and the seed must be finite. The recursion does not recover from non-finite values: if a return is NaN or infinite at a bar t >= 1, or the variance state is not finite at bar t, bars t to M-1 are NaN, and NaN stays NaN through the square root.
The result is in return units. Annualise a daily volatility by multiplying it by the square root of 252 (or of the caller’s count of bars per year). See GARCHVariance for the RiskMetrics recipe, Chan’s signal and the sources.
GarmanKlassVolatility
func (s Series) GarmanKlassVolatility(o, h, l, c Series, n int) Series
GarmanKlassVolatility returns the Garman-Klass range estimator of volatility over the trailing n bars (Garman and Klass 1980, Journal of Business 53(1)): the square root of the window mean of 0.5 (ln(h) - ln(l))^2 - (2 ln 2 - 1) (ln(c) - ln(o))^2, from the open o, high h, low l and close c of each bar. It assumes no drift and no opening gaps. Each value is the per-bar standard deviation of log returns, unitless and not annualised; annualise with .Mul(math.Sqrt(252)) for daily bars (256 or 260 are also used), or the square root of the bars per year. The receiver is not used.
o, h, l and c must have equal lengths; otherwise the result is an empty Series. For n < 1 or n > len(o) the result is len(o) zeros. Bars 0 .. n-2 are the warm-up and are +0, not NaN; the first valid value is at index n-1, and n = 1 gives the per-bar estimator. Trim or mask the warm-up (SetN(n-1, math.NaN())) before sizing positions on the result.
A bar with a zero, negative, NaN or infinite price has a NaN term, and exactly the windows that hold that bar are NaN; later windows recover. Bars are not validated otherwise: a bar with o or c outside [l, h], or with h < l, is computed as it is. On sound bars every term is >= 0, so a negative window mean needs such a bad bar; it gives NaN rather than 0, because a zero volatility would size an infinite position. Every value is exactly +0 when every bar of its window has h = l = o = c, positive and finite.
With o = c finite and positive on every bar the result equals ParkinsonVolatility(h, l, n) times sqrt(2 ln 2) to within 1e-14 relative, on every valid bar. It equals RogersSatchellVolatility / sqrt(2) on sound bars (l <= o, c <= h) only when every bar has o = c = l or o = c = h; otherwise it is larger. Each value costs O(n) and depends only on the bars of its own window, so the method is window-local and start-invariant.
ParkinsonVolatility
func (s Series) ParkinsonVolatility(h, l Series, n int) Series
ParkinsonVolatility returns the Parkinson range estimator of volatility over the trailing n bars (Parkinson 1980, Journal of Business 53(1)): the square root of the window mean of (ln(h) - ln(l))^2, times sqrt(1 / (4 ln 2)), from the high h and low l of each bar. It assumes no drift and no opening gaps. Each value is the per-bar standard deviation of log returns, unitless and not annualised; annualise with .Mul(math.Sqrt(252)) for daily bars (256 or 260 are also used), or the square root of the bars per year. The receiver is not used.
h and l must have equal lengths; otherwise the result is an empty Series. For n < 1 or n > len(h) the result is len(h) zeros. Bars 0 .. n-2 are the warm-up and are +0, not NaN; the first valid value is at index n-1, and n = 1 gives the per-bar estimator. Trim or mask the warm-up (SetN(n-1, math.NaN())) before sizing positions on the result: a zero volatility would size an infinite position.
A bar whose high or low is zero, negative, NaN or infinite has a NaN term, and exactly the windows that hold that bar are NaN; later windows recover. Bars are not validated otherwise: a bar with h < l is computed as it is and, as the squared log range is symmetric, gives the same value as the swapped bar. Every value is exactly +0 when every bar of its window has h = l, positive and finite. The log range is the difference of two logarithms, so on a nearly flat bar (a high within a few ulps of the low) it can be 0, or off by the logarithms’ rounding, rather than the tiny exact range.
ParkinsonVolatility(h, l, n) equals RogersSatchellVolatility(l, h, l, l, n) times sqrt(1 / (4 ln 2)) (the float64 0.60056120439322491) bit for bit, and, with o = c finite and positive on every bar, GarmanKlassVolatility equals this estimator times sqrt(2 ln 2) to within 1e-14 relative, on every valid bar. Each value costs O(n) and depends only on the bars of its own window, so the method is window-local and start-invariant.
RogersSatchellVolatility
func (s Series) RogersSatchellVolatility(o, h, l, c Series, n int) Series
RogersSatchellVolatility returns the Rogers-Satchell range estimator of volatility over the trailing n bars (Rogers and Satchell 1991, Annals of Applied Probability 1(4)): the square root of the window mean of (ln(h) - ln(c)) (ln(h) - ln(o)) + (ln(l) - ln(c)) (ln(l) - ln(o)), from the open o, high h, low l and close c of each bar. It allows a drift but assumes no opening gaps. Each value is the per-bar standard deviation of log returns, unitless and not annualised; annualise with .Mul(math.Sqrt(252)) for daily bars (256 or 260 are also used), or the square root of the bars per year. The receiver is not used.
o, h, l and c must have equal lengths; otherwise the result is an empty Series. For n < 1 or n > len(o) the result is len(o) zeros. Bars 0 .. n-2 are the warm-up and are +0, not NaN; the first valid value is at index n-1, and n = 1 gives the per-bar estimator. Trim or mask the warm-up (SetN(n-1, math.NaN())) before sizing positions on the result.
A bar with a zero, negative, NaN or infinite price has a NaN term, and exactly the windows that hold that bar are NaN; later windows recover. Bars are not validated otherwise: a bar with o or c outside [l, h], or with h < l, is computed as it is. On sound bars every term is >= 0, so a negative window mean needs such a bad bar; it gives NaN rather than 0, because a zero volatility would size an infinite position. Every value is exactly +0 when every bar of its window has h = l = o = c, positive and finite.
RogersSatchellVolatility(l, h, l, l, n) equals h.LogRatio(l).RMS(n) to within 1e-14 relative for n >= 2 (each term is then the squared log range), and ParkinsonVolatility(h, l, n) equals it times sqrt(1 / (4 ln 2)) bit for bit. Each value costs O(n) and depends only on the bars of its own window, so the method is window-local and start-invariant.
YangZhangVolatility
func (s Series) YangZhangVolatility(o, h, l, c Series, n int) Series
YangZhangVolatility returns the Yang-Zhang estimator of volatility over the trailing n bars (Yang and Zhang 2000, Journal of Business 73(3)), which allows both opening gaps and a drift. Over bars t-n+1 .. t it is the square root of Vo + k Vc + (1 - k) RS, where Vo is the sample variance (divisor n-1) of the n overnight gaps ln(o) - ln(previous c), Vc the sample variance of the n open-to-close log returns ln(c) - ln(o), RS the mean of the n Rogers-Satchell terms (see RogersSatchellVolatility) and k = 0.34 / (1.34 + (n+1)/(n-1)), which tends to 0.34/2.34 as n grows. This is TTR’s form: n bars plus the close before them, with demeaned sample variances. Each value is the per-bar standard deviation of log returns, unitless and not annualised; annualise with .Mul(math.Sqrt(252)) for daily bars (256 or 260 are also used), or the square root of the bars per year. The receiver is not used.
o, h, l and c must have equal lengths; otherwise the result is an empty Series. For n < 2 or n >= len(o) the result is len(o) zeros. Bars 0 .. n-1 are the warm-up and are +0, not NaN; the first valid value is at index n, as its window also needs the close before it. Trim or mask the warm-up (SetN(n, math.NaN())) before sizing positions on the result.
A bar with a zero, negative, NaN or infinite price has NaN terms, and exactly the windows that hold any of them are NaN; a bad close at bar p also spoils the gap of bar p+1, so it reaches the values up to bar p+n. Later windows recover. Bars are not validated otherwise: a bar with o or c outside [l, h], or with h < l, is computed as it is. Of the variance checks, only a negative total gives NaN, which needs such a bad bar; it is NaN rather than 0 because a zero volatility would size an infinite position. TTR also returns NaN when the Rogers-Satchell mean alone is negative; that extra NaN is not copied. Every value is exactly +0 when every bar of its window has h = l = o = c, positive and finite, and there are no gaps.
With o = c equal to one constant on every bar (no gaps and no open-to-close moves) the result equals sqrt(1 - k) times RogersSatchellVolatility to within 1e-14 relative, from bar n. Siroky’s modified Rogers-Satchell estimator (Stocks & Commodities 36(5), 2018) is Yang-Zhang with k = 0 and the gap variance replaced by the mean of the squared gaps; it is a recipe, not provided here. Close-to-close volatility is likewise a recipe: the sample standard deviation of the n-1 log returns r := c.LogRatio(c.Shift(1)) (Shift pads bar 0 with 0, so bar 0’s return is NaN), that is r.StdDevSample(n-1) for n >= 3; index n-2 is NaN, as its window holds bar 0’s return, and the values from n-1 match TTR’s close form, which uses n prices and n-1 returns. Each value costs O(n) and depends only on the bars of its own window, so the method is window-local and start-invariant.