TradingView Backtesting

Volatility of Volatility: Estimator Changes and Unstable Risk

Volatility of volatility describes variation in volatility itself. One descriptive measure is the standard deviation of a sequence of volatility estimates: two sequences can both average 20% annualized volatility yet have level standard deviations of 2 and 10 percentage points. The result depends on the volatility estimator, its window and how often it is sampled. Changing estimates do not by themselves prove that the underlying risk state changed.

See the decision guide

Three statistics that should not share an unlabeled column

Let v₁ through vₘ be nonnegative volatility estimates observed at equal intervals h. Let v̄ be their mean, dᵢ = vᵢ - vᵢ₋₁ and ℓᵢ = ln(vᵢ/vᵢ₋₁). This page describes each finite series using population denominators (ddof = 0). A mean of 0.20 denotes 20% annualized volatility when the input levels are annualized.

  • A sample variance uses m - 1 for levels and m - 2 for the m - 1 changes (ddof = 1). With eight levels its level SD is sqrt(8/7) times the descriptive SD. This correction does not remove serial dependence or volatility-estimation error.
  • Level dispersion ignores order. Change statistics depend on order. A steadily increasing volatility path has changing levels but can have zero variance of its constant increments; inspect mean change as well.
  • Squared increments averaged without centering are mean squared changes, not the centered change variance shown here. Report which calculation you use.
StatisticFormulaMeaning and units
Level dispersionSD(v) = sqrt[Σ(vᵢ - v̄)² / m]Spread around average volatility; same units as v. Multiply by 100 for volatility percentage points.
Change varianceVar(d) = Σ(dᵢ - d̄)² / (m - 1)Spread of changes over interval h; squared volatility units. The denominator counts m - 1 changes; it is still ddof = 0.
Log-change varianceVar(ℓ) = Σ(ℓᵢ - ℓ̄)² / (m - 1)Spread of proportional log changes over h; dimensionless squared log ratios. Requires strictly positive levels.

Same average volatility, different dispersion and timing

These are complete synthetic sequences of eight weekly observations of annualized volatility, not market measurements. All three average 20%. A and B differ in level dispersion; B and C contain the same levels in different orders. Each sequence produces seven one-week changes. Population statistics use the entire displayed sequence.

  • For A: v̄ = 0.20 and SD(v) = sqrt[8 × 0.02² / 8] = 0.02. For B and C: sqrt[8 × 0.10² / 8] = 0.10. These are 2 and 10 percentage points, not 2% and 10% relative changes.
  • B has changes +0.20, -0.20, +0.20, -0.20, +0.20, -0.20, +0.20. Their mean is 0.20/7, so centered variance is 0.04 - (0.20/7)² = 0.039183673.
  • C has six zero changes and one +0.20 change. Its variance is 0.04/7 - (0.20/7)² = 0.004897959. The same level SD therefore hides very different paths.
  • Change variance uses squared decimal volatility units. Multiply by 10,000 to express it in squared percentage points. Log-change variance is unchanged if every level is converted from decimals to percentages.
PathAll annualized volatility observationsLevel SDChange varianceLog-change variance
A18%, 22%, 18%, 22%, 18%, 22%, 18%, 22%2.000000 pp0.0015673470.039446917
B10%, 30%, 10%, 30%, 10%, 30%, 10%, 30%10.000000 pp0.0391836731.182317349
C10%, 10%, 10%, 10%, 30%, 30%, 30%, 30%10.000000 pp0.0048979590.147789669

The same returns produce different volatility-of-volatility readings

Use these twelve synthetic, equally spaced daily returns: +1%, -1%, +2%, -2%, +3%, -3%, +1%, -1%, +2%, -2%, +4%, -4%. Estimate daily volatility as the population SD of the last w returns, including their window mean. Sample the estimate every h observations, starting at endpoint w. No annualization is applied in this experiment.

  • The first four-return window has mean zero and variance (0.01² + 0.01² + 0.02² + 0.02²)/4 = 0.00025. Its daily volatility is sqrt(0.00025) = 1.581139%.
  • With w = 4 and h = 1, adjacent windows share three of four returns. With h = 4 they share none, but non-overlapping windows are not automatically independent when returns or risk states depend on the past.
  • The table changes both smoothing and the sampled endpoints/count. It is a sensitivity illustration, not a controlled estimate of the effect of overlap alone. For a window-length comparison on real data, also compare estimates at identical endpoint dates.
  • All rows use identical underlying returns. The lower four-day reading does not establish a more stable market; the estimator averages over a longer window. Keep w, h, dates and return definition with every reported number.
