Fisher + Three-Deviation Reversal Envelope For ThinkOrSwim

antwerks

Well-known member
VIP
VIP Enthusiast
mod note:
ANTWERKS FISHER + 3-DEV REVERSAL ENVELOPE V1.0

A Thinkorswim mean-reversion upper study engineered to catch intraday exhaustion reversals by combining statistical price bands with momentum confirmation.

How Day Traders Use It
  • Catching Overextended Spikes: Identifies high-probability mean-reversion setups when price pushes out to the 2nd or 3rd Standard Deviation bands during RTH (07:00–16:00 ET).
  • Filtering Fakeouts: Prevents "catching falling knives." Signals do not fire on a band touch alone—the internal Fisher Transform must turn away from extreme levels (+2.0 overbought / -2.0 oversold) and price must close back inside the band.
  • Non-Repainting Precision: Designed with closed-bar evaluation enabled by default, ensuring plotted arrows and alerts remain permanent once the candle closes.

HjgGvdI.png


2Ek3jPJ.png

Code:
# ANTWERKS FISHER + THREE-DEVIATION REVERSAL ENVELOPE V1.0
# Thinkorswim upper study
#
# Transparent reconstruction inspired by the supplied screenshot and Fisher
# source code. It is not represented as the undisclosed proprietary formula.
#
# MODEL
#   1. Smoothed price center.
#   2. Price envelope at +/- 1, 2 and 3 standard deviations.
#   3. Internal Fisher Transform normalized over the recent high/low range.
#   4. A reversal watch requires recent contact with the selected band plus
#      either a Fisher turn from an extreme or a stricter exit from an extreme.
#   5. Optional price reclaim requires the confirming close back inside the
#      selected band.
#
# NON-REPAINTING MODE
# When useClosedBarSignals = yes, the signal is evaluated from the last fully
# completed candle and plotted on the next candle. This creates a deliberate
# one-bar delay but prevents the signal from disappearing intrabar.
#
# TIME NOTE
# Thinkorswim session inputs use Eastern Time.

declare upper;

# =========================
# INPUTS: PRICE ENVELOPE
# =========================
input priceSource = close;
input bandLength = 34;
input centerAverageType = AverageType.EXPONENTIAL;
input deviationOne = 1.0;
input deviationTwo = 2.0;
input deviationThree = 3.0;
input signalBand = {ONE, default TWO, THREE};
input setupWindowBars = 4;
input requireCloseBackInsideBand = yes;

# =========================
# INPUTS: FISHER TRANSFORM
# =========================
input fisherLength = 10;
input priceSmoothing = 0.30;
input indexSmoothing = 0.30;
input fisherOverbought = 2.0;
input fisherOversold = -2.0;
input fisherTrigger = {default TURN_FROM_EXTREME, EXIT_EXTREME};

# =========================
# INPUTS: OPTIONAL FILTERS
# =========================
input useBasisSlopeFilter = no;
input basisSlopeBars = 3;
input useSessionFilter = no;
input sessionStartET = 0700;
input sessionEndET = 1600;

# =========================
# INPUTS: DISPLAY / ALERTS
# =========================
input useClosedBarSignals = yes;
input suppressRepeatedDirection = yes;
input showCenterLine = yes;
input showInnerBands = yes;
input showOuterClouds = no;
input showBandTouchDots = no;
input showSignalArrows = yes;
input showSignalBubbles = yes;
input showLabels = yes;
input enableAlerts = yes;

# =========================
# SESSION CONTROL
# =========================
def intradayChart = GetAggregationPeriod() < AggregationPeriod.DAY;
def sessionWindow =
    SecondsFromTime(sessionStartET) >= 0 and
    SecondsTillTime(sessionEndET) > 0;
def logicActive =
    if useSessionFilter then intradayChart and sessionWindow
    else yes;

# =========================
# PRICE CENTER AND 3 SD BANDS
# =========================
def center = MovingAverage(centerAverageType, priceSource, bandLength);
def priceDeviation = StDev(priceSource, bandLength);

