Percentile Rank Node

The Percentile Rank node tells you where a value sits among its own recent values, on a scale from 0 to just under 100. It turns fixed thresholds into adaptive ones, like the classic squeeze filter: Bollinger width in its narrowest fifth.

NodeBy AlgoLiftUpdated 4 min read
On this page 6How it worksExample: the squeeze filterSettingsUse something else whenWhen it doesn't behaveGoing further

"ATR above 10" means one thing on ES in a sleepy August, something else in a selloff, and nothing at all on NVDA. Fixed thresholds age badly. The Percentile Rank node swaps them for relative ones: where does the current value sit among its own last N values? Near 0 it's as low as it's been lately, near 100 as high, and "below 20" reads as "lower than usual, by this market's own recent standards".

Takes
One series: an indicator, a price, a level, price action, a memory or another modifier
Gives
A rank from 0 to just under 100 against its last N values
Reach for it
A threshold should adapt to how the market has been behaving

How it works

On every bar it takes the last N values of its input, the bar your rules are reading included, and counts how many sit below the current one. That share, as a percentage, is the rank. With Period 100, a rank of 15 means 15 of the last 100 values were lower.

Because the current value is one of the N and never counts as below itself, the top of the scale is 100 × (N − 1) ÷ N, not 100: a new 20-bar high ranks 95, a new 100-bar high 99. So set a high threshold with the period in mind (above 90 works on 20 values; above 95 never fires on them). A value tied with everything in the window ranks 0.

The output has no unit, and that's most of its appeal. A rank of 20 means the same thing on ES points, NVDA dollars or an RSI, so one threshold travels across markets and across years without retuning.

The period does two jobs. It defines "lately" (120 bars of 5-minute data is ten hours, and the rank knows nothing older), and it sets the resolution: the rank moves in steps of 100 ÷ N, so on 20 values it jumps 5 at a time and on 100 values, 1 at a time.

Like every modifier it works on a series the engine builds before the first bar: an indicator or one of its outputs, Current Price (read as the close of each completed bar), a price-action output, an external data source, a level (Price History, Pivot Levels, Event Day or Bands), a Distance in standard deviations, a Since or Capture, a Crossover on the Bar close clock, or another modifier. Math nodes, Constants and State Machines won't connect.

Example: the squeeze filter

Bollinger Bands pinch together when a market goes quiet, and plenty of breakout traders want the first push out of that pinch. "Narrow" is the hard part. A band 30 points wide is tight for NQ in some weeks and perfectly ordinary in others.

On NQ 5m, add Bollinger Bands (20, 2) and set its output to Channel Width. Wire the width into a Percentile Rank with Period 120, and the rank into a Comparison: Less Than, Constant 20. That's the squeeze: the bands are narrower than they've been for roughly 80% of the last ten hours.

For the trigger, add a Crossover with Current Price on A and the Bollinger upper band on B, direction Above, clock Intrabar. AND the squeeze and the cross through a Logic node into a long Entry.

The Intrabar clock matters here. The squeeze is judged on the latest completed bar, and an intrabar cross fires inside the next one, so the filter still sees the quiet bar the breakout came out of.

Settings

Period
Default 20
How many values the current one is ranked against, from 2 to 999. The current bar is one of them.

Use something else when

How far from normal, not only where it ranks
A rank gives the order, a z-score gives the distance. Two values can both rank at the very top while one is barely ahead of the pack and the other is off the chart.
The lowest or highest value in N bars
If the question is "is this the narrowest bar of the last seven", a Rolling Min answers it exactly.
The condition only needs to have held recently
Since remembers when a condition last held, so "a squeeze at some point in the last 5 bars" is one node.

When it doesn't behave

My squeeze filter blocks the breakout it was built for

On the Bar close clock, the breakout bar is judged with its own width, and a big bar widens the bands at once. The rank can leap out of the bottom fifth on the very bar you wanted. Judge the break Intrabar as in the example, or look one bar back: put a Bars Ago Modifier (Value 1) between the rank and the Comparison. For a looser version, feed "rank below 20" into a Since and require its Bars since to be Less Than 5.

It jumps around in big steps

The period is short. With 20 values the rank can only land on multiples of 5, so thresholds of 11 and 14 select exactly the same bars. Use 100 or more when the threshold needs to be precise.

The rank of price sits near the top for days

That's a trend, and the rank is reporting it faithfully: in a steady rally most of the last N closes are below the current one. A rank of price tells you position, not stretch. For "unusually far from normal", rank something that comes back to a center (an RSI, a channel width) or use a z-score.

Going further

Ranks combine well with other unitless readings. A low rank of Bollinger width beside a low ADX/DMI describes a quiet, directionless market in two Comparisons that work unchanged on ES, NQ and the stocks. That makes them a fair test of whether an idea is about market behavior or about one instrument's price level.

Two numbers here invite tuning: the period and the threshold. Choose the threshold for what it means ("the narrowest fifth") before you look at results, and if you sweep the period, keep the steps wide. A squeeze that only works at exactly 17% is a squeeze that worked once.

Or ask the copilot