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