TradingView Backtesting

Bipower Variation: Separate Continuous Volatility and Jumps

Bipower variation (BV) estimates continuous price variation by summing products of adjacent absolute intraday log returns and applying a normalization constant. Realized variance (RV) sums squared returns and includes jump variation. Their difference can estimate jump variation under suitable sampling and price-process assumptions; a positive difference alone does not establish a statistically significant jump.

See the decision guide

Define the returns, normalization and sample correction

Start with n + 1 positive prices P₀ through Pₙ on one equally spaced grid within a defined session. Let rᵢ = ln(Pᵢ/Pᵢ₋₁) in decimal units. For a standard normal variable Z, μ₁ = E|Z| = √(2/π), so μ₁⁻² = π/2. This rescales adjacent absolute products to the variance scale. Tauchen's lecture notes use the n/(n - 1) correction below.

  • The uncorrected estimator is common in theoretical definitions. Record which convention you use. The n/(n - 1) factor compensates for n - 1 products versus n returns; it does not correct every finite-sample bias.
  • Under independent zero-mean normal increments with constant variance v, E|rᵢ||rᵢ₋₁| = (2/π)v. The corrected sum therefore has expectation nv, matching expected RV in that model.
  • Do not subtract the sample mean or divide RV by n. This is variation accumulated over the session, not a centered sample variance per return.
  • Decimal returns produce decimal-squared variance. Multiplying returns by 100 multiplies RV and BV by 10,000. Take a square root before displaying volatility; do not subtract square roots to estimate jump variance.
Session variance, with n counting returns rather than pricestext
RV     = sum(r[i]^2, i = 1,...,n)
BV_raw = (pi / 2) * sum(abs(r[i]) * abs(r[i-1]), i = 2,...,n)
BV     = (n / (n - 1)) * BV_raw
D      = RV - BV
J      = max(D, 0)
Share  = J / RV, only when RV > 0

Why adjacent products respond differently to jumps

Barndorff-Nielsen and Shephard show that, in the relevant stochastic-volatility model with infrequent jumps, increasingly fine noise-free sampling makes RV approach integrated variance plus squared jumps, while normalized BV approaches integrated variance. An isolated jump enters RV as a squared return; in BV it multiplies neighboring returns that become small as the grid is refined. This is an asymptotic result, not exact removal of every large observation.

  • Several large neighboring returns can contribute heavily to BV. A price adjustment spread across bars need not behave like an isolated instantaneous jump.
  • D = RV - BV is a descriptive difference. Formal jump testing also needs a sampling-error estimate, an appropriate quarticity estimator and a stated test level. This page does not calculate a p-value.
  • Clipping D to zero gives the nonnegative proxy J. It does not prove that a session with J = 0 had no jumps. Retain the signed difference when auditing results.

Check the arithmetic with five hypothetical returns

Take r = [0.001, -0.002, 0.001, 0.010, -0.001]. These five invented log returns make the calculation inspectable; they are not historical prices or evidence of a jump. The four adjacent products sum to 0.000024, and the correction is 5/4.

  • RV = 0.001² + (-0.002)² + 0.001² + 0.010² + (-0.001)² = 0.000107.
  • BV_raw = (π/2) × 0.000024 = 0.0000376991. Corrected BV = (5/4) × BV_raw = 0.0000471239.
  • RV - BV = 0.0000598761. Calling this value a confirmed jump would overstate what five observations can establish.
  • Counterexample: five returns all equal to 0.001 give RV = 0.000005 and corrected BV = 0.0000078540. The negative difference is possible by construction; neither statistic is an upper bound for the other in a finite sample.
PairAbsolute-return product
r₁ and r₂0.001 × 0.002 = 0.000002
r₂ and r₃0.002 × 0.001 = 0.000002
r₃ and r₄0.001 × 0.010 = 0.000010
r₄ and r₅0.010 × 0.001 = 0.000010

Recalculate a real intraday archive at two sampling intervals

The linked Binance spot archive contains 288 five-minute BTCUSDT bars for 2024-01-01 UTC. Use the first bar's open of 42,283.58 USDT as P₀, followed by all 288 closes, ending at 44,179.55 USDT. Thus the first return is open-to-close and the rest are close-to-close. These are sampled transaction prices, not quote midpoints. Values below were independently calculated from that archive on 2026-10-18.

  • All three variance columns use decimal-squared units. Five-minute √RV is 1.7699% and √BV is 1.6565% for this day, without annualization.
  • The fifteen-minute series retains P₀ and every third close from the same archive. It sums three consecutive five-minute log returns before calculating each squared return or adjacent product.
  • The five-minute nonnegative difference divided by RV is 12.4003%; the fifteen-minute clipped share is zero. These are descriptive proxies, not verified percentages of true jump risk.
  • This observation demonstrates sampling sensitivity in one historical day. It does not establish which interval is statistically preferable or support a trading-performance claim.
Grid over the same UTC daynRVCorrected BVRV - BV
5 minutes2880.00031324940.00027440560.0000388438
15 minutes960.00037005250.0003918647-0.0000218122

Reproduce the archive calculation in Python

