Regime Study for Thinkorswim

SilentPartner

New member
I recently developed this study to help me screen regimes for a swing trading strategy I have. It is based on the simple higher high / higher low and vise versa price action regime concept. I'm using /ES as my "Market" with a HMA of 10.

So far it is doing its job pretty well. When Market is a 1 I am filtering out 80% negative trades. The other combinations of variables I still have to test.

mod note:
The study gives you a clean read on who has control by scoring both the market and your symbol on a 1–5 scale, then blocking trades when conditions turn hostile.
Treat 4–5 as expansion, 1–2 as contraction, 3 as transition, and use the no‑fly zone as your risk governor. When the symbol outruns the market, you have relative strength; when it lags, you stand down. The spring only matters when the market is pinned at 1 and your symbol just fired upward. Everything else is structure and confirmation doing the work for you.
1774030355287.png

ComponentFunction
Symbol scoreTracks higher‑high and lower‑low structure through a latched Hull Moving Average to classify trend strength from 1–5
Market scoreRuns the same structure logic on the benchmark to define the broader environment
No‑fly zoneBlocks entries when the market is at 1 or when the symbol drops by the configured threshold
Spring overrideAllows trades only when the market is at 1 and the symbol’s last move was upward
SpreadShows relative strength by subtracting market score from symbol score
CorrelationCompares long‑term and short‑term co‑movement to expose decorrelation shifts

All that said, know that I am not a very good programmer - AI/Claude helped me build this (and almost everything I build). I hope this can help someone and I'm very open to criticism or suggestions on how to make it better.
Code:
# ============================================
# HMA REGIME SCORE (Lower Study)
# ============================================
#
# SHORT-TERM REGIME MEASUREMENT TOOL
# Scores market benchmark and chart symbol 1-5
# based on Hull Moving Average swing structure.
#
# --- SCORING RULES ---
# 5 = 2HH + 2HL + 3rd HH (or HMA above all 3 previous highs)
# 4 = 2HH + 2HL (or HMA above 2 previous highs)
# 3 = mixed (or structure break from opposite direction)
# 2 = 2LH + 2LL (or HMA below 2 previous lows)
# 1 = 2LH + 2LL + 3rd LL (or HMA below all 3 previous lows)
#
# --- UNIFIED APPROACH ---
# Cascade position checks first: if HMA has cleared swing
# levels, score updates immediately. Each level cleared
# steps the score one notch. Uses Max/Min so HMA must
# clear ALL levels at that tier regardless of ordering.
# Cascade levels are LATCHED: highs only update on trough
# confirmation, lows only update on peak confirmation.
# This holds the score during normal pullbacks/bounces.
#
# When HMA is between levels, confirmed swing structure
# (higher highs/higher lows vs lower highs/lower lows)
# determines the score.
#
# Historical bars: 2-bar forward confirmation (hma[-1], hma[-2])
# filters false triggers. Current bar fires immediately.
#
# --- NO FLY ZONES ---
# Filter 1: Market at 1 (toggle on/off)
#   When market regime = 1, environment too hostile for entries.
# Filter 2: Symbol drop filter (0 = off, 1-3 = threshold)
#   Tracks the most recent symbol score change. If the score
#   dropped by >= threshold, no-fly is active until next change.
# Spring: Override for market no-fly (toggle on/off)
#   When market = 1 but symbol's last change was UP >= 1,
#   the coiled spring condition allows trades through.
#
# --- DISPLAY ---
# Market (default SPY) = BLACK line
# Chart symbol = RED line
# Both on same 1-5 scale
# No-fly zone = light red cloud across full range
# Labels: score, text, spread (symbol - market),
#         long-term correlation, short-term correlation,
#         decorrelation (short minus long), no-fly status
# ============================================
declare lower;
input marketSymbol = "SPY";
input hmaLength = 10;
input longCorrLength = 60;
input marketNoFly = yes;
input symDropFilter = 0;
input allowSpring = no;
# ============================================
# CHART SYMBOL
# ============================================
def hma = HullMovingAvg(close, hmaLength);
rec sDir = if hma > hma[1] and hma[-1] > hma and hma[-2] > hma[-1] then 1
           else if hma < hma[1] and hma[-1] < hma and hma[-2] < hma[-1] then -1
           else sDir[1];
