AI Source Switching Moving Average

Category: Indicators By: Iván González Created: August 7, 2026, 9:59 AM
August 7, 2026, 9:59 AM
Indicators
0 Comments

Introduction

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.

Theory Behind the Indicator

1. Six features, computed four times

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:

  • Trend: the gap between a fast and a slow exponential average of that price, divided by ATR. Positive when the source is trending up relative to volatility.
  • Mean reversion: the z-score of the source against its own 30-bar average, sign-flipped, so a stretched price scores as “due to revert”.
  • Momentum: the 14-bar rate of change, scaled so that a 5% move saturates the feature.
  • Volatility: the 20-bar standard deviation of the source, min-max normalised over the last 100 bars and mapped to -1..+1.
  • Range position: where the source sits inside the current bar, from -1 at the low to +1 at the high.
  • Slope: the 3-bar change of the source divided by ATR.

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.

2. Labelling the past with ATR

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.

3. The analog engine

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:

  • analog: the weighted average of the outcome labels. Direction and strength of the historical consensus.
  • agree: what fraction of the vote mass sits on the winning side. Unanimity.
  • tight: how close the analogs actually were, normalised by the total feature weight. Are we comparing against genuinely similar bars, or against the least-bad matches available?

That third one is the interesting one, and it is what drives the adaptive trail later.

4. The online neural layer

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.

5. Fisher automatic feature weights

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.

6. The ranking, and the switch

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.

7. The adaptive Supertrend

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.

Two Engineering Notes Worth Reading

The memory bank is a shifted window

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.

Class statistics without a loop

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.

The ProBuilder Code

 

//----------------------------------------------
//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)

Download
Filename: PRC_AI-Source-Switching-MovAvg.itf
Downloads: 27
Iván González Legend
This author is like an anonymous function, present but not directly identifiable. More details on this code architect as soon as they exit 'incognito' mode.
Author’s Profile

Comments

Logo Logo
Loading...