TradingView Backtesting

Permutation Entropy for TradingView: Ordinal Patterns and Ties

Permutation entropy measures how evenly ordinal patterns occur in a time series. It replaces each short vector with the ordering of its values, counts those patterns and calculates their Shannon entropy. A rising eight-point sequence has normalized entropy 0; the fixed rearrangement below has 0.742098 with m = 3 and delay = 1. Neither score measures return size, trade direction or profitability.

See the decision guide

Specify the vectors before calculating entropy

For N observations in chronological order, choose an integer embedding dimension m ≥ 2 and delay τ ≥ 1. Each vector is [x(i), x(i+τ), ..., x(i+(m-1)τ)]. Advance the start i by one observation. There are K = max(0, N-(m-1)τ) candidate vectors, each spanning (m-1)τ observation intervals. Delay changes the spacing inside a vector; it does not change the one-observation step between starts.

  • A label such as 201 means position 2 is smallest, position 0 is next and position 1 is largest. For [9, 10, 6], this is 201. A convention that writes the rank of each observation instead produces 120. Use one encoding throughout; consistent relabeling preserves the entropy but changes named counts.
  • For retained count M, estimate p(pattern) = count(pattern)/M. H = -Σ p ln(p), summing over positive counts, reports nats. Normalize with H_norm = H/ln(m!). The convention 0 ln(0) = 0 means absent patterns contribute nothing.
  • With M > 0, H_norm lies between 0 and 1. Zero means one observed pattern; one requires equal frequency of all m! patterns. Dividing by ln(number of observed patterns) instead changes the statistic and hides unobserved possibilities.
SettingMeaningExample
N = 8Observations in the analysis windowEight ordered values
m = 3Values per embedded vector3! = 6 possible strict ordinal patterns
τ = 1Observation spacing within a vectorK = 8 - 2 = 6 vectors
τ = 2Every other observation within a vectorK = 8 - 4 = 4 vectors
Time-order tiesSort equal values by their earlier position[2, 2, 1] becomes 201

Count a trend and a fixed shuffle of the same values

Use the invented trend T = [1, 2, 3, 4, 5, 6, 7, 8] and control S = [4, 1, 7, 3, 8, 2, 6, 5]. They have exactly the same values, mean and variance. Only temporal order differs. With m = 3 and τ = 1, all six T vectors have pattern 012. The six S vectors can be checked individually below.

Start (one-based)S vectorPositions sorted ascendingPattern
1[4, 1, 7]1, 0, 2102
2[1, 7, 3]0, 2, 1021
3[7, 3, 8]1, 0, 2102
4[3, 8, 2]2, 0, 1201
5[8, 2, 6]1, 2, 0120
6[2, 6, 5]0, 2, 1021

Check all six counts and the normalization

The counts below sum to six in each column. For S, two patterns have probability 2/6 and two have probability 1/6. Thus H = -2(2/6)ln(2/6) - 2(1/6)ln(1/6) = 1.329661 nats. Dividing by ln(6) = 1.791759 gives H_norm = 0.742098. For T, H = -1 ln(1) = 0.

  • Changing only τ to 2 gives S vectors [4,7,8], [1,3,2], [7,8,6], [3,2,5]. Their patterns are 012, 021, 201 and 102, each once: H = ln(4) = 1.386294 and H_norm = 0.773706. This is a different timescale and a smaller sample.
  • The control is a fixed rearrangement for reproducibility, not a claim that a random shuffle must yield entropy 1. One shuffle is not a null distribution or a p-value. A surrogate test needs a stated null model and repeated realizations; individual-value shuffling destroys serial dependence.
  • A strictly falling sequence also has entropy zero, because every vector is 210. Entropy alone cannot tell rising from falling. Keep the pattern distribution if direction matters.
PatternTrend T countShuffle S count
01260
02102
10202
12001
20101
21000

Ties can determine the result

Tick sizes, rounded indicator values and unchanged closes create exact ties. The default here orders equal values by time, with the earlier position first. That is a declared convention, not evidence that one equal observation is economically smaller. The implementation also supports dropping entire tied vectors, without deleting observations or closing gaps in the series.

  • In [1,1,2,3,2], [1,1,2] and [2,3,2] contain ties. Dropping them leaves only [1,2,3]. The resulting zero comes from a single retained vector, so it cannot support a claim of regular dynamics.
  • Report candidate K, retained M and tied-vector count. A constant series produces zero under time-order ties and no estimate under drop-ties. Converting the unavailable result to zero would erase this distinction.
  • Random jitter inserts artificial order among equal values. If used, document its scale, seed and sensitivity. An alphabet with explicit equality symbols is another estimator; its possible-pattern count and normalization must be defined separately.
  • Equality here means exact equality of the supplied finite values. Introducing a near-tie tolerance or rounding before the calculation changes the input rule and must be reported.
