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
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.
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: