Most trend envelopes wrap a moving average in a band of fixed width: a multiple of ATR, a percentage, a standard deviation. They look the same whether momentum is exploding or fading. Gravity Trend Levels, by BOSWaves, takes a different route. It measures the acceleration of the trend baseline, turns that acceleration into a decaying “gravity” score, and lets that score drive both the band width and the thickness of a one sided cloud. The result is an overlay that visibly tightens when momentum is strong and loosens as the move runs out of fuel, before any flip occurs.
It gives you three things in one tool: a latching trend regime (like a Supertrend), a real time momentum gauge (the cloud thickness), and a set of structural memory levels (the fail lines printed where each previous trend collapsed).
The baseline is an EMA of the close. Instead of ATR, the indicator measures volatility as the Mean Absolute Deviation (MAD) of price around that baseline: the average absolute distance between close and EMA over a lookback. MAD becomes the unit that both normalises acceleration and scales every band and cloud distance, so the whole construction stays in one consistent measure.
baseline = EMA(close, trendLen)
MAD = average( abs(close - baseline), madLen )
This is where the indicator earns its name. It does not look at price position, it looks at how fast the trend is bending:
velocity = baseline - baseline[gravityLookback] ' rate of change of the trend
acceleration = velocity - velocity[1] ' is the trend speeding up or stalling
normAccel = acceleration / MAD ' volatility normalised
The acceleration reading is converted into a single scalar, gravity:
Gravity is then squashed into a normalised 0 to 1 value called pull. High pull means an active, accelerating trend. Pull fading toward zero means the move is coasting.
Pull drives two adaptive behaviours:
The trend itself is a latching state machine, the same logic a Supertrend uses: a close beyond the upper band sets a bullish state, a close beyond the lower band sets a bearish state, and the state holds until the opposite band is breached. Noise inside the bands does not flip it.
Two more layers add structure:
The defaults follow the author suggested baseline configuration:
trendLen = 14: EMA period of the baseline.gravityLookback = 19: window for the velocity (rate of change) measurement.gravityDecay = 0.96: how slowly gravity fades between acceleration events. Higher (toward 0.99) sustains pull longer, lower (toward 0.80) lets it fade fast.madLen = 24: lookback for the MAD volatility unit.trailMin = 1.0 and trailMax = 1.0: tight and wide band multipliers. With both equal the outer band width is constant, while the cloud still breathes through its pull scaled thickness. Set trailMin below trailMax to make the outer band adapt as well.signalBuffer = 50: bars after a flip before retest diamonds are allowed.retestCooldown = 4: minimum spacing between consecutive diamonds.failExtension = 20: how far each fail level line projects forward from the flip bar.
//--------------------------------------------------------//
//PRC_Gravity Trend Levels [BOSWaves]
//version = 0
//16.06.26
//Iván González @ www.prorealcode.com
//Sharing ProRealTime knowledge
//--------------------------------------------------------//
//-----Inputs---------------------------------------------//
trendLen = 14
gravityLookback = 19
gravityDecay = 0.96
madLen = 24
trailMin = 1.0
trailMax = 1.0
signalBuffer = 50
retestCooldown = 4
failExtension = 20
//--------------------------------------------------------//
//-----Baseline + MAD volatility--------------------------//
baseline = average[trendLen, 1](close)
madVal = max(average[madLen](abs(close - baseline)), 0.000001)
//--------------------------------------------------------//
//-----Velocity / acceleration / gravity state-----------//
velocity = baseline - baseline[gravityLookback]
acceleration = velocity - velocity[1]
normAccel = acceleration / madVal
warmup = max(trendLen + gravityLookback + 2, madLen)
if barindex <= warmup then
gravity = 0
elsif abs(normAccel) > 0.1 then
gravity = min(abs(normAccel) * 8, 2.0)
else
gravity = gravity * gravityDecay
endif
pull = min(gravity / 1.5, 1.0)
//--------------------------------------------------------//
//-----Adaptive trail + bands-----------------------------//
trailMult = trailMax + (trailMin - trailMax) * pull
upperBand = baseline + madVal * trailMult
lowerBand = baseline - madVal * trailMult
//--------------------------------------------------------//
//-----Trend state machine (warmup-guarded)---------------//
if barindex <= warmup then
trendState = 0
elsif close crosses over upperBand then
trendState = 1
elsif close crosses under lowerBand then
trendState = -1
else
trendState = trendState[1]
endif
flipBull = trendState = 1 and trendState[1] = -1
flipBear = trendState = -1 and trendState[1] = 1
//--------------------------------------------------------//
//-----Gravity cloud (one-sided, pull-scaled)-------------//
thickness = madVal * (0.30 + 0.70 * pull)
if trendState = 1 then
upOuter = lowerBand
upInner = lowerBand + thickness
dnOuter = undefined
dnInner = undefined
cloudInnerVal = upInner
r = 0
g = 200
b = 120
alphaup = 255
alphadn = 0
elsif trendState = -1 then
upOuter = undefined
upInner = undefined
dnOuter = upperBand
dnInner = upperBand - thickness
cloudInnerVal = dnInner
r = 230
g = 45
b = 45
alphaup = 0
alphadn = 255
else
upOuter = undefined
upInner = undefined
dnOuter = undefined
dnInner = undefined
cloudInnerVal = undefined
r = 150
g = 150
b = 150
alphaup = 0
alphadn = 0
endif
//--------------------------------------------------------//
//-----Retest counters + detection------------------------//
if flipBull or flipBear then
barsSinceFlip = 0
elsif barindex <= warmup then
barsSinceFlip = 9999
else
barsSinceFlip = barsSinceFlip + 1
endif
if barindex <= warmup then
barsSinceDiamond = 9999
else
barsSinceDiamond = barsSinceDiamond + 1
endif
retestBull = trendState = 1 and close <= cloudInnerVal and barsSinceFlip > signalBuffer and barsSinceDiamond > retestCooldown
retestBear = trendState = -1 and close >= cloudInnerVal and barsSinceFlip > signalBuffer and barsSinceDiamond > retestCooldown
if retestBull or retestBear then
barsSinceDiamond = 0
endif
//--------------------------------------------------------//
//-----Drawings: fail levels, flip signals, diamonds------//
if flipBull then
drawsegment(barindex , low, barindex + failExtension, low) coloured(0, 200, 120) style(dottedline)
drawtext("B", barindex, low - thickness - madVal * 0.3) coloured(0, 200, 120)
endif
if flipBear then
drawsegment(barindex , high, barindex + failExtension, high) coloured(230, 45, 45) style(dottedline)
drawtext("S", barindex, high + thickness + madVal * 0.3) coloured(230, 45, 45)
endif
if retestBull then
drawtext("◆", barindex, low - madVal * 0.3) coloured(0, 200, 120)
endif
if retestBear then
drawtext("◆", barindex, high + madVal * 0.3) coloured(230, 45, 45)
endif
if trendState <> trendState[1] then
alphaup=0
alphadn=0
elsif trendState=1 then
alphaup=255
alphadn=0
elsif trendState=-1 then
alphaup=0
alphadn=255
else
alphaup=0
alphadn=0
endif
colorbetween(upOuter, upInner, 0, 200, 120, alphaup*0.3)
colorbetween(dnOuter, dnInner, 230, 45, 45, alphadn*0.3)
//--------------------------------------------------------//
RETURN baseline COLOURED(r, g, b) STYLE(line, 3) AS "Baseline", upOuter COLOURED(0, 200, 120,alphaup) STYLE(line, 1) AS "Cloud Up", dnOuter COLOURED(230, 45, 45,alphadn) STYLE(line, 1) AS "Cloud Dn"