def sPk = sDir == -1 and sDir[1] == 1;
def sTr = sDir == 1 and sDir[1] == -1;
rec sRunPeak = if sTr then hma
               else if sDir == 1 then Max(sRunPeak[1], hma)
               else sRunPeak[1];
rec sRunTrough = if sPk then hma
                 else if sDir == -1 then Min(sRunTrough[1], hma)
                 else sRunTrough[1];
rec sH1 = if sPk then sRunPeak[1] else sH1[1];
rec sH2 = if sPk then sH1[1] else sH2[1];
rec sH3 = if sPk then sH2[1] else sH3[1];
rec sL1 = if sTr then sRunTrough[1] else sL1[1];
rec sL2 = if sTr then sL1[1] else sL2[1];
rec sL3 = if sTr then sL2[1] else sL3[1];
def sHH = sH1 > sH2;
def sHL = sL1 > sL2;
def sLH = sH1 < sH2;
def sLL = sL1 < sL2;
def sIsLast = IsNaN(hma[-1]);
# Latched cascade levels
rec sCascH2 = if sTr then Max(sH1, sH2) else sCascH2[1];
rec sCascH3 = if sTr then Max(Max(sH1, sH2), sH3) else sCascH3[1];
rec sCascL2 = if sPk then Min(sL1, sL2) else sCascL2[1];
rec sCascL3 = if sPk then Min(Min(sL1, sL2), sL3) else sCascL3[1];
def sCrossAboveCascH3 = sCascH3 > 0 and hma > sCascH3 and (sIsLast or hma[-1] > sCascH3);
def sCrossAboveCascH2 = sCascH2 > 0 and hma > sCascH2 and (sIsLast or hma[-1] > sCascH2);
def sCrossAboveH1 = hma > sH1 and (sIsLast or hma[-1] > sH1);
def sCrossBelowCascL3 = hma < sCascL3 and (sIsLast or hma[-1] < sCascL3);
def sCrossBelowCascL2 = hma < sCascL2 and (sIsLast or hma[-1] < sCascL2);
def sCrossBelowL1 = hma < sL1 and (sIsLast or hma[-1] < sL1);
def symScoreRaw =
    if sCrossAboveCascH3 then 5
    else if sCrossBelowCascL3 then 1
    else if sCrossAboveCascH2 then 4
    else if sCrossBelowCascL2 then 2
    else if sLH and sLL and sCrossAboveH1 then 3
    else if sHH and sHL and sCrossBelowL1 then 3
    else if sHH and sHL and sH2 > sH3 then 5
    else if sHH and sHL then 4
    else if sLH and sLL and sL2 < sL3 then 1
    else if sLH and sLL then 2
    else 3;
# Carry forward when HMA has no value on current bar
rec symScore = if !IsNaN(symScoreRaw) then symScoreRaw else symScore[1];
# ============================================
# MARKET SYMBOL
# ============================================
def mHma = HullMovingAvg(close(symbol = marketSymbol), hmaLength);
rec mDir = if mHma > mHma[1] and mHma[-1] > mHma and mHma[-2] > mHma[-1] then 1
           else if mHma < mHma[1] and mHma[-1] < mHma and mHma[-2] < mHma[-1] then -1
           else mDir[1];
def mPk = mDir == -1 and mDir[1] == 1;
def mTr = mDir == 1 and mDir[1] == -1;
rec mRunPeak = if mTr then mHma
               else if mDir == 1 then Max(mRunPeak[1], mHma)
               else mRunPeak[1];
rec mRunTrough = if mPk then mHma
                 else if mDir == -1 then Min(mRunTrough[1], mHma)
                 else mRunTrough[1];
