Fisher-TMO Hybrid For ThinkOrSwim

antwerks

Well-known member
VIP
VIP Enthusiast
mod note:
ANTWERKS TMO + STOCHASTIC HYBRID V1.0

A Thinkorswim lower study oscillator that normalizes directional price persistence (TMO) and relative range location (Stochastic) onto a single -100 to +100 scale to pinpoint high-probability momentum turn points.

How Day Traders Use It
  • Dual-Confluence Timing: Blends market direction/speed (60% TMO weight) with structural price location (40% Stochastic weight) to eliminate fake oscillator crossovers.
  • Spotting Exhaustion Extremes: Pinpoints overbought (+60 or higher) and oversold (-60 or lower) zones where buying/selling pressure is losing steam.
  • Non-Repainting Signal Stability: Uses closed-bar evaluation by default (useClosedBarSignals = yes), ensuring printed arrows and alerts do not vanish mid-candle.

2GFIgep.png

xWMUpwu.png


Code:
# ANTWERKS TMO + STOCHASTIC HYBRID V1.0
# Thinkorswim lower study
#
# PURPOSE
# Combines two different momentum views on the same -100 to +100 scale:
#   TMO       = persistence of directional price pressure.
#   Stochastic = location of price within its recent high/low range.
# The weighted hybrid seeks turns that have both directional and location
# context. It is a test instrument, not a stand-alone trading system.
#
# STABLE MODE
# useClosedBarSignals = yes evaluates the last completed candle and plots its
# arrow on the next candle. This is deliberately one bar later and prevents an
# intrabar signal from disappearing.

declare lower;

# =========================
# INPUTS: TMO
# =========================
input tmoLength = 14;
input tmoCalcLength = 5;
input tmoSmoothLength = 3;

# =========================
# INPUTS: STOCHASTIC
# =========================
input stochasticLength = 13;
input stochasticKSmoothing = 4;
input stochasticDSmoothing = 3;
input stochasticAverageType = AverageType.SIMPLE;

# =========================
# INPUTS: HYBRID
# =========================
input tmoWeight = 0.60;
input stochasticWeight = 0.40;
input hybridSmoothing = 2;
input signalLength = 3;
input upperExtreme = 60.0;
input lowerExtreme = -60.0;
input triggerMode = {
    default TURN_FROM_EXTREME,
    EXIT_EXTREME,
    HYBRID_SIGNAL_CROSS
};

# =========================
# INPUTS: DISPLAY / ALERTS
# =========================
input useClosedBarSignals = yes;
input suppressRepeatedDirection = yes;
input showTMO = yes;
input showStochastic = yes;
input showSignalLine = yes;
input showExtremeClouds = yes;
input showSignalArrows = yes;
input showLabels = yes;
input enableAlerts = yes;

# =========================
# TMO CALCULATION
# =========================
# Common TMO construction: compare the current close with a sequence of recent
# opens, score each comparison +1/-1/0, then apply two smoothing stages.
def tmoScore = fold i = 0 to tmoLength with score = 0 do
    score +
    (if close > GetValue(open, i) then 1
     else if close < GetValue(open, i) then -1
     else 0);

def tmoFirstSmooth = ExpAverage(tmoScore, tmoCalcLength);
def tmoMainRaw = ExpAverage(tmoFirstSmooth, tmoSmoothLength);
def tmoNormalized =
    if tmoLength > 0 then 100 * tmoMainRaw / tmoLength
    else 0;

# =========================
# STOCHASTIC CALCULATION
# =========================
def stochasticHigh = Highest(high, stochasticLength);
def stochasticLow = Lowest(low, stochasticLength);
def stochasticRange = stochasticHigh - stochasticLow;
def stochasticRawK =
    if stochasticRange > 0 then
        100 * (close - stochasticLow) / stochasticRange
    else 50;
def stochasticK = MovingAverage(
    stochasticAverageType,
    stochasticRawK,
    stochasticKSmoothing
);
def stochasticD = MovingAverage(
    stochasticAverageType,
    stochasticK,
    stochasticDSmoothing
);

# Recenter %K from 0..100 to -100..+100 so it can be blended with TMO.
def stochasticCentered = 2 * (stochasticK - 50);
def stochasticSignalCentered = 2 * (stochasticD - 50);

# =========================
# WEIGHTED HYBRID
# =========================
def effectiveTMOWeight = Max(0, tmoWeight);
def effectiveStochasticWeight = Max(0, stochasticWeight);
def totalWeight = Max(
    0.0001,
    effectiveTMOWeight + effectiveStochasticWeight
);
def hybridUnsmoothed =
    (effectiveTMOWeight * tmoNormalized +
     effectiveStochasticWeight * stochasticCentered) / totalWeight;