Input, m = 3, τ = 1Time-order countsH_normDrop-ties result
[5,5,5,5,5,5,5,5]012: 60.000000M = 0; undefined
[0,0,1,0,0,1,0,1]012: 2, 021: 2, 102: 1, 120: 10.742098M = 0; undefined
[1,1,2,3,2]012: 2, 021: 10.355245012: 1; M = 1; H_norm = 0

Compare sample entropy using a fixed matching distance

Sample entropy asks whether nearby amplitude templates remain nearby after one extension. It does not count ordinal-pattern frequencies. For this comparison, use length m_s = 2, Chebyshev distance max|x(i+k)-x(j+k)| ≤ r, absolute r = 2, unordered distinct pairs and the common six starts that have a third observation. Let B count length-2 matches and A their length-3 extensions; SampEn = -ln(A/B). The parameter m_s is distinct from the permutation dimension m = 3.

  • For S, B pairs (one-based starts) are (2,4), (2,6), (3,5) and (4,6). Only (4,6) fails extension, so A/B = 3/4. This supplies a count audit as well as the final number.
  • Multiplying by 10 preserves every ordering but makes every pair too far apart under the unchanged r = 2. PE ignores this amplitude change; fixed-distance SampEn does not. Scaling r by a standard deviation would define a different comparison.
  • With r = 1 on S, B = 2 and A = 0: SampEn is +∞. With r = 0.2, B = 0 and the ratio is undefined. Neither status is equivalent to high normalized PE. Sample entropy has no universal upper bound of 1.
Same control dataPE normalized, m = 3SampEn counts (A, B)SampEn, r = 2
Trend T0.000000(9, 9)0.000000 nats
Shuffle S0.742098(3, 4)0.287682 nats
Trend T multiplied by 100.000000(0, 0)Undefined
Shuffle S multiplied by 100.742098(0, 0)Undefined

Run the counts in Python

This Python 3 example uses the standard library. It returns counts and status with both entropy values, rejects nonfinite observations and invalid settings, and keeps every candidate start at its original position. It stores only observed patterns, so it does not allocate all m! possibilities. The sample-entropy helper reproduces the fixed-distance comparison above.

  • For an exported CSV, read the chosen numeric column with csv.DictReader, preserve chronological order and pass that list to permutation_entropy. Check timestamps and missing observations before calculating; this function cannot infer the session schedule of a chart from values alone.
  • For a trailing N-value estimate at index t, pass values[t-N+1:t+1] only once t ≥ N-1. The first output is available after the last observation in that window is known. Never use a centered window for a historical trading decision.
  • Sorting each vector costs about O(K m log m); the transparent sample-entropy pair loop costs O(N² m_s). Check large rolling workloads separately before using this reference implementation in repeated research runs.
Save as permutation_entropy.py and run python3 permutation_entropy.py. Counts omitted from the dictionary are zero.python
from collections import Counter
from math import isfinite, lgamma, log


def permutation_entropy(values, m=3, delay=1, ties="time"):
    if type(m) is not int or m < 2:
        raise ValueError("m must be an integer >= 2")
    if type(delay) is not int or delay < 1:
        raise ValueError("delay must be a positive integer")
    if ties not in ("time", "drop"):
        raise ValueError("ties must be time or drop")
    x = [float(v) for v in values]
    if not all(isfinite(v) for v in x):
        raise ValueError("Use finite observations without missing rows")
    candidates = max(0, len(x) - (m - 1) * delay)
    counts, tied = Counter(), 0
    for start in range(candidates):
        v = [x[start + k * delay] for k in range(m)]
        has_tie = len(set(v)) < m
        tied += int(has_tie)
        if has_tie and ties == "drop":
            continue
        pattern = tuple(sorted(range(m), key=lambda k: (v[k], k)))
        counts[pattern] += 1
    retained = sum(counts.values())
    result = dict(N=len(x), m=m, delay=delay, ties=ties,
                  candidates=candidates, retained=retained,
                  tied=tied, counts=dict(counts), H=None, normalized=None)
    if candidates == 0:
        return dict(result, status="insufficient_data")
    if retained == 0:
        return dict(result, status="no_untied_vectors")
    h = sum(-(c / retained) * log(c / retained) for c in counts.values())
    return dict(result, H=h, normalized=h / lgamma(m + 1), status="ok")