rec mH1 = if mPk then mRunPeak[1] else mH1[1];
rec mH2 = if mPk then mH1[1] else mH2[1];
rec mH3 = if mPk then mH2[1] else mH3[1];
rec mL1 = if mTr then mRunTrough[1] else mL1[1];
rec mL2 = if mTr then mL1[1] else mL2[1];
rec mL3 = if mTr then mL2[1] else mL3[1];
def mHH = mH1 > mH2;
def mHL = mL1 > mL2;
def mLH = mH1 < mH2;
def mLL = mL1 < mL2;
def mIsLast = IsNaN(mHma[-1]);
# Latched cascade levels
rec mCascH2 = if mTr then Max(mH1, mH2) else mCascH2[1];
rec mCascH3 = if mTr then Max(Max(mH1, mH2), mH3) else mCascH3[1];
rec mCascL2 = if mPk then Min(mL1, mL2) else mCascL2[1];
rec mCascL3 = if mPk then Min(Min(mL1, mL2), mL3) else mCascL3[1];
def mCrossAboveCascH3 = mCascH3 > 0 and mHma > mCascH3 and (mIsLast or mHma[-1] > mCascH3);
def mCrossAboveCascH2 = mCascH2 > 0 and mHma > mCascH2 and (mIsLast or mHma[-1] > mCascH2);
def mCrossAboveH1 = mHma > mH1 and (mIsLast or mHma[-1] > mH1);
def mCrossBelowCascL3 = mHma < mCascL3 and (mIsLast or mHma[-1] < mCascL3);
def mCrossBelowCascL2 = mHma < mCascL2 and (mIsLast or mHma[-1] < mCascL2);
def mCrossBelowL1 = mHma < mL1 and (mIsLast or mHma[-1] < mL1);
def mktScoreRaw =
    if mCrossAboveCascH3 then 5
    else if mCrossBelowCascL3 then 1
    else if mCrossAboveCascH2 then 4
    else if mCrossBelowCascL2 then 2
    else if mLH and mLL and mCrossAboveH1 then 3
    else if mHH and mHL and mCrossBelowL1 then 3
    else if mHH and mHL and mH2 > mH3 then 5
    else if mHH and mHL then 4
    else if mLH and mLL and mL2 < mL3 then 1
    else if mLH and mLL then 2
    else 3;
# Carry forward when HMA has no value on current bar
rec mktScore = if !IsNaN(mktScoreRaw) then mktScoreRaw else mktScore[1];
# ============================================
# NO FLY ZONE LOGIC
# ============================================
# Filter 1: Market at 1
def noFlyMkt = marketNoFly and mktScore == 1;
# Filter 2: Symbol drop - track last score change
# symPrevScore holds the score BEFORE the most recent change
rec symPrevScore = if symScore != symScore[1] then symScore[1]
                   else symPrevScore[1];
def symLastDelta = symScore - symPrevScore;
def noFlySym = symDropFilter > 0 and symLastDelta <= -symDropFilter;
# Spring: market at 1 but symbol just moved UP >= 1
def springActive = allowSpring and mktScore == 1 and symLastDelta >= 1;
# Combined no-fly zone
# Market no-fly can be overridden by spring; symbol drop cannot
def noFlyActive = (noFlyMkt and !springActive) or noFlySym;
# ============================================
# PLOTS
# ============================================
plot Market = mktScore;
Market.SetDefaultColor(Color.BLACK);
Market.SetLineWeight(2);
Market.SetStyle(Curve.FIRM);
plot Symbol = symScore;
Symbol.SetDefaultColor(Color.RED);
Symbol.SetLineWeight(2);
Symbol.SetStyle(Curve.FIRM);
# NoFly plot for PE Strategy reference (hidden visually)
plot NoFly = if noFlyActive then 1 else 0;
# Spring plot for PE Strategy reference (hidden visually)
plot Spring = if springActive then 1 else 0;
Spring.SetDefaultColor(Color.GRAY);
Spring.SetLineWeight(1);
Spring.SetStyle(Curve.SHORT_DASH);
Spring.SetHiding(yes);
Spring.HideBubble();
Spring.HideTitle();
NoFly.SetDefaultColor(Color.GRAY);
NoFly.SetLineWeight(1);
NoFly.SetStyle(Curve.SHORT_DASH);
NoFly.SetHiding(yes);
NoFly.HideBubble();
NoFly.HideTitle();
plot Top = 5;
plot Mid = 3;
plot Bot = 1;
Top.SetDefaultColor(Color.GRAY);
Top.SetStyle(Curve.SHORT_DASH);
Top.HideBubble();
Top.HideTitle();
Mid.SetDefaultColor(Color.GRAY);
Mid.SetStyle(Curve.SHORT_DASH);
Mid.HideBubble();
Mid.HideTitle();
Bot.SetDefaultColor(Color.GRAY);
Bot.SetStyle(Curve.SHORT_DASH);
Bot.HideBubble();
Bot.HideTitle();
# No-fly cloud shading (red = no fly, cyan = spring override)
AddCloud(if noFlyActive then Top else Double.NaN, Bot, Color.LIGHT_RED, Color.LIGHT_RED);
AddCloud(if springActive then Top else Double.NaN, Bot, Color.CYAN, Color.CYAN);
# ============================================
# LABELS
# ============================================
AddLabel(yes,
    if mktScore == 5 then "MKT: 5 Very Strong"
    else if mktScore == 4 then "MKT: 4 Strong"
    else if mktScore == 3 then "MKT: 3 Neutral"
    else if mktScore == 2 then "MKT: 2 Weak"
    else "MKT: 1 Very Weak",
    Color.LIGHT_GRAY);
