TradingView Backtesting

Wavelet Analysis for Trading Research: Scale and Boundary Effects

Wavelet trading research studies how variation at different scales changes through time. A wavelet coefficient describes a local group of observations, so it can locate a short burst that a whole-window spectrum would summarize across the sample. That location is not necessarily a tradable timestamp: centered analysis and reconstructed offline curves can depend on future observations. Test the calculation on a known signal, inspect boundary assumptions, and record when each value actually becomes available.

See the decision guide

Read scale and time together

A continuous wavelet transform compares the series with shifted, stretched wavelets. A scalogram displays coefficient magnitude or power against time and scale, sometimes with scale converted to frequency. Longer wavelets summarize longer spans and give less precise timing. Torrence and Compo describe the scale-to-frequency relation and finite-sample edge effects; neither a large coefficient nor a bright patch establishes a forecast.

  • This page uses the discrete Haar transform because every coefficient can be checked by hand. It forms adjacent sums and differences, then repeats the operation on the sums. It produces dyadic scales and fewer coefficients at each level, rather than a continuous scalogram.
  • Haar detail level j uses blocks of L = 2^j samples. Under idealized dyadic band labels its frequencies are approximately fs/2^(j+1) to fs/2^j, but Haar filters have broad, overlapping responses. A coefficient is not an isolated Fourier frequency or an exact cycle length.
  • A whole-window Fourier transform describes frequency content over that window. Wavelets add a scale-dependent time location. Short-time Fourier analysis also localizes time, using its chosen window. Choose the representation to answer the research question, not to assume one transform predicts better.

Build a signal whose local pattern is known

Use equally spaced observations indexed t = 0,...,15. Set x(t) = 2 × (-1)^floor(t/2) for t < 8 and x(t) = (-1)^t for t ≥ 8. The resulting values are [2,2,-2,-2,2,2,-2,-2,1,-1,1,-1,1,-1,1,-1]. These unitless, invented observations contain a four-sample square wave followed by a two-sample alternation. The switch at t = 8 is known by construction, not inferred from market prices.

  • For adjacent inputs u and v, define approximation a = (u+v)/sqrt(2) and detail d = (u-v)/sqrt(2). Keep this sign and normalization throughout. Repeat on the approximation vector, never on the detail vector, for this ordinary DWT.
  • At level j, coefficient k spans original indices kL through (k+1)L-1. Its midpoint is kL+(L-1)/2; its earliest availability is after index (k+1)L-1. Plotting at the midpoint does not move its availability backward.
SegmentValuesPeriod within segmentSum of squares
t = 0...7[2,2,-2,-2,2,2,-2,-2]4 samples32
t = 8...15[1,-1,1,-1,1,-1,1,-1]2 samples8
Complete sample16 observations; mean = 0Changes at t = 840

Locate the change in the actual coefficients

The first four level-1 differences are zero because each early adjacent pair is equal. The last four equal sqrt(2) because each late pair is [1,-1]. At level 2, the first two differences are 4 and the last two are zero. The aligned change at index 8 makes this unusually clean; shifting the sample origin by one observation changes the decimated coefficients.

  • Use only the final approximation and all detail levels for energy accounting: ||x||² = ||A4||² + ||D4||² + ||D3||² + ||D2||² + ||D1||² = 40. A1 is an intermediate result; adding its energy again would double count.
  • For D2[0], the direct calculation is (x0+x1-x2-x3)/2 = (2+2+2+2)/2 = 4. It describes indices 0...3 and becomes available after observation 3, even though its midpoint is 1.5.
  • The inverse step is u = (a+d)/sqrt(2), v = (a-d)/sqrt(2). Retaining every coefficient reconstructs all 16 inputs. Zeroing D1 removes the late alternation entirely in this example; that is a deliberate information loss, not proof that it was noise.
OutputSupport per coefficientValues in time orderEnergy: sum of squares
D12 samples[0,0,0,0,1.414214,1.414214,1.414214,1.414214]8
A12 samples[2.828427,-2.828427,2.828427,-2.828427,0,0,0,0]32
D24 samples[4,4,0,0]32
A24 samples[0,0,0,0]0
D3, D4, A48, 16, 16 samples[0,0], [0], [0]0

Measure what padding invents at the endpoint

Stop after t = 8, leaving nine observed values. The last Haar pair starts with x8 = 1 but x9 is unknown. For that pair d = (1-q)/sqrt(2), where q is the chosen extension. Earlier complete pairs need no extension. These one-step padding choices follow the PyWavelets definitions; periodic wrapping here uses x0 = 2 and is different from its separate periodization mode.

  • With the known full sample, the later observation is -1. Substituting it into a signal recorded at t = 8 would leak future data. Replacing a provisional padded coefficient after t = 9 also changes the historical display unless that earlier snapshot is preserved.
  • For this aligned Haar example, completed blocks stay unchanged when more data is appended at the same origin. Do not generalize the endpoint problem into a claim that every wavelet coefficient repaints. Rolling the window origin, using longer filters, or reconstructing a centered smoothed series can change a larger region.
  • In a CWT, the cone of influence marks an edge-affected region under a specified wavelet and convention. It gets wider in time for longer scales. It is not a confidence interval, and padding does not supply the unobserved market values. Mask affected coefficients or report their sensitivity instead of treating them as confirmed evidence.
Ninth-observation calculationAssumed qLast detail dInterpretation
Zero padding00.707107An artificial zero follows the endpoint
Symmetric padding10.000000The endpoint repeats for this first padded value
Periodic padding2-0.707107The start of the sample follows its end
After x9 arrives-11.414214The pair is now observed

Define a causal feature with an explicit clock

