TradingView Backtesting

Fourier Analysis for Trading: Sampling, Leakage and Nonstationarity

Fourier transform trading research measures how a finite sample projects onto sine and cosine components. A spectral peak describes that sample after specified preprocessing; it does not establish a persistent market cycle or predict the next price. Use trailing windows, state the sampling clock, and test how detrending and window choice change the result before designing a trading rule.

See the decision guide

A trend can dominate the finite-window spectrum

Construct x(t) = 10 + 0.05t + 2 cos(2π × 10.5t/128), for integer t = 0,...,127. These are invented values in arbitrary units, not observed prices. The oscillation has amplitude 2 and period 128/10.5 = 12.190476 bars. The table reports the largest non-DC bin, excluding k = 0, and the one-sided amplitudes from the executable code below. All values use the same 128 observations and six-decimal rounding.

  • Subtracting the mean removes the rectangular-window DC term but leaves the slope. The strongest nonzero bin still describes the trend across the record, not the constructed oscillation.
  • Linear detrending subtracts the least-squares line fitted inside this window. It can also remove part of a slow oscillation. Detrending does not guarantee stationarity, and the rectangular bin-20 amplitude actually increases here.
  • The 10.5-cycle tone falls between bins 10 and 11. Neither peak amplitude recovers 2 exactly. A corrected Hann spectrum still has off-bin amplitude loss, called scalloping.
  • The Hann-weighted residual need not have zero mean even when the unweighted residual does. Its small DC value is expected; it is excluded from peak selection.
PreprocessingWindowDC amplitudePeak bin kPeriod N/k (bars)Peak amplitudeAmplitude at k = 20
NoneRectangular13.1906251128.0000002.0487730.021703
Subtract meanRectangular0.0000001128.0000002.0487730.021703
Linear detrendRectangular0.0000001111.6363641.3000610.088153
Linear detrendPeriodic Hann0.0155841012.8000001.6977230.000772

Define the sampling clock before converting a bin to a period

For N equally spaced samples separated by Δt, the DFT grid is f(k) = k/(NΔt). The nominal spacing is Δf = 1/(NΔt), and period is 1/f for k > 0. With Δt = 1 bar and N = 128, bin 10 is 0.078125 cycles per bar, or 12.8 bars. Bin 0 is the constant component and has no finite period.

  • The Nyquist frequency is 1/(2Δt), or 0.5 cycles per bar. A cosine at 0.6 cycles per bar produces the same samples as one at 0.4. A larger FFT cannot recover information already lost to aliasing.
  • On consecutive 5-minute bars, 12.8 bars corresponds to 64 minutes of bar time. Across session closures, bar time is not continuous calendar time. State whether your clock counts observations, trading time or elapsed time.
  • TradingView chart-data export includes chart and indicator values from loaded data. Retain timestamps, symbol, chart interval, session, timezone, adjustment policy and export coverage before selecting a series.
  • Do not silently remove missing timestamps and then label the result regularly sampled elapsed time. Filling gaps inserts assumptions. Uneven observation times require an explicit resampling model or a method such as Lomb-Scargle that uses those times.

Normalize the windowed DFT for the quantity you report

For detrended samples y(n), calculate X(k) = Σ[n=0..N-1] w(n)y(n) exp(-i2πkn/N). This page uses the unscaled forward DFT convention documented by NumPy. A rectangular window has w(n) = 1. The periodic Hann used here is w(n) = 0.5 - 0.5 cos(2πn/N), matching SciPy hann(N, sym=False); its sum is N/2.

  • For N = 128 and y(n) = 2 cos(2π × 8n/128), without detrending, |X(8)| is 128 for a rectangular window and 64 for periodic Hann. Corrected amplitude is 2 in both cases. Dividing the Hann result by N would report 1.
  • Amplitude normalization uses the coherent sum Σw. Noise-density normalization uses Σw². Squaring the amplitude column does not turn it into a correctly scaled PSD.
  • This example uses amplitudes throughout. PSD, spectral entropy and significance tests answer different questions; do not compare their raw numeric values with these amplitudes.
QuantityOne-sided calculation for real inputMeaning
Interior-bin amplitudeA(k) = 2|X(k)| / ΣwSame units as the input; calibrates an isolated bin-centered cosine away from DC and Nyquist
DC and even-N Nyquist amplitudeA(k) = |X(k)| / ΣwDo not double bins that have no distinct negative-frequency partner
Power spectral densityS(k) = 2|X(k)|² / (fs × Σw²) for interior binsInput units squared per frequency unit; omit the factor 2 at DC and even-N Nyquist

