PercentX Trend Follower [Trendoscope] for ThinkOrSwim

samer800

Moderator - Expert
VIP
Lifetime
W2IlHFl.png

Author Message:
Concepts

Introducing the innovative PercentX Oscillator, a representation of Bollinger PercentB and Keltner Percent K. This powerful tool offers users the flexibility to customize their PercentK oscillator, including options for the type of moving average and length.
The Oscillator Range is derived dynamically, utilizing two lengths - inner and outer. The inner length initiates the calculation of the oscillator's highest and lowest range, while the outer length is used for further calculations, involving either a moving average or the opposite side of the highest/lowest range, to obtain the oscillator ranges.
Next, the Oscillator Boundaries are derived by applying another round of high/low or moving average calculations on the oscillator range values.
Breakouts occur when the close price crosses above the upper boundary or below the lower boundary, signaling potential trading opportunities.
More Details : https://www.tradingview.com/v/WKgRAD0V/

CODE:
CSS:
#//  https://www.tradingview.com/v/WKgRAD0V/
#// © Trendoscope Pty Ltd
#strategy('PercentX Trend Follower [Trendoscope]', shorttitle='pX-TF [Trendoscope]'
# Converted by Sam4Cok@Samer800    - 09/2023
declare lower;
input bandType = {default "Keltner Channel", "Bollinger Bands"}; # 'Select the band type'
input maType = {"EMA", "SMA", default "HMA", "RMA", "VWMA", "WMA"};
input maLength = 40;
input useTrueRange = yes;# 'Use True Range (KC)','Use true range - applicable only for Keltner Channel'
input rangeMethod = {default "High/Low", "SMA", "EMA", "RMA", "HMA", "WMA", "VWMA", "SWMA"}; #'Range Calculation'
input loopbackPeriod = 80;    # 'Range Calculation'
input loopbackPeriodLast = 20;# tooltip = 'Range Calculation method, length and range length')
input stickyBorders = yes;# 'Sticky Borders'
input outerRangeMethod = {default "High/Low", "SMA", "EMA", "RMA", "HMA", "WMA", "VWMA", "SWMA"};
input outerLoopback = 80; # tooltip='Method and length for calculating outer range. Outer range is extreme of oversold and overbought ranges')
input doubleSided = yes;# 'Double Sided'
input useInitialStop = yes; #'Enable Stop'
input atrLength = 14;
input trendMultiplier = 1;
input reverseMultiplier = 3;#'Enable Stop and Configure ATR length, Trend and reverse Multiplier for the stop'

def na = Double.NaN;
def last = isNaN(close);
#getStickyRange(highsource, lowsource, upper, lower, sticky=false)=>
script getStickyRange {
    input source = high;
    input upper = high;
    input lower = low;
    input sticky = no;
    def newUpper;
    def newLower;
    def newUpperPrev = if (isNaN(newUpper[1]) or !newUpper[1]) then upper else newUpper[1];
    def newLowerPrev = if (isNaN(newLower[1]) or !newLower[1]) then lower else newLower[1];
    def highBreakout = source[1] >= newUpperPrev;
    def lowBreakout  = source[1] <= newLowerPrev;
    def breakout = (highBreakout or lowBreakout);
    newUpper = if !sticky  then upper else
               if breakout then upper else newUpperPrev;
    newLower = if !sticky  then lower else
               if breakout then lower else newLowerPrev;
    plot UpBand = newUpper;
    plot LoBand = newLower;
}
script customseries {
    input source = close;
    input type = "SMA";
    input length = 14;
    def hh = Highest(high, length);
    def ll = Lowest(low, length);
    def voAvg = Average(volume, length);
    def srcVolAvg = Average(source * volume, length);
    def VWMA = srcVolAvg / voAvg;
    def swma = source[3] * 1 / 6 + source[2] * 2 / 6 +  source[1] * 2 / 6 + source[0] * 1 / 6;
    def ma = if type == "EMA" then ExpAverage(source, length) else
             if type == "SMA" then Average(source, length) else
             if type == "RMA" then WildersAverage(source, length) else
             if type == "HMA" then HullMovingAvg(source, length) else
             if type == "WMA" then WMA(source, length) else
             if type == "VWMA" then VWMA else
             if type == "SWMA" then swma else Double.NaN;#(hh + ll) / 2;
    plot out = ma;
}
def MovAvgMid = customseries(close, maType, maLength);
def bbMiddle = MovAvgMid;
def bbUpper = bbMiddle + StDev(close, maLength) * 0.01;
def bbLower = bbMiddle - StDev(close, maLength) * 0.01;

