EMA 20 50 Trend and Regime Labels For ThinkOrSwim

antwerks

Well-known member
VIP
VIP Enthusiast
Below is a complete Thinkorswim upper-chart study using three labels:
  1. TREND: EMA stack and recent crossover.
  2. REGIME: spread width and whether it is widening, narrowing, or braiding.
  3. ACTION: continuation, caution, consolidation, or transition.
The EMA spread is normalized by ATR, allowing the same thresholds to work more consistently across stocks with different prices and volatility.


xMUjMgT.png


How the labels work

EMA conditionInterpretation
20 EMA above 50 EMABullish directional bias
20 EMA below 50 EMABearish directional bias
Wide and wideningTrend is strengthening
Wide but stableEstablished trend is sustaining
Wide and narrowingTrend remains intact but is weakening
Narrow spreadNeutral or transitional
Several recent crosses with narrow spreadBraiding and likely consolidation

A narrowing bullish spread is not immediately bearish, and a narrowing bearish spread is not immediately bullish. Narrowing is a caution signal showing that the existing trend is losing separation. A reversal is more credible only after the EMAs cross and begin widening in the new direction.

Code:
# ============================================================
# EMA 20/50 TREND AND REGIME LABELS
# Thinkorswim Upper Study
# ANTWERKS
# TREND:
# EMA 20 above EMA 50 = bullish
# EMA 20 below EMA 50 = bearish
#
# REGIME:
# Wide and widening = strengthening trend
# Wide but narrowing = trend losing strength
# Narrow = neutral/transition
# Repeated crosses with narrow spread = braiding/consolidation
# ============================================================

declare upper;

# ============================================================
# INPUTS
# ============================================================

input fastEMALength = 20;
input slowEMALength = 50;

input atrLength = 14;

# Spread thresholds normalized by ATR
input narrowSpreadATR = 0.15;
input wideSpreadATR = 0.50;

# Measures whether the EMA spread is widening or narrowing
input spreadSlopeBars = 3;
input spreadChangeTolerance = 0.02;

# Braiding/consolidation detection
input braidLookback = 20;
input minimumCrossesForBraid = 2;
input braidMaximumSpreadATR = 0.25;

# A crossover remains labeled as recent for this many bars
input recentCrossBars = 3;

input showTrendLabel = yes;
input showRegimeLabel = yes;
input showActionLabel = yes;

input showEMAPlots = no;

# ============================================================
# EMA CALCULATIONS
# ============================================================

def ema20 = ExpAverage(close, fastEMALength);
def ema50 = ExpAverage(close, slowEMALength);

def bullishStack = ema20 > ema50;
def bearishStack = ema20 < ema50;

# ============================================================
# ATR-NORMALIZED EMA SPREAD
# ============================================================

def atr = Average(TrueRange(high, close, low), atrLength);

def safeATR =
    if atr > 0
    then atr
    else 0.01;

# Signed spread shows direction
def signedSpreadATR =
    (ema20 - ema50) / safeATR;

# Absolute spread shows width
def absoluteSpreadATR =
    AbsValue(signedSpreadATR);

# ============================================================
# CROSSOVER DETECTION
# ============================================================

def bullishCross =
    ema20 > ema50 and
    ema20[1] <= ema50[1];

def bearishCross =
    ema20 < ema50 and
    ema20[1] >= ema50[1];

def anyCross =
    bullishCross or bearishCross;

def recentBullishCross =
    Sum(
        if bullishCross then 1 else 0,
        recentCrossBars
    ) > 0;

def recentBearishCross =
    Sum(
        if bearishCross then 1 else 0,
        recentCrossBars
    ) > 0;

# ============================================================
# SPREAD EXPANSION AND CONTRACTION
# ============================================================

def spreadChange =
    absoluteSpreadATR -
    absoluteSpreadATR[spreadSlopeBars];

def spreadWidening =
    spreadChange > spreadChangeTolerance;

def spreadNarrowing =
    spreadChange < -spreadChangeTolerance;

def spreadStable =
    !spreadWidening and
    !spreadNarrowing;

# ============================================================
# SPREAD WIDTH
# ============================================================

def narrowSpread =
    absoluteSpreadATR <= narrowSpreadATR;

def wideSpread =
    absoluteSpreadATR >= wideSpreadATR;

