Every moving average starts with a decision nobody ever revisits: which price do we feed it? Almost always close, out of habit rather than analysis. Yet the four prices of a bar carry different information. The open holds the overnight gap and the auction imbalance. The high and the low are where the bar met resistance and support, and they lead the close when a trend is extending. The close is consensus, but it is also the most crowded and the most mean-reverting of the four.
The AI Source Switching Moving Average by Zeiierman takes that decision away from the user and hands it to a model. On every bar it scores open, high, low and close independently, feeds the winner into the moving average, and adds a trailing stop whose width breathes with how confident the model is. When the engine sees a clean, one-sided historical picture the trail tightens; when the picture is muddy the trail backs off and gives price room.
There are three scoring layers stacked on top of each other: an analog engine that compares the current bar against recent history, a small neural network trained online while the chart runs, and an automatic feature-weighting scheme based on Fisher’s discriminant. This article walks through all three, shows the full ProBuilder code, and — because that matters more than the marketing — is honest about what the engine actually does when you leave it on its default settings.
Each of the four prices is described by the same six numbers, all of them normalised into roughly the same range so they can be compared and weighted:
Dividing by ATR in two of the six is what makes the features portable across instruments and timeframes: a 20-point move means something different on an index than on a currency pair, but “0.8 ATR” means the same thing everywhere.
One honest observation about this block. Range position applied to high is always exactly +1, and applied to low always exactly -1 — by construction, since those two prices define the range. For those two sources that feature carries no information at all. It is harmless, but it tells you the feature block was designed around close and then reused for the other three.
The engine needs to know what “worked” historically, so every bar gets a label. It measures the move over the last horizonBars bars and compares it against an ATR-scaled band:
move = close - close[horizonBars]
band = learnAtrFactor * ATR[horizonBars]
The result is a seven-level outcome from -3 to +3: beyond two bands is a strong move, beyond one band a normal move, anything smaller a weak move, and exactly flat is zero. Crucially, the features stored alongside that label are the features as they were horizonBars bars ago, not the current ones. Feature vector from the past, outcome that followed it. That is what makes it supervised learning rather than a description of a bar that has already closed — a distinction a surprising number of published “machine learning” indicators get wrong.
For the current bar and each of the four sources, the engine looks back through recent history for bars whose feature vector resembles today’s, and lets them vote. Distance is not Euclidean but logarithmic:
gap = SUM over the six features of weight * log(1 + |feature_now - feature_then|)
The logarithm compresses large discrepancies. Two feature vectors that disagree wildly on one dimension are not pushed infinitely far apart, so a single outlier dimension cannot veto an otherwise good analog. It is the same reasoning behind the Lorentzian distance used in several well-known classifiers.
The closest analogs then vote, each weighted by 1 / (1 + gap), and the engine extracts three numbers per source:
That third one is the interesting one, and it is what drives the adaptive trail later.
Running in parallel is a small linear model — six weights and a bias — trained bar by bar with Adam and a Huber loss. It learns to predict the sign of the outcome from the lagged feature vector of close.
Huber matters here. A plain squared error would let a single violent bar yank the weights around; Huber clips the gradient once the error exceeds huberD, so the model keeps learning smoothly through shocks instead of overreacting to them. Adam supplies the per-weight adaptive step size, so features that move on very different scales all converge at a reasonable rate.
The trained model is then applied to all four sources and squashed through a sigmoid, contributing up to neuralInfluence to each source’s score. Note that it is a single shared scorer, not four separate networks: it answers “how bullish does this feature vector look”, and each source presents its own vector to it.
The six features are not equally useful, and which ones matter changes with the regime. Rather than hard-coding weights, the engine measures them. For each feature it splits recent history into bullish-outcome and bearish-outcome bars and computes Fisher’s discriminant ratio:
F = (meanBull - meanBear)^2 / (varBull + varBear)
A feature whose two class means are far apart relative to their spread separates the classes well and earns a large weight; a feature whose distributions overlap gets pushed down to the configured floor. The weights are then normalised against the best feature, scaled, and eased toward their new values with an exponential smoother so they drift rather than jump.
This feeds straight back into the distance calculation of step 3, which is what makes the analog engine adaptive: it does not just look for similar bars, it continuously re-learns what “similar” should mean.
Each source ends up with a single score between 0 and 1, combining directional conviction, unanimity, tightness of the analogs, the neural score, and a small bonus for having found a full set of analogs. The best-scoring source becomes the price for this bar, a short EMA smooths the hand-off so the switch does not produce a step, and the final moving average is calculated on that.
The four sources’ conviction numbers are averaged into a single drive value, and that value modulates the ATR multiplier of a Supertrend trail:
adaptMult = stMult * (1 + stAdapt * (1 - aiDrive))
High drive, tight trail. Low drive, wide trail. This is the piece of the indicator that visibly changes behaviour on the chart, and arguably the most useful output of the whole engine: a trailing stop that automatically stops trying to be precise when the model admits it does not recognise the current conditions.
The model description talks about a “memory bank” of learning rows per source. Look closely at how it is filled: exactly one row per closed bar, newest first, oldest dropped off the end. That means row number p is never anything other than the feature vector from p + horizonBars bars ago paired with the outcome from p bars ago.
So the bank does not need to exist. Reading history directly at those offsets gives byte-identical results with no storage, no write pointer, and no risk of the buffer drifting out of alignment. The ProBuilder code below does exactly that, which is why an engine with five conceptual data structures compiles into something that holds none of them.
Fisher’s discriminant needs, per feature, the sum and the sum of squares over bullish bars and over bearish bars — twenty-four running statistics, plus two counts. Computing those with a loop over the window on every bar is thousands of operations for no reason.
SUMMATION solves it in one line each. The trick is to have every bar contribute its value to its class and zero to the other, then sum the contribution series over the window:
sumBullTrend = summation[memoryDepth](bullRow * trendAggregate)
Where bullRow is 1 on bullish-outcome bars and 0 otherwise. The rolling window comes free, warm-up is handled internally, and the variance follows from E[x²] - E[x]², which is the population variance — exactly what is wanted here. Twenty-six statistics, twenty-six lines, zero loops.
//----------------------------------------------
//PRC_AI Source Switching Moving Average [Zeiierman]
//version = 1
//07.08.2026
//Ivan Gonzalez @ www.prorealcode.com
//Sharing ProRealTime knowledge
//----------------------------------------------
// Overlay indicator. An analog KNN engine scores Open, High, Low and Close
// separately, the best scoring one feeds the moving average, and an adaptive
// ATR trail widens when the engine has no conviction.
//----------------------------------------------
defparam calculateonlastbars = 3000
// === AI MOVING AVERAGE ===
maType = 1 // 0=SMA 1=EMA 2=WMA 3=VWMA 4=RMA 5=HMA 6=TEMA 7=ZLEMA 8=KAMA 9=ALMA
maLen = 50 // final moving average length
srcSmooth = 3 // EMA smoothing applied to the hard OHLC switch
// === MACHINE LEARNING ENGINE ===
memoryDepth = 40 // learning rows kept per source
kNeighbors = 9 // analogs used when scoring a source
horizonBars = 4 // forward bars used to label past outcomes
spacingBars = 4 // distance between sampled analogs
learnAtrFactor = 0.45 // ATR threshold that classifies outcome strength
// === NEURAL ONLINE TRAINING ===
useNeural = 1 // 1=on 0=off
neuralInfluence = 0.35 // weight of the neural score in the source ranking
learnRate = 0.01 // Adam step size
huberD = 0.02 // Huber loss threshold
// === FISHER AUTO WEIGHTS ===
useFisher = 1 // 1=on 0=off
fisherSpeed = 0.20 // adaptation speed of the feature weights
fisherFloor = 0.40 // minimum allowed feature weight
// === AI SUPERTREND ===
showST = 1 // 1=show the adaptive trail
stLen = 10 // ATR length of the trail
stMult = 1.7 // base ATR multiplier
stAdapt = 0.80 // how much the band reacts to AI confidence
// === STYLE ===
showCandles = 0 // 1=repaint chart candles with the trend colour
showSourceMarks = 0 // 1=mark every O/H/L/C source switch
showFlipMarks = 1 // 1=mark the Supertrend flips
showMAGlow = 1 // 1=fill between the AI average and price
showTrailGlow = 1 // 1=fill between the trail and price
bullR = 0
bullG = 230
bullB = 118
bearR = 255
bearG = 82
bearB = 82
neutR = 120
neutG = 123
neutB = 134
// === SHARED SERIES ===
atrNow = averagetruerange[14]
if atrNow > 0 then
atrInv = 1.0 / atrNow
else
atrInv = 0.0
endif
rngBar = high - low
// === FEATURES: OPEN ===
oFast = average[10, 1](open)
oSlow = average[34, 1](open)
oT = max(-3.0, min(3.0, (oFast - oSlow) * atrInv)) / 3.0
oBas = average[30](open)
oDev = std[30](open)
if oDev > 0 then
oZ = (open - oBas) / oDev
else
oZ = 0.0
endif
oM = max(-3.0, min(3.0, -oZ)) / 3.0
oMo = max(-3.0, min(3.0, (open / open[14] - 1.0) / 0.05)) / 3.0
oSd = std[20](open)
oSdLo = lowest[100](oSd)
oSdHi = highest[100](oSd)
if oSdHi = oSdLo then
oV = 0.0
else
oV = max(0.0, min(1.0, (oSd - oSdLo) / (oSdHi - oSdLo))) * 2.0 - 1.0
endif
if rngBar > 0 then
oRg = max(-1.0, min(1.0, ((open - low) / rngBar) * 2.0 - 1.0))
else
oRg = 0.0
endif
oS = max(-3.0, min(3.0, (open - open[3]) * atrInv)) / 3.0
// === FEATURES: HIGH ===
hFast = average[10, 1](high)
hSlow = average[34, 1](high)
hT = max(-3.0, min(3.0, (hFast - hSlow) * atrInv)) / 3.0
hBas = average[30](high)
hDev = std[30](high)
if hDev > 0 then
hZ = (high - hBas) / hDev
else
hZ = 0.0
endif
hM = max(-3.0, min(3.0, -hZ)) / 3.0
hMo = max(-3.0, min(3.0, (high / high[14] - 1.0) / 0.05)) / 3.0
hSd = std[20](high)
hSdLo = lowest[100](hSd)
hSdHi = highest[100](hSd)
if hSdHi = hSdLo then
hV = 0.0
else
hV = max(0.0, min(1.0, (hSd - hSdLo) / (hSdHi - hSdLo))) * 2.0 - 1.0
endif
if rngBar > 0 then
hRg = max(-1.0, min(1.0, ((high - low) / rngBar) * 2.0 - 1.0))
else
hRg = 0.0
endif
hS = max(-3.0, min(3.0, (high - high[3]) * atrInv)) / 3.0
// === FEATURES: LOW ===
lFast = average[10, 1](low)
lSlow = average[34, 1](low)
lT = max(-3.0, min(3.0, (lFast - lSlow) * atrInv)) / 3.0
lBas = average[30](low)
lDev = std[30](low)
if lDev > 0 then
lZ = (low - lBas) / lDev
else
lZ = 0.0
endif
lM = max(-3.0, min(3.0, -lZ)) / 3.0
lMo = max(-3.0, min(3.0, (low / low[14] - 1.0) / 0.05)) / 3.0
lSd = std[20](low)
lSdLo = lowest[100](lSd)
lSdHi = highest[100](lSd)
if lSdHi = lSdLo then
lV = 0.0
else
lV = max(0.0, min(1.0, (lSd - lSdLo) / (lSdHi - lSdLo))) * 2.0 - 1.0
endif
if rngBar > 0 then
lRg = max(-1.0, min(1.0, ((low - low) / rngBar) * 2.0 - 1.0))
else
lRg = 0.0
endif
lS = max(-3.0, min(3.0, (low - low[3]) * atrInv)) / 3.0
// === FEATURES: CLOSE ===
cFast = average[10, 1](close)
cSlow = average[34, 1](close)
cT = max(-3.0, min(3.0, (cFast - cSlow) * atrInv)) / 3.0
cBas = average[30](close)
cDev = std[30](close)
if cDev > 0 then
cZ = (close - cBas) / cDev
else
cZ = 0.0
endif
cM = max(-3.0, min(3.0, -cZ)) / 3.0
cMo = max(-3.0, min(3.0, (close / close[14] - 1.0) / 0.05)) / 3.0
cSd = std[20](close)
cSdLo = lowest[100](cSd)
cSdHi = highest[100](cSd)
if cSdHi = cSdLo then
cV = 0.0
else
cV = max(0.0, min(1.0, (cSd - cSdLo) / (cSdHi - cSdLo))) * 2.0 - 1.0
endif
if rngBar > 0 then
cRg = max(-1.0, min(1.0, ((close - low) / rngBar) * 2.0 - 1.0))
else
cRg = 0.0
endif
cS = max(-3.0, min(3.0, (close - close[3]) * atrInv)) / 3.0
// === SUPERVISED LABEL ===
// The learning bank takes one row per closed bar, so row p is always the feature
// vector of bar t-p-horizonBars paired with the outcome of bar t-p. It is a
// shifted window, not storage: plain historical offsets reproduce it exactly.
moveFwd = close - close[horizonBars]
bandFwd = learnAtrFactor * atrNow[horizonBars]
if moveFwd > 2 * bandFwd then
outcome = 3
elsif moveFwd > bandFwd then
outcome = 2
elsif moveFwd > 0 then
outcome = 1
elsif moveFwd < -2 * bandFwd then
outcome = -3
elsif moveFwd < -bandFwd then
outcome = -2
elsif moveFwd < 0 then
outcome = -1
else
outcome = 0
endif
warmBars = horizonBars + 120 + memoryDepth
if barindex > warmBars then
warmOK = 1
else
warmOK = 0
endif
// === FISHER AUTO WEIGHTS ===
// The Fisher window spans the last memoryDepth bars times the four sources.
// Class conditional sums over that window are exact with SUMMATION, no loop.
bullRow = 0
bearRow = 0
if outcome > 0 then
bullRow = 1
elsif outcome < 0 then
bearRow = 1
endif
agT = oT[horizonBars] + hT[horizonBars] + lT[horizonBars] + cT[horizonBars]
agM = oM[horizonBars] + hM[horizonBars] + lM[horizonBars] + cM[horizonBars]
agMo = oMo[horizonBars] + hMo[horizonBars] + lMo[horizonBars] + cMo[horizonBars]
agV = oV[horizonBars] + hV[horizonBars] + lV[horizonBars] + cV[horizonBars]
agRg = oRg[horizonBars] + hRg[horizonBars] + lRg[horizonBars] + cRg[horizonBars]
agS = oS[horizonBars] + hS[horizonBars] + lS[horizonBars] + cS[horizonBars]
qT = oT[horizonBars] * oT[horizonBars] + hT[horizonBars] * hT[horizonBars] + lT[horizonBars] * lT[horizonBars] + cT[horizonBars] * cT[horizonBars]
qM = oM[horizonBars] * oM[horizonBars] + hM[horizonBars] * hM[horizonBars] + lM[horizonBars] * lM[horizonBars] + cM[horizonBars] * cM[horizonBars]
qMo = oMo[horizonBars] * oMo[horizonBars] + hMo[horizonBars] * hMo[horizonBars] + lMo[horizonBars] * lMo[horizonBars] + cMo[horizonBars] * cMo[horizonBars]
qV = oV[horizonBars] * oV[horizonBars] + hV[horizonBars] * hV[horizonBars] + lV[horizonBars] * lV[horizonBars] + cV[horizonBars] * cV[horizonBars]
qR = oRg[horizonBars] * oRg[horizonBars] + hRg[horizonBars] * hRg[horizonBars] + lRg[horizonBars] * lRg[horizonBars] + cRg[horizonBars] * cRg[horizonBars]
qS = oS[horizonBars] * oS[horizonBars] + hS[horizonBars] * hS[horizonBars] + lS[horizonBars] * lS[horizonBars] + cS[horizonBars] * cS[horizonBars]
cntB = 4 * summation[memoryDepth](bullRow)
cntS = 4 * summation[memoryDepth](bearRow)
sumBT = summation[memoryDepth](bullRow * agT)
sumBM = summation[memoryDepth](bullRow * agM)
sumBMo = summation[memoryDepth](bullRow * agMo)
sumBV = summation[memoryDepth](bullRow * agV)
sumBR = summation[memoryDepth](bullRow * agRg)
sumBS = summation[memoryDepth](bullRow * agS)
sumST = summation[memoryDepth](bearRow * agT)
sumSM = summation[memoryDepth](bearRow * agM)
sumSMo = summation[memoryDepth](bearRow * agMo)
sumSV = summation[memoryDepth](bearRow * agV)
sumSR = summation[memoryDepth](bearRow * agRg)
sumSS = summation[memoryDepth](bearRow * agS)
sqBT = summation[memoryDepth](bullRow * qT)
sqBM = summation[memoryDepth](bullRow * qM)
sqBMo = summation[memoryDepth](bullRow * qMo)
sqBV = summation[memoryDepth](bullRow * qV)
sqBR = summation[memoryDepth](bullRow * qR)
sqBS = summation[memoryDepth](bullRow * qS)
sqST = summation[memoryDepth](bearRow * qT)
sqSM = summation[memoryDepth](bearRow * qM)
sqSMo = summation[memoryDepth](bearRow * qMo)
sqSV = summation[memoryDepth](bearRow * qV)
sqSR = summation[memoryDepth](bearRow * qR)
sqSS = summation[memoryDepth](bearRow * qS)
rawT = 1.0
rawM = 1.0
rawMo = 1.0
rawV = 1.0
rawR = 1.0
rawS = 1.0
if useFisher = 1 and warmOK = 1 and cntB > 3 and cntS > 3 then
mbT = sumBT / cntB
msT = sumST / cntS
fshT = (mbT - msT) * (mbT - msT) / (max(0.0, sqBT / cntB - mbT * mbT) + max(0.0, sqST / cntS - msT * msT) + 0.000001)
mbM = sumBM / cntB
msM = sumSM / cntS
fshM = (mbM - msM) * (mbM - msM) / (max(0.0, sqBM / cntB - mbM * mbM) + max(0.0, sqSM / cntS - msM * msM) + 0.000001)
mbMo = sumBMo / cntB
msMo = sumSMo / cntS
fshMo = (mbMo - msMo) * (mbMo - msMo) / (max(0.0, sqBMo / cntB - mbMo * mbMo) + max(0.0, sqSMo / cntS - msMo * msMo) + 0.000001)
mbV = sumBV / cntB
msV = sumSV / cntS
fshV = (mbV - msV) * (mbV - msV) / (max(0.0, sqBV / cntB - mbV * mbV) + max(0.0, sqSV / cntS - msV * msV) + 0.000001)
mbR = sumBR / cntB
msR = sumSR / cntS
fshR = (mbR - msR) * (mbR - msR) / (max(0.0, sqBR / cntB - mbR * mbR) + max(0.0, sqSR / cntS - msR * msR) + 0.000001)
mbS = sumBS / cntB
msS = sumSS / cntS
fshS = (mbS - msS) * (mbS - msS) / (max(0.0, sqBS / cntB - mbS * mbS) + max(0.0, sqSS / cntS - msS * msS) + 0.000001)
maxF = max(fshT, max(fshM, max(fshMo, max(fshV, max(fshR, fshS)))))
if maxF > 0 then
rawT = max(fisherFloor, fshT / maxF * 8.0)
rawM = max(fisherFloor, fshM / maxF * 8.0)
rawMo = max(fisherFloor, fshMo / maxF * 8.0)
rawV = max(fisherFloor, fshV / maxF * 8.0)
rawR = max(fisherFloor, fshR / maxF * 8.0)
rawS = max(fisherFloor, fshS / maxF * 8.0)
else
rawT = 8.0
rawM = 8.0
rawMo = 8.0
rawV = 8.0
rawR = 8.0
rawS = 8.0
endif
endif
once wgT = 1.0
once wgM = 1.0
once wgMo = 1.0
once wgV = 1.0
once wgR = 1.0
once wgS = 1.0
if useFisher = 1 then
wgT = wgT + fisherSpeed * (rawT - wgT)
wgM = wgM + fisherSpeed * (rawM - wgM)
wgMo = wgMo + fisherSpeed * (rawMo - wgMo)
wgV = wgV + fisherSpeed * (rawV - wgV)
wgR = wgR + fisherSpeed * (rawR - wgR)
wgS = wgS + fisherSpeed * (rawS - wgS)
endif
// === KNN ANALOG ENGINE ===
bigGap = 1000000000
nCand = floor((memoryDepth - 1) / spacingBars) + 1
for jSel = 0 to kNeighbors - 1 do
$gpO[jSel] = bigGap
$clO[jSel] = 0
$gpH[jSel] = bigGap
$clH[jSel] = 0
$gpL[jSel] = bigGap
$clL[jSel] = 0
$gpC[jSel] = bigGap
$clC[jSel] = 0
next
if warmOK = 1 then
for pIdx = 0 to nCand - 1 do
pOff = pIdx * spacingBars
clsP = outcome[pOff]
if clsP <> 0 then
hOff = pOff + horizonBars
gapO = wgT * log(1.0 + abs(oT - oT[hOff])) + wgM * log(1.0 + abs(oM - oM[hOff])) + wgMo * log(1.0 + abs(oMo - oMo[hOff])) + wgV * log(1.0 + abs(oV - oV[hOff])) + wgR * log(1.0 + abs(oRg - oRg[hOff])) + wgS * log(1.0 + abs(oS - oS[hOff]))
worstO = 0
wgapO = $gpO[0]
for jSel = 1 to kNeighbors - 1 do
if $gpO[jSel] > wgapO then
wgapO = $gpO[jSel]
worstO = jSel
endif
next
if gapO < wgapO then
$gpO[worstO] = gapO
$clO[worstO] = clsP
endif
gapH = wgT * log(1.0 + abs(hT - hT[hOff])) + wgM * log(1.0 + abs(hM - hM[hOff])) + wgMo * log(1.0 + abs(hMo - hMo[hOff])) + wgV * log(1.0 + abs(hV - hV[hOff])) + wgR * log(1.0 + abs(hRg - hRg[hOff])) + wgS * log(1.0 + abs(hS - hS[hOff]))
worstH = 0
wgapH = $gpH[0]
for jSel = 1 to kNeighbors - 1 do
if $gpH[jSel] > wgapH then
wgapH = $gpH[jSel]
worstH = jSel
endif
next
if gapH < wgapH then
$gpH[worstH] = gapH
$clH[worstH] = clsP
endif
gapL = wgT * log(1.0 + abs(lT - lT[hOff])) + wgM * log(1.0 + abs(lM - lM[hOff])) + wgMo * log(1.0 + abs(lMo - lMo[hOff])) + wgV * log(1.0 + abs(lV - lV[hOff])) + wgR * log(1.0 + abs(lRg - lRg[hOff])) + wgS * log(1.0 + abs(lS - lS[hOff]))
worstL = 0
wgapL = $gpL[0]
for jSel = 1 to kNeighbors - 1 do
if $gpL[jSel] > wgapL then
wgapL = $gpL[jSel]
worstL = jSel
endif
next
if gapL < wgapL then
$gpL[worstL] = gapL
$clL[worstL] = clsP
endif
gapC = wgT * log(1.0 + abs(cT - cT[hOff])) + wgM * log(1.0 + abs(cM - cM[hOff])) + wgMo * log(1.0 + abs(cMo - cMo[hOff])) + wgV * log(1.0 + abs(cV - cV[hOff])) + wgR * log(1.0 + abs(cRg - cRg[hOff])) + wgS * log(1.0 + abs(cS - cS[hOff]))
worstC = 0
wgapC = $gpC[0]
for jSel = 1 to kNeighbors - 1 do
if $gpC[jSel] > wgapC then
wgapC = $gpC[jSel]
worstC = jSel
endif
next
if gapC < wgapC then
$gpC[worstC] = gapC
$clC[worstC] = clsP
endif
endif
next
endif
totO = 0.0
scoO = 0.0
bulO = 0.0
beaO = 0.0
gsmO = 0.0
kctO = 0
totH = 0.0
scoH = 0.0
bulH = 0.0
beaH = 0.0
gsmH = 0.0
kctH = 0
totL = 0.0
scoL = 0.0
bulL = 0.0
beaL = 0.0
gsmL = 0.0
kctL = 0
totC = 0.0
scoC = 0.0
bulC = 0.0
beaC = 0.0
gsmC = 0.0
kctC = 0
for jSel = 0 to kNeighbors - 1 do
if $gpO[jSel] < bigGap then
wtO = 1.0 / (1.0 + $gpO[jSel])
totO = totO + wtO
scoO = scoO + $clO[jSel] * wtO
if $clO[jSel] > 0 then
bulO = bulO + wtO
else
beaO = beaO + wtO
endif
gsmO = gsmO + $gpO[jSel]
kctO = kctO + 1
endif
if $gpH[jSel] < bigGap then
wtH = 1.0 / (1.0 + $gpH[jSel])
totH = totH + wtH
scoH = scoH + $clH[jSel] * wtH
if $clH[jSel] > 0 then
bulH = bulH + wtH
else
beaH = beaH + wtH
endif
gsmH = gsmH + $gpH[jSel]
kctH = kctH + 1
endif
if $gpL[jSel] < bigGap then
wtL = 1.0 / (1.0 + $gpL[jSel])
totL = totL + wtL
scoL = scoL + $clL[jSel] * wtL
if $clL[jSel] > 0 then
bulL = bulL + wtL
else
beaL = beaL + wtL
endif
gsmL = gsmL + $gpL[jSel]
kctL = kctL + 1
endif
if $gpC[jSel] < bigGap then
wtC = 1.0 / (1.0 + $gpC[jSel])
totC = totC + wtC
scoC = scoC + $clC[jSel] * wtC
if $clC[jSel] > 0 then
bulC = bulC + wtC
else
beaC = beaC + wtC
endif
gsmC = gsmC + $gpC[jSel]
kctC = kctC + 1
endif
next
gapScale = (wgT + wgM + wgMo + wgV + wgR + wgS) * 0.45 + 0.000001
if totO > 0 then
analogO = scoO / totO
else
analogO = 0.0
endif
if analogO > 0.15 then
agreeO = bulO / totO
elsif analogO < -0.15 then
agreeO = beaO / totO
else
agreeO = 0.0
endif
if kctO > 0 then
tightO = max(0.0, min(1.0, 1.0 - gsmO / kctO / gapScale))
else
tightO = 1.0
endif
if totH > 0 then
analogH = scoH / totH
else
analogH = 0.0
endif
if analogH > 0.15 then
agreeH = bulH / totH
elsif analogH < -0.15 then
agreeH = beaH / totH
else
agreeH = 0.0
endif
if kctH > 0 then
tightH = max(0.0, min(1.0, 1.0 - gsmH / kctH / gapScale))
else
tightH = 1.0
endif
if totL > 0 then
analogL = scoL / totL
else
analogL = 0.0
endif
if analogL > 0.15 then
agreeL = bulL / totL
elsif analogL < -0.15 then
agreeL = beaL / totL
else
agreeL = 0.0
endif
if kctL > 0 then
tightL = max(0.0, min(1.0, 1.0 - gsmL / kctL / gapScale))
else
tightL = 1.0
endif
if totC > 0 then
analogC = scoC / totC
else
analogC = 0.0
endif
if analogC > 0.15 then
agreeC = bulC / totC
elsif analogC < -0.15 then
agreeC = beaC / totC
else
agreeC = 0.0
endif
if kctC > 0 then
tightC = max(0.0, min(1.0, 1.0 - gsmC / kctC / gapScale))
else
tightC = 1.0
endif
// === NEURAL ONLINE TRAINING (Adam) ===
beta1 = 0.9
beta2 = 0.999
epsA = 0.00000001
once nwT = 0.01
once nwM = 0.01
once nwMo = 0.01
once nwV = 0.01
once nwR = 0.01
once nwS = 0.01
once nwB = 0.0
once moT = 0.0
once moM = 0.0
once moMo = 0.0
once moV = 0.0
once moR = 0.0
once moS = 0.0
once moB = 0.0
once veT = 0.0
once veM = 0.0
once veMo = 0.0
once veV = 0.0
once veR = 0.0
once veS = 0.0
once veB = 0.0
once pw1 = 1.0
once pw2 = 1.0
if outcome > 0 then
targetDir = 1.0
elsif outcome < 0 then
targetDir = -1.0
else
targetDir = 0.0
endif
xT = cT[horizonBars]
xM = cM[horizonBars]
xMo = cMo[horizonBars]
xV = cV[horizonBars]
xR = cRg[horizonBars]
xS = cS[horizonBars]
trainErr = nwT * xT + nwM * xM + nwMo * xMo + nwV * xV + nwR * xR + nwS * xS + nwB - targetDir
if abs(trainErr) <= huberD then
trainGrad = trainErr
else
trainGrad = huberD * sgn(trainErr)
endif
if useNeural = 1 and warmOK = 1 and targetDir <> 0 then
// Bias correction kept as a running product: exact, and no pow/exp underflow
pw1 = pw1 * beta1
pw2 = pw2 * beta2
bc1 = 1.0 - pw1
bc2 = 1.0 - pw2
gdT = trainGrad * xT
moT = beta1 * moT + (1.0 - beta1) * gdT
veT = beta2 * veT + (1.0 - beta2) * gdT * gdT
nwT = nwT - learnRate * (moT / bc1) / (sqrt(veT / bc2) + epsA)
gdM = trainGrad * xM
moM = beta1 * moM + (1.0 - beta1) * gdM
veM = beta2 * veM + (1.0 - beta2) * gdM * gdM
nwM = nwM - learnRate * (moM / bc1) / (sqrt(veM / bc2) + epsA)
gdMo = trainGrad * xMo
moMo = beta1 * moMo + (1.0 - beta1) * gdMo
veMo = beta2 * veMo + (1.0 - beta2) * gdMo * gdMo
nwMo = nwMo - learnRate * (moMo / bc1) / (sqrt(veMo / bc2) + epsA)
gdV = trainGrad * xV
moV = beta1 * moV + (1.0 - beta1) * gdV
veV = beta2 * veV + (1.0 - beta2) * gdV * gdV
nwV = nwV - learnRate * (moV / bc1) / (sqrt(veV / bc2) + epsA)
gdR = trainGrad * xR
moR = beta1 * moR + (1.0 - beta1) * gdR
veR = beta2 * veR + (1.0 - beta2) * gdR * gdR
nwR = nwR - learnRate * (moR / bc1) / (sqrt(veR / bc2) + epsA)
gdS = trainGrad * xS
moS = beta1 * moS + (1.0 - beta1) * gdS
veS = beta2 * veS + (1.0 - beta2) * gdS * gdS
nwS = nwS - learnRate * (moS / bc1) / (sqrt(veS / bc2) + epsA)
moB = beta1 * moB + (1.0 - beta1) * trainGrad
veB = beta2 * veB + (1.0 - beta2) * trainGrad * trainGrad
nwB = nwB - learnRate * (moB / bc1) / (sqrt(veB / bc2) + epsA)
endif
// === SOURCE RANKING ===
// With useNeural = 0 the neural score is zero and the sigmoid returns 0.5 for
// every source: it shifts all ranks equally and never changes the order.
if useNeural = 1 then
nsO = 1.0 / (1.0 + exp(-max(-8.0, min(8.0, nwT * oT + nwM * oM + nwMo * oMo + nwV * oV + nwR * oRg + nwS * oS + nwB))))
nsH = 1.0 / (1.0 + exp(-max(-8.0, min(8.0, nwT * hT + nwM * hM + nwMo * hMo + nwV * hV + nwR * hRg + nwS * hS + nwB))))
nsL = 1.0 / (1.0 + exp(-max(-8.0, min(8.0, nwT * lT + nwM * lM + nwMo * lMo + nwV * lV + nwR * lRg + nwS * lS + nwB))))
nsC = 1.0 / (1.0 + exp(-max(-8.0, min(8.0, nwT * cT + nwM * cM + nwMo * cMo + nwV * cV + nwR * cRg + nwS * cS + nwB))))
else
nsO = 0.5
nsH = 0.5
nsL = 0.5
nsC = 0.5
endif
if kctO >= kNeighbors then
bonO = 0.10
else
bonO = 0.0
endif
if kctH >= kNeighbors then
bonH = 0.10
else
bonH = 0.0
endif
if kctL >= kNeighbors then
bonL = 0.10
else
bonL = 0.0
endif
if kctC >= kNeighbors then
bonC = 0.10
else
bonC = 0.0
endif
rnkO = max(0.0, min(1.0, abs(analogO) / 3.0 * 0.35 + agreeO * 0.25 + tightO * 0.20 + nsO * neuralInfluence + bonO))
rnkH = max(0.0, min(1.0, abs(analogH) / 3.0 * 0.35 + agreeH * 0.25 + tightH * 0.20 + nsH * neuralInfluence + bonH))
rnkL = max(0.0, min(1.0, abs(analogL) / 3.0 * 0.35 + agreeL * 0.25 + tightL * 0.20 + nsL * neuralInfluence + bonL))
rnkC = max(0.0, min(1.0, abs(analogC) / 3.0 * 0.35 + agreeC * 0.25 + tightC * 0.20 + nsC * neuralInfluence + bonC))
if warmOK = 1 then
safeO = rnkO
safeH = rnkH
safeL = rnkL
safeC = rnkC
else
safeO = 0.25
safeH = 0.25
safeL = 0.25
safeC = 0.25
endif
if safeO >= safeH and safeO >= safeL and safeO >= safeC then
bestId = 0
elsif safeH >= safeL and safeH >= safeC then
bestId = 1
elsif safeL >= safeC then
bestId = 2
else
bestId = 3
endif
// === AI SOURCE SELECTION ===
if bestId = 0 then
hardSrc = open
elsif bestId = 1 then
hardSrc = high
elsif bestId = 2 then
hardSrc = low
else
hardSrc = close
endif
aiSource = average[srcSmooth, 1](hardSrc)
// === FINAL MOVING AVERAGE ===
if maType = 0 then
aiMA = average[maLen](aiSource)
elsif maType = 1 then
aiMA = average[maLen, 1](aiSource)
elsif maType = 2 then
aiMA = average[maLen, 2](aiSource)
elsif maType = 3 then
volSum = summation[maLen](volume)
if volSum > 0 then
aiMA = summation[maLen](aiSource * volume) / volSum
else
aiMA = aiSource
endif
elsif maType = 4 then
aiMA = average[maLen, 3](aiSource)
elsif maType = 5 then
aiMA = average[maLen, 7](aiSource)
elsif maType = 6 then
ema1 = average[maLen, 1](aiSource)
ema2 = average[maLen, 1](ema1)
ema3 = average[maLen, 1](ema2)
aiMA = 3.0 * (ema1 - ema2) + ema3
elsif maType = 7 then
aiMA = average[maLen, 8](aiSource)
elsif maType = 8 then
volatK = summation[maLen](abs(aiSource - aiSource[1]))
if volatK > 0 then
erK = abs(aiSource - aiSource[maLen]) / volatK
else
erK = 0.0
endif
scK = (erK * (0.6666667 - 0.0645161) + 0.0645161) * (erK * (0.6666667 - 0.0645161) + 0.0645161)
if barindex <= maLen then
aiMA = aiSource
else
aiMA = aiMA[1] + scK * (aiSource - aiMA[1])
endif
else
almaM = 0.85 * (maLen - 1)
almaS = maLen / 6.0
almaNum = 0.0
almaDen = 0.0
for iAl = 0 to maLen - 1 do
wAl = exp(-(iAl - almaM) * (iAl - almaM) / (2.0 * almaS * almaS))
almaNum = almaNum + wAl * aiSource[maLen - 1 - iAl]
almaDen = almaDen + wAl
next
aiMA = almaNum / almaDen
endif
// === AI SUPERTREND ===
aiDrive = max(0.0, min(1.0, abs((analogO + analogH + analogL + analogC) / 4.0) * 0.20 + (agreeO + agreeH + agreeL + agreeC) / 4.0 * 0.40 + (tightO + tightH + tightL + tightC) / 4.0 * 0.40))
adaptMult = stMult * (1.0 + stAdapt * (1.0 - aiDrive))
stAtr = averagetruerange[stLen]
upBand = aiSource - adaptMult * stAtr
dnBand = aiSource + adaptMult * stAtr
if barindex <= stLen then
stLong = low
stShort = high
stDir = 1
else
if close[1] > stLong[1] then
stLong = max(upBand, stLong[1])
else
stLong = upBand
endif
if close[1] < stShort[1] then
stShort = min(dnBand, stShort[1])
else
stShort = dnBand
endif
if stDir[1] = -1 and close > stShort[1] then
stDir = 1
elsif stDir[1] = 1 and close < stLong[1] then
stDir = -1
else
stDir = stDir[1]
endif
endif
if stDir = 1 then
stLine = stLong
maR = bullR
maG = bullG
maB = bullB
else
stLine = stShort
maR = bearR
maG = bearG
maB = bearB
endif
stFlipUp = stDir = 1 and stDir[1] = -1
stFlipDn = stDir = -1 and stDir[1] = 1
// === TRAIL PLOTS ===
// Visibility is driven by ALPHA, never by undefined. Two lines that alternate
// sides are not cut by an undefined value: the inactive one holds its last
// price and drags a flat line across the whole opposite regime. Each side keeps
// its own band, so both series stay continuous and only the alpha switches, and
// both go transparent on the flip bar so no connecting segment is drawn.
trailUp = stLong
trailDn = stShort
if showST = 0 then
upAlpha = 0
dnAlpha = 0
elsif stDir <> stDir[1] then
upAlpha = 0
dnAlpha = 0
elsif stDir = 1 then
upAlpha = 255
dnAlpha = 0
else
upAlpha = 0
dnAlpha = 255
endif
// === GLOW FILLS ===
// Single unconditional call with a variable alpha. The trail fill also drops to
// zero on the flip bar: the band swaps sides there and the polygon would be
// dragged from one side of price to the other.
maAlpha = 0
if showMAGlow = 1 then
maAlpha = 38
endif
colorbetween(aiMA, close, maR, maG, maB, maAlpha)
trailAlpha = 0
if showTrailGlow = 1 and showST = 1 and stDir = stDir[1] then
trailAlpha = 50
endif
colorbetween(stLine, close, maR, maG, maB, trailAlpha)
// === TREND CANDLES ===
if showCandles = 1 then
drawcandle(open, high, low, close) coloured(maR, maG, maB)
endif
// === SOURCE SWITCH MARKS ===
srcChanged = bestId <> bestId[1]
if showSourceMarks = 1 and srcChanged then
if bestId = 0 then
drawtext("O", barindex, low - 0.6 * atrNow) coloured(neutR, neutG, neutB)
elsif bestId = 1 then
drawtext("H", barindex, low - 0.6 * atrNow) coloured(bullR, bullG, bullB)
elsif bestId = 2 then
drawtext("L", barindex, high + 0.6 * atrNow) coloured(bearR, bearG, bearB)
else
drawtext("C", barindex, low - 0.6 * atrNow) coloured(60, 60, 60)
endif
endif
// === SUPERTREND FLIP MARKS ===
if showFlipMarks = 1 and stFlipUp then
drawtext("▲", barindex, stLine - 0.4 * atrNow) coloured(bullR, bullG, bullB)
endif
if showFlipMarks = 1 and stFlipDn then
drawtext("▼", barindex, stLine + 0.4 * atrNow) coloured(bearR, bearG, bearB)
endif
return aiMA as "AI Source Adaptive MA" coloured(maR, maG, maB) style(line, 3), trailUp as "AI Supertrend Up" coloured(bullR, bullG, bullB, upAlpha) style(line, 1), trailDn as "AI Supertrend Down" coloured(bearR, bearG, bearB, dnAlpha) style(line, 1)