TradingView Backtesting

Block Bootstrap for Trading Returns: Preserve Local Dependence

Block bootstrap resamples consecutive stretches of returns to retain some local time dependence. Moving blocks have a fixed length; stationary bootstrap uses random geometric lengths and circular continuation. Both create artificial joins. The block length, endpoint rule and statistic determine what the resampling preserves, so retaining nearby observations is not a guarantee of valid inference.

See the decision guide

Keep the time unit and target statistic fixed

Use consecutive, non-overlapping returns on a declared calendar. Here the target is the arithmetic mean daily return, r̄ = Σrₜ / n, not compound growth, annualized return or maximum drawdown. An independent bootstrap samples individual observations and discards their time order. Blocks instead retain observed runs within each selected stretch.

  • For asset returns, obtain a consistent price series and calculate rₜ = Pₜ / Pₜ₋₁ - 1 with the chosen adjustment policy. TradingView chart-data export includes loaded chart data; check timestamps, coverage and missing observations.
  • For strategy returns, use account equity at regular boundaries, including open-position valuations, costs and an explicit treatment of cash flows. Irregular closed-trade percentages are not daily account returns.
  • Resampling consecutive trades answers a trade-order question. A block of five trades can span different calendar durations, so its length cannot be interpreted as five days.
  • For a portfolio, sample the same time indices across all asset columns. Sampling each asset separately destroys contemporaneous dependence.

Moving blocks and stationary bootstrap use different boundaries

Define n as the number of observations and ℓ as the fixed or expected block length. In a non-circular moving block bootstrap, the overlapping source blocks start at 1 through n - ℓ + 1. Draw starts independently with replacement, concatenate the blocks, then cut the result to n observations. This differs from partitioning history into disjoint blocks.

  • For stationary bootstrap, P(L = k) = p(1 - p)^(k - 1), k = 1, 2, ...; E[L] = 1 / p. At mean length 4, p = 0.25 and P(L = 1) = 25%. Some blocks exceed four observations.
  • The probability of no restart across three transitions at mean length 4 is 0.75³ = 42.1875%. Long-distance dependence is attenuated even though short runs survive.
  • With n = 8 and mean length 4, the expected number of generated block starts is 1 + 7 / 4 = 2.75. The final block is censored by the eight-observation output length, so realized average block length need not equal four.
  • Mean length 1 restarts at every step and becomes independent resampling. The word stationary describes the resampling construction; it does not establish stationarity of the input returns.
ConstructionBlock selectionEndpoint behavior
Moving blockFixed integer length ℓ; choose among n - ℓ + 1 overlapping blocks.No wrapping. Edge observations appear in fewer eligible blocks than interior observations.
Circular fixed blockFixed integer length ℓ; choose among all n starting indices.Continue from observation n to observation 1. Each source index has equal marginal weight.
Stationary bootstrapUniform starting index; continue with probability 1 - p or restart uniformly with probability p = 1 / ℓ.Continuation wraps n to 1. Block lengths are geometric before final truncation; ℓ is their mean.

An exact mean-return interval and block-length comparison

Take eight hypothetical daily returns, in order: -1%, -1%, 0%, 0%, 1%, 2%, 2%, 1%. Treat these as net account returns with no external cash flows. Their arithmetic mean is 4 / 8 = 0.50% per day. The table uses the complete conditional distribution of eight-observation resampled means, not a finite Monte Carlo run. Define each quantile as the smallest mean whose cumulative probability reaches 2.5% or 97.5%.

  • For fixed length 2, the seven source-block sums in percentage points are [-2, -1, 0, 1, 3, 4, 3]. Four draws give 7⁴ = 2,401 equally likely ordered block combinations. Sort their means; ranks ceil(0.025 × 2,401) = 61 and ceil(0.975 × 2,401) = 2,341 give -0.500% and 1.625%.
  • The moving-block center is not exactly the observed mean: four draws have expected total 4 × (8 / 7) percentage points, giving (4 × 8 / 7) / 8 = 4 / 7%. Endpoint weighting causes this shift. A larger sample may reduce its relative importance, but it must not be silently called an unbiased construction.
  • At fixed length 4, only 5² = 25 ordered combinations remain. The percentile endpoints are coarse. Stationary lengths 2 and 4 happen to share the same two quantiles here even though their distributions differ.
  • The wider length-2 moving interval does not identify a correct length. Eight observations are too few to justify the inferential assumptions; this is an arithmetic comparison of resampling rules. A percentile interval is for the mean parameter under those assumptions, not the range of the next daily return.
