Self-Adaptive Trend Signals [AlgoAlpha] for ThinkOrSwim

chewie76

Well-known member
VIP
VIP Enthusiast
5 minute SPY chart.
1784600034168.png

This is a conversion and modification. I added the code to include bands that indicate good take profit areas. All of the visuals can be turned on/off in the settings.

Original Concept:
https://www.tradingview.com/script/Yw4LSvQg-Self-Adaptive-Trend-Signals-AlgoAlpha/

OVERVIEW

This script builds on the SuperTrend by replacing its fixed ATR multiplier with one that learns from recent pullback behavior. Instead of assuming every market needs the same amount of room, it continuously measures how deep pullbacks normally become during uptrends and downtrends, then adjusts the trend line to match.

The learning process is separated for bullish and bearish markets because both often behave differently. As new price data arrives, the trend line gradually adapts while remaining stable through smoothing and configurable limits. The result is a SuperTrend that responds to changing market conditions without requiring manual multiplier adjustments.

Code:
# Self_Adaptive_Trend_Signals - ThinkScript conversion
# Original Concept: https://www.tradingview.com/script/Yw4LSvQg-Self-Adaptive-Trend-Signals-AlgoAlpha/
# Added Take Profit bands
# Converted and modified by Chewie 7/19/2026

declare upper;

# === Inputs ===

input colorBars = yes;
input showTPzone = yes;
input showTrendLine = yes;
input showTrendZone = yes;
input showpricecloud = yes;
input showMarkers = yes;
input normalMoveLength = 10;
input startingRoom = 3.0;
input pullbackCoverage = 85.0;
input learningMemory = 150;
input examplesUntilReady = 25;
input learningSmoothness = 5;
input minimumRoom = 0.75;
input maximumRoom = 6.0;
input scanWindowBars = 300;
input lineWidth = 2;

# === Core engine ===

def priceSource = (high + low) / 2;
def tr = TrueRange(high, close, low);
def atr = WildersAverage(tr, normalMoveLength);
def bounceBandWidth = Average(tr, normalMoveLength);
def validAtr = !IsNaN(atr) and atr > 0;
def requiredSamples = Min(examplesUntilReady, learningMemory);

# --- forward-declared recursive state; formulas assigned further down ---
rec trend;
rec trendExtreme;
rec lowerBand;
rec upperBand;

def bullFlag = validAtr and trend == 1 and trend[1] == 1;
def bearFlag = validAtr and trend == -1 and trend[1] == -1;
def bullExcursion = Max(trendExtreme - low, 0) / atr;
def bearExcursion = Max(high - trendExtreme, 0) / atr;

# --- Bullish pullback-room estimate: binary search for the pullbackCoverage
def bullTotal = fold bt = 0 to scanWindowBars with btc = 0 do btc + GetValue(bullFlag, bt + 1);

def bullMid0 = (minimumRoom + maximumRoom) / 2;
def bullBelow0 = fold bi0 = 0 to scanWindowBars with bb0 = 0 do bb0 + (GetValue(bullFlag, bi0 + 1) and GetValue(bullExcursion, bi0 + 1) <= bullMid0);
def bullFrac0 = bullBelow0 / Max(bullTotal, 1) * 100;
def bullDir0 = bullFrac0 < pullbackCoverage;
def bullLo1 = minimumRoom + (bullMid0 - minimumRoom) * bullDir0;
def bullHi1 = maximumRoom + (bullMid0 - maximumRoom) * (1 - bullDir0);

def bullMid1 = (bullLo1 + bullHi1) / 2;
def bullBelow1 = fold bi1 = 0 to scanWindowBars with bb1 = 0 do bb1 + (GetValue(bullFlag, bi1 + 1) and GetValue(bullExcursion, bi1 + 1) <= bullMid1);
def bullFrac1 = bullBelow1 / Max(bullTotal, 1) * 100;
def bullDir1 = bullFrac1 < pullbackCoverage;
def bullLo2 = bullLo1 + (bullMid1 - bullLo1) * bullDir1;
def bullHi2 = bullHi1 + (bullMid1 - bullHi1) * (1 - bullDir1);

def bullMid2 = (bullLo2 + bullHi2) / 2;
def bullBelow2 = fold bi2 = 0 to scanWindowBars with bb2 = 0 do bb2 + (GetValue(bullFlag, bi2 + 1) and GetValue(bullExcursion, bi2 + 1) <= bullMid2);
def bullFrac2 = bullBelow2 / Max(bullTotal, 1) * 100;
def bullDir2 = bullFrac2 < pullbackCoverage;
def bullLo3 = bullLo2 + (bullMid2 - bullLo2) * bullDir2;
def bullHi3 = bullHi2 + (bullMid2 - bullHi2) * (1 - bullDir2);

