// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © SimpleCryptoLife
//@version=4
// ============================== ABOUT THIS SCRIPT ==================================
// == Description ==
// This indicator finds all Williams high and low fractals and marks them on the chart.
// You can configure how many bars backwards and forwards should be counted in order to confirm a fractal.
// You can also plot a stop line that trails the fractals up and down.
// You can choose whether the trail flips long-short based on the price being exceeded within a candle or on candle close.
// Includes alerts for crossing the long and short trailing stops.
// This indicator displays over the main chart.
// == Usage ==
// Works on all markets and timeframes, but it doesn't make much sense on really small timeframes or on assets with very low prices for their denominations (like very low-sat cryptos).
// This indicator is not intended to be used to generate entries and exits on its own. It can quickly show approximate market structure.
// For example, if you want to go long, but price hasn't broken the short trail yet, it's a reminder to be aware that price is yet to make a higher high.
// == Alerts==
// Crossed Williams Long Stop: Alerts when the selected price source exceeds an active long trail
// Crossed Williams Short Stop: Alerts when the selected price source exceeds an active short trail
// == Repainting ==
// Williams fractals can only be confirmed a number of bars after they form. So this indicator must repaint in the sense of going back to mark them once they're confirmed.
// After a fractal has been confirmed and marked, nothing is altered in that bar or any of the previous bars.
// == Credits ==
// Thanks to reading the highest()/lowest() function replacement script by the brilliant Ricardo Santos I finally understood why mine wasn't working.
// All code is my own.
// == Disclaimer ==
// You retain full responsibility for your trading at all times. Before trading with actual money, first make sure your risk management is professional-level.
study(title="Williams Fractal Trailing Stops", shorttitle="Williams Trailing Stops", overlay=true, linktoseries=true)
bool inputShowWilliamsFractals = input(defval=true, title="Show All Williams Fractals", type=input.bool, group="Display", tooltip="Displays little triangles above/below each Williams fractal")
bool inputShowWilliamsStops = input(defval=true, title="Show Williams Trailing Stop", type=input.bool, tooltip="Displays a trailing stop based on the most recent fractal")
bool inputShowWilliamsStopPrice = input(defval=true, title="Show Current Price of Williams Trailing Stop", type=input.bool, tooltip="Displays a price label for the current active stop")
int inputWilliamsLeftRange = input(defval=2, minval=1, maxval=50, title="Williams Fractal Range Back", group="Fractal Settings", tooltip="How many bars back to measure for the range. Traditionally, two bars back and two bars forward are used.")
int inputWilliamsRightRange = input(defval=2, minval=1, maxval=50, title="Williams Fractal Range Forwards", tooltip="How many bars forwards to measure for the range. Traditionally,an equal number of bars two bars back and two bars forward are used.")
float inputWilliamsStopBufferInput = input(defval=0, minval=0, maxval=20, step=0.5, title="Buffer for Williams Stops %", type=input.float, group="Advanced",
tooltip="Adds a buffer between the actual fractal price and the trail, as a percentage of the fractal price")
string inputWilliamsFlipInput = input(title="Flip Trail on:", defval="Close", options=["Close","Wick"], tooltip="Defines which measurement of the candle (high/low, or close) must exceed the trail for it to flip.")
// ============================== WILLIAMS PRICE FRACTALS: CALCULATE AND PLOT FRACTALS ==================================
f_IsWilliamsFractal(_williamsLeftRange, _williamsRightRange, _type) =>
// This function checks whether a Williams High/Low has formed. If so, it returns a bool true for Yes, the number of bars back from the current bar that the fractal formed, and the value of the fractal.
// Note: Because fractals are always confirmed a known number of bars after the fact, we have to be careful when using this bool to look up values later.
_isWilliamsFractal = (_type == "high" and high[_williamsRightRange] >= highest(high,_williamsLeftRange+_williamsRightRange+1))
or (_type == "low" and low[_williamsRightRange] <= lowest(low,_williamsLeftRange+_williamsRightRange+1)) // Passing in the _type allows us to use just one function to do both highs and lows
_fractalValue = _isWilliamsFractal and _type == "high" ? high[_williamsRightRange] : _isWilliamsFractal and _type == "low" ? low[_williamsRightRange] : na
[_isWilliamsFractal,_fractalValue]
// Check if current bar confirms a Williams High or Low
[isWilliamsHigh,williamsHighPrice] = f_IsWilliamsFractal(inputWilliamsLeftRange, inputWilliamsRightRange, "high")
[isWilliamsLow,williamsLowPrice] = f_IsWilliamsFractal(inputWilliamsLeftRange, inputWilliamsRightRange, "low")
// Suppress fractals if the previous bar was a fractal. This will take care of the edge case where you have two bars closing at the same price in a row.
isWilliamsHigh := isWilliamsHigh[1] ? false : isWilliamsHigh
isWilliamsLow := isWilliamsLow[1] ? false : isWilliamsLow
// Plot the fractal shapes. They need to be offset backwards so they appear over the correct bar.
williamsHighOffset = 0 - inputWilliamsRightRange // Using this variable is a workaround because if you add -inputWilliamsRightRange to the offset it doesn't work
color white10 = color.new(color.white,10) // Define a new colour with transparency of 10 to avoid compilation warnings
plotshape(isWilliamsHigh and inputShowWilliamsFractals, title="Shape for Williams High", style=shape.triangledown, location=location.abovebar, color=white10, size=size.tiny, offset=williamsHighOffset)
williamsLowOffset = 0 - inputWilliamsRightRange
plotshape(isWilliamsLow and inputShowWilliamsFractals, title="Shape for Williams Low", style=shape.triangleup, location=location.belowbar, color=white10, size=size.tiny, offset=williamsLowOffset)
// ============================== WILLIAMS PRICE FRACTALS: CALCULATE AND PLOT TRAILING STOP LINES ==================================
f_addPercentBuffer(_input, _buffer, _direction) =>
// This function adds/removes a percentage buffer to/from the input. It's easy enough to include the calculation each time but I was on a roll with functions by this point.
_direction == "plus" ? _input * (1 + (_buffer / 100)) :
_direction == "minus" ? _input * (1 - (_buffer / 100)) : na
// Add the percentage buffer to the fractal price.
williamsLowPriceBuffered = f_addPercentBuffer(williamsLowPrice,inputWilliamsStopBufferInput,"minus")
williamsHighPriceBuffered = f_addPercentBuffer(williamsHighPrice,inputWilliamsStopBufferInput,"plus")
f_persistAndReset(_trigger, _source) =>
// This function returns the value of _source when _trigger is true, persists it, and resets it to the new value of _source when _trigger is true again.
var float _output = 0.0
_output := _trigger ? _source : _output[1]
// Persist the buffered stop price and reset it each time there is a new fractal
williamsLongStopPrice = f_persistAndReset(isWilliamsLow,williamsLowPriceBuffered)
williamsShortStopPrice = f_persistAndReset(isWilliamsHigh,williamsHighPriceBuffered)
f_trail(_source, _trail, _direction) =>
// This function trails the source series up or down.
// Note: depending what you're trailing, and how you're resetting it later, you might want to test against _trail[0] instead of _trail[1]
_direction == "down" and _source >= _trail[1] ? _trail : _direction == "up" and _source <= _trail[1] ? _trail : _source
// Need to declare these variables here, in the global scope, so we can use them in other functions later
var float williamsLongStopPriceTrail = williamsLongStopPrice
var float williamsShortStopPriceTrail = williamsShortStopPrice
// Trail the high (short) stop down and the low (long) stop up
williamsShortStopPriceTrail := f_trail(williamsShortStopPrice, williamsShortStopPriceTrail, "down")
williamsLongStopPriceTrail := f_trail(williamsLongStopPrice, williamsLongStopPriceTrail, "up")
f_flip(_flipInput, _longTrail, _shortTrail, _longReset, _shortReset) =>
// This function flips from a trailing long stop to a trailing short stop and vice-versa. It takes the following inputs:
// _flipInput - A string that defines whether to consider a touch of the stop line as a flip, or a close beyond it
// _longTrail - The long trailing stop (trails up, below the price)
// _shortTrail - The short trailing stop (trails down, above the price)
// _longReset - The value to reset the long stop to, when it's hit
// _shortReset - The value to reset the short stop to, when it's hit
// The function returns the following outputs:
// _longTrailOutput - The long trailing stop. Normally the same as it was input, unless it's reset.
// _shortTrailOutput - The short trailing stop. Normally the same as it was input, unless it's reset.
// _longTrailPlot - The long trailing stop to plot. This is na if we are not long.
// _shortTrailPlot - The short trailing stop to plot. This is na if we are not short.
//
// These variables say whether we are flipping long or short this very bar. Usually they are both false. Only one of them can be true at once.
var bool _flipLongNow = false
var bool _flipShortNow = false
// These variables say what state we're in: long or short. One or both are always true.
// In the beginning, we haven't hit any trails yet, so we start off both long and short, to display both lines.
var bool _isLong = true
var bool _isShort = true
// Get the source, depending whether it's on close or on touch
float _flipLongSource = _flipInput == "Close" ? close : _flipInput == "Wick" ? high : na
float _flipShortSource = _flipInput == "Close" ? close : _flipInput == "Wick" ? low : na
// Are we flipping long or short this bar?
_flipLongNow := _isShort[1] and _flipLongSource > _shortTrail ? true : false
_flipShortNow := _isLong[1] and _flipShortSource < _longTrail ? true : false
// In the edge case where we manage to flip both long and short, we need to reset that based on the close. The close is definitive for history and intra-candle it will take the current value.
_flipLongNow := _flipShortNow and _flipLongNow and close > _longTrail ? true : _flipShortNow and _flipLongNow and close <= _longTrail ? false : _flipLongNow
_flipShortNow := _flipLongNow and _flipShortNow and close < _shortTrail ? true : _flipShortNow and _flipLongNow and close >= _shortTrail ? false : _flipShortNow
// Set the long and short state variables. Set if we flip (simple), initialise to true if this is the first time (needed), otherwise persist.
_isLong := _flipLongNow ? true : _flipShortNow ? false : na(_isLong[1]) ? true : _isLong[1]
_isShort := _flipShortNow ? true : _flipLongNow ? false : na(_isShort[1]) ? true : _isShort[1]
// Update the trailing price. If we flip this bar, reset to the nearest fractal - which goes against the trail direction, which is why we need to use another series.
_longTrailOutput = _longTrail
_shortTrailOutput = _shortTrail
_longTrailOutput := _isLong and not _isLong[1] ? _longReset : _longTrailOutput
_shortTrailOutput := _isShort and not _isShort[1] ? _shortReset : _shortTrailOutput
// Hide the trailing long stop if we are short, and hide the trailing short stop if we are long. Show both if we are both long and short.
float _longTrailPlot = _isLong ? _longTrailOutput : _isLong and _isShort ? _longTrailOutput : na
float _shortTrailPlot = _isShort ? _shortTrailOutput : _isLong and _isShort ? _shortTrailOutput: na
[_longTrailOutput, _shortTrailOutput, _longTrailPlot, _shortTrailPlot]
f_getFlipResetWilliamsLong(_longTrail, _buffer, _isWilliamsLow, _williamsRightRange) =>
// This function gets the long stop price to reset to. This is unnecessary for ATR trails, but Williams fractals trails are more complicated.
// The fractal that we would reset to is not always confirmed at the time we flip, so we take the lower of the last confirmed fractal and the Lows of the intervening bars.
_barIndexWhenLastFractalConfirmed = valuewhen(_isWilliamsLow, bar_index, 0) // Get the number of bars since the last fractal was confirmed (not formed)
_barsSinceLastFractalConfirmed = bar_index - _barIndexWhenLastFractalConfirmed // And get the total number of bars back to go from the current bar
int _barsToGoBack = _barsSinceLastFractalConfirmed + _williamsRightRange // Then add the number of bars in the Williams lag
// Go that number of bars back and get the lowest low
float _lowestLow = low
for i = 0 to _barsToGoBack
_lowestLow := min(low[i], _lowestLow)
_lowestLowAdjusted = _lowestLow * (1 - (_buffer / 100))
f_getFlipResetWilliamsShort(_shortTrail, _buffer, _isWilliamsHigh, _williamsLeftRange) =>
// This function is the short stop equivalent of f_getFlipResetWilliamsLong
_barIndexWhenLastFractalConfirmed = valuewhen(_isWilliamsHigh, bar_index, 0)
_barsSinceLastFractalConfirmed = bar_index - _barIndexWhenLastFractalConfirmed
int _barsToGoBack = _barsSinceLastFractalConfirmed + _williamsLeftRange
float _highestHigh = high
for i = 0 to _barsToGoBack
_highestHigh := max(high[i], _highestHigh)
_highestHighAdjusted = _highestHigh * (1 + (_buffer / 100))
// Get the long/short stop price to reset to. It's normally the most recent opposing fractal, but the function takes care of edge cases where the most recent fractal isn't confirmed yet.
williamsLongStopPrice := f_getFlipResetWilliamsLong(williamsLongStopPrice, inputWilliamsStopBufferInput, isWilliamsLow, inputWilliamsRightRange)
williamsShortStopPrice := f_getFlipResetWilliamsShort(williamsShortStopPrice, inputWilliamsStopBufferInput, isWilliamsHigh, inputWilliamsRightRange)
// Get the plots for the trails, to show only long stop when long and short stop when short.
[williamsLongStopPriceTrailTemp, williamsShortStopPriceTrailTemp, williamsLongStopPriceTrailPlot, williamsShortStopPriceTrailPlot] =
f_flip(inputWilliamsFlipInput, williamsLongStopPriceTrail,williamsShortStopPriceTrail,williamsLongStopPrice, williamsShortStopPrice)
// Have to have these variables back in the global scope so we can persist them and use them as inputs to the function next bar.
// I don't think there's a way round this because assigning multiple mutable variables via a function doesn't seem to work.
williamsLongStopPriceTrail := williamsLongStopPriceTrailTemp
williamsShortStopPriceTrail := williamsShortStopPriceTrailTemp
// Hide trailing stop plots if the user selected that (input bool inputShowWilliamsStops)
williamsShortStopPriceTrailPlotDisplay = inputShowWilliamsStops ? williamsShortStopPriceTrailPlot : na
williamsLongStopPriceTrailPlotDisplay = inputShowWilliamsStops ? williamsLongStopPriceTrailPlot : na
// Plot the trailing stops
color yellow50 = color.new(color.yellow,50), color yellow0 = color.new(color.yellow,0)
color orange50 = color.new(color.orange,50), color orange0 = color.new(color.orange,0)
plot(williamsShortStopPriceTrailPlotDisplay, "Williams Trailing Stop High Price", color=yellow50, style=plot.style_linebr, linewidth=3)
plot(williamsShortStopPriceTrailPlotDisplay, "Williams Trailing Stop High Price Highlight", color=yellow0, style=plot.style_linebr, linewidth=1)
plot(williamsLongStopPriceTrailPlotDisplay, "Williams Trailing Stop Low Price", color=orange50, style=plot.style_linebr, linewidth=3)
plot(williamsLongStopPriceTrailPlotDisplay, "Williams Trailing Stop Low Price Highlight", color=orange0, style=plot.style_linebr, linewidth=1)
// ============================== WILLIAMS PRICE FRACTALS: PRINT PRICE LABEL ==================================
// If the user hides trailing stop lines, OR hides the price label, don't print the label
// If we are long, print a label below the trailing stop line. If we are short, print a label above the trailing stop line.
label williamsLongStopPriceLabel = if inputShowWilliamsStopPrice and not na(williamsLongStopPriceTrailPlotDisplay)
label.new(bar_index, na, 'Long stop price: \n' + tostring(williamsLongStopPriceTrailPlotDisplay), color=color.gray, textcolor=color.white, style=label.style_labelup, yloc=yloc.belowbar)
else
na
label williamsShortStopPriceLabel = if inputShowWilliamsStopPrice and not na(williamsShortStopPriceTrailPlotDisplay)
label.new(bar_index, na, 'Short stop price: \n' + tostring(williamsShortStopPriceTrailPlotDisplay), color=color.gray, textcolor=color.white, style=label.style_labeldown, yloc=yloc.abovebar)
else
na
// Delete the previous long/short label, i.e. only keep the current one
label.delete(williamsLongStopPriceLabel[1])
label.delete(williamsShortStopPriceLabel[1])
// ============================== WILLIAMS PRICE FRACTALS: ALERTS ==================================
// Alert for crossing the trailing stop. You can set this to trigger Once, or Once per bar close, in the TradingView alert configuration screen. You can use this for soft stops or to look for entries.
// Calculate if we crossed a trailing line. Not going to do the calculation again, just cheat and use the plot that we did before.
// Have to choose the right one. It does matter that it was shown and hidden according to the flip, but it doesn't matter if the user hid it.
alertCrossWilliamsLongStop = na(williamsLongStopPriceTrailPlot) and not na(williamsLongStopPriceTrailPlot[1])
alertCrossWilliamsShortStop = na(williamsShortStopPriceTrailPlot) and not na(williamsShortStopPriceTrailPlot[1])
alertcondition(alertCrossWilliamsLongStop, title="Crossed Williams Long Stop", message="Alert from Williams Fractal Trailing Stops: \n {{ticker}} price crossed long stop")
alertcondition(alertCrossWilliamsShortStop, title="Crossed Williams Short Stop", message="Alert from Williams Fractal Trailing Stops: \n {{ticker}} price crossed short stop")
alertcondition(isWilliamsHigh, title="High Printed", message="Alert from Williams Fractal Trailing Stops: \n {{ticker}} Williams High has been confirmed")
alertcondition(isWilliamsLow, title="Low Printed", message="Alert from Williams Fractal Trailing Stops: \n {{ticker}} Williams Low has been confirmed")
// ================================ //
// //
// (╯°□°)╯︵ sʞloɟ `llɐ s,ʇɐɥ┴ //
// //
// ================================ //