MethodFixed or mean lengthBootstrap expected meanNominal 95% percentile interval
Independent (either method at ℓ = 1)1 day0.5000%[-0.250%, 1.250%]
Moving block, no wrap2 days fixed0.5714%[-0.500%, 1.625%]
Moving block, no wrap4 days fixed0.6000%[-0.500%, 1.500%]
Stationary, circular continuation2 days on average0.5000%[-0.375%, 1.375%]
Stationary, circular continuation4 days on average0.5000%[-0.375%, 1.375%]

Reproduce the table without simulation noise

This Python 3 example uses only the standard library and exact rational probabilities. Moving blocks accumulate the distribution of return sums; stationary bootstrap accumulates probability by last source index and sum. Integer returns are percentage-point units, so a printed 0.5 means 0.5%, not 50%. The code is designed for this small discrete dataset; ordinary longer series need a suitable bootstrap implementation, a recorded random seed and enough replications to assess quantile stability.

Exact conditional means and percentile endpoints for the eight-return teaching samplepython
from collections import defaultdict
from fractions import Fraction as F

# Hypothetical daily returns in percentage points, in time order.
r = [-1, -1, 0, 0, 1, 2, 2, 1]
n = len(r)


def moving(length):
    if not isinstance(length, int) or not 1 <= length <= n:
        raise ValueError('Fixed block length must be an integer in [1, n]')
    starts = range(n - length + 1)
    dist = {0: F(1)}
    for filled in range(0, n, length):
        take = min(length, n - filled)
        nxt = defaultdict(F)
        for total, prob in dist.items():
            for start in starts:
                nxt[total + sum(r[start:start + take])] += prob / len(starts)
        dist = nxt
    return dist


def stationary(mean_length):
    if not isinstance(mean_length, int) or mean_length < 1:
        raise ValueError('This example requires an integer mean length >= 1')
    p = F(1, mean_length)
    states = {(i, r[i]): F(1, n) for i in range(n)}
    for _ in range(n - 1):
        nxt = defaultdict(F)
        for (last, total), prob in states.items():
            following = (last + 1) % n
            nxt[(following, total + r[following])] += prob * (1 - p)
            for start in range(n):
                nxt[(start, total + r[start])] += prob * p / n
        states = nxt
    dist = defaultdict(F)
    for (_, total), prob in states.items():
        dist[total] += prob
    return dist


def quantile(dist, q):
    cumulative = F(0)
    for total, prob in sorted(dist.items()):
        cumulative += prob
        if cumulative >= q:
            return F(total, n)
    raise ValueError('Invalid probability distribution')


def summary(dist):
    assert sum(dist.values()) == 1
    center = sum(F(total, n) * prob for total, prob in dist.items())
    lo, hi = (quantile(dist, q) for q in (F(1, 40), F(39, 40)))
    return tuple(float(x) for x in (center, lo, hi))


if __name__ == '__main__':
    print('Observed mean:', sum(r) / n)
    for length in (1, 2, 4):
        print('Moving', length, summary(moving(length)))
        print('Stationary', length, summary(stationary(length)))

Inspect every join, including the wrap from last to first