def tr = TrueRange(high, close, low);
def kcMiddle = MovAvgMid;
def span = if (useTrueRange) then tr else (high - low);
def rangeMa = customseries(span, maType, maLength);
def kcUpper = kcMiddle + rangeMa * 0.01;
def kcLower = kcMiddle - rangeMa * 0.01;

def middle;
def upper;
def lower;
switch (bandType) {
case "Keltner Channel" :
    middle = kcMiddle;
    upper = kcUpper;
    lower = kcLower;
case "Bollinger Bands" :
    middle = bbMiddle;
    upper = bbUpper;
    lower = bbLower;
}

def distance = close - middle;
def midist  = middle - lower;
def oscillator = distance / midist;

def oscHighest = Highest(oscillator, loopbackPeriod);
def oscLowest  = Lowest(oscillator, loopbackPeriod);

def methodHiLo =  rangeMethod == rangeMethod."High/Low";
def oscOverbought = if methodHiLo then Lowest(oscHighest, loopbackPeriodLast) else
                    customseries(oscHighest, rangeMethod, loopbackPeriodLast);
def oscOversold = if methodHiLo then Highest(oscLowest, loopbackPeriodLast) else
                    customseries(oscLowest, rangeMethod, loopbackPeriodLast);

def ob = getStickyRange(oscillator, oscOverbought, oscOversold, stickyBorders).UpBand;
def os = getStickyRange(oscillator, oscOverbought, oscOversold, stickyBorders).LoBand;

def overbought = ob;
def oversold = os;

def highOverboughtMa = customseries(overbought, outerRangeMethod, outerLoopback);
def highestOverbought = highest(overbought, outerLoopback);

def lowOversoldMa = customseries(oversold, outerRangeMethod, outerLoopback);
def lowestOversold = lowest(oversold, outerLoopback);

def outRangeHiLo = outerRangeMethod == outerRangeMethod."High/Low";
def upperRange = if outRangeHiLo then highestOverbought else highOverboughtMa;
def lowerRange = if outRangeHiLo then lowestOversold else lowOversoldMa;

def longSignal  = (oscillator > upperRange) and (oscillator[1] <= upperRange[1]);
def shortSignal = (oscillator < lowerRange) and (oscillator[1] >= lowerRange[1]);

def upOverflow = if isNaN(upOverflow[1]) then 0 else
                 longSignal or (upOverflow[1] and oscillator > overbought);
def downOverflow = if isNaN(downOverflow[1]) then 0 else
                 shortSignal or (downOverflow[1] and oscillator < oversold);

plot STATE = oscillator;#, title='PercentX Oscillator'
STATE.SetLineWeight(2);
STATE.AssignValueColor( if upOverflow then Color.GREEN else
                        if downOverflow then Color.RED else CreateColor(33,150,243));

plot lowerBandFill = if downOverflow then oversold else na;#, "Lower Overflow"
lowerBandFill.SetDefaultColor(Color.RED);

plot lowerBand = if last then na else oversold;#, "Lower"
lowerBand.AssignValueColor(if downOverflow then Color.RED else Color.DARK_RED);
lowerBand.SetStyle(Curve.SHORT_DASH);

plot llowerBand = if last then na else lowerRange;#, "Extreme Lower"
llowerBand.AssignValueColor(if downOverflow then Color.RED else Color.DARK_RED);

plot upperBandFill = if upOverflow then overbought else na;#, "Upper Overflow"
upperBandFill.SetDefaultColor(Color.GREEN);

plot upperBand = if last then na else overbought;#, "Upper"
upperBand.AssignValueColor(if upOverflow then Color.GREEN else Color.DARK_GREEN);
upperBand.SetStyle(Curve.SHORT_DASH);

plot uupperBand = if last then na else upperRange;#, "Extreme Upper"
uupperBand.AssignValueColor(if upOverflow then Color.GREEN else Color.DARK_GREEN);

AddCloud(lowerBandFill, STATE, color.RED);
AddCloud(STATE, upperBandFill, color.GREEN);