Window w / step hDaily volatility estimates (%)CountMean daily volLevel SD (pp)
2 / 11, 1.5, 2, 2.5, 3, 2, 1, 1.5, 2, 3, 4112.136364%0.881396
2 / 21, 2, 3, 1, 2, 462.166667%1.067187
4 / 11.581139, 2.061553, 2.549510, 2.384848, 2.236068, 1.920286, 1.581139, 2.384848, 3.16227892.206852%0.469129
4 / 41.581139, 2.236068, 3.16227832.326495%0.648656

Run both examples in Python

This complete Python 3 example needs only the standard library. Returns are decimal, equally spaced observations supplied in time order. The first calculation accepts already estimated volatility levels; the second constructs rolling volatility from returns. It rejects missing observations rather than silently deleting dates. At least three volatility levels are required here so that change statistics contain at least two observations.

All denominators use ddof = 0. The return example uses periods_per_year = 1, so its levels remain daily volatility. Endpoint numbers are one-based return counts.python
from math import isfinite, log
from statistics import fmean, pstdev, pvariance

# Decimal returns: 0.01 means 1%. Rows must be equally spaced.
def finite_values(values):
    if not values or any(x is None or not isfinite(x) for x in values):
        raise ValueError("Empty, missing, or non-finite observations")
    return list(values)

def rolling_vol(returns, window, step=1, periods_per_year=1):
    r = finite_values(returns)
    if (not isinstance(window, int) or window < 2
            or not isinstance(step, int) or step < 1
            or not isfinite(periods_per_year) or periods_per_year <= 0):
        raise ValueError("Invalid window, step, or annualization basis")
    if len(r) < window:
        raise ValueError("Insufficient return history")
    # Population SD within each window: denominator = window (ddof=0).
    scale = periods_per_year ** 0.5
    return [(end, pstdev(r[end-window:end]) * scale)
            for end in range(window, len(r) + 1, step)]

def describe_vol(levels):
    v = finite_values(levels)
    if len(v) < 3 or any(x < 0 for x in v):
        raise ValueError("Need >=3 nonnegative volatility observations")
    change = [b - a for a, b in zip(v, v[1:])]
    # Zero levels are valid; their log changes are undefined.
    log_var = None
    if all(x > 0 for x in v):
        log_change = [log(b) - log(a) for a, b in zip(v, v[1:])]
        log_var = pvariance(log_change)
    return dict(mean=fmean(v), level_sd=pstdev(v),
                change_var=pvariance(change), log_change_var=log_var)

if __name__ == "__main__":
    paths = {
        "A": [.18, .22, .18, .22, .18, .22, .18, .22],
        "B": [.10, .30, .10, .30, .10, .30, .10, .30],
        "C": [.10, .10, .10, .10, .30, .30, .30, .30],
    }
    for name, path in paths.items():
        print(name, describe_vol(path))
    returns = [.01, -.01, .02, -.02, .03, -.03,
               .01, -.01, .02, -.02, .04, -.04]
    for window, step in [(2, 1), (2, 2), (4, 1), (4, 4)]:
        pairs = rolling_vol(returns, window, step)
        stats = describe_vol([vol for _, vol in pairs])
        print("window, step:", window, step)
        print("end observation, daily vol %:",
              [(end, round(100 * vol, 6)) for end, vol in pairs])
        print("mean daily vol %, level SD in percentage points:",
              round(100 * stats["mean"], 6),
              round(100 * stats["level_sd"], 6))

Annualizing the input is different from scaling changes through time

If daily volatility estimates are converted to annualized levels by a common sqrt(252), their level SD also multiplies by sqrt(252), and their change variance multiplies by 252. Their log changes stay the same because the common factor cancels in each ratio. The weekly or daily observation interval h still needs a label.

  • There is no extra automatic sqrt(252) conversion for the SD of annualized volatility levels. It already has the units of those levels. Applying another factor changes the statistic without a stated model.
  • For a specified diffusion model, a short-interval variance of log changes divided by elapsed time can approximate a local variance rate under additional assumptions. Mean reversion, jumps, serial dependence and noisy estimates can invalidate that shortcut. It is not a universal annualization rule for the descriptive table.
  • In the Heston variance model dV = κ(θ - V)dt + ξ sqrt(V)dW, κ sets mean reversion, θ is long-run variance and W is Brownian motion. The state V is variance, while volatility is sqrt(V). The diffusion parameter ξ cannot be read directly from SD(sqrt(V)) or compared with a log-volatility diffusion coefficient without transforming the model and matching time units.
  • Square-root-of-time scaling is a convention justified for return aggregation under suitable variance and serial-covariance assumptions; it does not prove future risk scales that way. A 252-session convention is only an explicit input choice. Use a consistent calendar for the instrument and series; do not mix daily returns, weekly changes and yearly levels in one unlabeled number.