A block preserves observed adjacency only inside its copied stretch. The join between independently selected blocks is artificial. Circular methods also treat the final source observation as adjacent to the first. This provides equal marginal sampling weights, but it does not make that historical transition real.

  • With n = 8 and fixed length 3, sampled starts 2, 6 and 1 produce source indices [2, 3, 4 | 6, 7, 8 | 1, 2]. The last block was cut after two observations. The joins 4 to 6 and 8 to 1 were not observed consecutive daily transitions.
  • A circular length-3 block starting at 7 is [7, 8, 1]. A stationary block can take the same route if neither continuation restarts. If the first and last dates belong to different regimes, this wrap can be particularly implausible.
  • Do not interpolate or blend boundary returns to make a smoother path; that changes the data and the resampling method. Record the chosen boundary convention and compare sensitivity to it.
  • Returns resampled after a strategy run condition on that fitted strategy and its realized history. They do not rerun indicators, fills, warm-up or position rules across artificial price joins. A price-path strategy simulation requires a separate, explicitly defined experiment.

Treat block length as a sensitivity choice, not a universal setting

Choose plausible lengths before examining favorable intervals. Inspect dependence in the statistic-relevant series: signed returns for a mean study, and potentially squared returns for variance behavior. Document a short, central and longer choice in the same time unit, compare endpoints and centers, and retain results that contradict the preferred conclusion.

  • Short blocks cut dependence frequently. Long blocks leave fewer effectively distinct stretches and can make inference unstable. For fixed non-circular length n, every replicate is the original series and the interval collapses to the sample mean; that is a degenerate design, not certainty.
  • The arch automatic block-length selector estimates separate lengths for stationary and circular methods using autocovariances. Its output is a starting point under its criterion, not a universal number for a different frequency, statistic, sample size or market.
  • Classical validity requires suitable stationary, weakly dependent observations and statistic-specific moment and regularity conditions. Standard asymptotic constructions let block length increase with sample size while remaining small relative to the sample. Preserving a few correlations alone does not meet these conditions.
  • Structural breaks, changing leverage, severe tails, long memory and deterministic intraday seasonality can invalidate a naive pooled bootstrap. A stationary bootstrap cannot repair them simply by randomizing block lengths. Diagnose the data, justify transformations or regimes, and seek a method appropriate to that target.
  • Do not select the length that makes the lower endpoint positive. Resampling also does not correct strategy selection across many trials, look-ahead bias, missing costs or survivorship bias. Keep an untouched later evaluation period for the strategy decision.

Keep TradingView report review and block inference separate

TradingView exports each Strategy Report tab separately. The List of Trades CSV contains trades, while the Performance Summary export contains metrics. Backtest Deep Report imports trade records and computes supported net-profit and closed-trade equity drawdown results. Reconcile trades, initial capital and cost settings before interpreting its report.

  • The current report Monte Carlo samples individual net P&L values independently with replacement. It is not the moving-block or stationary bootstrap described here. Calculate these mean-return intervals separately from a suitable ordered return series.
  • Store the input timestamps, return definition, missing-data policy, bootstrap method, length grid, boundary rule, statistic and quantile convention with the result. For simulated estimates, also store the software version, seed and replication count.
  • Pine Script AI Coding Agent can help draft custom chart output from a stated formula. Specify confirmed-bar timing and the required equity or return definition, then compare exported values with independent calculations. Code generation does not establish the statistical validity of a bootstrap.
Where Pineify fits

Backtest Deep Report

Review supported profit and drawdown metrics from your TradingView trade CSV. Run the block-bootstrap calculation separately with a suitable ordered return series.

Also useful: Pine Script AI Coding Agent. Draft custom chart outputs with explicit formulas and timing, then reconcile their values before using them as research inputs.

Review exported trades

Frequently asked questions

Educational information, not investment advice. All return values are hypothetical. Bootstrap intervals depend on the data, method and assumptions; they do not guarantee future returns, statistical coverage or a limit on losses.

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.