A trailing Haar contrast of even length L is H_L(t) = (sum of the older L/2 observations minus sum of the newer L/2 observations)/sqrt(L), using exactly x(t-L+1),...,x(t). For dyadic L it equals the Haar coefficient of that trailing block. Evaluating it every bar creates overlapping contrasts, not the same downsampled coefficient series as the full-sample DWT.

  • This orientation makes a positive contrast mean that the older half has the larger sum. It is not an automatic long entry. Thresholds, trade direction, exits and sizing require a separate hypothesis.
  • If a centered finite filter needs m future bars, its result at t is available only at t+m. Timestamp the decision there. Simply moving its plotted label left creates an apparent early signal without executable information.
  • Replay prefixes: save the feature emitted at each close, append future observations, and compare the saved value with the historical value the implementation now displays. Test both the first valid window and late endpoints. Freeze normalization and thresholds on development data, then evaluate later dates with explicit order timing and costs.
  • Overlapping trailing blocks share observations. A smoother curve and many consecutive threshold hits are not independent confirmations. A causal filter can still lag, react to jumps, or discard useful variation.
FeatureObserved blockValueEarliest calculation
H4(7)[2,2,-2,-2]4.000000After observation 7 closes
H4(8)[2,-2,-2,1]0.500000After observation 8 closes
H4(9)[-2,-2,1,-1]-2.000000After observation 9 closes
H2(15)[1,-1]1.414214After observation 15 closes

Run the complete arithmetic without a wavelet library

The Python code below uses only the standard library. The multilevel transform accepts a finite, nonempty power-of-two sample and never pads it; the short prefix experiment makes the padding assumption explicit. The trailing function returns None during warm-up. All results are synthetic and contain no strategy orders or measured returns.

Haar DWT, inverse reconstruction, endpoint comparison and a causal trailing contrast. Indices are zero-based.python
from math import isfinite, sqrt


def finite_values(values):
    x = [float(v) for v in values]
    if not all(isfinite(v) for v in x):
        raise ValueError("Values must be finite")
    return x


def haar(values):
    a = finite_values(values)
    n = len(a)
    if n == 0 or n & (n - 1):
        raise ValueError("Length must be a positive power of two")
    details = []  # D1, D2, ...
    while len(a) > 1:
        d = [(a[i] - a[i+1]) / sqrt(2)
             for i in range(0, len(a), 2)]
        a = [(a[i] + a[i+1]) / sqrt(2)
             for i in range(0, len(a), 2)]
        if not all(isfinite(v) for v in a + d):
            raise ValueError("Transform overflow")
        details.append(d)
    return a, details


def inverse_haar(a, details):
    a = list(a)
    for d in reversed(details):
        if len(a) != len(d):
            raise ValueError("Coefficient lengths do not match")
        a = [v for u, w in zip(a, d)
             for v in ((u+w)/sqrt(2), (u-w)/sqrt(2))]
    return a


def trailing_haar(values, length):
    if type(length) is not int or length < 2 or length & (length-1):
        raise ValueError("Length must be a power of two, at least 2")
    x = finite_values(values)
    if len(x) < length:
        return None
    block = x[-length:]
    half = length // 2
    result = (sum(block[:half]) - sum(block[half:])) / sqrt(length)
    if not isfinite(result):
        raise ValueError("Contrast overflow")
    return result


x = [2 * (-1)**(t//2) if t < 8 else (-1)**t for t in range(16)]
a, details = haar(x)
for j, d in enumerate(details, start=1):
    print("D", j, [round(v, 6) for v in d])
print("energy", sum(v*v for v in a) +
      sum(v*v for d in details for v in d))
print("max reconstruction error",
      max(abs(u-v) for u, v in zip(x, inverse_haar(a, details))))
for label, q in (("zero", 0), ("symmetric", x[8]),
                 ("periodic", x[0]), ("observed", x[9])):
    print(label, round((x[8]-q)/sqrt(2), 6))
for t in (7, 8, 9):
    print("H4", t, trailing_haar(x[:t+1], 4))
print("H2", 15, trailing_haar(x, 2))

Carry the timing audit into TradingView research

Export regularly sampled chart observations for the wavelet calculation. Record the symbol, bar interval, session, time zone, adjustment policy and missing-data rule. TradingView chart-data export includes chart and indicator values from loaded data; its strategy export is a separate report workflow. A List of Trades file is not a uniformly sampled close series.

  • For positive prices, log returns ln(Pt/Pt-1) can remove the price unit, but the choice between levels, differences and returns changes the question. Do not silently fill gaps or treat irregular trade events as equally spaced clock time.
  • Compare exported feature values with the Python reference, including warm-up, transition blocks and the latest bar. A bar-close input becomes usable after that close. Specify the fill model separately and include fees and slippage when evaluating a frozen rule on later data.
  • Pineify Backtest Deep Report reads compatible TradingView trade CSV fields including timestamps, prices, quantity and P&L. Its implemented overview includes net profit, profit factor and drawdown. Use those fields to review the trades generated by your separately tested rule; this page does not claim a native wavelet transform, scalogram or endpoint audit in the report.
  • Pine Script AI Coding Agent can help draft a custom feature from an exact specification. Supply the trailing formula, sign convention, warm-up and availability time. Review the generated code and compare its values independently; a trade report cannot establish that the input signal was causal.
Where Pineify fits

Backtest Deep Report

Review exported strategy trades and supported performance metrics after independently checking your wavelet feature and its timing.

Also useful: Pine Script AI Coding Agent. Draft a custom causal feature from a precise formula, then compare the generated values with an independent reference.

Review Backtest Deep Report

Frequently asked questions

Educational information, not investment advice. All numerical examples are constructed signals, not market results. A transform, denoised curve or historical backtest does not guarantee future returns.

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.