#-- END of CODE
 
This is a ThinkOrSwim forum.
All scripts found here are ThinkOrSwim versions.
 
Hi Samer,

Could be possible to have a version of the strategy like in tradingview?

Thank you for all your help,
Happy holidays!
 
Thank you MerryDay!
Just a question, since I am new in useThinkScript. Now in trading view this script is part of a strategy as the reference Samer give to us: https://www.tradingview.com/v/WKgRAD0V/ . My question is if still the request would be at https://usethinkscript.com/forums/conversion-requests.78/ or can be done here?
I will appreciate your help.
Happy Holidays!
lower study can't have strategy in TOS I guess. However, check the below which I converted to upper study. Add the lower study to see the indicator.

CSS:
#//  https://www.tradingview.com/v/WKgRAD0V/
#// © Trendoscope Pty Ltd
#strategy('PercentX Trend Follower [Trendoscope]', shorttitle='pX-TF [Trendoscope]'
# Converted by Sam4Cok@Samer800    - 09/2023
#-- Upper study - by Sam4Cok@Samer800    - 12/2023

input bandType = {default "Keltner Channel", "Bollinger Bands"}; # 'Select the band type'
input maType = {"EMA", "SMA", default "HMA", "RMA", "VWMA", "WMA"};
input maLength = 40;
input useTrueRange = yes;# 'Use True Range (KC)','Use true range - applicable only for Keltner Channel'
input rangeMethod = {default "High/Low", "SMA", "EMA", "RMA", "HMA", "WMA", "VWMA", "SWMA"}; #'Range Calculation'
input loopbackPeriod = 80;    # 'Range Calculation'
input loopbackPeriodLast = 20;# tooltip = 'Range Calculation method, length and range length')
input stickyBorders = yes;# 'Sticky Borders'
input outerRangeMethod = {default "High/Low", "SMA", "EMA", "RMA", "HMA", "WMA", "VWMA", "SWMA"};
input outerLoopback = 80; # tooltip='Method and length for calculating outer range. Outer range is extreme of oversold and overbought ranges')
input doubleSided = yes;# 'Double Sided'
input useInitialStop = yes; #'Enable Stop'
input atrLength = 14;
input trendMultiplier = 3;
input reverseMultiplier = 3;#'Enable Stop and Configure ATR length, Trend and reverse Multiplier for the stop'
input lotSize = 100;
input BacktestLabel = yes;

def na = Double.NaN;
def last = isNaN(close);
#getStickyRange(highsource, lowsource, upper, lower, sticky=false)=>
script getStickyRange {
    input source = high;
    input upper = high;
    input lower = low;
    input sticky = no;
    def newUpper;
    def newLower;
    def newUpperPrev = if (isNaN(newUpper[1]) or !newUpper[1]) then upper else newUpper[1];
    def newLowerPrev = if (isNaN(newLower[1]) or !newLower[1]) then lower else newLower[1];
    def highBreakout = source[1] >= newUpperPrev;
    def lowBreakout  = source[1] <= newLowerPrev;
    def breakout = (highBreakout or lowBreakout);
    newUpper = if !sticky  then upper else
               if breakout then upper else newUpperPrev;
    newLower = if !sticky  then lower else
               if breakout then lower else newLowerPrev;
    plot UpBand = newUpper;
    plot LoBand = newLower;
}
script customseries {
    input source = close;
    input type = "SMA";
    input length = 14;
    def hh = Highest(high, length);
    def ll = Lowest(low, length);
    def voAvg = Average(volume, length);
    def srcVolAvg = Average(source * volume, length);
    def VWMA = srcVolAvg / voAvg;
    def swma = source[3] * 1 / 6 + source[2] * 2 / 6 +  source[1] * 2 / 6 + source[0] * 1 / 6;
    def ma = if type == "EMA" then ExpAverage(source, length) else
             if type == "SMA" then Average(source, length) else
             if type == "RMA" then WildersAverage(source, length) else
             if type == "HMA" then HullMovingAvg(source, length) else
             if type == "WMA" then WMA(source, length) else
             if type == "VWMA" then VWMA else
             if type == "SWMA" then swma else Double.NaN;#(hh + ll) / 2;
    plot out = ma;
}
def MovAvgMid = customseries(close, maType, maLength);
def bbMiddle = MovAvgMid;
def bbUpper = bbMiddle + StDev(close, maLength) * 0.01;
def bbLower = bbMiddle - StDev(close, maLength) * 0.01;