def upperOneValue = center + deviationOne * priceDeviation;
def upperTwoValue = center + deviationTwo * priceDeviation;
def upperThreeValue = center + deviationThree * priceDeviation;
def lowerOneValue = center - deviationOne * priceDeviation;
def lowerTwoValue = center - deviationTwo * priceDeviation;
def lowerThreeValue = center - deviationThree * priceDeviation;

plot CenterLine = if showCenterLine then center else Double.NaN;
CenterLine.SetDefaultColor(Color.GRAY);
CenterLine.SetLineWeight(2);

plot UpperOne = if showInnerBands then upperOneValue else Double.NaN;
UpperOne.SetDefaultColor(Color.LIGHT_RED);
UpperOne.SetStyle(Curve.SHORT_DASH);

plot UpperTwo = upperTwoValue;
UpperTwo.SetDefaultColor(Color.RED);
UpperTwo.SetLineWeight(2);

plot UpperThree = upperThreeValue;
UpperThree.SetDefaultColor(Color.DARK_RED);
UpperThree.SetLineWeight(2);

plot LowerOne = if showInnerBands then lowerOneValue else Double.NaN;
LowerOne.SetDefaultColor(Color.LIGHT_GREEN);
LowerOne.SetStyle(Curve.SHORT_DASH);

plot LowerTwo = lowerTwoValue;
LowerTwo.SetDefaultColor(Color.GREEN);
LowerTwo.SetLineWeight(2);

plot LowerThree = lowerThreeValue;
LowerThree.SetDefaultColor(Color.DARK_GREEN);
LowerThree.SetLineWeight(2);

AddCloud(
    if showOuterClouds then UpperThree else Double.NaN,
    if showOuterClouds then UpperTwo else Double.NaN,
    Color.LIGHT_RED,
    Color.LIGHT_RED
);

AddCloud(
    if showOuterClouds then LowerTwo else Double.NaN,
    if showOuterClouds then LowerThree else Double.NaN,
    Color.LIGHT_GREEN,
    Color.LIGHT_GREEN
);

def selectedUpper;
def selectedLower;

switch (signalBand) {
case ONE:
    selectedUpper = upperOneValue;
    selectedLower = lowerOneValue;
case TWO:
    selectedUpper = upperTwoValue;
    selectedLower = lowerTwoValue;
case THREE:
    selectedUpper = upperThreeValue;
    selectedLower = lowerThreeValue;
}

# =========================
# FISHER TRANSFORM
# =========================
# This follows the supplied Pine logic: candle midpoint is normalized within
# the recent range, smoothed, clamped, log-transformed and smoothed again.
def highestHigh = Highest(high, fisherLength);
def lowestLow = Lowest(low, fisherLength);
def minimumMove = TickSize();
def greatestRange = Max(highestHigh - lowestLow, minimumMove);
def midpointPrice = (high + low) / 2;
def priceLocationRaw = 2 * ((midpointPrice - lowestLow) / greatestRange) - 1;
def priceLocation = Max(-0.9999, Min(0.9999, priceLocationRaw));

def smoothedLocation = CompoundValue(
    1,
    priceSmoothing * smoothedLocation[1] +
    (1 - priceSmoothing) * priceLocation,
    0
);
def clampedLocation = Max(-0.9999, Min(0.9999, smoothedLocation));
def fisherIndex = Log((1 + clampedLocation) / (1 - clampedLocation));
def fisher = CompoundValue(
    1,
    indexSmoothing * fisher[1] +
    (1 - indexSmoothing) * fisherIndex,
    0
);

# =========================
# STABLE OR LIVE EVALUATION
# =========================
def evaluatedFisher = if useClosedBarSignals then fisher[1] else fisher;
def priorEvaluatedFisher = if useClosedBarSignals then fisher[2] else fisher[1];
def evaluatedClose = if useClosedBarSignals then close[1] else close;
def evaluatedCenter = if useClosedBarSignals then center[1] else center;
def evaluatedUpper = if useClosedBarSignals then selectedUpper[1] else selectedUpper;
def evaluatedLower = if useClosedBarSignals then selectedLower[1] else selectedLower;
def evaluatedLogicActive = if useClosedBarSignals then logicActive[1] else logicActive;