A window trades leakage against separation of nearby components

A finite observation cuts a signal at its endpoints. If the periodic extension does not join smoothly, energy spreads across frequencies. Hann tapering lowers distant sidelobes but broadens the main lobe. The lower bin-20 value in the first experiment therefore does not mean Hann can separate arbitrarily close cycles.

  • With N = 128 and one sample per bar, grid spacing is 0.0078125 cycles per bar. The neighboring periods around 12 bars are 12.8 and 11.636364. Frequency bins are evenly spaced; period labels are not.
  • Appending zeros to reach 1,024 transform points samples the same finite-record spectrum on a denser grid of 1/1,024 cycles per bar. It supplies no extra observations and does not narrow the underlying main lobe.
  • Increasing the actual record length improves nominal frequency spacing but may combine different regimes. A shorter window localizes changes more closely while estimating low frequencies from fewer repetitions.
  • A peak at the lowest nonzero bin contains only one cycle across the record. It can reflect drift or a boundary effect. Its presence alone is weak evidence for a repeating mechanism.

Rolling peaks reveal a frequency change that one spectrum hides

Construct 384 observations z(t) = 20 + 0.02t + cos(2πt/P(t)), with P(t) = 16 before t = 192 and P(t) = 8 thereafter. Use trailing 128-point windows, fit the line within each window, and apply periodic Hann. The endpoint is included and is the latest available sample. The table reports two prespecified bins as well as the maximum, rather than suppressing the smaller component.

  • At endpoint 255, the leading amplitudes differ by only 0.000299. Calling the window a 16-bar cycle discards the nearly equal 8-bar component. Small changes in the data or preprocessing could change the selected winner.
  • Hann gives little weight to observations near the trailing window endpoints. A newly changed regime may take time to influence the estimate. A trailing calculation avoids future input but still has this timing tradeoff.
  • The construction is deliberately nonstationary. A spectrum computed over all 384 observations mixes both regimes and loses their location in time. Fitting it once and extending its waves forward is not a forecast validated by this experiment.
  • Adjacent rolling windows share observations. Repeated similar peaks across overlapping windows are not independent confirmations of a market cycle.
Window, inclusive indicesPeak binPeak period (bars)Amplitude k = 8 (16 bars)Amplitude k = 16 (8 bars)
0 to 1278161.0000000.000004
64 to 1918161.0000000.000004
128 to 2558160.5022140.501915
192 to 3191680.0000301.000000
256 to 3831680.0000301.000000

Run the experiments without external Python packages

Save this code as research.py and run python3 research.py. It prints both tables as JSON. The direct DFT costs O(N²) per window and makes the calculation visible; FFT algorithms calculate the same transform faster. This is a small research reference, not a high-throughput scanner. The synthetic construction removes random-seed and data-vendor differences.

  • spectrum rejects fewer than four samples, nonfinite observations and unknown preprocessing names. The four-sample minimum is an implementation boundary, not a statistically sufficient sample size.
  • peak returns None when all non-DC amplitudes are below 1e-10 in input units. That numerical tolerance suits these examples; scale it deliberately for other units. Near-tied peaks still need inspection.
  • For a longer implementation, compare an FFT result with this DFT on the same short inputs, including odd lengths, DC and the even-length Nyquist bin. Keep the window convention and normalization identical.
Python 3 standard library; fixed synthetic inputs; trailing windows include their endpoint.python
"""Finite-window spectral experiment. Python 3; no external packages."""
import cmath
import json
from math import cos, isfinite, pi