def bullMid3 = (bullLo3 + bullHi3) / 2;
def bullBelow3 = fold bi3 = 0 to scanWindowBars with bb3 = 0 do bb3 + (GetValue(bullFlag, bi3 + 1) and GetValue(bullExcursion, bi3 + 1) <= bullMid3);
def bullFrac3 = bullBelow3 / Max(bullTotal, 1) * 100;
def bullDir3 = bullFrac3 < pullbackCoverage;
def bullLo4 = bullLo3 + (bullMid3 - bullLo3) * bullDir3;
def bullHi4 = bullHi3 + (bullMid3 - bullHi3) * (1 - bullDir3);

def bullMid4 = (bullLo4 + bullHi4) / 2;
def bullBelow4 = fold bi4 = 0 to scanWindowBars with bb4 = 0 do bb4 + (GetValue(bullFlag, bi4 + 1) and GetValue(bullExcursion, bi4 + 1) <= bullMid4);
def bullFrac4 = bullBelow4 / Max(bullTotal, 1) * 100;
def bullDir4 = bullFrac4 < pullbackCoverage;
def bullLo5 = bullLo4 + (bullMid4 - bullLo4) * bullDir4;
def bullHi5 = bullHi4 + (bullMid4 - bullHi4) * (1 - bullDir4);

def bullEst = (bullLo5 + bullHi5) / 2;

# --- Bearish pullback-room estimate: same procedure, mirrored ---
def bearTotal = fold rt = 0 to scanWindowBars with rtc = 0 do rtc + GetValue(bearFlag, rt + 1);

def bearMid0 = (minimumRoom + maximumRoom) / 2;
def bearBelow0 = fold ri0 = 0 to scanWindowBars with rb0 = 0 do rb0 + (GetValue(bearFlag, ri0 + 1) and GetValue(bearExcursion, ri0 + 1) <= bearMid0);
def bearFrac0 = bearBelow0 / Max(bearTotal, 1) * 100;
def bearDir0 = bearFrac0 < pullbackCoverage;
def bearLo1 = minimumRoom + (bearMid0 - minimumRoom) * bearDir0;
def bearHi1 = maximumRoom + (bearMid0 - maximumRoom) * (1 - bearDir0);

def bearMid1 = (bearLo1 + bearHi1) / 2;
def bearBelow1 = fold ri1 = 0 to scanWindowBars with rb1 = 0 do rb1 + (GetValue(bearFlag, ri1 + 1) and GetValue(bearExcursion, ri1 + 1) <= bearMid1);
def bearFrac1 = bearBelow1 / Max(bearTotal, 1) * 100;
def bearDir1 = bearFrac1 < pullbackCoverage;
def bearLo2 = bearLo1 + (bearMid1 - bearLo1) * bearDir1;
def bearHi2 = bearHi1 + (bearMid1 - bearHi1) * (1 - bearDir1);

def bearMid2 = (bearLo2 + bearHi2) / 2;
def bearBelow2 = fold ri2 = 0 to scanWindowBars with rb2 = 0 do rb2 + (GetValue(bearFlag, ri2 + 1) and GetValue(bearExcursion, ri2 + 1) <= bearMid2);
def bearFrac2 = bearBelow2 / Max(bearTotal, 1) * 100;
def bearDir2 = bearFrac2 < pullbackCoverage;
def bearLo3 = bearLo2 + (bearMid2 - bearLo2) * bearDir2;
def bearHi3 = bearHi2 + (bearMid2 - bearHi2) * (1 - bearDir2);

def bearMid3 = (bearLo3 + bearHi3) / 2;
def bearBelow3 = fold ri3 = 0 to scanWindowBars with rb3 = 0 do rb3 + (GetValue(bearFlag, ri3 + 1) and GetValue(bearExcursion, ri3 + 1) <= bearMid3);
def bearFrac3 = bearBelow3 / Max(bearTotal, 1) * 100;
def bearDir3 = bearFrac3 < pullbackCoverage;
def bearLo4 = bearLo3 + (bearMid3 - bearLo3) * bearDir3;
def bearHi4 = bearHi3 + (bearMid3 - bearHi3) * (1 - bearDir3);

def bearMid4 = (bearLo4 + bearHi4) / 2;
def bearBelow4 = fold ri4 = 0 to scanWindowBars with rb4 = 0 do rb4 + (GetValue(bearFlag, ri4 + 1) and GetValue(bearExcursion, ri4 + 1) <= bearMid4);
def bearFrac4 = bearBelow4 / Max(bearTotal, 1) * 100;
def bearDir4 = bearFrac4 < pullbackCoverage;
def bearLo5 = bearLo4 + (bearMid4 - bearLo4) * bearDir4;
def bearHi5 = bearHi4 + (bearMid4 - bearHi4) * (1 - bearDir4);