def evaluatedBasisRising =
    if useClosedBarSignals then center[1] > center[1 + basisSlopeBars]
    else center > center[basisSlopeBars];
def evaluatedBasisFalling =
    if useClosedBarSignals then center[1] < center[1 + basisSlopeBars]
    else center < center[basisSlopeBars];

# A recent contact lets Fisher confirm one or more bars after the actual touch.
def bullishTouchNow =
    if useClosedBarSignals then low[1] <= selectedLower[1]
    else low <= selectedLower;
def bearishTouchNow =
    if useClosedBarSignals then high[1] >= selectedUpper[1]
    else high >= selectedUpper;
def recentLowerBandTouch = Sum(bullishTouchNow, setupWindowBars) > 0;
def recentUpperBandTouch = Sum(bearishTouchNow, setupWindowBars) > 0;

def fisherTurnsUpFromExtreme =
    evaluatedFisher > priorEvaluatedFisher and
    priorEvaluatedFisher <= fisherOversold;
def fisherTurnsDownFromExtreme =
    evaluatedFisher < priorEvaluatedFisher and
    priorEvaluatedFisher >= fisherOverbought;
def fisherExitsLowerExtreme =
    priorEvaluatedFisher <= fisherOversold and
    evaluatedFisher > fisherOversold;
def fisherExitsUpperExtreme =
    priorEvaluatedFisher >= fisherOverbought and
    evaluatedFisher < fisherOverbought;

def bullishFisherTrigger;
def bearishFisherTrigger;

switch (fisherTrigger) {
case TURN_FROM_EXTREME:
    bullishFisherTrigger = fisherTurnsUpFromExtreme;
    bearishFisherTrigger = fisherTurnsDownFromExtreme;
case EXIT_EXTREME:
    bullishFisherTrigger = fisherExitsLowerExtreme;
    bearishFisherTrigger = fisherExitsUpperExtreme;
}

def bullishPriceConfirmation =
    !requireCloseBackInsideBand or evaluatedClose > evaluatedLower;
def bearishPriceConfirmation =
    !requireCloseBackInsideBand or evaluatedClose < evaluatedUpper;

# The optional slope filter blocks fading a sharply falling/rising center.
def bullishSlopeAllowed = !useBasisSlopeFilter or !evaluatedBasisFalling;
def bearishSlopeAllowed = !useBasisSlopeFilter or !evaluatedBasisRising;

def rawBullishSignal =
    evaluatedLogicActive and
    recentLowerBandTouch and
    bullishFisherTrigger and
    bullishPriceConfirmation and
    bullishSlopeAllowed;

def rawBearishSignal =
    evaluatedLogicActive and
    recentUpperBandTouch and
    bearishFisherTrigger and
    bearishPriceConfirmation and
    bearishSlopeAllowed;

# One signal per direction until the opposite direction fires, when enabled.
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);

# =========================
# TOUCH MARKERS AND SIGNALS
# =========================
plot LowerBandTouch =
    if showBandTouchDots and bullishTouchNow then evaluatedLower
    else Double.NaN;
LowerBandTouch.SetPaintingStrategy(PaintingStrategy.POINTS);
LowerBandTouch.SetDefaultColor(Color.YELLOW);
LowerBandTouch.SetLineWeight(3);

plot UpperBandTouch =
    if showBandTouchDots and bearishTouchNow then evaluatedUpper
    else Double.NaN;
UpperBandTouch.SetPaintingStrategy(PaintingStrategy.POINTS);
UpperBandTouch.SetDefaultColor(Color.YELLOW);
UpperBandTouch.SetLineWeight(3);

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