AddLabel(yes,
    if symScore == 5 then "SYM: 5 Very Strong"
    else if symScore == 4 then "SYM: 4 Strong"
    else if symScore == 3 then "SYM: 3 Neutral"
    else if symScore == 2 then "SYM: 2 Weak"
    else "SYM: 1 Very Weak",
    Color.LIGHT_GRAY);
def symScoreR = Round(symScore, 0);
def mktScoreR = Round(mktScore, 0);
def spread = symScoreR - mktScoreR;
AddLabel(yes,
    if IsNaN(spread) then "SPREAD: ---"
    else "SPREAD: " + spread,
    Color.LIGHT_GRAY);
# No-fly status label
AddLabel(yes,
    if noFlyActive and noFlyMkt and noFlySym then "NO FLY: MKT + SYM DROP"
    else if noFlyActive and noFlyMkt then "NO FLY: MKT AT 1"
    else if noFlyActive and noFlySym then "NO FLY: SYM DROP " + symLastDelta
    else "FLY ZONE: CLEAR",
    if noFlyActive then Color.RED else Color.GREEN);
# ============================================
# CORRELATION - DUAL TIMEFRAME
# ============================================
# Long-term: structural relationship (do they move together?)
# Short-term: current co-movement (are they moving together NOW?)
# Gap between them = decorrelation signal
# ============================================
def mktClose = close(symbol = marketSymbol);
def longCorr = Correlation(close, mktClose, longCorrLength);
def shortCorr = Correlation(close, mktClose, hmaLength);
# Decorrelation = how far short-term has drifted from long-term
# Positive = currently MORE correlated than normal
# Negative = currently LESS correlated than normal (decorrelating)
def decorr = if IsNaN(shortCorr) or IsNaN(longCorr) then Double.NaN
             else shortCorr - longCorr;
AddLabel(yes,
    if IsNaN(longCorr) then "LT CORR: ---"
    else "LT CORR(" + longCorrLength + "): " + Round(longCorr, 2),
    Color.LIGHT_GRAY);
AddLabel(yes,
    if IsNaN(shortCorr) then "ST CORR: ---"
    else "ST CORR(" + hmaLength + "): " + Round(shortCorr, 2),
    Color.LIGHT_GRAY);
# Decorrelation label - highlights when short-term diverges from long-term
AddLabel(yes,
    if IsNaN(decorr) then "DECORR: ---"
    else "DECORR: " + Round(decorr, 2),
    Color.LIGHT_GRAY);
def mktBelow3 = if mktScore < 3 then 1 else 0;
def totalBars = BarNumber();
def countBelow3 = TotalSum(mktBelow3);
def pctBelow3 = Round((countBelow3 / totalBars) * 100, 1);
AddLabel(yes,
    "MKT < 3: " + countBelow3 + " days (" + pctBelow3 + "%) ",
    Color.Black);
def mktBelow2 = if mktScore < 2 then 1 else 0;
def totalBars2 = BarNumber();
def countBelow2 = TotalSum(mktBelow2);
def pctBelow2 = Round((countBelow2 / totalBars2) * 100, 1);
AddLabel(yes,
    "MKT < 2: " + countBelow2 + " days (" + pctBelow2 + "%) ",
    Color.Black);
 
Last edited by a moderator:

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

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
1360 Online
Create Post

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