def hybrid = ExpAverage(hybridUnsmoothed, hybridSmoothing);
def hybridSignal = ExpAverage(hybrid, signalLength);
def hybridSlope = hybrid - hybrid[1];

# =========================
# VISUAL PLOTS
# =========================
plot TMOline = if showTMO then tmoNormalized else Double.NaN;
TMOline.SetDefaultColor(Color.CYAN);
TMOline.SetLineWeight(1);

plot StochasticLine =
    if showStochastic then stochasticCentered else Double.NaN;
StochasticLine.SetDefaultColor(Color.ORANGE);
StochasticLine.SetLineWeight(1);

plot HybridLine = hybrid;
HybridLine.SetLineWeight(3);
HybridLine.AssignValueColor(
    if hybridSlope > 0 then Color.GREEN
    else if hybridSlope < 0 then Color.RED
    else Color.GRAY
);

plot HybridSignalLine =
    if showSignalLine then hybridSignal else Double.NaN;
HybridSignalLine.SetDefaultColor(Color.WHITE);
HybridSignalLine.SetStyle(Curve.SHORT_DASH);
HybridSignalLine.SetLineWeight(2);

plot UpperExtremeLine = upperExtreme;
UpperExtremeLine.SetDefaultColor(Color.RED);
UpperExtremeLine.SetStyle(Curve.SHORT_DASH);

plot LowerExtremeLine = lowerExtreme;
LowerExtremeLine.SetDefaultColor(Color.GREEN);
LowerExtremeLine.SetStyle(Curve.SHORT_DASH);

plot ZeroLine = 0;
ZeroLine.SetDefaultColor(Color.GRAY);
ZeroLine.SetStyle(Curve.LONG_DASH);

def UpperLimit = 100;
def LowerLimit = -100;

AddCloud(
    if showExtremeClouds then UpperLimit else Double.NaN,
    if showExtremeClouds then UpperExtremeLine else Double.NaN,
    Color.DARK_RED,
    Color.DARK_RED
);
AddCloud(
    if showExtremeClouds then LowerExtremeLine else Double.NaN,
    if showExtremeClouds then LowerLimit else Double.NaN,
    Color.DARK_GREEN,
    Color.DARK_GREEN
);

# =========================
# CLOSED-BAR OR LIVE EVALUATION
# =========================
def evaluatedHybrid = if useClosedBarSignals then hybrid[1] else hybrid;
def priorEvaluatedHybrid = if useClosedBarSignals then hybrid[2] else hybrid[1];
def evaluatedSignal = if useClosedBarSignals then hybridSignal[1] else hybridSignal;
def priorEvaluatedSignal =
    if useClosedBarSignals then hybridSignal[2] else hybridSignal[1];
def evaluatedTMO =
    if useClosedBarSignals then tmoNormalized[1] else tmoNormalized;
def evaluatedStochastic =
    if useClosedBarSignals then stochasticCentered[1] else stochasticCentered;

def turnsUpFromLowerExtreme =
    evaluatedHybrid > priorEvaluatedHybrid and
    priorEvaluatedHybrid <= lowerExtreme;
def turnsDownFromUpperExtreme =
    evaluatedHybrid < priorEvaluatedHybrid and
    priorEvaluatedHybrid >= upperExtreme;

def exitsLowerExtreme =
    priorEvaluatedHybrid <= lowerExtreme and
    evaluatedHybrid > lowerExtreme;
def exitsUpperExtreme =
    priorEvaluatedHybrid >= upperExtreme and
    evaluatedHybrid < upperExtreme;

def bullishSignalCross =
    priorEvaluatedHybrid <= priorEvaluatedSignal and
    evaluatedHybrid > evaluatedSignal and
    (evaluatedHybrid <= lowerExtreme or
     priorEvaluatedHybrid <= lowerExtreme);
def bearishSignalCross =
    priorEvaluatedHybrid >= priorEvaluatedSignal and
    evaluatedHybrid < evaluatedSignal and
    (evaluatedHybrid >= upperExtreme or
     priorEvaluatedHybrid >= upperExtreme);

def rawBullishSignal;
def rawBearishSignal;

switch (triggerMode) {
case TURN_FROM_EXTREME:
    rawBullishSignal = turnsUpFromLowerExtreme;
    rawBearishSignal = turnsDownFromUpperExtreme;
case EXIT_EXTREME:
    rawBullishSignal = exitsLowerExtreme;
    rawBearishSignal = exitsUpperExtreme;
case HYBRID_SIGNAL_CROSS:
    rawBullishSignal = bullishSignalCross;
    rawBearishSignal = bearishSignalCross;
}