plot BearishArrow =
    if showSignalArrows and bearishSignal then high + 3 * TickSize()
    else Double.NaN;
BearishArrow.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
BearishArrow.SetDefaultColor(Color.RED);
BearishArrow.SetLineWeight(4);

AddChartBubble(
    showSignalBubbles and bullishSignal,
    low - 5 * TickSize(),
    "REVERSAL LONG WATCH\nLower band + Fisher confirmation\nWait for structure / risk level",
    Color.GREEN,
    no
);

AddChartBubble(
    showSignalBubbles and bearishSignal,
    high + 5 * TickSize(),
    "REVERSAL SHORT WATCH\nUpper band + Fisher confirmation\nWait for structure / risk level",
    Color.RED,
    yes
);

# =========================
# PLAIN-ENGLISH LABELS
# =========================
def aboveUpperExtreme = evaluatedClose >= evaluatedUpper;
def belowLowerExtreme = evaluatedClose <= evaluatedLower;

AddLabel(
    showLabels,
    if evaluatedBasisRising then "REGIME: CENTER RISING"
    else if evaluatedBasisFalling then "REGIME: CENTER FALLING"
    else "REGIME: CENTER FLAT",
    if evaluatedBasisRising then Color.GREEN
    else if evaluatedBasisFalling then Color.RED
    else Color.YELLOW
);

AddLabel(
    showLabels,
    if aboveUpperExtreme then "LOCATION: AT / ABOVE UPPER SIGNAL BAND"
    else if belowLowerExtreme then "LOCATION: AT / BELOW LOWER SIGNAL BAND"
    else if evaluatedClose > evaluatedCenter then "LOCATION: ABOVE CENTER"
    else if evaluatedClose < evaluatedCenter then "LOCATION: BELOW CENTER"
    else "LOCATION: AT CENTER",
    if aboveUpperExtreme then Color.RED
    else if belowLowerExtreme then Color.GREEN
    else Color.GRAY
);

AddLabel(
    showLabels,
    "FISHER: " + Round(evaluatedFisher, 2) +
    (if evaluatedFisher >= fisherOverbought then " | UPPER EXTREME"
     else if evaluatedFisher <= fisherOversold then " | LOWER EXTREME"
     else if evaluatedFisher > priorEvaluatedFisher then " | RISING"
     else if evaluatedFisher < priorEvaluatedFisher then " | FALLING"
     else " | FLAT"),
    if evaluatedFisher >= fisherOverbought then Color.RED
    else if evaluatedFisher <= fisherOversold then Color.GREEN
    else if evaluatedFisher > priorEvaluatedFisher then Color.CYAN
    else Color.LIGHT_GRAY
);

AddLabel(
    showLabels,
    if bullishSignal then
        "ACTION: LONG WATCH — CONFIRM REVERSAL; DO NOT BLINDLY BUY THE BAND"
    else if bearishSignal then
        "ACTION: SHORT WATCH — CONFIRM REVERSAL; DO NOT BLINDLY SHORT THE BAND"
    else if recentLowerBandTouch and evaluatedFisher <= fisherOversold then
        "HEADS UP: LOWER BAND TOUCHED — WAIT FOR FISHER TO TURN / EXIT"
    else if recentUpperBandTouch and evaluatedFisher >= fisherOverbought then
        "HEADS UP: UPPER BAND TOUCHED — WAIT FOR FISHER TO TURN / EXIT"
    else "ACTION: NO QUALIFIED BAND + FISHER REVERSAL",
    if bullishSignal then Color.GREEN
    else if bearishSignal then Color.RED
    else if (recentLowerBandTouch and evaluatedFisher <= fisherOversold) or
            (recentUpperBandTouch and evaluatedFisher >= fisherOverbought) 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,
    "Long reversal watch: lower deviation band and Fisher confirmation",
    Alert.BAR,
    Sound.Ding
);

Alert(
    enableAlerts and bearishSignal,
    "Short reversal watch: upper deviation band and Fisher confirmation",
    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
1158 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