def bearEst = (bearLo5 + bearHi5) / 2;

# === Blend learned estimate with the starting/fallback factor by confidence ===
def bullCount = bullTotal;
def bearCount = bearTotal;
def bullConfidence = Min(bullCount / requiredSamples, 1);
def bearConfidence = Min(bearCount / requiredSamples, 1);
def bullBoundedObs = Max(minimumRoom, Min(maximumRoom, bullEst));
def bearBoundedObs = Max(minimumRoom, Min(maximumRoom, bearEst));
def bullFactorTarget = startingRoom * (1 - bullConfidence) + bullBoundedObs * bullConfidence;
def bearFactorTarget = startingRoom * (1 - bearConfidence) + bearBoundedObs * bearConfidence;
def bullishFactor = ExpAverage(bullFactorTarget, learningSmoothness);
def bearishFactor = ExpAverage(bearFactorTarget, learningSmoothness);

# === Bands  ===
def rawLower = priceSource - bullishFactor * atr;
def rawUpper = priceSource + bearishFactor * atr;

def lowerRatchetUp = rawLower > lowerBand[1] or close[1] < lowerBand[1];
def lowerRecursive = lowerBand[1] + (rawLower - lowerBand[1]) * lowerRatchetUp;
lowerBand = CompoundValue(1, lowerRecursive, rawLower);

def upperRatchetDown = rawUpper < upperBand[1] or close[1] > upperBand[1];
def upperRecursive = upperBand[1] + (rawUpper - upperBand[1]) * upperRatchetDown;
upperBand = CompoundValue(1, upperRecursive, rawUpper);

def priceUp = close >= priceSource;
def initialTrend = 2 * priceUp - 1;

def wasBull = trend[1] == 1;
def bullContinuation = 2 * (close >= lowerBand) - 1;
def bearContinuation = 2 * (close > upperBand) - 1;
def recursiveTrendRaw = bearContinuation + (bullContinuation - bearContinuation) * wasBull;
def recursiveTrend = trend[1] + (recursiveTrendRaw - trend[1]) * validAtr;

trend = CompoundValue(1, recursiveTrend, initialTrend);

def freshExtreme = ((trend + 1) / 2) * high + ((1 - trend) / 2) * low;
def continuedExtreme = ((trend + 1) / 2) * Max(trendExtreme[1], high) + ((1 - trend) / 2) * Min(trendExtreme[1], low);
def isFreshTrend = trend != trend[1];
def recursiveExtreme = continuedExtreme + (freshExtreme - continuedExtreme) * isFreshTrend;

trendExtreme = CompoundValue(1, recursiveExtreme, freshExtreme);

def superTrend = ((trend + 1) / 2) * lowerBand + ((1 - trend) / 2) * upperBand;
def bullishShift = trend == 1 and trend[1] == -1;
def bearishShift = trend == -1 and trend[1] == 1;

# === Visuals ===
def showUpLine = showTrendLine and trend == 1;
def showDownLine = showTrendLine and trend == -1;

plot UpTrendLine = if showUpLine then superTrend else Double.NaN;
UpTrendLine.SetDefaultColor(Color.green);
UpTrendLine.SetLineWeight(lineWidth);

plot DownTrendLine = if showDownLine then superTrend else Double.NaN;
DownTrendLine.SetDefaultColor(Color.RED);
DownTrendLine.SetLineWeight(lineWidth);

def upBounceRaw = Max(superTrend, Min(superTrend + bounceBandWidth, priceSource));
def downBounceRaw = Min(superTrend, Max(superTrend - bounceBandWidth, priceSource));

plot UpBounceLine = if showUpLine then upBounceRaw else Double.NaN;
UpBounceLine.SetDefaultColor(Color.green);
UpBounceLine.SetLineWeight(1);

plot DownBounceLine = if showDownLine then downBounceRaw else Double.NaN;
DownBounceLine.SetDefaultColor(Color.RED);
DownBounceLine.SetLineWeight(1);

AddCloud(if showpricecloud then UpTrendLine else Double.NaN, if showpricecloud then close else Double.NaN, Color.CURRENT, Color.green);
AddCloud(if showpricecloud then close else Double.NaN, if showpricecloud then DownTrendLine else Double.NaN, Color.CURRENT, Color.RED);
AddCloud(if showTrendZone then UpTrendLine else Double.NaN, if showTrendZone then UpBounceLine else Double.NaN, Color.green, Color.green);
AddCloud(if showTrendZone then DownBounceLine else Double.NaN, if showTrendZone then DownTrendLine else Double.NaN, Color.RED, Color.RED);