# One signal in a direction until an opposite signal occurs.
def lastSignalDirection = CompoundValue(
    1,
    if rawBullishSignal and
       (!suppressRepeatedDirection or lastSignalDirection[1] != 1) then 1
    else if rawBearishSignal and
            (!suppressRepeatedDirection or lastSignalDirection[1] != -1) then -1
    else lastSignalDirection[1],
    0
);

def bullishSignal =
    rawBullishSignal and
    (!suppressRepeatedDirection or lastSignalDirection[1] != 1);
def bearishSignal =
    rawBearishSignal and
    (!suppressRepeatedDirection or lastSignalDirection[1] != -1);

plot BullishArrow =
    if showSignalArrows and bullishSignal then -92
    else Double.NaN;
BullishArrow.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
BullishArrow.SetDefaultColor(Color.GREEN);
BullishArrow.SetLineWeight(4);

plot BearishArrow =
    if showSignalArrows and bearishSignal then 92
    else Double.NaN;
BearishArrow.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
BearishArrow.SetDefaultColor(Color.RED);
BearishArrow.SetLineWeight(4);

# =========================
# PLAIN-ENGLISH LABELS
# =========================
AddLabel(
    showLabels,
    "TMO: " + Round(evaluatedTMO, 1) +
    (if evaluatedTMO > 0 then " BULLISH PRESSURE"
     else if evaluatedTMO < 0 then " BEARISH PRESSURE"
     else " BALANCED"),
    if evaluatedTMO > 0 then Color.GREEN
    else if evaluatedTMO < 0 then Color.RED
    else Color.GRAY
);

AddLabel(
    showLabels,
    "STOCH LOCATION: " + Round(evaluatedStochastic, 1) +
    (if evaluatedStochastic >= upperExtreme then " UPPER EXTREME"
     else if evaluatedStochastic <= lowerExtreme then " LOWER EXTREME"
     else " MIDDLE RANGE"),
    if evaluatedStochastic >= upperExtreme then Color.RED
    else if evaluatedStochastic <= lowerExtreme then Color.GREEN
    else Color.GRAY
);

AddLabel(
    showLabels,
    "HYBRID: " + Round(evaluatedHybrid, 1) +
    (if evaluatedHybrid > priorEvaluatedHybrid then " RISING"
     else if evaluatedHybrid < priorEvaluatedHybrid then " FALLING"
     else " FLAT"),
    if evaluatedHybrid > priorEvaluatedHybrid then Color.GREEN
    else if evaluatedHybrid < priorEvaluatedHybrid then Color.RED
    else Color.GRAY
);

AddLabel(
    showLabels,
    if bullishSignal then
        "ACTION: LOWER-EXTREME REVERSAL WATCH - REQUIRE PRICE CONFIRMATION"
    else if bearishSignal then
        "ACTION: UPPER-EXTREME REVERSAL WATCH - REQUIRE PRICE CONFIRMATION"
    else if evaluatedHybrid <= lowerExtreme and
            evaluatedHybrid <= priorEvaluatedHybrid then
        "HEADS UP: LOWER EXTREME BUT STILL FALLING - DO NOT BUY YET"
    else if evaluatedHybrid >= upperExtreme and
            evaluatedHybrid >= priorEvaluatedHybrid then
        "HEADS UP: UPPER EXTREME BUT STILL RISING - DO NOT SHORT YET"
    else if evaluatedHybrid > 0 and evaluatedHybrid > priorEvaluatedHybrid then
        "BEHAVIOR: BULLISH MOMENTUM EXPANDING"
    else if evaluatedHybrid < 0 and evaluatedHybrid < priorEvaluatedHybrid then
        "BEHAVIOR: BEARISH MOMENTUM EXPANDING"
    else "ACTION: NEUTRAL / DEVELOPING - WAIT",
    if bullishSignal then Color.CYAN
    else if bearishSignal then Color.MAGENTA
    else if evaluatedHybrid <= lowerExtreme or
            evaluatedHybrid >= upperExtreme then Color.YELLOW
    else Color.GRAY
);

AddLabel(
    showLabels,
    if useClosedBarSignals then
        "MODE: CLOSED-BAR - STABLE / ONE BAR LATER"
    else "MODE: LIVE - EARLIER BUT CAN CHANGE INTRABAR",
    if useClosedBarSignals then Color.GREEN else Color.YELLOW
);

# =========================
# ALERTS
# =========================
Alert(
    enableAlerts and bullishSignal,
    "TMO-Stochastic Hybrid: lower-extreme bullish reversal watch",
    Alert.BAR,
    Sound.Ding
);

Alert(
    enableAlerts and bearishSignal,
    "TMO-Stochastic Hybrid: upper-extreme bearish reversal watch",
    Alert.BAR,
    Sound.Ding
);
 
Last edited by a moderator:

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
963 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