//@version=6
// ============================================================================
// MA Cascade Engine [WavesUnchained]
// Version: 1.5.0
// Author: WavesUnchained
// Build: 2026-10-02
// ============================================================================
// Moving averages are not treated as isolated crossover signals.
//
// Architecture:
// 1. Visual Layer - Fast, Base and Anchor curves
// 2. Feature Layer - Direction, slope, curvature, separation,
// extension, cross frequency and MA respect
// 3. State Layer - Expansion, pullback, compression,
// transition and overextension
// 4. Decision Layer - Permission, setup, trigger, weakness and exit
//
// Important:
// The included JMA is an open Jurik-style approximation. It is not the
// proprietary original Jurik Moving Average implementation.
// ============================================================================
indicator(
"MA Cascade Engine [WavesUnchained]",
shorttitle = "MA Cascade",
overlay = true,
max_labels_count = 500
)
// ============================================================================
// CONSTANTS
// ============================================================================
int STATE_BULL_EXPANSION = 1
int STATE_BULL_PULLBACK = 2
int STATE_NEUTRAL = 3
int STATE_BEAR_PULLBACK = 4
int STATE_BEAR_EXPANSION = 5
int STATE_OVEREXTENDED = 6
int STATE_TRANSITION = 7
// ============================================================================
// INPUT GROUPS
// ============================================================================
string GROUP_PRESET = "01. Moving Average Preset"
string GROUP_CUSTOM = "02. Custom Moving Averages"
string GROUP_SLOPE = "03. Slope and Curvature"
string GROUP_STRUCTURE = "04. Structure and Extension"
string GROUP_RESPECT = "05. MA Respect"
string GROUP_SIGNALS = "06. Signals"
string GROUP_SCORE = "07. Score"
string GROUP_VISUAL = "08. Visualisation"
// ============================================================================
// PRESET INPUTS
// ============================================================================
string presetInput = input.string(
"Balanced",
"Preset",
options = [
"Responsive",
"Balanced",
"Structural",
"Custom"
],
group = GROUP_PRESET
)
series float sourceInput = input.source(
close,
"Source",
group = GROUP_PRESET
)
int jmaPhaseInput = input.int(
0,
"JMA Phase",
minval = -100,
maxval = 100,
tooltip = "Controls the responsiveness and possible overshoot of the Jurik-style approximation.",
group = GROUP_PRESET
)
float jmaPowerInput = input.float(
2.0,
"JMA Power",
minval = 1.0,
maxval = 8.0,
step = 0.1,
group = GROUP_PRESET
)
// ============================================================================
// CUSTOM MA INPUTS
// ============================================================================
string customFastTypeInput = input.string(
"JMA",
"Fast Type",
options = [
"JMA",
"EMA",
"SMA",
"RMA",
"WMA",
"HMA",
"DEMA",
"KAMA"
],
group = GROUP_CUSTOM
)
int customFastLengthInput = input.int(
21,
"Fast Length",
minval = 1,
group = GROUP_CUSTOM
)
string customBaseTypeInput = input.string(
"EMA",
"Base Type",
options = [
"JMA",
"EMA",
"SMA",
"RMA",
"WMA",
"HMA",
"DEMA",
"KAMA"
],
group = GROUP_CUSTOM
)
int customBaseLengthInput = input.int(
50,
"Base Length",
minval = 1,
group = GROUP_CUSTOM
)
string customAnchorTypeInput = input.string(
"SMA",
"Anchor Type",
options = [
"JMA",
"EMA",
"SMA",
"RMA",
"WMA",
"HMA",
"DEMA",
"KAMA"
],
group = GROUP_CUSTOM
)
int customAnchorLengthInput = input.int(
200,
"Anchor Length",
minval = 2,
group = GROUP_CUSTOM
)
// ============================================================================
// SLOPE AND CURVATURE INPUTS
// ============================================================================
int atrLengthInput = input.int(
14,
"ATR Length",
minval = 2,
group = GROUP_SLOPE
)
int slopeLookbackInput = input.int(
5,
"Slope Lookback",
minval = 1,
maxval = 100,
group = GROUP_SLOPE
)
float minimumFastSlopeInput = input.float(
0.015,
"Minimum Fast Slope per Bar",
minval = 0.0,
step = 0.005,
tooltip = "ATR-normalised minimum slope per bar required for a trigger.",
group = GROUP_SLOPE
)
float minimumBaseSlopeInput = input.float(
0.008,
"Minimum Base Slope per Bar",
minval = 0.0,
step = 0.001,
tooltip = "ATR-normalised minimum slope per bar required for a directional regime.",
group = GROUP_SLOPE
)
int curvatureLookbackInput = input.int(
3,
"Curvature Lookback",
minval = 1,
maxval = 50,
group = GROUP_SLOPE
)
float minimumCurvatureInput = input.float(
0.005,
"Minimum Trigger Curvature",
minval = 0.0,
step = 0.001,
group = GROUP_SLOPE
)
// ============================================================================
// STRUCTURE INPUTS
// ============================================================================
float entanglementSpreadInput = input.float(
0.15,
"Entanglement Spread",
minval = 0.01,
step = 0.01,
tooltip = "If both MA separations are smaller than this ATR value, the averages are considered entangled.",
group = GROUP_STRUCTURE
)
int crossFrequencyLookbackInput = input.int(
30,
"Cross Frequency Lookback",
minval = 5,
maxval = 300,
group = GROUP_STRUCTURE
)
int maximumCrossesInput = input.int(
6,
"Maximum Crosses",
minval = 1,
maxval = 50,
tooltip = "Maximum permitted price/Base-MA crossings within the lookback.",
group = GROUP_STRUCTURE
)
float pullbackDistanceInput = input.float(
0.70,
"Maximum Pullback Distance",
minval = 0.05,
step = 0.05,
tooltip = "Maximum ATR distance from the Base MA for a Long/Short Setup. Applies to setups only — the Bull/Bear Pullback states are not gated by it.",
group = GROUP_STRUCTURE
)
float pullbackSpreadInput = input.float(
0.75,
"Maximum Fast/Base Pullback Spread",
minval = 0.05,
step = 0.05,
group = GROUP_STRUCTURE
)
float maximumExtensionInput = input.float(
1.75,
"Maximum Price Extension",
minval = 0.25,
step = 0.05,
tooltip = "Price distance from the Fast MA in ATR units after which new entries are blocked.",
group = GROUP_STRUCTURE
)
float extremeVolatilityRatioInput = input.float(
1.80,
"Extreme Volatility Ratio",
minval = 1.0,
step = 0.05,
tooltip = "Current ATR divided by its long-term average ATR.",
group = GROUP_STRUCTURE
)
int volatilityBaselineInput = input.int(
100,
"Volatility Baseline",
minval = 20,
maxval = 500,
group = GROUP_STRUCTURE
)
// ============================================================================
// MA RESPECT INPUTS
// ============================================================================
bool useRespectInput = input.bool(
true,
"Use Base Respect in Score",
group = GROUP_RESPECT
)
bool useAnchorRespectInput = input.bool(
true,
"Use Anchor Respect in Score",
group = GROUP_RESPECT
)
float touchToleranceInput = input.float(
0.15,
"Touch Tolerance",
minval = 0.01,
step = 0.01,
tooltip = "ATR tolerance around the Base and Anchor MA.",
group = GROUP_RESPECT
)
int reactionBarsInput = input.int(
5,
"Reaction Horizon",
minval = 1,
maxval = 30,
tooltip = "Number of bars after a touch at which the reaction is evaluated.",
group = GROUP_RESPECT
)
float reactionMoveInput = input.float(
0.50,
"Required Reaction Move",
minval = 0.05,
step = 0.05,
tooltip = "Required move away from the Base or Anchor MA in ATR units.",
group = GROUP_RESPECT
)
float respectInvalidationInput = input.float(
0.35,
"Respect Invalidation",
minval = 0.05,
step = 0.05,
tooltip = "Maximum adverse penetration through the Base or Anchor MA in ATR units.",
group = GROUP_RESPECT
)
int respectLookbackInput = input.int(
150,
"Respect Statistics Lookback",
minval = 20,
maxval = 1000,
group = GROUP_RESPECT
)
// ============================================================================
// SIGNAL INPUTS
// ============================================================================
bool requirePriceConfirmationInput = input.bool(
true,
"Require Previous-Bar Break",
tooltip = "Long triggers must close above the previous high; short triggers below the previous low.",
group = GROUP_SIGNALS
)
bool requireCurvatureInput = input.bool(
true,
"Require Positive Trigger Curvature",
group = GROUP_SIGNALS
)
bool showPermissionSignalsInput = input.bool(
false,
"Show Permission Changes",
group = GROUP_SIGNALS
)
bool showReactivationSignalsInput = input.bool(
true,
"Show Reactivation Signals",
group = GROUP_SIGNALS
)
bool showAnchorRejectionSignalsInput = input.bool(
true,
"Show Anchor Rejection Signals",
tooltip = "Confirmed bounce off the Anchor MA: price approached from the reaction side, touched it without also touching the Base MA, then moved away and held for the Reaction Horizon.",
group = GROUP_SIGNALS
)
bool showExitSignalsInput = input.bool(
false,
"Show Exit Signals",
group = GROUP_SIGNALS
)
// ============================================================================
// SCORE INPUTS
// ============================================================================
float strongBiasThresholdInput = input.float(
60.0,
"Strong Bias Threshold",
minval = 25.0,
maxval = 100.0,
step = 1.0,
group = GROUP_SCORE
)
float directionalBiasThresholdInput = input.float(
25.0,
"Directional Bias Threshold",
minval = 5.0,
maxval = 75.0,
step = 1.0,
group = GROUP_SCORE
)
// ============================================================================
// VISUAL INPUTS
// ============================================================================
bool showFastInput = input.bool(
true,
"Show Fast Curve",
group = GROUP_VISUAL
)
bool showBaseInput = input.bool(
true,
"Show Base Curve",
group = GROUP_VISUAL
)
bool showAnchorInput = input.bool(
true,
"Show Anchor Curve",
group = GROUP_VISUAL
)
bool colourCurvesBySlopeInput = input.bool(
true,
"Colour Curves by Slope",
group = GROUP_VISUAL
)
bool showStateBackgroundInput = input.bool(
false,
"Show State Background",
group = GROUP_VISUAL
)
bool showBarColoursInput = input.bool(
false,
"Colour Price Bars",
group = GROUP_VISUAL
)
bool showStatusLabelInput = input.bool(
true,
"Show Right-Side Status",
tooltip = "Small state-coloured marker to the right of price. Hover it for the full readout (state, bias, score, permission, setup, MA respect, preset).",
group = GROUP_VISUAL
)
int statusOffsetInput = input.int(
10,
"Status Horizontal Offset",
minval = 0,
maxval = 50,
group = GROUP_VISUAL
)
float statusVerticalOffsetInput = input.float(
0.30,
"Status Vertical Offset in ATR",
minval = -5.0,
maxval = 5.0,
step = 0.10,
group = GROUP_VISUAL
)
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
f_clamp(float value, float minimum, float maximum) =>
math.max(minimum, math.min(maximum, value))
f_safeDivide(float numerator, float denominator) =>
denominator != 0.0 ? numerator / denominator : 0.0
f_sign(float value) =>
value > 0.0 ? 1.0 : value < 0.0 ? -1.0 : 0.0
// Open Jurik-style approximation.
// This is not the proprietary original JMA.
f_jma(series float source, int length, int phase, float power) =>
float safeLength = math.max(length, 1)
float phaseRatio =
phase < -100 ? 0.5 :
phase > 100 ? 2.5 :
phase / 100.0 + 1.5
float beta =
0.45 * (safeLength - 1.0) /
(0.45 * (safeLength - 1.0) + 2.0)
float alpha = math.pow(beta, power)
var float e0 = na
var float e1 = na
var float e2 = na
var float result = na
e0 := (1.0 - alpha) * source + alpha * nz(e0[1], source)
e1 := (source - e0) * (1.0 - beta) + beta * nz(e1[1], 0.0)
e2 :=
(
e0 +
phaseRatio * e1 -
nz(result[1], source)
) *
math.pow(1.0 - alpha, 2.0) +
math.pow(alpha, 2.0) *
nz(e2[1], 0.0)
result := e2 + nz(result[1], source)
result
f_dema(series float source, int length) =>
float emaOne = ta.ema(source, length)
float emaTwo = ta.ema(emaOne, length)
2.0 * emaOne - emaTwo
f_kama(series float source, int length) =>
float fastSc = 2.0 / (2.0 + 1.0)
float slowSc = 2.0 / (30.0 + 1.0)
float change = math.abs(source - source[length])
float volatility = ta.sma(math.abs(source - source[1]), length) * length
float efficiencyRatio = f_safeDivide(change, volatility)
float smoothingConstant = math.pow(efficiencyRatio * (fastSc - slowSc) + slowSc, 2.0)
var float result = na
result := nz(result[1], source) + smoothingConstant * (source - nz(result[1], source))
result
f_ma(
series float source,
int length,
string maType,
int jmaPhase,
float jmaPower
) =>
switch maType
"JMA" => f_jma(source, length, jmaPhase, jmaPower)
"EMA" => ta.ema(source, length)
"SMA" => ta.sma(source, length)
"RMA" => ta.rma(source, length)
"WMA" => ta.wma(source, length)
"HMA" => ta.hma(source, length)
"DEMA" => f_dema(source, length)
"KAMA" => f_kama(source, length)
=> ta.ema(source, length)
f_slopeColour(float slope) =>
slope > 0.0 ? color.lime :
slope < 0.0 ? color.red :
color.gray
// ============================================================================
// PRESET RESOLUTION
// ============================================================================
string fastType =
presetInput == "Responsive" ? "JMA" :
presetInput == "Balanced" ? "JMA" :
presetInput == "Structural" ? "EMA" :
customFastTypeInput
int fastLength =
presetInput == "Responsive" ? 9 :
presetInput == "Balanced" ? 21 :
presetInput == "Structural" ? 20 :
customFastLengthInput
string baseType =
presetInput == "Responsive" ? "JMA" :
presetInput == "Balanced" ? "EMA" :
presetInput == "Structural" ? "SMA" :
customBaseTypeInput
int baseLength =
presetInput == "Responsive" ? 21 :
presetInput == "Balanced" ? 50 :
presetInput == "Structural" ? 50 :
customBaseLengthInput
string anchorType =
presetInput == "Responsive" ? "JMA" :
presetInput == "Balanced" ? "SMA" :
presetInput == "Structural" ? "SMA" :
customAnchorTypeInput
int anchorLength =
presetInput == "Responsive" ? 50 :
presetInput == "Balanced" ? 200 :
presetInput == "Structural" ? 200 :
customAnchorLengthInput
// ============================================================================
// MOVING AVERAGES
// ============================================================================
float fastMa = f_ma(
sourceInput,
fastLength,
fastType,
jmaPhaseInput,
jmaPowerInput
)
float baseMa = f_ma(
sourceInput,
baseLength,
baseType,
jmaPhaseInput,
jmaPowerInput
)
float anchorMa = f_ma(
sourceInput,
anchorLength,
anchorType,
jmaPhaseInput,
jmaPowerInput
)
// ============================================================================
// VOLATILITY NORMALISATION
// ============================================================================
float atr = ta.atr(atrLengthInput)
float safeAtr = math.max(atr, syminfo.mintick)
float atrBaseline = ta.sma(atr, volatilityBaselineInput)
float volatilityRatio = f_safeDivide(atr, atrBaseline)
bool extremeVolatility =
not na(volatilityRatio) and
volatilityRatio >= extremeVolatilityRatioInput
// ============================================================================
// DIRECTION
// ============================================================================
bool bullishOrder =
fastMa > baseMa and
baseMa > anchorMa
bool bearishOrder =
fastMa < baseMa and
baseMa < anchorMa
bool priceAboveFast = close > fastMa
bool priceBelowFast = close < fastMa
bool priceAboveBase = close > baseMa
bool priceBelowBase = close < baseMa
// ============================================================================
// ATR-NORMALISED SLOPE
// ============================================================================
float fastSlope =
f_safeDivide(
fastMa - fastMa[slopeLookbackInput],
safeAtr * slopeLookbackInput
)
float baseSlope =
f_safeDivide(
baseMa - baseMa[slopeLookbackInput],
safeAtr * slopeLookbackInput
)
float anchorSlope =
f_safeDivide(
anchorMa - anchorMa[slopeLookbackInput],
safeAtr * slopeLookbackInput
)
bool fastRising = fastSlope > minimumFastSlopeInput
bool fastFalling = fastSlope < -minimumFastSlopeInput
bool baseRising = baseSlope > minimumBaseSlopeInput
bool baseFalling = baseSlope < -minimumBaseSlopeInput
// ============================================================================
// CURVATURE
// ============================================================================
float fastCurvature =
fastSlope -
fastSlope[curvatureLookbackInput]
float baseCurvature =
baseSlope -
baseSlope[curvatureLookbackInput]
bool bullishCurvature =
fastCurvature > minimumCurvatureInput
bool bearishCurvature =
fastCurvature < -minimumCurvatureInput
// ============================================================================
// SEPARATION
// ============================================================================
float fastBaseSpread =
f_safeDivide(fastMa - baseMa, safeAtr)
float baseAnchorSpread =
f_safeDivide(baseMa - anchorMa, safeAtr)
float absoluteFastBaseSpread = math.abs(fastBaseSpread)
float absoluteBaseAnchorSpread = math.abs(baseAnchorSpread)
float fastBaseSpreadChange =
fastBaseSpread -
fastBaseSpread[1]
float baseAnchorSpreadChange =
baseAnchorSpread -
baseAnchorSpread[1]
bool bullishSpreadExpansion =
fastBaseSpread > 0.0 and
baseAnchorSpread > 0.0 and
fastBaseSpreadChange > 0.0 and
baseAnchorSpreadChange > 0.0
bool bearishSpreadExpansion =
fastBaseSpread < 0.0 and
baseAnchorSpread < 0.0 and
fastBaseSpreadChange < 0.0 and
baseAnchorSpreadChange < 0.0
bool fastBaseCompression =
absoluteFastBaseSpread <
absoluteFastBaseSpread[1]
bool baseAnchorCompression =
absoluteBaseAnchorSpread <
absoluteBaseAnchorSpread[1]
bool spreadCompression =
fastBaseCompression or
baseAnchorCompression
// ============================================================================
// PRICE EXTENSION
// ============================================================================
float priceExtension =
f_safeDivide(close - fastMa, safeAtr)
float priceBaseDistance =
f_safeDivide(close - baseMa, safeAtr)
bool bullishOverextension =
priceExtension > maximumExtensionInput
bool bearishOverextension =
priceExtension < -maximumExtensionInput
// ============================================================================
// CROSS FREQUENCY AND ENTANGLEMENT
// ============================================================================
bool priceBaseCross =
ta.cross(close, baseMa)
bool fastBaseCross =
ta.cross(fastMa, baseMa)
float priceBaseCrossCount =
ta.sma(
priceBaseCross ? 1.0 : 0.0,
crossFrequencyLookbackInput
) * crossFrequencyLookbackInput
float fastBaseCrossCount =
ta.sma(
fastBaseCross ? 1.0 : 0.0,
crossFrequencyLookbackInput
) * crossFrequencyLookbackInput
float combinedCrossCount =
priceBaseCrossCount +
fastBaseCrossCount
bool spreadEntanglement =
absoluteFastBaseSpread < entanglementSpreadInput and
absoluteBaseAnchorSpread < entanglementSpreadInput
bool crossEntanglement =
combinedCrossCount >= maximumCrossesInput
bool flatEntanglement =
math.abs(fastSlope) < minimumFastSlopeInput and
math.abs(baseSlope) < minimumBaseSlopeInput
bool entangled =
spreadEntanglement or
crossEntanglement or
(
flatEntanglement and
absoluteFastBaseSpread <
entanglementSpreadInput * 1.5
)
// ============================================================================
// MA RESPECT
// ============================================================================
bool touchedBase =
low <= baseMa + touchToleranceInput * safeAtr and
high >= baseMa - touchToleranceInput * safeAtr
// One sample per touch episode: only the first bar of a run of touching bars
// is logged, so a multi-bar hug of the line counts once, not once per bar.
bool baseTouchStart =
touchedBase and
not touchedBase[1]
bool eligibleHistoricTouch =
baseTouchStart[reactionBarsInput]
// Approach side: the bar before the touch episode closed above (bullish) or
// below (bearish) the MA. A move through the line from the other side is a
// breakout, not a bounce, and is never logged as respect.
bool baseApproachFromAbove =
close[1] > baseMa[1]
bool baseApproachFromBelow =
close[1] < baseMa[1]
bool historicBaseApproachFromAbove =
baseApproachFromAbove[reactionBarsInput]
bool historicBaseApproachFromBelow =
baseApproachFromBelow[reactionBarsInput]
float historicTouchBase =
baseMa[reactionBarsInput]
float historicTouchAtr =
safeAtr[reactionBarsInput]
float reactionPeriodLow =
ta.lowest(low, reactionBarsInput + 1)
float reactionPeriodHigh =
ta.highest(high, reactionBarsInput + 1)
bool bullishRespectSuccess =
eligibleHistoricTouch and
historicBaseApproachFromAbove and
close >=
historicTouchBase +
reactionMoveInput *
historicTouchAtr and
reactionPeriodLow >=
historicTouchBase -
respectInvalidationInput *
historicTouchAtr
bool bearishRespectSuccess =
eligibleHistoricTouch and
historicBaseApproachFromBelow and
close <=
historicTouchBase -
reactionMoveInput *
historicTouchAtr and
reactionPeriodHigh <=
historicTouchBase +
respectInvalidationInput *
historicTouchAtr
float validTouchCount =
ta.sma(
eligibleHistoricTouch ? 1.0 : 0.0,
respectLookbackInput
) * respectLookbackInput
float bullishRespectCount =
ta.sma(
bullishRespectSuccess ? 1.0 : 0.0,
respectLookbackInput
) * respectLookbackInput
float bearishRespectCount =
ta.sma(
bearishRespectSuccess ? 1.0 : 0.0,
respectLookbackInput
) * respectLookbackInput
float bullishRespectRate =
validTouchCount > 0.0 ?
bullishRespectCount /
validTouchCount :
0.0
float bearishRespectRate =
validTouchCount > 0.0 ?
bearishRespectCount /
validTouchCount :
0.0
float directionalRespect =
bullishRespectRate -
bearishRespectRate
// ============================================================================
// ANCHOR RESPECT
// ============================================================================
bool touchedAnchor =
low <= anchorMa + touchToleranceInput * safeAtr and
high >= anchorMa - touchToleranceInput * safeAtr
bool anchorTouchStart =
touchedAnchor and
not touchedAnchor[1]
// An Anchor touch that also touches the Base MA is the same touch already
// logged by Base Respect — only Anchor-only touches are counted here.
bool anchorOnlyTouchStart =
anchorTouchStart and
not touchedBase
bool eligibleHistoricAnchorTouch =
anchorOnlyTouchStart[reactionBarsInput]
bool anchorApproachFromAbove =
close[1] > anchorMa[1]
bool anchorApproachFromBelow =
close[1] < anchorMa[1]
bool historicAnchorApproachFromAbove =
anchorApproachFromAbove[reactionBarsInput]
bool historicAnchorApproachFromBelow =
anchorApproachFromBelow[reactionBarsInput]
float historicTouchAnchor =
anchorMa[reactionBarsInput]
float historicTouchAnchorAtr =
safeAtr[reactionBarsInput]
bool bullishAnchorRespectSuccess =
eligibleHistoricAnchorTouch and
historicAnchorApproachFromAbove and
close >=
historicTouchAnchor +
reactionMoveInput *
historicTouchAnchorAtr and
reactionPeriodLow >=
historicTouchAnchor -
respectInvalidationInput *
historicTouchAnchorAtr
bool bearishAnchorRespectSuccess =
eligibleHistoricAnchorTouch and
historicAnchorApproachFromBelow and
close <=
historicTouchAnchor -
reactionMoveInput *
historicTouchAnchorAtr and
reactionPeriodHigh <=
historicTouchAnchor +
respectInvalidationInput *
historicTouchAnchorAtr
float validAnchorTouchCount =
ta.sma(
eligibleHistoricAnchorTouch ? 1.0 : 0.0,
respectLookbackInput
) * respectLookbackInput
float bullishAnchorRespectCount =
ta.sma(
bullishAnchorRespectSuccess ? 1.0 : 0.0,
respectLookbackInput
) * respectLookbackInput
float bearishAnchorRespectCount =
ta.sma(
bearishAnchorRespectSuccess ? 1.0 : 0.0,
respectLookbackInput
) * respectLookbackInput
float bullishAnchorRespectRate =
validAnchorTouchCount > 0.0 ?
bullishAnchorRespectCount /
validAnchorTouchCount :
0.0
float bearishAnchorRespectRate =
validAnchorTouchCount > 0.0 ?
bearishAnchorRespectCount /
validAnchorTouchCount :
0.0
float directionalAnchorRespect =
bullishAnchorRespectRate -
bearishAnchorRespectRate
// ============================================================================
// REGIME
// ============================================================================
bool longRegime =
baseMa > anchorMa and
baseSlope > minimumBaseSlopeInput
bool shortRegime =
baseMa < anchorMa and
baseSlope < -minimumBaseSlopeInput
// Permission describes direction, not entry timing.
bool longPermission =
longRegime and
not entangled and
not bullishOverextension
bool shortPermission =
shortRegime and
not entangled and
not bearishOverextension
// ============================================================================
// SETUPS
// ============================================================================
bool priceNearBase =
math.abs(priceBaseDistance) <=
pullbackDistanceInput
bool fastNearBase =
absoluteFastBaseSpread <=
pullbackSpreadInput
bool longSetup =
longRegime and
not entangled and
not bullishOverextension and
priceNearBase
bool shortSetup =
shortRegime and
not entangled and
not bearishOverextension and
priceNearBase
// ============================================================================
// TRIGGERS
// ============================================================================
bool longPriceReclaim =
close > fastMa and
close[1] <= fastMa[1]
bool shortPriceReclaim =
close < fastMa and
close[1] >= fastMa[1]
bool longPriceConfirmation =
not requirePriceConfirmationInput or
close > high[1]
bool shortPriceConfirmation =
not requirePriceConfirmationInput or
close < low[1]
bool longCurvatureConfirmation =
not requireCurvatureInput or
bullishCurvature
bool shortCurvatureConfirmation =
not requireCurvatureInput or
bearishCurvature
bool longTrigger =
longPriceReclaim and
fastSlope > minimumFastSlopeInput and
fastSlope > fastSlope[1] and
longCurvatureConfirmation and
longPriceConfirmation
bool shortTrigger =
shortPriceReclaim and
fastSlope < -minimumFastSlopeInput and
fastSlope < fastSlope[1] and
shortCurvatureConfirmation and
shortPriceConfirmation
bool enterLong =
longSetup and
longTrigger and
not bullishOverextension and
not entangled
bool enterShort =
shortSetup and
shortTrigger and
not bearishOverextension and
not entangled
// ============================================================================
// EXIT
// ============================================================================
bool exitLong =
close < baseMa and
(
baseSlope <= 0.0 or
ta.crossunder(fastMa, baseMa)
)
bool exitShort =
close > baseMa and
(
baseSlope >= 0.0 or
ta.crossover(fastMa, baseMa)
)
// ============================================================================
// STATE ENGINE
// ============================================================================
bool bullExpansionState =
bullishOrder and
fastRising and
baseRising and
bullishSpreadExpansion and
priceAboveFast and
not bullishOverextension and
not entangled
bool bearExpansionState =
bearishOrder and
fastFalling and
baseFalling and
bearishSpreadExpansion and
priceBelowFast and
not bearishOverextension and
not entangled
bool bullPullbackState =
longRegime and
not bullExpansionState and
not entangled and
not bullishOverextension and
(
priceNearBase or
fastNearBase or
spreadCompression
)
bool bearPullbackState =
shortRegime and
not bearExpansionState and
not entangled and
not bearishOverextension and
(
priceNearBase or
fastNearBase or
spreadCompression
)
bool overextendedState =
(
longRegime and
bullishOverextension
) or
(
shortRegime and
bearishOverextension
)
bool transitionState =
not entangled and
not longRegime and
not shortRegime and
(
math.abs(fastSlope) >=
minimumFastSlopeInput or
math.abs(baseSlope) >=
minimumBaseSlopeInput
)
int marketState =
overextendedState ? STATE_OVEREXTENDED :
bullExpansionState ? STATE_BULL_EXPANSION :
bearExpansionState ? STATE_BEAR_EXPANSION :
bullPullbackState ? STATE_BULL_PULLBACK :
bearPullbackState ? STATE_BEAR_PULLBACK :
transitionState ? STATE_TRANSITION :
STATE_NEUTRAL
// ============================================================================
// FADING
// ============================================================================
bool longFading =
close < fastMa and
fastCurvature < 0.0 and
fastBaseCompression
bool shortFading =
close > fastMa and
fastCurvature > 0.0 and
fastBaseCompression
bool bullRegimeFading =
longRegime and
longFading
bool bearRegimeFading =
shortRegime and
shortFading
bool stateFading =
bullRegimeFading or
bearRegimeFading
// ============================================================================
// SCORE
// ============================================================================
// Direction: maximum +/-30.
float directionScore =
10.0 * f_sign(fastMa - baseMa) +
15.0 * f_sign(baseMa - anchorMa) +
5.0 * f_sign(close - baseMa)
// Slope: maximum +/-25.
float fastSlopeScore =
10.0 *
f_clamp(
f_safeDivide(
fastSlope,
math.max(
minimumFastSlopeInput,
0.001
)
),
-1.0,
1.0
)
float baseSlopeScore =
10.0 *
f_clamp(
f_safeDivide(
baseSlope,
math.max(
minimumBaseSlopeInput,
0.001
)
),
-1.0,
1.0
)
float anchorSlopeScore =
5.0 *
f_clamp(
f_safeDivide(
anchorSlope,
math.max(
minimumBaseSlopeInput * 0.5,
0.001
)
),
-1.0,
1.0
)
float slopeScore =
fastSlopeScore +
baseSlopeScore +
anchorSlopeScore
// Expansion: maximum +/-20.
float fastBaseExpansionScore =
10.0 *
f_clamp(
f_safeDivide(
fastBaseSpreadChange,
0.05
),
-1.0,
1.0
)
float baseAnchorExpansionScore =
10.0 *
f_clamp(
f_safeDivide(
baseAnchorSpreadChange,
0.03
),
-1.0,
1.0
)
float expansionScore =
fastBaseExpansionScore +
baseAnchorExpansionScore
// Curvature: maximum +/-10.
float curvatureScore =
6.0 *
f_clamp(
f_safeDivide(
fastCurvature,
math.max(
minimumCurvatureInput,
0.001
)
),
-1.0,
1.0
) +
4.0 *
f_clamp(
f_safeDivide(
baseCurvature,
math.max(
minimumCurvatureInput,
0.001
)
),
-1.0,
1.0
)
float preQualityScore =
directionScore +
slopeScore +
expansionScore +
curvatureScore
// Base Respect: maximum +/-8.
float respectScore =
useRespectInput ?
8.0 *
f_clamp(
directionalRespect,
-1.0,
1.0
) :
0.0
// Anchor Respect: maximum +/-5.
float anchorRespectScore =
useAnchorRespectInput ?
5.0 *
f_clamp(
directionalAnchorRespect,
-1.0,
1.0
) :
0.0
// Low-cross quality reinforces the current directional score.
// It does not independently create bullish or bearish bias.
float crossQuality =
1.0 -
f_clamp(
f_safeDivide(
combinedCrossCount,
maximumCrossesInput
),
0.0,
1.0
)
float crossQualityScore =
7.0 *
crossQuality *
f_sign(preQualityScore)
float rawScore =
preQualityScore +
respectScore +
anchorRespectScore +
crossQualityScore
// Penalties reduce conviction toward zero instead of introducing
// an artificial bearish bias.
float penaltyFraction =
(overextendedState ? 0.20 : 0.0) +
(entangled ? 0.25 : 0.0) +
(extremeVolatility ? 0.10 : 0.0)
float score =
f_clamp(
rawScore *
math.max(
0.0,
1.0 - penaltyFraction
),
-100.0,
100.0
)
// ============================================================================
// STATE TEXT AND COLOURS
// ============================================================================
string stateText =
marketState == STATE_BULL_EXPANSION ? "Bull Expansion" :
marketState == STATE_BULL_PULLBACK ? "Bull Pullback" :
marketState == STATE_BEAR_EXPANSION ? "Bear Expansion" :
marketState == STATE_BEAR_PULLBACK ? "Bear Pullback" :
marketState == STATE_OVEREXTENDED ? "Overextended" :
marketState == STATE_TRANSITION ? "Transition" :
"Neutral Compression"
color stateColour =
marketState == STATE_BULL_EXPANSION ? color.lime :
marketState == STATE_BULL_PULLBACK ? color.teal :
marketState == STATE_BEAR_EXPANSION ? color.red :
marketState == STATE_BEAR_PULLBACK ? color.orange :
marketState == STATE_OVEREXTENDED ? color.yellow :
marketState == STATE_TRANSITION ? color.aqua :
color.gray
string stateFadingSuffix =
stateFading ?
" (Fading)" :
""
color statusMarkerColour =
stateFading ?
color.new(stateColour, 45) :
stateColour
string biasText =
score >= strongBiasThresholdInput ?
"Strong Long" :
score >= directionalBiasThresholdInput ?
"Long Bias" :
score <= -strongBiasThresholdInput ?
"Strong Short" :
score <= -directionalBiasThresholdInput ?
"Short Bias" :
"Neutral"
string permissionText =
longPermission ?
"Long permitted" :
shortPermission ?
"Short permitted" :
"No trend permission"
string setupText =
longSetup ?
"Long setup active" :
shortSetup ?
"Short setup active" :
"No setup"
string respectText =
bullishRespectRate >
bearishRespectRate + 0.10 ?
"Base respected bullish" :
bearishRespectRate >
bullishRespectRate + 0.10 ?
"Base respected bearish" :
"No clear base respect"
string anchorRespectText =
bullishAnchorRespectRate >
bearishAnchorRespectRate + 0.10 ?
"Anchor respected bullish" :
bearishAnchorRespectRate >
bullishAnchorRespectRate + 0.10 ?
"Anchor respected bearish" :
"No clear anchor respect"
// ============================================================================
// MA PLOTS
// ============================================================================
color fastColour =
colourCurvesBySlopeInput ?
f_slopeColour(fastSlope) :
color.aqua
color baseColour =
colourCurvesBySlopeInput ?
f_slopeColour(baseSlope) :
color.orange
color anchorColour =
colourCurvesBySlopeInput ?
f_slopeColour(anchorSlope) :
color.gray
plot(
showFastInput ? fastMa : na,
"Fast Curve",
color = fastColour,
linewidth = 2
)
plot(
showBaseInput ? baseMa : na,
"Base Curve",
color = baseColour,
linewidth = 2
)
plot(
showAnchorInput ? anchorMa : na,
"Anchor Curve",
color = anchorColour,
linewidth = 3
)
// ============================================================================
// BACKGROUND AND BAR COLOURS
// ============================================================================
color backgroundColour =
marketState == STATE_BULL_EXPANSION ?
color.new(color.lime, 90) :
marketState == STATE_BULL_PULLBACK ?
color.new(color.teal, 91) :
marketState == STATE_BEAR_EXPANSION ?
color.new(color.red, 90) :
marketState == STATE_BEAR_PULLBACK ?
color.new(color.orange, 91) :
marketState == STATE_OVEREXTENDED ?
color.new(color.yellow, 90) :
marketState == STATE_TRANSITION ?
color.new(color.aqua, 93) :
color.new(color.gray, 95)
bgcolor(
showStateBackgroundInput ?
backgroundColour :
na,
title = "State Background"
)
color calculatedBarColour =
score >= strongBiasThresholdInput ?
color.lime :
score >= directionalBiasThresholdInput ?
color.teal :
score <= -strongBiasThresholdInput ?
color.red :
score <= -directionalBiasThresholdInput ?
color.orange :
color.gray
barcolor(
showBarColoursInput ?
calculatedBarColour :
na
)
// ============================================================================
// SIGNAL PLOTS
// ============================================================================
bool longPermissionStarted =
longPermission and
not longPermission[1]
bool shortPermissionStarted =
shortPermission and
not shortPermission[1]
plotshape(
showPermissionSignalsInput and
longPermissionStarted,
title = "Long Permission",
style = shape.circle,
location = location.belowbar,
size = size.tiny,
color = color.teal,
text = "LP",
textcolor = color.white
)
plotshape(
showPermissionSignalsInput and
shortPermissionStarted,
title = "Short Permission",
style = shape.circle,
location = location.abovebar,
size = size.tiny,
color = color.orange,
text = "SP",
textcolor = color.white
)
plotshape(
showReactivationSignalsInput and
enterLong,
title = "Long Reactivation",
style = shape.triangleup,
location = location.belowbar,
size = size.small,
color = color.lime,
text = "LONG",
textcolor = color.black
)
plotshape(
showReactivationSignalsInput and
enterShort,
title = "Short Reactivation",
style = shape.triangledown,
location = location.abovebar,
size = size.small,
color = color.red,
text = "SHORT",
textcolor = color.white
)
plotshape(
showAnchorRejectionSignalsInput and
bullishAnchorRespectSuccess,
title = "Bullish Anchor Rejection",
style = shape.triangleup,
location = location.belowbar,
size = size.tiny,
color = color.blue,
text = "AR",
textcolor = color.white,
offset = -reactionBarsInput
)
plotshape(
showAnchorRejectionSignalsInput and
bearishAnchorRespectSuccess,
title = "Bearish Anchor Rejection",
style = shape.triangledown,
location = location.abovebar,
size = size.tiny,
color = color.purple,
text = "AR",
textcolor = color.white,
offset = -reactionBarsInput
)
plotshape(
showExitSignalsInput and
exitLong,
title = "Exit Long",
style = shape.xcross,
location = location.abovebar,
size = size.tiny,
color = color.red,
text = "XL",
textcolor = color.white
)
plotshape(
showExitSignalsInput and
exitShort,
title = "Exit Short",
style = shape.xcross,
location = location.belowbar,
size = size.tiny,
color = color.lime,
text = "XS",
textcolor = color.black
)
// ============================================================================
// RIGHT-SIDE STATUS LABEL
// ============================================================================
var label statusLabel = na
if barstate.islast
if not na(statusLabel)
label.delete(statusLabel)
if showStatusLabelInput
string presetText =
presetInput +
" · " +
fastType +
" " +
str.tostring(fastLength) +
" / " +
baseType +
" " +
str.tostring(baseLength) +
" / " +
anchorType +
" " +
str.tostring(anchorLength)
string statusTooltip =
stateText +
stateFadingSuffix +
"\n" +
biasText +
" · Score " +
str.tostring(
score,
"#.0"
) +
"\n" +
permissionText +
"\n" +
setupText +
"\n" +
respectText +
"\n" +
anchorRespectText +
"\n" +
presetText
statusLabel := label.new(
bar_index + statusOffsetInput,
close +
statusVerticalOffsetInput *
safeAtr,
"",
xloc = xloc.bar_index,
yloc = yloc.price,
style = label.style_circle,
color = statusMarkerColour,
size = size.tiny,
tooltip = statusTooltip
)
// ============================================================================
// DATA WINDOW OUTPUTS
// ============================================================================
plot(
score,
"MA State Score",
display = display.data_window
)
plot(
fastSlope,
"Fast Slope",
display = display.data_window
)
plot(
baseSlope,
"Base Slope",
display = display.data_window
)
plot(
anchorSlope,
"Anchor Slope",
display = display.data_window
)
plot(
fastCurvature,
"Fast Curvature",
display = display.data_window
)
plot(
fastBaseSpread,
"Fast/Base Spread",
display = display.data_window
)
plot(
baseAnchorSpread,
"Base/Anchor Spread",
display = display.data_window
)
plot(
priceExtension,
"Price Extension",
display = display.data_window
)
plot(
bullishRespectRate * 100.0,
"Bullish Respect Rate",
display = display.data_window
)
plot(
bearishRespectRate * 100.0,
"Bearish Respect Rate",
display = display.data_window
)
plot(
bullishAnchorRespectRate * 100.0,
"Bullish Anchor Respect Rate",
display = display.data_window
)
plot(
bearishAnchorRespectRate * 100.0,
"Bearish Anchor Respect Rate",
display = display.data_window
)
plot(
combinedCrossCount,
"Cross Count",
display = display.data_window
)
plot(
stateFading ? 1.0 : 0.0,
"State Fading",
display = display.data_window
)
// ============================================================================
// ALERTS
// ============================================================================
alertcondition(
longPermissionStarted,
"Long Permission Started",
"MA Cascade Engine: Long permission has started."
)
alertcondition(
shortPermissionStarted,
"Short Permission Started",
"MA Cascade Engine: Short permission has started."
)
alertcondition(
enterLong,
"Long Reactivation",
"MA Cascade Engine: Bullish pullback reactivation detected."
)
alertcondition(
enterShort,
"Short Reactivation",
"MA Cascade Engine: Bearish pullback reactivation detected."
)
alertcondition(
bullishAnchorRespectSuccess,
"Bullish Anchor Rejection",
"MA Cascade Engine: Price bounced off the Anchor MA to the upside."
)
alertcondition(
bearishAnchorRespectSuccess,
"Bearish Anchor Rejection",
"MA Cascade Engine: Price bounced off the Anchor MA to the downside."
)
alertcondition(
bullRegimeFading and not bullRegimeFading[1],
"Bull Regime Fading",
"MA Cascade Engine: The active bull regime is losing momentum (Fast MA decelerating, Fast/Base compressing)."
)
alertcondition(
bearRegimeFading and not bearRegimeFading[1],
"Bear Regime Fading",
"MA Cascade Engine: The active bear regime is losing momentum (Fast MA decelerating, Fast/Base compressing)."
)
alertcondition(
exitLong,
"Exit Long",
"MA Cascade Engine: Long exit condition detected."
)
alertcondition(
exitShort,
"Exit Short",
"MA Cascade Engine: Short exit condition detected."
)
alertcondition(
marketState == STATE_BULL_EXPANSION and
marketState[1] != STATE_BULL_EXPANSION,
"Bull Expansion Started",
"MA Cascade Engine: Bull Expansion state has started."
)
alertcondition(
marketState == STATE_BEAR_EXPANSION and
marketState[1] != STATE_BEAR_EXPANSION,
"Bear Expansion Started",
"MA Cascade Engine: Bear Expansion state has started."
)
alertcondition(
marketState == STATE_OVEREXTENDED and
marketState[1] != STATE_OVEREXTENDED,
"Market Overextended",
"MA Cascade Engine: Direction remains present, but price is overextended."
)
Overview
MA Cascade Engine [WavesUnchained] evaluates moving average slopes, curvature, separation, and price extension to categorize market conditions into states such as expansion, pullback, compression, transition, or overextension. Configurable parameters include preset sensitivity profiles, custom moving average types (including Jurik-style JMA, EMA, and SMA), ATR volatility normalization, and MA respect tracking metrics.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Preset
string
Balanced
presetInput
Source
source
close
sourceInput
JMA Phase
int
0
jmaPhaseInput
JMA Power
float
2.0
jmaPowerInput
Fast Type
string
JMA
customFastTypeInput
Fast Length
int
21
customFastLengthInput
Base Type
string
EMA
customBaseTypeInput
Base Length
int
50
customBaseLengthInput
Anchor Type
string
SMA
customAnchorTypeInput
Anchor Length
int
200
customAnchorLengthInput
ATR Length
int
14
atrLengthInput
Slope Lookback
int
5
slopeLookbackInput
Show all 40 settings
Setting
Type
Default
Variable
Minimum Fast Slope per Bar
float
0.015
minimumFastSlopeInput
Minimum Base Slope per Bar
float
0.008
minimumBaseSlopeInput
Curvature Lookback
int
3
curvatureLookbackInput
Minimum Trigger Curvature
float
0.005
minimumCurvatureInput
Entanglement Spread
float
0.15
entanglementSpreadInput
Cross Frequency Lookback
int
30
crossFrequencyLookbackInput
Maximum Crosses
int
6
maximumCrossesInput
Maximum Pullback Distance
float
0.70
pullbackDistanceInput
Maximum Fast/Base Pullback Spread
float
0.75
pullbackSpreadInput
Maximum Price Extension
float
1.75
maximumExtensionInput
Extreme Volatility Ratio
float
1.80
extremeVolatilityRatioInput
Volatility Baseline
int
100
volatilityBaselineInput
Use Base Respect in Score
bool
true
useRespectInput
Use Anchor Respect in Score
bool
true
useAnchorRespectInput
Touch Tolerance
float
0.15
touchToleranceInput
Reaction Horizon
int
5
reactionBarsInput
Required Reaction Move
float
0.50
reactionMoveInput
Respect Invalidation
float
0.35
respectInvalidationInput
Respect Statistics Lookback
int
150
respectLookbackInput
Require Previous-Bar Break
bool
true
requirePriceConfirmationInput
Require Positive Trigger Curvature
bool
true
requireCurvatureInput
Show Permission Changes
bool
false
showPermissionSignalsInput
Show Reactivation Signals
bool
true
showReactivationSignalsInput
Show Anchor Rejection Signals
bool
true
showAnchorRejectionSignalsInput
Show Exit Signals
bool
false
showExitSignalsInput
Strong Bias Threshold
float
60.0
strongBiasThresholdInput
Directional Bias Threshold
float
25.0
directionalBiasThresholdInput
Show Fast Curve
bool
true
showFastInput
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.
The Adaptive Laguerre Filter calculates a smoothed, dynamically adjusted trendline plotted directly on the main price chart. It dynamically adjusts its damping factor (alpha) based on recent price range variations and median percentile calculations over a spec
This script plots three moving averages on the price chart: a fast EMA or SMA with 10 periods, a slow EMA or SMA with 20 periods, and a trend EMA or SMA with 100 periods. It signals trend changes by drawing a white cross when the fast moving average crosses ab
This indicator plots four customizable moving averages directly on the main price chart using default lengths of 3, 8, 21, and 50 periods. It fills the space between adjacent averages in green or maroon depending on trend alignment. Additionally, it computes B
This indicator plots three exponential moving averages and ATR volatility envelopes on the price chart. It compares the fast and slow EMAs to shade a trend ribbon and marks confirmed upward and downward crossovers with triangle labels. Settings change the thre
Donald SPX - Options Trading is an overlay indicator designed for intraday SPX analysis. It plots a 200-period Weighted Moving Average (WMA) on the price chart while evaluating multi-timeframe market structure across Daily, 15-minute, 5-minute, and 2-minute ba
This multi-timeframe indicator calculates and plots 20-period and 60-period simple moving averages from the 60-minute timeframe directly onto the price chart. The 20-period average displays as a green line, while the 60-period average appears in blue. It serve