Rolling Percentile Indicator
- Göstergeler
- Sürüm: 1.0
1. What the indicator does
Rolling Percentiles draws three lines on the price chart. Each line is a percentile of the last N prices (the rolling window):
- Upper line (green): the upper percentile, 75th by default.
- Median (white, thick): the 50th percentile, always fixed.
- Lower line (red): the lower percentile, 25th by default.
It is a non-parametric channel. Where Bollinger Bands assume price is roughly bell-shaped and measure it with a mean and standard deviation, this indicator simply ranks the recent prices and reads off where the chosen percentiles sit. It makes no distribution assumption, and one extreme bar cannot drag the lines the way it drags a standard deviation.
2. How it is calculated
For every bar i the indicator does the following:
- Collects the selected price from the last InpPeriod bars, ending at and including bar i .
- Sorts those prices from lowest to highest.
- For each percentile p , computes the position pos = (p / 100) × (N − 1) .
- If pos falls between two sorted values, it interpolates linearly between them. Otherwise it takes the value at pos directly.
This is the same “linear” method used by pandas rolling().quantile() and NumPy’s default percentile. I reproduced the algorithm in Python and compared it with pandas on a 500-bar series: the largest difference was about 2×10⁻¹³, which is floating-point noise. The first InpPeriod − 1 bars have no values, exactly like pandas with a full-window requirement.
For reference, with the default window of 50 the 25th percentile sits at sorted position 12.25 and the 75th at position 36.75, so both are interpolated values and not actual prices.
3. Inputs
| Input | Default | Meaning | Valid range |
|---|---|---|---|
| InpPeriod | 50 | Rolling window size in bars. | Integer of at least 2 (see section 8) |
| InpP75 | 75.0 | Upper percentile. Despite the name, it can be set to 90, 95, 99, etc. | Above InpP25 , at most 100 |
| InpP25 | 25.0 | Lower percentile. | At least 0, below InpP75 |
| InpPriceType | PRICE_CLOSE | Price series ranked: Open, High, Low, Close, Median, Typical or Weighted. | Any ENUM_APPLIED_PRICE |
The median percentile is fixed at 50 and has no input.
Important: version 1.00 does not validate these inputs. Values outside the ranges above can stop the indicator with an array-out-of-range error. See section 8.
4. Choosing your settings
Percentile pair
| Pair | Band contains | Typical use |
|---|---|---|
| 25 / 75 | The middle 50% of prices (interquartile range) | Value area and equilibrium. Price is outside it about half the time, so it is a context tool, not a trigger. |
| 10 / 90 | Middle 80% | Balanced: frequent enough to be useful, selective enough to mean something. |
| 5 / 95 | Middle 90% | Stretch zones for fade or sweep setups. |
| 1 / 99 | Middle 98% | Near-extremes. With N = 50 these are almost the window high and low, so the channel behaves like a Donchian channel. |
Window length
| Window | Character |
|---|---|
| 20–30 | Fast, reacts to the current leg, noisier. |
| 50 (default) | Intraday swing context. |
| 100–200 | Structural range or dealing-range context, slower and smoother. |
The percentile of a window needs enough observations to be meaningful: below about 20 bars the 5th and 95th percentiles collapse onto the extreme values and add nothing.
Applied price
- Close: the default and the cleanest for closing-basis rules.
- Typical or Median: smoother, less sensitive to wicks.
- High and Low together: attach two copies, one on High (for the upper line) and one on Low (for the lower line), to build a range channel that respects wicks. This is a common configuration when you want liquidity-sweep references.
5. Installing and attaching
- Save RollingPercentiles.mq5 in your terminal’s MQL5\Indicators folder.
- Open it in MetaEditor and compile (F7). It must report 0 errors.
- Attach it to a chart from the Navigator. It draws in the main chart window, not a sub-window.
Color note: the median is white by default. On a light chart template it is invisible, so change it in the indicator’s Colors tab.
6. Reading the three lines
Median (equilibrium). The middle of recent price. Price above a rising median means buyers have controlled the window; price below a falling median means sellers have. The median is robust, so a single spike barely moves it.
Band width (volatility regime). The distance between the upper and lower lines is a volatility gauge. A narrowing band means compression; a widening band means expansion. Because percentiles ignore tails, the width responds to sustained volatility rather than to one outlier bar.
Position inside the window (premium and discount). Price above the median is trading in the upper half of the recent window (premium); below it, in the lower half (discount). This maps neatly onto an equilibrium framework.
Buffer indices for EA or iCustom use: buffer 0 is the upper percentile, buffer 1 is the median and buffer 2 is the lower percentile. Note that the upper line is index 0 and the lower line is index 2.
One property you must understand
The window includes the current bar, and the bands are percentiles of price itself. By construction, price spends roughly 25% of the time above the 75th percentile and 25% below the 25th. A close above the upper line is therefore common and is not a breakout signal on its own. Use a high percentile (95 or 99), or combine the lines with market structure.
7. Example trading frameworks
These are process templates, not signals. Every one of them requires confirmation from market structure and session context, and a clear invalidation.
A. Range fade at the extreme (ranging regime)
- Context: band width flat or contracting, median roughly flat, no break of structure on the higher timeframe.
- Setup: price sweeps beyond the 5th or 95th percentile line, takes obvious liquidity (equal highs or lows), and closes back inside the band.
- Entry: on the close back inside the band.
- Stop: beyond the sweep extreme plus 0.5 × ATR(14).
- Target 1: the median (close roughly half the position). Target 2: the opposite 25th or 75th line.
- Risk: fixed fractional, 0.25–0.50% of equity per trade. Skip the trade if the target is under 1.5R.
B. Trend continuation off the median (trending regime)
- Context: median sloping clearly, band width expanding, higher-timeframe structure making HH/HL (or LH/LL).
- Setup: pullback to the median in discount (long) or premium (short), with a CHoCH or an order-block reaction on the lower timeframe.
- Entry: on the confirmation candle’s close.
- Stop: beyond the pullback swing, or beyond the structural low or high, with ATR padding.
- Target: the 90th or 95th percentile line, then trail behind structure.
- Risk: 0.25–0.75% per trade, with hard daily and weekly loss limits. No averaging down.
Avoid: trading every touch of the 25/75 lines. The lines say where price is in its recent distribution, not why it will reverse.
8. Comparison with the nearest similar indicator
The nearest built-in MT5 equivalent is Bollinger Bands. It has the same structure: three lines, one window, one applied price, a centre line and an upper and lower envelope. Donchian Channel is the limiting case of this indicator (percentiles 0 and 100 are the window minimum and maximum). I confirmed that numerically in the pandas test: the 100th-percentile line equals the rolling maximum.
| Aspect | Rolling Percentiles | Bollinger Bands |
|---|---|---|
| Centre line | Median (50th percentile) | Moving average (SMA by default) |
| Envelope | Chosen percentiles of actual prices | Mean ± k × standard deviation |
| Distribution assumption | None | Roughly normal |
| Outlier sensitivity | Low | High: a spike inflates the deviation for the whole window |
| Band width meaning | Spread of the middle X% of prices | A multiple of dispersion |
| Fat tails (FX, gold) | Handles naturally | Bands under- or over-estimate real extremes |
| Coverage | Fixed by design (a 5/95 pair always holds 90% of the window) | Varies with the shape of the distribution |
| Window weighting | Equal weight, so lag is similar to an SMA | Equal weight (SMA mode) |
| Parameters | Window, two percentiles, price | Window, deviation multiple, shift, price |
| Computation cost | Sort per bar (heavier) | Running sums (very light) |
| Shift and style options | None in v1.00 | Yes |
Outlier test (synthetic). On a simulated 500-bar random walk with window 50, I added a single 40-point spike inside one window. A standard 2σ Bollinger band width grew from 4.69 to 23.08 (about 4.9 times wider), while the 25/75 percentile band width went from 1.77 to 1.79. For news spikes and stop-run wicks this is the practical advantage: the percentile bands keep describing where most trading happened.
Where Bollinger Bands are still better: very fast computation, built-in shift and style options, and a widely shared convention (20, 2) that other traders recognise. Where Rolling Percentiles is better: skewed or spiky instruments, liquidity-sweep work, and stable range references.
9. Review findings and known limitations in v1.00
The core algorithm is correct. The weaknesses are all in robustness and polish.
| # | Finding | Severity | Recommended fix |
|---|---|---|---|
| 1 | No input validation. InpPeriod of 0 or less gives a negative start index and an array-out-of-range stop. Percentiles above 100 (or below 0) index outside the window. I reproduced this in the pandas-equivalent test: 101 raises an index error. | High | Validate in OnInit and return INIT_PARAMETERS_INCORRECT with a printed reason. Require period of at least 2, 0 ≤ lower < upper ≤ 100. |
| 2 | Inverted percentiles are silently accepted (lower input above upper input), so the colours and labels lie. | Medium | Same validation, or swap automatically. |
| 3 | Per-bar allocation and full sort. A new array is created and re-filled for every bar, with the applied price recomputed for each element. Cost is roughly bars × window × log(window), noticeable on large windows and long history. | Medium | Compute the price once per bar into an array. Reuse one window buffer. For heavy use, keep an incrementally sorted window (binary search insert/remove), which makes each bar roughly linear in the window. |
| 4 | Static plot labels. The Data Window always says “75th” and “25th”, even if you set 90 and 10. | Low | Set labels in OnInit with PlotIndexSetString(..., PLOT_LABEL, ...) . |
| 5 | No PLOT_DRAW_BEGIN and no explicit empty-value fill for the warm-up bars. It works because the terminal initialises buffers to the empty value, but it is better to be explicit. | Low | Set PLOT_DRAW_BEGIN to InpPeriod − 1 and PLOT_EMPTY_VALUE to EMPTY_VALUE . |
| 6 | White median is invisible on light templates. | Low | Choose a neutral colour such as gold or dodger blue. |
| 7 | No short name. | Low | IndicatorSetString(INDICATOR_SHORTNAME, ...) showing the period and percentiles. |
| 8 | Live-bar behaviour (by design). The newest bar is included in its own window, so the lines at the last bar move every tick. Closed bars do not change. | Info | Use bar-close values (shift 1) in any EA or rule. |
What is correct and worth keeping: the series direction handling (arrays are used oldest to newest, consistently), the incremental recalculation from prev_calculated − 1 , the clean OnCalculate return value, and the interpolation math.
10. Troubleshooting
The indicator disappears right after attaching, with an “array out of range” message in the Experts or Journal log. Check the inputs against the table in section 3. This is the unvalidated-input issue.
No lines are drawn. Either the chart has fewer bars than InpPeriod , or history is still loading. Scroll back or wait for the history to finish downloading.
I can’t see the median. It is white on a white chart. Change its colour in the Colors tab.
The last value keeps changing. Expected: the live bar is still inside its own window. Closed bars never change.
Terminal is slow with a big window. Reduce InpPeriod , reduce the number of bars loaded on the chart, or ask for the optimised incremental version.
11. Quick reference
| Item | Value |
|---|---|
| Window | Chart (main) |
| Plots and buffers | 3 and 3 |
| Buffer 0 | Upper percentile (green line) |
| Buffer 1 | Median (white thick line) |
| Buffer 2 | Lower percentile (red line) |
| Method | Sorted window, linear interpolation (pandas-compatible) |
| First valid bar | InpPeriod − 1 |
| Repaints | Last bar only, until it closes |
| Safe starting settings | 50 bars, 10 / 90, Close |