def sample_entropy_fixed(values, m=2, r=2.0):
    if type(m) is not int or m < 1:
        raise ValueError("m must be a positive integer")
    x, r = [float(v) for v in values], float(r)
    if not isfinite(r) or r < 0 or not all(isfinite(v) for v in x):
        raise ValueError("Use finite values and a nonnegative tolerance")
    a = b = 0
    if len(x) < m + 2:
        return dict(A=a, B=b, value=None, status="insufficient_data")
    for i in range(len(x) - m):
        for j in range(i + 1, len(x) - m):
            if max(abs(x[i+k] - x[j+k]) for k in range(m)) <= r:
                b += 1
                a += int(abs(x[i+m] - x[j+m]) <= r)
    if b == 0:
        return dict(A=a, B=b, value=None, status="no_matches")
    if a == 0:
        return dict(A=a, B=b, value=float("inf"), status="no_extensions")
    return dict(A=a, B=b, value=0.0 if a == b else -log(a / b), status="ok")


if __name__ == "__main__":
    trend = list(range(1, 9))
    shuffled = [4, 1, 7, 3, 8, 2, 6, 5]
    for name, x in (("trend", trend), ("shuffle", shuffled)):
        print(name, permutation_entropy(x))
        print(name, "SampEn", sample_entropy_fixed(x))
        print(name, "scaled SampEn", sample_entropy_fixed([10*v for v in x]))
    print("delay 2", permutation_entropy(shuffled, delay=2))
    for x in ([5]*8, [0,0,1,0,0,1,0,1], [1,1,2,3,2]):
        for tie_rule in ("time", "drop"):
            print("ties", permutation_entropy(x, ties=tie_rule))

Finite samples and overlapping windows constrain interpretation

The full alphabet grows factorially: m = 3 has 6 patterns, m = 5 has 120 and m = 7 has 5,040. At most min(M, m!) patterns can be observed, giving the arithmetic ceiling H_norm ≤ ln(min(M,m!))/ln(m!). With N = 100, m = 7 and τ = 1, M = 94 and the ceiling is about 0.532928 even before considering dependence. A low score in this setting can reflect sparse coverage.

  • The empirical distribution can miss rare patterns, so a zero count does not prove a pattern is impossible. Normalization puts the score on a fixed scale; it does not correct finite-sample bias or make different m values equally well estimated.
  • Adjacent vectors and trailing windows reuse data. K vectors are not K independent observations, even when the source observations are independent. A confidence calculation must respect the dependence; more overlapping outputs do not create proportionally more evidence.
  • Compare prespecified m, delay and window settings with their occupancy, tie fraction and retained count. There is no universal sufficient bar count. Choose settings on development data and keep later evaluation dates untouched.
  • Strictly increasing transformations preserve the pattern distribution when they preserve ties numerically. Large moves and tiny moves can therefore share PE. Pair entropy with amplitude and cost information before testing any trading hypothesis.

Use the right TradingView export and keep the report separate

TradingView chart-data export includes loaded chart and indicator values. Select one series, verify its timestamps, timeframe, session and price adjustments, and keep the original file. Strategy Report exports each tab separately: its List of Trades is an event-time record, not an equally spaced bar history. Trade P&L permutation entropy answers a different question from price or return permutation entropy.

  • Price levels describe the ordering of levels. Log returns ln(P(t)/P(t-1)) require positive prices and describe changes; N prices yield N-1 returns. A rising price series can have varying return patterns. Returns do not automatically make the process stationary.
  • Do not fill missing bars with unchanged prices merely to reach a target window: that creates ties. Removing missing rows also changes what a delay of one means. Specify gap and session treatment before comparing instruments or periods.
  • If PE becomes a strategy filter, freeze the observation series, m, delay, window, tie policy and threshold before later-date testing. A bar-close calculation becomes known at that close; evaluate orders with feasible timing, fees and slippage.
  • Pineify Backtest Deep Report parses trade timestamps, prices, quantity and P&L and provides supported metrics such as net profit and profit factor. The independent PE and SampEn calculations here are not built-in report fields. Use the report to inspect trades from a separately tested rule.
  • Pine Script AI Coding Agent can help draft custom code from the specified rules. Review the generated code, validate its syntax and compare exported values with these control counts. A compiled script still needs numerical and timing checks.
Where Pineify fits

Backtest Deep Report

Inspect supported performance metrics from a TradingView trade CSV after independently calculating and testing an entropy feature.

Also useful: Pine Script AI Coding Agent. Draft a custom series calculation from explicit pattern and timing rules, then verify its output against the reference counts.

Review Backtest Deep Report

Frequently asked questions

Educational information, not investment advice. All control sequences are hypothetical. Ordinal regularity and historical backtest performance do not establish future profitability.

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.