def tr = TrueRange(high, close, low);
def kcMiddle = MovAvgMid;
def span = if (useTrueRange) then tr else (high - low);
def rangeMa = customseries(span, maType, maLength);
def kcUpper = kcMiddle + rangeMa * 0.01;
def kcLower = kcMiddle - rangeMa * 0.01;

def middle;
def upper;
def lower;
switch (bandType) {
case "Keltner Channel" :
    middle = kcMiddle;
    upper = kcUpper;
    lower = kcLower;
case "Bollinger Bands" :
    middle = bbMiddle;
    upper = bbUpper;
    lower = bbLower;
}

def distance = close - middle;
def midist  = middle - lower;
def oscillator = distance / midist;

def oscHighest = Highest(oscillator, loopbackPeriod);
def oscLowest  = Lowest(oscillator, loopbackPeriod);

def methodHiLo =  rangeMethod == rangeMethod."High/Low";
def oscOverbought = if methodHiLo then Lowest(oscHighest, loopbackPeriodLast) else
                    customseries(oscHighest, rangeMethod, loopbackPeriodLast);
def oscOversold = if methodHiLo then Highest(oscLowest, loopbackPeriodLast) else
                    customseries(oscLowest, rangeMethod, loopbackPeriodLast);

def ob = getStickyRange(oscillator, oscOverbought, oscOversold, stickyBorders).UpBand;
def os = getStickyRange(oscillator, oscOverbought, oscOversold, stickyBorders).LoBand;

def overbought = ob;
def oversold = os;

def highOverboughtMa = customseries(overbought, outerRangeMethod, outerLoopback);
def highestOverbought = highest(overbought, outerLoopback);

def lowOversoldMa = customseries(oversold, outerRangeMethod, outerLoopback);
def lowestOversold = lowest(oversold, outerLoopback);

def outRangeHiLo = outerRangeMethod == outerRangeMethod."High/Low";
def upperRange = if outRangeHiLo then highestOverbought else highOverboughtMa;
def lowerRange = if outRangeHiLo then lowestOversold else lowOversoldMa;

def longSignal  = oscillator Crosses above upperRange;
def shortSignal = (oscillator Crosses Below lowerRange);

#-- Backtesting

def inTradeLong;def inTradeShort;

def LongCon  = longSignal and !inTradeLong[1];
def ShortCon = shortSignal and !inTradeShort[1];

AddChartBubble(LongCon, low, "B", Color.GREEN, no);
AddChartBubble(ShortCon, high, "S", Color.RED, yes);

#//---Calc

def CurrentPrice = hlc3[-1];
def nATR1 = ATR(Length=14);
def entryLong  = if LongCon then CurrentPrice else entryLong[1];
def entryShort  = if ShortCon then CurrentPrice else entryShort[1];

def profit = nATR1 * trendMultiplier;
def loss   = nATR1 * reverseMultiplier;

def profitLongAmt  = entryLong + profit;
def lossLongAmt    = entryLong - loss;

def profitShortAmt = entryShort - profit;
def lossShortAmt   = entryShort + loss;

def profitLong = if LongCon then profitLongAmt else profitLong[1];
def stopLong   = if LongCon then lossLongAmt else stopLong[1];

def profitShort = if ShortCon then profitShortAmt else profitShort[1];
def stopShort   = if ShortCon then lossShortAmt else stopShort[1];

def AboveLong  = high  >= profitLong;
def BelowLong  = close <  stopLong;
def AboveShort = close >  stopShort;
def BelowShort = low   <= profitShort;

def exitLong = inTradeLong[1] and (AboveLong or BelowLong);
def exitShort = inTradeShort[1] and (AboveShort or BelowShort);

plot WinLong = if inTradeLong[1] and AboveLong then profitLong else na;
WinLong.SetDefaultColor(Color.GREEN);
WinLong.SetPaintingStrategy(PaintingStrategy.SQUARES);
WinLong.SetLineWeight(3);
plot LoseLong = if inTradeLong[1] and BelowLong then stopLong else na;
LoseLong.SetDefaultColor(Color.RED);
LoseLong.SetPaintingStrategy(PaintingStrategy.SQUARES);
LoseLong.SetLineWeight(3);