def moderateSpread =
    !narrowSpread and
    !wideSpread;

# ============================================================
# BRAIDING / CONSOLIDATION
# ============================================================

def crossCount =
    Sum(
        if anyCross then 1 else 0,
        braidLookback
    );

def emaBraiding =
    crossCount >= minimumCrossesForBraid and
    absoluteSpreadATR <= braidMaximumSpreadATR;

# ============================================================
# TREND STATES
# ============================================================

def strongBullishTrend =
    bullishStack and
    wideSpread and
    spreadWidening;

def bullishTrend =
    bullishStack and
    !narrowSpread and
    !strongBullishTrend;

def weakBullishTrend =
    bullishStack and
    narrowSpread;

def strongBearishTrend =
    bearishStack and
    wideSpread and
    spreadWidening;

def bearishTrend =
    bearishStack and
    !narrowSpread and
    !strongBearishTrend;

def weakBearishTrend =
    bearishStack and
    narrowSpread;

# ============================================================
# CAUTION CONDITIONS
# ============================================================

def bullishCaution =
    bullishStack and
    spreadNarrowing and
    !emaBraiding;

def bearishCaution =
    bearishStack and
    spreadNarrowing and
    !emaBraiding;

# ============================================================
# OPTIONAL EMA PLOTS
# ============================================================

plot EMA20Plot =
    if showEMAPlots
    then ema20
    else Double.NaN;

EMA20Plot.SetDefaultColor(Color.GREEN);
EMA20Plot.SetLineWeight(2);

plot EMA50Plot =
    if showEMAPlots
    then ema50
    else Double.NaN;

EMA50Plot.SetDefaultColor(Color.RED);
EMA50Plot.SetLineWeight(2);

# ============================================================
# LABEL 1: TREND
# ============================================================

AddLabel(
    showTrendLabel,

    if emaBraiding then
        "TREND: NEUTRAL"

    else if recentBullishCross then
        "TREND: BULLISH CROSS"

    else if recentBearishCross then
        "TREND: BEARISH CROSS"

    else if strongBullishTrend then
        "TREND: STRONG BULLISH"

    else if strongBearishTrend then
        "TREND: STRONG BEARISH"

    else if bullishStack then
        "TREND: BULLISH"

    else if bearishStack then
        "TREND: BEARISH"

    else
        "TREND: NEUTRAL",

    if emaBraiding then
        Color.GRAY

    else if recentBullishCross then
        Color.GREEN

    else if recentBearishCross then
        Color.RED

    else if strongBullishTrend then
        Color.DARK_GREEN

    else if strongBearishTrend then
        Color.DARK_RED

    else if bullishStack then
        Color.GREEN

    else if bearishStack then
        Color.RED

    else
        Color.GRAY
);

# ============================================================
# LABEL 2: REGIME
# ============================================================

AddLabel(
    showRegimeLabel,

    if emaBraiding then
        "REGIME: BRAIDING / CONSOLIDATION"

    else if bullishStack and wideSpread and spreadWidening then
        "REGIME: BULLISH EXPANSION"

    else if bearishStack and wideSpread and spreadWidening then
        "REGIME: BEARISH EXPANSION"

    else if bullishStack and spreadNarrowing then
        "REGIME: BULLISH NARROWING"

    else if bearishStack and spreadNarrowing then
        "REGIME: BEARISH NARROWING"

    else if narrowSpread then
        "REGIME: NARROW / NEUTRAL"

    else if wideSpread then
        "REGIME: WIDE / SUSTAINING"

    else if moderateSpread and spreadWidening then
        "REGIME: DEVELOPING"

    else
        "REGIME: STABLE",

    if emaBraiding then
        Color.GRAY

    else if bullishStack and wideSpread and spreadWidening then
        Color.DARK_GREEN

    else if bearishStack and wideSpread and spreadWidening then
        Color.DARK_RED

    else if spreadNarrowing then
        Color.YELLOW

    else if narrowSpread then
        Color.LIGHT_GRAY

    else if bullishStack and spreadWidening then
        Color.GREEN

    else if bearishStack and spreadWidening then
        Color.RED

    else
        Color.GRAY
);

# ============================================================
# LABEL 3: ACTION
# ============================================================

