Everybody has drawn a fan of moving averages on a chart at some point. Ten, twelve lines of increasing period, and the eye does the rest: when they are stacked cleanly in order and pointing the same way, the trend is healthy. When they knot together, it is not.
That reading is entirely visual, and it stops working the moment you want to compare two instruments, scan a list, or write a rule around it. madnessMA, an indicator by karceWZROKIEM, turns it into a number.
It takes a list of moving averages — by default the Fibonacci series 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 — and outputs three readings bounded between -100 and +100. Each one answers a different question about the same fan, and all three use the same formula.
Every reading is built like this:
reading = 100 * SUM(signed differences) / SUM(absolute differences)
The numerator keeps the sign, the denominator throws it away. The consequences are worth stating explicitly, because they are what makes the indicator readable:
That last point is the whole idea. This is a measure of agreement, not of distance. It is closer in spirit to a breadth indicator than to a momentum oscillator: it counts how many components of the fan vote in the same direction, weighted by how strongly each one votes.
TrendDegree compares the closing price against every average in the fan. At +100 price is above all of them; at -100 it is below all of them. Crossing zero means price has worked its way through the middle of the fan.
MASortDegree compares a reference average — maCheck, default 8 — against every other average. Each comparison is signed so that a correctly stacked fan always adds in the same direction: for averages slower than the reference the sign is kept, for faster ones it is flipped. At +100 the fan is perfectly ordered for an uptrend, with every fast average above every slow one. At -100 it is perfectly ordered for a downtrend. Near zero the fan is tangled.
SortDegree compares every average against the last one on the list — the 233 by default. At +100 the whole fan sits above its slowest member; at -100, below it.
The natural instinct is to make SortDegree compare every average against every other average, all pairs at once. It is worth showing why that does not work, because it is a genuinely counter-intuitive result.
Take the sum over all ordered pairs:
SUM over i, SUM over j not equal to i, of ( a[j] – a[i] )
This is exactly zero. Always. For any set of numbers, on any chart, on any timeframe. Every pair contributes a – b and also b – a, and the two cancel. The sum is antisymmetric, so the numerator vanishes before the data is even involved. An “all pairs” SortDegree would be a flat line on zero forever.
The absolute-value denominator survives that cancellation, but a zero numerator over anything is still zero.
So the comparison has to be anchored on one reference member for the sum to carry information, and the indicator anchors it on the last average of the list. This has a practical consequence that is easy to miss: the order of the list matters. Reordering the periods, or switching off the last slot, changes SortDegree completely. It is not cosmetic.
A pleasant side effect is that anchoring collapses what looks like a nested double loop into a single pass. With twelve averages that is 12 iterations per bar instead of 144, for an identical result.
Two dot series mark the conditions the author defined, at +110 and -110 so they sit clear of the curves.
Open fires when SortDegree is below both other readings and below zero, or the mirrored condition above zero. Note the polarity: the upward dot appears when SortDegree is negative — that is, when the fan is stretched below its slowest average. This is a mean-reversion reading, not a trend-following one, and the indicator does not label it as buy or sell.
Close fires when TrendDegree turns against its previous value while SortDegree sits on the other side of it, or whenever an opposite Open condition appears. Both dots showing on the same bar is normal and expected.
Apply it in a separate panel below price.
Start with MASortDegree, the dashed blue line. It is the cleanest of the three. Sustained readings beyond +80 or -80 mean the fan is stacked almost perfectly — a trend that all timeframes in the list agree on. Readings oscillating around zero mean the averages are interleaved, which is what a range looks like from the inside.
TrendDegree, in grey, is the fastest to react because price moves before any average does. Treat the gap between TrendDegree and MASortDegree as an early warning: price leaving the fan while the fan is still ordered the old way is the normal signature of a turn beginning.
SortDegree, the solid blue line, is the slowest and the most structural. It tells you where the entire fan sits relative to its own long-term anchor.
Two practical notes. First, the readings are bounded, so they saturate: once all three sit at +100 the indicator cannot tell you the trend is getting stronger, only that it has not broken. Saturation is information, but it is not momentum. Second, with the default list the indicator needs 233 bars before it starts plotting anything.
Regime filter. The most robust use. Require MASortDegree beyond a threshold before allowing any trend-following entry, and stand aside while it hovers near zero. Because the scale is instrument-independent, the same threshold works across a portfolio without retuning.
Divergence between the readings. TrendDegree turning down while MASortDegree stays pinned near +100 tells you price has broken away from a fan that is still fully ordered. That is a different situation from both readings falling together, and the pair separates the two cleanly.
Screener input. MASortDegree > 80 is a one-line condition that returns instruments whose entire moving average structure is aligned. It is a considerably stricter filter than any single moving average cross, and it maps directly onto ProScreener.
Fan design. The Fibonacci spacing is a choice, not a requirement. A linear list (10, 20, 30, 40…) weights the slow end differently and gives visibly different readings. Since the code packs the active slots automatically, comparing spacings is a matter of editing the numbers.
Colours follow the original: grey for TrendDegree, dashed blue for MASortDegree, solid blue for SortDegree, black dots for Open and red for Close. The black dots will not show on a dark chart template — change COLOURED(0, 0, 0, aOpen) to COLOURED(255, 255, 255, aOpen) on the last block if that is your setup.
//--------------------------------------------
// PRC_madnessMA (by karceWZROKIEM)
// version = 0
// 03.08.2026
// Ivan Gonzalez @ www.prorealcode.com
// Sharing ProRealTime knowledge
//--------------------------------------------
// Conversion del indicador "Mix.mq4" (madnessMA) de MetaTrader 4.
// Mide el grado de ordenacion de un abanico de medias moviles y lo
// normaliza en tres lecturas acotadas entre -100 y +100:
// TrendDegree : posicion del precio respecto a TODAS las medias
// MASortDegree : orden del abanico respecto a la media de referencia
// SortDegree : dispersion del abanico respecto a la media mas lenta
// Los puntos Open/Close replican los buffers de flechas del original.
//--------------------------------------------
//=== LISTA DE MEDIAS (0 = posicion desactivada) ===
// Equivale al input "listMA" del original. El orden importa:
// la ULTIMA posicion activa es la que ancla el calculo de SortDegree.
per01 = 1
per02 = 2
per03 = 3
per04 = 5
per05 = 8
per06 = 13
per07 = 21
per08 = 34
per09 = 55
per10 = 89
per11 = 144
per12 = 233
//=== PARAMETROS ===
maCheck = 8 // media de referencia para MASortDegree (input MAcheck)
maMethod = 1 // 0=SMA 1=EMA 2=Smoothed(Wilder) 3=Linear Weighted
//--------------------------------------------
//=== MAPEO DEL METODO MT4 AL TIPO DE average[] DE PROBUILDER ===
// MT4: 0 SMA / 1 EMA / 2 SMMA / 3 LWMA
// PRT: 0 SMA / 1 EMA / 2 WMA / 3 Wilder
IF maMethod = 0 THEN
mType = 0
ELSIF maMethod = 2 THEN
mType = 3
ELSIF maMethod = 3 THEN
mType = 2
ELSE
mType = 1
ENDIF
//--------------------------------------------
//=== CARGA DEL ABANICO (compacta solo las posiciones activas) ===
nMA = 0
IF per01 > 0 THEN
$maPer[nMA] = per01
$maVal[nMA] = average[per01, mType](close)
nMA = nMA + 1
ENDIF
IF per02 > 0 THEN
$maPer[nMA] = per02
$maVal[nMA] = average[per02, mType](close)
nMA = nMA + 1
ENDIF
IF per03 > 0 THEN
$maPer[nMA] = per03
$maVal[nMA] = average[per03, mType](close)
nMA = nMA + 1
ENDIF
IF per04 > 0 THEN
$maPer[nMA] = per04
$maVal[nMA] = average[per04, mType](close)
nMA = nMA + 1
ENDIF
IF per05 > 0 THEN
$maPer[nMA] = per05
$maVal[nMA] = average[per05, mType](close)
nMA = nMA + 1
ENDIF
IF per06 > 0 THEN
$maPer[nMA] = per06
$maVal[nMA] = average[per06, mType](close)
nMA = nMA + 1
ENDIF
IF per07 > 0 THEN
$maPer[nMA] = per07
$maVal[nMA] = average[per07, mType](close)
nMA = nMA + 1
ENDIF
IF per08 > 0 THEN
$maPer[nMA] = per08
$maVal[nMA] = average[per08, mType](close)
nMA = nMA + 1
ENDIF
IF per09 > 0 THEN
$maPer[nMA] = per09
$maVal[nMA] = average[per09, mType](close)
nMA = nMA + 1
ENDIF
IF per10 > 0 THEN
$maPer[nMA] = per10
$maVal[nMA] = average[per10, mType](close)
nMA = nMA + 1
ENDIF
IF per11 > 0 THEN
$maPer[nMA] = per11
$maVal[nMA] = average[per11, mType](close)
nMA = nMA + 1
ENDIF
IF per12 > 0 THEN
$maPer[nMA] = per12
$maVal[nMA] = average[per12, mType](close)
nMA = nMA + 1
ENDIF
maCheckVal = average[maCheck, mType](close)
//=== Periodo maximo -> barras de calentamiento ===
maxPer = maCheck
FOR kMx = 0 TO nMA - 1 DO
IF $maPer[kMx] > maxPer THEN
maxPer = $maPer[kMx]
ENDIF
NEXT
//--------------------------------------------
//=== CALCULO ===
ONCE trendDeg = undefined
ONCE maSortDeg = undefined
ONCE sortDeg = undefined
IF nMA > 1 THEN
IF barindex > maxPer THEN
trendBuff = 0
trendSum = 0
srtBuff = 0
srtSum = 0
// --- Recorrido del abanico: TrendDegree y MASortDegree ---
FOR kMa = 0 TO nMA - 1 DO
// Distancia de la media de referencia a cada media del abanico.
// Signo invertido para las medias mas rapidas o iguales que maCheck,
// de forma que un abanico bien ordenado suma siempre en el mismo sentido.
dRef = maCheckVal - $maVal[kMa]
IF $maPer[kMa] <= maCheck THEN
dRef = 0 - dRef
ENDIF
srtBuff = srtBuff + dRef
srtSum = srtSum + abs(dRef)
// Distancia del precio a cada media del abanico
dPrc = close - $maVal[kMa]
trendBuff = trendBuff + dPrc
trendSum = trendSum + abs(dPrc)
NEXT
// --- SortDegree: abanico frente a la ULTIMA media de la lista ---
kLast = nMA - 1
maLast = $maVal[kLast]
relBuff = 0
relSum = 0
FOR jMa = 0 TO nMA - 1 DO
IF jMa <> kLast THEN
dRel = $maVal[jMa] - maLast
relBuff = relBuff + dRel
relSum = relSum + abs(dRel)
ENDIF
NEXT
// --- Normalizacion a -100..+100 ---
IF trendSum > 0 THEN
trendDeg = 100 * trendBuff / trendSum
ENDIF
IF srtSum > 0 THEN
maSortDeg = 100 * srtBuff / srtSum
ENDIF
IF relSum > 0 THEN
sortDeg = 100 * relBuff / relSum
ENDIF
ENDIF
ENDIF
//--------------------------------------------
//=== SENALES (buffers openOrder / closeOrder del original) ===
sigDir = 0
dotOpen = undefined
dotClose = undefined
aOpen = 0
aClose = 0
IF barindex > maxPer THEN
IF sortDeg < trendDeg AND sortDeg <= maSortDeg AND sortDeg < 0 THEN
sigDir = 1
ELSIF sortDeg > trendDeg AND sortDeg >= maSortDeg AND sortDeg > 0 THEN
sigDir = 0 - 1
ENDIF
IF sigDir > 0 THEN
dotOpen = 110
aOpen = 255
ELSIF sigDir < 0 THEN
dotOpen = 0 - 110
aOpen = 255
ENDIF
IF (trendDeg < trendDeg[1] AND sortDeg < trendDeg) OR sigDir < 0 THEN
dotClose = 110
aClose = 255
ELSIF (trendDeg > trendDeg[1] AND sortDeg > trendDeg) OR sigDir > 0 THEN
dotClose = 0 - 110
aClose = 255
ENDIF
ENDIF
//--------------------------------------------
//=== NIVELES DE REFERENCIA (SetLevelValue +-80 del original) ===
lvlUp = 80
lvlDn = 0 - 80
//--------------------------------------------
RETURN trendDeg AS "TrendDegree" COLOURED(160, 160, 160)STYLE(line, 2),maSortDeg AS "MASortDegree" COLOURED(0, 0, 255)STYLE(dottedLine2, 1), sortDeg AS "SortDegree" COLOURED(0, 0, 255) STYLE(line, 2),dotOpen AS "Open" COLOURED(0, 0, 0, aOpen) STYLE(point, 4), dotClose AS "Close" COLOURED(255, 0, 0, aClose) STYLE(point, 3),lvlUp AS "+80" COLOURED(160, 160, 160) STYLE(dottedLine, 1), lvlDn AS "-80" COLOURED(160, 160, 160)STYLE(dottedLine, 1)