def showUpMarker = showMarkers and bullishShift;
def showDownMarker = showMarkers and bearishShift;

plot TrendUpMarker = if showUpMarker then superTrend else Double.NaN;
TrendUpMarker.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
TrendUpMarker.SetDefaultColor(Color.green);
TrendUpMarker.SetLineWeight(3);

plot TrendDownMarker = if showDownMarker then superTrend else Double.NaN;
TrendDownMarker.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
TrendDownMarker.SetDefaultColor(Color.RED);
TrendDownMarker.SetLineWeight(3);

def bullBounceReaction = trend == 1 and trend[1] == 1 and !IsNaN(UpBounceLine) and !IsNaN(UpBounceLine[1]) and close > UpBounceLine and close[1] <= UpBounceLine[1] and close[1] >= superTrend[1];
def bearBounceReaction = trend == -1 and trend[1] == -1 and !IsNaN(DownBounceLine) and !IsNaN(DownBounceLine[1]) and close < DownBounceLine and close[1] >= DownBounceLine[1] and close[1] <= superTrend[1];

def showBounceUp = showMarkers and bullBounceReaction;
def showBounceDown = showMarkers and bearBounceReaction;

plot BounceUp = if showBounceUp then superTrend - bounceBandWidth * 0.5 else Double.NaN;
BounceUp.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
BounceUp.SetDefaultColor(Color.green);

plot BounceDown = if showBounceDown then superTrend + bounceBandWidth * 0.5 else Double.NaN;
BounceDown.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
BounceDown.SetDefaultColor(Color.RED);

# Bar coloring
AssignPriceColor(if colorBars then (if trend == 1 then Color.green else Color.RED) else Color.CURRENT);


# === Take-profit zone ===
input innerMult = 2.5;         # inner band distance, in ATRs, at baseline (scale = 1)
input outerMult = 3.0;         # outer band distance, in ATRs, at baseline (scale = 1)

def bullScale = bullishFactor / startingRoom;
def bearScale = bearishFactor / startingRoom;

DefineGlobalColor("UpperChannel", CreateColor(120, 0, 120));
DefineGlobalColor("LowerChannel", CreateColor(00, 120, 120));

# Upper (bullish-side) target zone, scaled by how wide the bull room is now
def upperInnerVal = UpBounceLine + innerMult * bullScale * atr;
def upperOuterVal = UpBounceLine + outerMult * bullScale * atr;

# Lower (bearish-side) target zone, scaled by how wide the bear room is now
def lowerInnerValD = DownBounceLine - innerMult * bearScale * atr;
def lowerOuterValD = DownBounceLine - outerMult * bearScale * atr;

plot UpperInnerU = if showTPzone then upperInnerVal else double.nan;
plot UpperOuterU = if showTPzone then upperOuterVal else double.nan;

plot LowerInnerD = if showTPzone then lowerInnerValD else double.nan;
plot LowerOuterD = if showTPzone then lowerOuterValD else double.nan;

UpperInnerU.SetDefaultColor(GlobalColor("UpperChannel"));
UpperInnerU.SetLineWeight(1);

UpperOuterU.SetDefaultColor(GlobalColor("UpperChannel"));
UpperOuterU.SetLineWeight(2);

LowerInnerD.SetDefaultColor(GlobalColor("LowerChannel"));
LowerInnerD.SetLineWeight(1);

LowerOuterD.SetDefaultColor(GlobalColor("LowerChannel"));
LowerOuterD.SetLineWeight(2);

# Shade the inner channel solidly; leave the inner-to-outer zone unshaded
AddCloud(UpperInnerU, UpperOuterU, GlobalColor("UpperChannel"), GlobalColor("UpperChannel"), yes);
AddCloud(LowerOuterD, LowerInnerD, GlobalColor("LowerChannel"), GlobalColor("LowerChannel"), yes);


def upperInnerValM = upTrendLine + 2 * bullScale * atr;
def lowerInnerValDM = downTrendLine - 2 * bearScale * atr;

plot UpperInnerUM = if showTPzone then upperInnerValM else double.nan;
UpperInnerUM.setdefaultColor(CreateColor(220, 100, 220));

plot LowerInnerDM = if showTPzone then lowerInnerValDM else double.nan;
LowerInnerDM.SetDefaultColor(CreateColor(100, 220, 220));
 
Last edited:

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
1832 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top