AddLabel(
    showActionLabel,

    if emaBraiding then
        "ACTION: WAIT FOR BREAKOUT"

    else if recentBullishCross and spreadWidening then
        "ACTION: BULLISH TURN"

    else if recentBearishCross and spreadWidening then
        "ACTION: BEARISH TURN"

    else if strongBullishTrend then
        "ACTION: FAVOR LONGS"

    else if strongBearishTrend then
        "ACTION: FAVOR SHORTS"

    else if bullishCaution then
        "ACTION: CAUTION — BULL TREND WEAKENING"

    else if bearishCaution then
        "ACTION: CAUTION — BEAR TREND WEAKENING"

    else if weakBullishTrend then
        "ACTION: WAIT — WEAK BULLISH EDGE"

    else if weakBearishTrend then
        "ACTION: WAIT — WEAK BEARISH EDGE"

    else if bullishTrend then
        "ACTION: BULLISH BIAS"

    else if bearishTrend then
        "ACTION: BEARISH BIAS"

    else
        "ACTION: WAIT",

    if emaBraiding then
        Color.GRAY

    else if recentBullishCross and spreadWidening then
        Color.GREEN

    else if recentBearishCross and spreadWidening then
        Color.RED

    else if strongBullishTrend then
        Color.DARK_GREEN

    else if strongBearishTrend then
        Color.DARK_RED

    else if bullishCaution or bearishCaution then
        Color.YELLOW

    else if narrowSpread then
        Color.GRAY

    else if bullishTrend then
        Color.GREEN

    else if bearishTrend then
        Color.RED

    else
        Color.GRAY
);

# ============================================================
# DATA LABEL
# ============================================================

AddLabel(
    no,
    "EMA SPREAD: " + Round(signedSpreadATR, 2) + " ATR",
    if signedSpreadATR > 0
    then Color.GREEN
    else if signedSpreadATR < 0
    then Color.RED
    else Color.GRAY
);

 
Last edited by a moderator:

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

If you use scanners then using the premise from the above labels script, the scanner can look for two types of early bullish conditions:
  1. Fresh bullish cross: The 20 EMA recently crossed above the 50 EMA and separation is beginning to increase.
  2. Early bullish expansion: The 20 EMA is already above the 50 EMA, both averages are rising, and their ATR-normalized spread is widening but not yet overextended.
You have 3 scan modes to use within one scanner. What the three scan modes find:

EITHER​

This is the best starting point. It returns stocks meeting either condition:
  • A fresh bullish 20/50 crossover.
  • An established bullish stack that is still in its early expansion stage.

FRESH_CROSS​

This finds stocks where:
  • The 20 EMA crossed above the 50 EMA within the last three bars.
  • The 20 EMA remains above the 50 EMA.
  • Price is above the 20 EMA.
  • EMA separation is beginning to widen.
  • Price is not more than 1.5 ATR above the 20 EMA.
This is the earliest scan, but it will also produce more false starts around consolidation.

EARLY_EXPANSION​

This is the higher-quality continuation scan. It requires:
  • The 20 EMA above the 50 EMA.
  • Both averages rising.
  • Separation widening for at least two consecutive bars.
  • Spread between 0.05 and 0.50 ATR.
  • Price above the 20 EMA but not excessively extended.
The maximum spread of 0.50 ATR is important. Once the averages are widely separated, the trend may be strong but the early-entry opportunity may already have passed.

Recommended scanner setup​

For swing-trade candidates:
  • Aggregation: Daily
  • Scan mode: EITHER
  • Fresh cross bars: 3
  • Widening confirmation bars: 2
  • Maximum early spread: 0.50
  • Maximum price distance: 1.50 ATR
For earlier intraday entries:
  • Aggregation: 15 minutes
  • Scan mode: EARLY_EXPANSION
  • Fresh cross bars: 3
  • Widening confirmation bars: 2
  • Maximum early spread: 0.40
  • Maximum price distance: 1.00 ATR

The scanner identifies the developing bullish environment. The strongest results will be those where B-Xtrender’s slow line is green, Income Regime is above zero, SpreadATR is moving toward dark green, and VSLRT’s short-term plot has changed from red to green while its long-term plot remains above zero. Those other scripts can be found in the VIP section of this platform.