plot WinShort = if inTradeShort[1] and BelowShort then profitShort else na;
WinShort.SetDefaultColor(Color.GREEN);
WinShort.SetPaintingStrategy(PaintingStrategy.SQUARES);
WinShort.SetLineWeight(3);
plot LoseShort = if inTradeShort[1] and AboveShort then stopShort else na;
LoseShort.SetDefaultColor(Color.RED);
LoseShort.SetPaintingStrategy(PaintingStrategy.SQUARES);
LoseShort.SetLineWeight(3);

if LongCon {
    inTradeLong = yes;
} else
if exitLong {
    inTradeLong = no;
} else {
    inTradeLong = inTradeLong[1];
}
if ShortCon {
    inTradeShort = yes;
} else
if exitShort {
    inTradeShort = no;
} else {
    inTradeShort = inTradeShort[1];
}

plot PriceLong     = if !inTradeLong  then na else entryLong;
plot TargetLong    = if !inTradeLong  then na else profitLong;
plot StoplossLong  = if !inTradeLong  then na else stopLong;

plot PriceShort    = if !inTradeShort then na else entryShort;
plot TargetShort   = if !inTradeShort then na else profitShort;
plot StoplossShort = if !inTradeShort then na else stopShort;
PriceLong.SetDefaultColor(Color.GRAY);
PriceShort.SetDefaultColor(Color.GRAY);
TargetLong.SetDefaultColor(Color.CYAN);
StoplossLong.SetDefaultColor(Color.VIOLET);
TargetShort.SetDefaultColor(Color.MAGENTA);
StoplossShort.SetDefaultColor(Color.PLUM);
 
def longPrice = if !inTradeLong  then na else entryLong;
def upLong    = if !inTradeLong   then na else profitLong;;
def dnLong    = if !inTradeLong   then na else stopLong;

def shortPrice = if !inTradeShort  then na else entryShort;
def upShort    = if !inTradeShort  then na else stopShort;
def dnShor     = if !inTradeShort  then na else profitShort;

AddCloud(upLong, longPrice, Color.DARK_GREEN);
AddCloud(longPrice, dnLong, Color.DARK_RED);

AddCloud(upShort, shortPrice, Color.DARK_RED);
AddCloud(shortPrice, dnShor, Color.DARK_GREEN);

def long_trades;
def short_trades;
def longCount;
def shortCount;
def LongWins;
def ShortWins;


if exitLong and !exitLong[1] {
    longCount   = longCount[1] + 1;
    LongWins    = if AboveLong then LongWins[1] + 1 else LongWins[1];
    long_trades = long_trades[1] + (hlc3 - entryLong) * lotSize;
} else {
    longCount   = longCount[1];
    LongWins    = LongWins[1];
    long_trades = long_trades[1];
}

if exitShort and !exitShort[1] {
    shortCount   =  shortCount[1] + 1;
    ShortWins    = if BelowShort then ShortWins[1] + 1 else ShortWins[1];
    short_trades = short_trades[1] + (entryShort - hlc3) * lotSize;
} else {
    shortCount   =  shortCount[1];
    ShortWins    = ShortWins[1];
    short_trades = short_trades[1];
}
def cumreturn   = (long_trades) + (short_trades);
def totaltrades = (longCount) + (shortCount);
def totalwins   = (LongWins)  + (ShortWins);
def totallosses = if (totaltrades - totalwins) == 0 then 1 else totaltrades - totalwins;
def winloss = totalwins / totallosses;
def Winrate = totalwins / totaltrades * 100;
#//-------------------- Information

AddLabel(BacktestLabel, "Trades: " + totaltrades, Color.WHITE);
AddLabel(BacktestLabel, "CR/DR= $" + Round(cumreturn, 1), if cumreturn > 0 then Color.LIGHT_GREEN else Color.PINK);
AddLabel(BacktestLabel, "Win/Loss: " + Round(winloss, 2), if winloss > 1 then Color.LIGHT_GREEN else Color.PINK);
AddLabel(BacktestLabel, "WinRate: " + Round(Winrate, 1) + "%", if Winrate > 50 then Color.LIGHT_GREEN else Color.PINK);

#-- END of CODE
 

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