This standard-library example downloads the exact public archive, checks its SHA-256 fingerprint and rejects an incomplete timestamp grid or invalid price. The expected hash fixes the data version used above. An updated archive requires a fresh review; silently accepting changed data would break reproducibility. The timestamp check is specific to this 2024 file, whose times are milliseconds.

Python 3: real Binance spot archive; no TradingView account data requiredpython
import csv, hashlib, io, math, urllib.request, zipfile

def variation(prices):
    if len(prices) < 3:
        raise ValueError("Need at least three prices for two returns")
    if not all(math.isfinite(p) and p > 0 for p in prices):
        raise ValueError("Prices must be finite and positive")
    r = [math.log(b) - math.log(a)
         for a, b in zip(prices, prices[1:])]
    n = len(r)
    rv = math.fsum(x * x for x in r)
    pairs = math.fsum(abs(a) * abs(b) for a, b in zip(r, r[1:]))
    bv = (math.pi / 2) * (n / (n - 1)) * pairs
    difference = rv - bv
    share = max(difference, 0) / rv if rv > 0 else None
    return n, rv, bv, difference, share

filename = "BTCUSDT-5m-2024-01-01"
url = ("https://data.binance.vision/data/spot/daily/klines/"
       "BTCUSDT/5m/" + filename + ".zip")
with urllib.request.urlopen(url, timeout=30) as response:
    archive = response.read()
expected = "4d98aa7b6af0d8a6ee8ba89be8aad529a2bf7d93b109214cd1de810db0e10d85"
if hashlib.sha256(archive).hexdigest() != expected:
    raise ValueError("Archive version changed: review before calculating")
with zipfile.ZipFile(io.BytesIO(archive)) as zipped:
    text = zipped.read(filename + ".csv").decode("utf-8")
rows = list(csv.reader(io.StringIO(text)))
times = [int(row[0]) for row in rows]
if times != [1704067200000 + 300000 * i for i in range(288)]:
    raise ValueError("Missing, duplicate or out-of-order five-minute bars")
prices = [float(rows[0][1])] + [float(row[4]) for row in rows]
for stride in (1, 3):
    n, rv, bv, difference, share = variation(prices[::stride])
    print(f"{5 * stride}m n={n} RV={rv:.10f} BV={bv:.10f} "
          f"D={difference:.10f} share={share}")

Sampling and microstructure can dominate the difference

The noise-free limiting argument does not mean that the shortest available bar is best. Bid-ask bounce and discrete transaction prices can contaminate adjacent returns. Patton and Sheppard discuss subsampling and skipped-return versions of BV to reduce sensitivity to market microstructure and price movements spread across trades. Those are different estimator specifications, not options that can be mixed without changing the formula.

  • Do not join adjacent products across different sessions. Compute each session separately and then aggregate comparable variance estimates.
  • If every price is unchanged, RV = BV = 0 and the relative share is undefined. The Python function returns None for the share rather than dividing by zero.
  • For a trading rule, today's full-session BV becomes available only after the session ends. Use a completed prior session or a clearly defined partial-session statistic to avoid future-data leakage.
Data conditionConsequenceResearch handling
Very fine transaction samplingBid-ask bounce and rounding may affect both RV and BV.Compare several fixed grids on the same sessions; document the price source.
Sparse trades or stale pricesZero returns suppress adjacent products; a later update can look like a jump.Audit trade activity and stale runs before interpreting a large difference.
Missing observationsJoining surviving rows silently changes interval length.Flag the incomplete session; do not fill unknown returns with zeros.
Overnight closure or session boundaryA close-to-open return spans a different interval.Keep overnight changes separate from equally spaced intraday returns.
Split, futures roll or bad tickA mechanical discontinuity can mimic a price jump.Check adjustment and contract conventions against the source record.

Keep chart exports separate from strategy trade reports

TradingView documents CSV chart downloads through the upper-toolbar menu, and states that exports contain loaded chart data. Load the intended history before exporting, then check the CSV timestamps and closes. Use standard time bars with a recorded symbol, venue, interval, timezone, session and adjustment basis. Access to a particular history still depends on the data available to your chart.

  • A CSV containing only bar closes has one fewer close-to-close return than rows. Include a valid starting boundary price if you need the full session; do not prepend yesterday's close to an intraday-only calculation.
  • Daily OHLC lacks the ordered intraday prices that BV requires. Heikin Ashi closes describe a transformed series. Neither is a substitute for the intended observed price grid.
  • Backtest Deep Report imports strategy trades and calculates supported metrics such as net profit, profit factor and drawdown. Its current metric definitions do not include bipower variation or an RV/BV jump decomposition. Calculate these independently from market prices.
  • Pine Script AI Coding Agent can help draft a custom statistic after you specify the return grid, session reset and correction convention. Compare its completed-session output against an independent calculation before using it in a strategy.
Where Pineify fits

Backtest Deep Report

Review supported trade-performance metrics alongside your separate price-variation research. The report does not calculate bipower variation.

Also useful: Pine Script AI Coding Agent. Draft a custom chart statistic with explicit sampling and session rules, then reconcile its output against independent arithmetic.

Review exported trades

Frequently asked questions

Educational information, not investment advice. The five-return example is hypothetical; the BTCUSDT calculations use the named historical archive. Neither example is a trading backtest, a forecast or proof of a statistically significant jump.

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.