Code:
# ============================================================
# EMA 20/50 EARLY BULLISH TREND SCANNER
# antwerks
# Finds:
# 1. Fresh bullish 20/50 EMA crosses
# 2. Early bullish trends with widening EMA separation
#
# IMPORTANT:
# Use as a Stock Hacker custom Study Filter.
# Exactly one plot is included for scanning.
# ============================================================

# ============================================================
# SCAN MODE
# ============================================================

input scanMode = {
    default EITHER,
    FRESH_CROSS,
    EARLY_EXPANSION
};

# ============================================================
# EMA INPUTS
# ============================================================

input fastEMALength = 20;
input slowEMALength = 50;

# ============================================================
# FRESH-CROSS SETTINGS
# ============================================================

# A cross remains "fresh" for this many bars
input freshCrossBars = 3;

# Require the EMA spread to be widening after the cross
input requireWideningAfterCross = yes;

# ============================================================
# EXPANSION SETTINGS
# ============================================================

input atrLength = 14;

# Minimum spread eliminates meaningless separation
input minimumSpreadATR = 0.05;

# Maximum spread helps avoid already-mature trends
input maximumEarlySpreadATR = 0.50;

# Number of consecutive widening bars required
input wideningConfirmationBars = 2;

# Bars used to determine EMA slope
input slopeBars = 3;

# ============================================================
# OPTIONAL PRICE-LOCATION FILTER
# ============================================================

# Prevents results where price is already far above the 20 EMA
input avoidExtendedPrice = yes;
input maximumPriceDistanceATR = 1.50;

# ============================================================
# CORE CALCULATIONS
# ============================================================

def ema20 =
    ExpAverage(close, fastEMALength);

def ema50 =
    ExpAverage(close, slowEMALength);

def atr =
    Average(
        TrueRange(high, close, low),
        atrLength
    );

def safeATR =
    if atr > 0
    then atr
    else 0.01;

# ============================================================
# EMA STACK
# ============================================================

def bullishStack =
    ema20 > ema50;

# ============================================================
# BULLISH CROSS
# ============================================================

def bullishCross =
    ema20 > ema50 and
    ema20[1] <= ema50[1];

def freshBullishCross =
    Sum(
        if bullishCross
        then 1
        else 0,
        freshCrossBars
    ) > 0;

# ============================================================
# ATR-NORMALIZED EMA SPREAD
# ============================================================

def spreadATR =
    AbsValue(ema20 - ema50) / safeATR;

def spreadWiderThisBar =
    spreadATR > spreadATR[1];

def wideningCount =
    Sum(
        if spreadWiderThisBar
        then 1
        else 0,
        wideningConfirmationBars
    );

def confirmedWidening =
    wideningCount >= wideningConfirmationBars;

# ============================================================
# EMA DIRECTION
# ============================================================

def fastEMARising =
    ema20 > ema20[slopeBars];

def slowEMARising =
    ema50 > ema50[slopeBars];

def bothEMAsRising =
    fastEMARising and
    slowEMARising;

# ============================================================
# PRICE LOCATION
# ============================================================

def priceAboveFastEMA =
    close > ema20;

def priceDistanceATR =
    (close - ema20) / safeATR;

def acceptablePriceLocation =
    !avoidExtendedPrice or
    priceDistanceATR <= maximumPriceDistanceATR;

# ============================================================
# FRESH BULLISH CROSS CONDITION
# ============================================================

def freshCrossSetup =
    freshBullishCross and
    bullishStack and
    priceAboveFastEMA and
    acceptablePriceLocation and
    (
        !requireWideningAfterCross or
        spreadWiderThisBar
    );

# ============================================================
# EARLY BULLISH EXPANSION CONDITION
# ============================================================

def spreadInEarlyStage =
    spreadATR >= minimumSpreadATR and
    spreadATR <= maximumEarlySpreadATR;

def earlyExpansionSetup =
    bullishStack and
    bothEMAsRising and
    confirmedWidening and
    spreadInEarlyStage and
    priceAboveFastEMA and
    acceptablePriceLocation;

# ============================================================
# SINGLE SCANNER PLOT
# ============================================================

plot Scan =

    if scanMode == scanMode.FRESH_CROSS
    then freshCrossSetup

    else if scanMode == scanMode.EARLY_EXPANSION
    then earlyExpansionSetup

    else
        freshCrossSetup or
        earlyExpansionSetup;
 

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
776 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