Loxx’s FDI-Adaptive, Jurik-Filtered, TMA with Price Zones is a trend indicator that wraps three distinct ideas into a single moving average:
On top of the line, two adaptive bands are drawn at TMA ± dev × triangular_range, providing visual price zones that scale with recent intra-bar dispersion. A signal line (TMA[1]) crosses the main line on every direction change, producing buy/sell triangles.
The Fractal Dimension Index measures how “rough” a price series looks over a window. A perfectly straight trending line has FDI = 1; a completely random walk approaches FDI = 2. Real markets sit in between, and the value moves around as the regime changes.
The classical formula sums the Euclidean lengths of the normalised price path over the window:
fmax = highest(src, N)
fmin = lowest(src, N)
length = 0
for i = 1 to N - 1:
diffCurr = (src[i-1] - fmin) / (fmax - fmin)
diffPrev = (src[i] - fmin) / (fmax - fmin)
length += sqrt((diffCurr - diffPrev)^2 + 1/N^2)
fdi = 1 + (log(length) + log(2)) / log(2 * N)
Once the FDI is known, the indicator derives an adaptive period from it:
traildim = 1 / (2 - fdi) // grows as the market becomes noisier
alpha = traildim / 2 // half of that
fdiPer = round(speed * alpha) // final adaptive length
The intuition: when the market trends cleanly (fdi → 1), traildim → 1 and alpha → 0.5, so the adaptive length collapses to about half of the user’s speed parameter — the average becomes responsive. When the market goes fractal-noisy (fdi → 1.7+), traildim and alpha grow super-linearly, and the window stretches out to filter the chop.
The TMA is a weighted average whose weights form a triangle, peaking at the most recent bar and decreasing linearly into the past:
sumv = (N + 1) × src
sumw = (N + 1)
k = N
for j = 1 to N:
sumv += k × src[j]
sumw += k
k -= 1
tma = sumv / sumw
This is mathematically equivalent to two cascaded SMAs, but more compact. Compared to a plain SMA of the same length, the TMA front-loads the weight onto recent bars while still giving older bars meaningful representation — a smoother response without the artificial flatness of a simple double-smoothing.
The interesting detail is N: it is fdiPer from the previous step, which changes every bar. ProBuilder allows FOR j = 1 TO N where N is a runtime variable, so this loop translates directly without contortions.
The TMA output is fed into a full Jurik filter. The Jurik MA is a four-stage smoother with adaptive volatility tracking:
The full Jurik block runs to ~50 lines of ProBuilder code; the canonical implementation is taken from the existing PRC_Jurik DMX Histogram translation and renamed with the suffix jk to keep its variables isolated. Applied on top of the TMA, the JMA removes the residual jitter that the TMA cannot eliminate on its own — particularly during transitions, when the TMA’s window length itself is changing.
The bands are computed as TMA ± dev × rng, where rng is a triangular-weighted measure of the recent high-low spread:
lsum = (rngper + 1) × low
hsum = (rngper + 1) × high
sumw = (rngper + 1)
k = rngper
for j = 1 to rngper:
lsum += k × low[j]
hsum += k × high[j]
sumw += k
k -= 1
rng = (hsum - lsum) / sumw
This is the same triangular weighting as the TMA but applied to the high-low channel. Because it tracks the width of the window rather than the centre, it gives a smoother, more representative volatility measure than ATR over the same span — particularly on instruments that gap.
| Feature | Behaviour |
|---|---|
| Adaptive window length | Driven by FDI of the source series; trends shrink the window, noise expands it |
| Triangular weighting | Front-loads recent bars without losing the contribution of older ones |
| Jurik post-smoothing | Removes residual jitter, especially during regime changes |
| Volatility-aware bands | `TMA ± dev × triangular_range` — channel width adapts with the asset |
| Crossover signals | `TMA crosses TMA[1]` — direction change events |
| Bar coloring | Optional, via `drawcandle` — green when TMA is rising, red when falling |
| Parameter | Default | Description |
|---|---|---|
| `src` | `(high+low)/2` | Source series. Use `close` for a more reactive line. |
| `per` | 30 | Window length over which the FDI is computed. Larger `per` = smoother, slower-adapting FDI. |
| `speedin` | 20 | Base length the FDI adaptation scales. Higher = generally larger windows. |
| `smthper` | 30 | Jurik smoothing length applied to the TMA output. |
| `smthphs` | 0 | Jurik phase: −100 to +100. Negative biases lag, positive biases lead. 0 is neutral. |
| `rngper` | 5 | Window for the triangular range (bands). Short by default — the bands react fast. |
| `dev` | 1.8 | Band multiplier. 1.8 keeps most price action inside the bands; raise for an envelope, lower for a tight channel. |
| `colorbars` | 1 | 1 = recolour candles by TMA direction; 0 = leave the platform's native colouring. |
| `showsignals` | 1 | 1 = draw triangles on direction changes; 0 = TMA line only. |
// PRC_FDI-Adaptive Jurik-Filtered TMA w/ Price Zones
// version = 0
// 28.04.26
// Ivan Gonzalez @ www.prorealcode.com
// Sharing ProRealTime knowledge
// Original: FDI-Adaptive, Jurik-Filtered, TMA w/ Price Zones [Loxx] (TradingView)
//////////////////////////////////////////////////////////////
// Inputs
src = (high+low)/2
per = 30
speedin = 20
smthper = 30
smthphs = 0.0
rngper = 5
dev = 1.8
colorbars = 1
showsignals = 1
//////////////////////////////////////////////////////////////
// === FDI calculation — period adaptive via Fractal Dimension Index
fmax = highest[per](src)
fmin = lowest[per](src)
IF fmax > fmin THEN
fdiLen = 0
FOR i = 1 TO per - 1 DO
diffCurr = (src[i-1] - fmin) / (fmax - fmin)
diffPrev = (src[i] - fmin) / (fmax - fmin)
fdiLen = fdiLen + sqrt((diffCurr - diffPrev)*(diffCurr - diffPrev) + 1/(per*per))
NEXT
IF fdiLen > 0 THEN
fdi = 1 + (log(fdiLen) + log(2)) / log(2 * per)
ELSE
fdi = 1.5
ENDIF
ELSE
fdi = 1.5
ENDIF
IF fdi < 2 THEN
traildim = 1 / (2 - fdi)
ELSE
traildim = 1
ENDIF
alphaFdi = traildim / 2
fdiper = round(speedin * alphaFdi)
IF fdiper < 1 THEN
fdiper = 1
ENDIF
IF fdiper > 200 THEN
fdiper = 200
ENDIF
//////////////////////////////////////////////////////////////
// === Triangular weighted MA with adaptive period
sumv = (fdiper + 1) * src
sumw = (fdiper + 1)
k = fdiper
FOR j = 1 TO fdiper DO
sumv = sumv + k * src[j]
sumw = sumw + k
k = k - 1
NEXT
tmaRaw = sumv / sumw
//////////////////////////////////////////////////////////////
// === Jurik MA filter applied to tmaRaw
len = max(smthper, 1)
phs = smthphs
len1 = max(log(sqrt(0.5*(len-1)))/log(2)+2, 0)
pow1 = max(len1-2, 0.5)
avglen = 65
IF barindex < avglen+1 THEN
Filt0jk = tmaRaw
Filt1jk = tmaRaw
juriktma = tmaRaw
bsmaxjk = 0
bsminjk = 0
vsumjk = 0
avoltyjk = 0
ELSE
del1jk = tmaRaw - bsmaxjk[1]
del2jk = tmaRaw - bsminjk[1]
div = 1/(10+10*(min(max(len-10,0),100))/100)
IF abs(del1jk) > abs(del2jk) THEN
voltyjk = abs(del1jk)
ELSE
voltyjk = abs(del2jk)
ENDIF
vsumjk = vsumjk[1] + div*(voltyjk - voltyjk[10])
tempavgjk = average[avglen](vsumjk)
yjk = barindex+1
IF yjk <= avglen+1 THEN
avoltyjk = avoltyjk[1] + 2*(vsumjk-avoltyjk[1])/(avglen+1)
ELSE
avoltyjk = tempavgjk
ENDIF
IF avoltyjk > 0 THEN
dvoltyjk = voltyjk/avoltyjk
ELSE
dvoltyjk = 0
ENDIF
IF dvoltyjk > pow(len1,1/pow1) THEN
dvoltyjk = pow(len1,1/pow1)
ENDIF
IF dvoltyjk < 1 THEN
dvoltyjk = 1
ENDIF
pow2jk = pow(dvoltyjk, pow1)
len2 = sqrt(0.5*(len-1))*len1
kvjk = pow(len2/(len2+1), sqrt(pow2jk))
IF del1jk > 0 THEN
bsmaxjk = tmaRaw
ELSE
bsmaxjk = tmaRaw - kvjk*del1jk
ENDIF
IF del2jk < 0 THEN
bsminjk = tmaRaw
ELSE
bsminjk = tmaRaw - kvjk*del2jk
ENDIF
IF phs < -100 THEN
phaseratio = 0.5
ELSIF phs > 100 THEN
phaseratio = 2.5
ELSE
phaseratio = phs/100+1.5
ENDIF
beta = 0.45 * (len - 1) / (0.45 * (len - 1) + 2)
alphajk = pow(beta, pow2jk)
Filt0jk = (1 - alphajk) * tmaRaw + alphajk * Filt0jk[1]
Det0jk = (tmaRaw - Filt0jk) * (1 - beta) + beta * Det0jk[1]
Filt1jk = Filt0jk + phaseratio * Det0jk
Det1jk = (Filt1jk - juriktma[1]) * ((1 - alphajk) * (1 - alphajk)) + (alphajk * alphajk) * Det1jk[1]
juriktma = juriktma[1] + Det1jk
ENDIF
tma = juriktma
sig = tma[1]
//////////////////////////////////////////////////////////////
// === Range calculation (calcrng) — triangular weighted high-low spread
lsum = (rngper + 1) * low
hsum = (rngper + 1) * high
sumwr = (rngper + 1)
kr = rngper
FOR jr = 1 TO rngper DO
lsum = lsum + kr * low[jr]
hsum = hsum + kr * high[jr]
sumwr = sumwr + kr
kr = kr - 1
NEXT
rng = (hsum - lsum) / sumwr
//////////////////////////////////////////////////////////////
// === Price zones
uplvl = tma + dev * rng
dnlvl = tma - dev * rng
//////////////////////////////////////////////////////////////
// === Color (TMA up/down)
IF tma > sig THEN
r = 45
g = 210
b = 4
ELSE
r = 210
g = 4
b = 45
ENDIF
//////////////////////////////////////////////////////////////
// === Color bars (replica barcolor de Pine)
IF colorbars = 1 THEN
drawcandle(open,high,low,close) coloured(r,g,b)
ENDIF
//////////////////////////////////////////////////////////////
// === Signals
goLong = tma CROSSES OVER sig
goShort = tma CROSSES UNDER sig
IF showsignals = 1 AND goLong THEN
drawtext("▲", barindex, low - 0.3*rng) coloured(0,255,255)
ENDIF
IF showsignals = 1 AND goShort THEN
drawtext("▼", barindex, high + 0.3*rng) coloured(255,0,255)
ENDIF
//////////////////////////////////////////////////////////////
return tma coloured(r,g,b) style(line,3) as "TMA", uplvl coloured(120,120,120) as "Upper Channel", dnlvl coloured(120,120,120) as "Lower Channel"