Separate estimation noise from a changing risk state

Even a process with constant true volatility produces different short-window volatility estimates as random returns enter and leave the window. Observed estimate dispersion mixes genuine variation and estimation error. In the decomposition v̂ = v + e, Var(v̂) = Var(v) + Var(e) + 2 Cov(v,e); subtracting an arbitrary noise allowance cannot identify the latent process.

  • Use a predeclared estimator and sample interval, inspect the full series, and rerun with longer windows and non-overlapping sampling. Report how many estimates remain. A stable conclusion across choices is useful evidence, but does not prove a structural state change.
  • For inference, simulate a constant-volatility null with the same sample length, return assumptions and complete estimation pipeline, or use a justified dependence-aware method. An iid test applied to overlapping window estimates overstates the available independent evidence.
  • High-frequency data add market microstructure noise and possible jumps. Vetter studies bias-corrected estimators of integrated volatility of volatility from spot-estimator increments. The simple descriptive SD here does not implement that estimator or its confidence intervals.
  • Cboe VVIX measures expected volatility associated with the 30-day forward price of VIX using VIX option prices. A historical SD of rolling return-volatility estimates cannot reproduce it. Implied, historical and fitted model measures need separate labels.

Handle zeros, gaps and too little history explicitly

Constant positive volatility levels have zero level SD and zero change variances. Zero volatility levels also permit level and absolute-change calculations, but their log changes are undefined. The example returns None for log-change variance whenever a zero occurs; it does not add an arbitrary epsilon.

  • A negative volatility value, NaN, infinity or missing observation is an input error. Correct or mark the affected segment before proceeding. Deleting a missing date silently changes the sampling interval.
  • Fewer returns than w cannot form a volatility window. With n returns, the code yields floor((n - w)/h) + 1 estimates; fewer than three cause describe_vol to reject the summary. Passing this minimum does not establish statistical reliability.
  • When using closing prices, preserve timestamps, corporate-action treatment and the simple-return or log-return convention. For account risk, use a consistently valued equity series and handle external cash flows; individual trade percentages do not supply equal daily account returns.
  • Low observed VoV can reflect smoothing, stale prices or a narrow sample. High observed VoV gives no direction forecast and is not by itself a trading signal.

Keep the custom statistic separate from the backtest report

Pineify Backtest Deep Report imports TradingView trade CSV data. Its report includes trade P&L, drawdown, Sharpe, Sortino and other supported metrics. The current equity curve adds trade net P&L at exit timestamps, and rolling statistics contain Sharpe, Sortino and win rate. These fields do not supply a built-in volatility-of-volatility result or a complete regularly sampled market-return series.

  • Reconcile the CSV trades, initial capital, dates and costs in Backtest Deep Report. Separately obtain the timestamped price or mark-to-market equity observations needed for your chosen volatility estimator.
  • Use the Python calculation as an independent research artifact. Save its inputs, window, sampling step, denominator and output beside the report instead of relabeling an existing report field as VoV.
  • For a later custom TradingView implementation, give the Pine Script AI Coding Agent the same estimator contract and boundary behavior. Compare its output with the synthetic cases before testing a rule on data excluded from its design.
Where Pineify fits

Backtest Deep Report

Inspect supported trade and risk metrics from a TradingView CSV. Calculate volatility of volatility separately from a suitable time series.

Also useful: Pine Script AI Coding Agent. Specify the volatility estimator, observation interval and missing-data rules before requesting a custom implementation.

Explore Backtest Deep Report

Frequently asked questions

Educational information, not investment advice. All displayed sequences are synthetic. These descriptive statistics do not identify a latent volatility model, establish a trading edge or predict future performance.

Sources and verification

Published by Pineify. Pineify is an independent product and is not affiliated with TradingView. Sources support the technical explanations; examples do not establish trading performance.