def spectrum(values, detrend="linear", window="hann"):
    x = [float(v) for v in values]
    n = len(x)
    if n < 4 or not all(isfinite(v) for v in x):
        raise ValueError("Need at least four finite real observations")
    if detrend not in ("none", "mean", "linear"):
        raise ValueError("Unknown detrending method")
    if window not in ("boxcar", "hann"):
        raise ValueError("Unknown window")
    center = (n - 1) / 2
    mean = sum(x) / n
    slope = (sum((i - center) * (v - mean) for i, v in enumerate(x))
             / sum((i - center)**2 for i in range(n)))
    y = [v if detrend == "none" else
         v - mean - (slope * (i - center) if detrend == "linear" else 0)
         for i, v in enumerate(x)]
    w = [1.0 if window == "boxcar" else
         0.5 - 0.5 * cos(2 * pi * i / n) for i in range(n)]
    amplitude = []
    for k in range(n // 2 + 1):
        z = sum(w[i] * y[i] * cmath.exp(-2j * pi * k * i / n)
                for i in range(n))
        multiplier = 1 if k == 0 or (n % 2 == 0 and k == n // 2) else 2
        amplitude.append(multiplier * abs(z) / sum(w))
    return amplitude


def peak(amplitude):
    # No non-DC peak is defined for a numerically flat residual.
    if max(amplitude[1:]) < 1e-10:
        return None
    return max(range(1, len(amplitude)), key=amplitude.__getitem__)


def experiment():
    n = 128
    x = [10 + 0.05*t + 2*cos(2*pi*10.5*t/n) for t in range(n)]
    finite = []
    for method, window in (("none", "boxcar"), ("mean", "boxcar"),
                           ("linear", "boxcar"), ("linear", "hann")):
        a = spectrum(x, method, window)
        k = peak(a)
        finite.append({"detrend": method, "window": window,
                       "DC": round(a[0], 6), "peak_bin": k,
                       "period_bars": round(n/k, 6),
                       "peak_amplitude": round(a[k], 6),
                       "bin_20_amplitude": round(a[20], 6)})
    changing = [20 + 0.02*t + cos(2*pi*t/(16 if t < 192 else 8))
                for t in range(384)]
    rolling = []
    for end in (127, 191, 255, 319, 383):
        a = spectrum(changing[end-n+1:end+1])
        k = peak(a)
        rolling.append({"start": end-n+1, "end": end, "peak_bin": k,
                        "period_bars": round(n/k, 6),
                        "amplitude_bin_8": round(a[8], 6),
                        "amplitude_bin_16": round(a[16], 6)})
    return {"finite_window": finite, "rolling": rolling}


if __name__ == "__main__":
    print(json.dumps(experiment(), indent=2))

Turn a spectral observation into a testable rule

A frequency estimate supplies neither an entry direction nor an execution price. Specify the input series, preprocessing, window length, eligible frequency band, amplitude threshold and order timing before examining later outcomes. Prices, detrended log prices and returns have different spectra; first differencing changes the frequency response and does not merely remove a price unit.

  • Compute the complete feature using data available through each decision time. Fitting a trend or choosing a peak on the full history and then backfilling earlier signals leaks future information.
  • Fit and choose settings on a development period, freeze them, and evaluate later observations. Searching many windows, frequencies and thresholds creates selection bias; retain the unsuccessful comparisons as part of the research record.
  • If the feature uses a completed bar close, an order must respect when that close becomes known. Model fees, spread, slippage and executable fills, then compare with a prespecified baseline.
  • Treat persistence as an empirical question. Market volatility, liquidity, jumps and sessions can change the spectrum. A fit on past observations cannot establish a fixed market period or a future return.

Use chart exports for spectra and trade exports for performance

TradingView exports each Strategy Report tab separately: List of Trades contains trade records, while Performance Summary contains metrics. Neither is a substitute for evenly sampled chart observations. A closed-trade P&L sequence measures event time with variable elapsed gaps, so a period of ten trades is not a period of ten bars.

  • Run the frequency calculations independently on the chart series. Keep the source CSV and its preprocessing alongside the results so the same window can be reconstructed.
  • After testing a fully specified strategy, Pineify Backtest Deep Report can parse exported trade timestamps, entry and exit prices, quantity and P&L, and calculate supported metrics such as net profit and profit factor. These trade fields cannot recreate the complete intratrade price path.
  • The Fourier calculations on this page are an independent experiment, not a claim that Backtest Deep Report has a built-in FFT or rolling-spectrum field.
  • Pine Script AI Coding Agent can help draft custom indicator or strategy code from an explicit specification. Compare generated values with this reference and validate order timing before relying on the implementation. No Pine Script source or trading signal is supplied by this Python experiment.
Where Pineify fits

Backtest Deep Report

Review exported strategy trades and supported performance metrics after testing a separately calculated spectral feature. The report does not supply the Fourier experiment shown here.

Also useful: Pine Script AI Coding Agent. Draft a custom implementation from a fixed research specification, then compare its values and timing with an independent reference.

Review Backtest Deep Report

Frequently asked questions

Educational research, not investment advice. Every numerical spectrum on this page uses synthetic data and is not trading performance. Spectral peaks do not guarantee stable market cycles, forecasts or profits.

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.