// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ // © HeWhoMustNotBeNamed // __ __ __ __ __ __ __ __ __ __ __ _______ __ __ __ // / | / | / | _ / |/ | / \ / | / | / \ / | / | / \ / \ / | / | // $$ | $$ | ______ $$ | / \ $$ |$$ |____ ______ $$ \ /$$ | __ __ _______ _$$ |_ $$ \ $$ | ______ _$$ |_ $$$$$$$ | ______ $$ \ $$ | ______ _____ ____ ______ ____$$ | // $$ |__$$ | / \ $$ |/$ \$$ |$$ \ / \ $$$ \ /$$$ |/ | / | / |/ $$ | $$$ \$$ | / \ / $$ | $$ |__$$ | / \ $$$ \$$ | / \ / \/ \ / \ / $$ | // $$ $$ |/$$$$$$ |$$ /$$$ $$ |$$$$$$$ |/$$$$$$ |$$$$ /$$$$ |$$ | $$ |/$$$$$$$/ $$$$$$/ $$$$ $$ |/$$$$$$ |$$$$$$/ $$ $$< /$$$$$$ |$$$$ $$ | $$$$$$ |$$$$$$ $$$$ |/$$$$$$ |/$$$$$$$ | // $$$$$$$$ |$$ $$ |$$ $$/$$ $$ |$$ | $$ |$$ | $$ |$$ $$ $$/$$ |$$ | $$ |$$ \ $$ | __ $$ $$ $$ |$$ | $$ | $$ | __ $$$$$$$ |$$ $$ |$$ $$ $$ | / $$ |$$ | $$ | $$ |$$ $$ |$$ | $$ | // $$ | $$ |$$$$$$$$/ $$$$/ $$$$ |$$ | $$ |$$ \__$$ |$$ |$$$/ $$ |$$ \__$$ | $$$$$$ | $$ |/ |$$ |$$$$ |$$ \__$$ | $$ |/ |$$ |__$$ |$$$$$$$$/ $$ |$$$$ |/$$$$$$$ |$$ | $$ | $$ |$$$$$$$$/ $$ \__$$ | // $$ | $$ |$$ |$$$/ $$$ |$$ | $$ |$$ $$/ $$ | $/ $$ |$$ $$/ / $$/ $$ $$/ $$ | $$$ |$$ $$/ $$ $$/ $$ $$/ $$ |$$ | $$$ |$$ $$ |$$ | $$ | $$ |$$ |$$ $$ | // $$/ $$/ $$$$$$$/ $$/ $$/ $$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ $$$$$$$/ $$$$/ $$/ $$/ $$$$$$/ $$$$/ $$$$$$$/ $$$$$$$/ $$/ $$/ $$$$$$$/ $$/ $$/ $$/ $$$$$$$/ $$$$$$$/ // // // //@version=4 study("Double Top/Bottom - Ultimate (OS)", shorttitle="W/M - Ultimate(OS)", overlay=true, max_lines_count=500, max_labels_count=500, max_bars_back=1000) length = input(10, step=5, minval=5) showZigzag = input(false) showPivots = input(true) showStats = input(true) bullishColor = input(color.green) bullTrapColor = input(color.orange) bearishColor = input(color.red) bearTrapColor = input(color.lime) textColor = input(color.black) MaxRiskPerReward = input(30, step=5, minval=5, maxval=100) DisplayRiskPerReward = input(true) var zigzagvalues = array.new_float(0) var zigzagindexes = array.new_int(0) var zigzagdir = array.new_int(0) var doubleTopBottomValues = array.new_float(3) var doubleTopBottomIndexes = array.new_int(3) var doubleTopBottomDir = array.new_int(3) int max_array_size = 10 max_bars_back(high, 1000) max_bars_back(low, 1000) var lineArray = array.new_line(0) var labelArray = array.new_label(0) pivots(length)=> float ph = highestbars(high, length) == 0 ? high : na float pl = lowestbars(low, length) == 0 ? low : na dir = 0 dir := iff(ph and na(pl), 1, iff(pl and na(ph), -1, dir[1])) [dir, ph, pl] add_to_array(value, index, dir)=> mult = array.size(zigzagvalues) < 2? 1 : (dir*value > dir*array.get(zigzagvalues,1))? 2 : 1 array.unshift(zigzagindexes, index) array.unshift(zigzagvalues, value) array.unshift(zigzagdir, dir*mult) if array.size(zigzagindexes) > max_array_size array.pop(zigzagindexes) array.pop(zigzagvalues) array.pop(zigzagdir) add_to_zigzag(dir, dirchanged, ph, pl, index)=> value = (dir == 1? ph : pl) if array.size(zigzagvalues) == 0 or dirchanged add_to_array(value, index, dir) else if(dir == 1 and value > array.get(zigzagvalues, 0)) or (dir == -1 and value < array.get(zigzagvalues, 0)) array.shift(zigzagvalues) array.shift(zigzagindexes) array.shift(zigzagdir) add_to_array(value, index, dir) zigzag(length)=> [dir, ph, pl] = pivots(length) dirchanged = change(dir) if(ph or pl) add_to_zigzag(dir, dirchanged, ph, pl, bar_index) calculate_double_pattern()=> doubleTop = false doubleTopConfirmation = 0 doubleBottom = false doubleBottomConfirmation = 0 if(array.size(zigzagvalues) >= 4) index = array.get(zigzagindexes, 1) value = array.get(zigzagvalues, 1) highLow = array.get(zigzagdir, 1) lindex = array.get(zigzagindexes, 2) lvalue = array.get(zigzagvalues, 2) lhighLow = array.get(zigzagdir, 2) llindex = array.get(zigzagindexes, 3) llvalue = array.get(zigzagvalues, 3) llhighLow = array.get(zigzagdir, 3) risk = abs(value-llvalue) reward = abs(value-lvalue) riskPerReward = risk*100/(risk+reward) if(highLow == 1 and llhighLow == 2 and lhighLow < 0 and riskPerReward < MaxRiskPerReward) doubleTop := true if(highLow == -1 and llhighLow == -2 and lhighLow > 0 and riskPerReward < MaxRiskPerReward) doubleBottom := true if(doubleTop or doubleBottom) array.set(doubleTopBottomValues, 0, value) array.set(doubleTopBottomValues, 1, lvalue) array.set(doubleTopBottomValues, 2, llvalue) array.set(doubleTopBottomIndexes, 0, index) array.set(doubleTopBottomIndexes, 1, lindex) array.set(doubleTopBottomIndexes, 2, llindex) array.set(doubleTopBottomDir, 0, highLow) array.set(doubleTopBottomDir, 1, lhighLow) array.set(doubleTopBottomDir, 2, llhighLow) [doubleTop, doubleBottom] get_crossover_info(doubleTop, doubleBottom)=> index = array.get(doubleTopBottomIndexes, 0) value = array.get(doubleTopBottomValues, 0) highLow = array.get(doubleTopBottomDir, 0) lindex = array.get(doubleTopBottomIndexes, 1) lvalue = array.get(doubleTopBottomValues, 1) lhighLow = array.get(doubleTopBottomDir, 1) llindex = array.get(doubleTopBottomIndexes, 2) llvalue = array.get(doubleTopBottomValues, 2) llhighLow = array.get(doubleTopBottomDir, 2) latestDoubleTop = false latestDoubleBottom = false latestDoubleTop := doubleTop ? true : doubleBottom? false : latestDoubleTop[1] latestDoubleBottom := doubleBottom? true : doubleTop? false : latestDoubleBottom[1] doubleTopConfirmation = 0 doubleBottomConfirmation = 0 doubleTopConfirmation := latestDoubleTop? (crossunder(low, lvalue) ? 1 : crossover(high, llvalue) ? -1 : 0) : 0 doubleBottomConfirmation := latestDoubleBottom? (crossover(high, lvalue) ? 1 : crossunder(low, llvalue) ? -1 : 0) : 0 [doubleTopConfirmation, doubleBottomConfirmation] draw_double_pattern(doubleTop,doubleBottom,doubleTopConfirmation,doubleBottomConfirmation)=> index = array.get(doubleTopBottomIndexes, 0) value = array.get(doubleTopBottomValues, 0) highLow = array.get(doubleTopBottomDir, 0) lindex = array.get(doubleTopBottomIndexes, 1) lvalue = array.get(doubleTopBottomValues, 1) lhighLow = array.get(doubleTopBottomDir, 1) llindex = array.get(doubleTopBottomIndexes, 2) llvalue = array.get(doubleTopBottomValues, 2) llhighLow = array.get(doubleTopBottomDir, 2) isBullish = true isBullish := (doubleTop or doubleBottom)? doubleTop : isBullish[1] risk = abs(value-llvalue) reward = abs(value-lvalue) riskPerReward = risk*100/(risk+reward) base = line.new(x1=index, y1=value, x2 = llindex, y2=llvalue, color=doubleTop? bearishColor : bullishColor, width=2, style=line.style_solid) l1 = line.new(x1=index, y1=value, x2 = lindex, y2=lvalue, color=doubleTop? bearishColor : bullishColor, width=2, style=line.style_dotted) l2 = line.new(x1=lindex, y1=lvalue, x2 = llindex, y2=llvalue, color=doubleTop? bearishColor : bullishColor, width=2, style=line.style_dotted) labelText = (doubleTop? "Double Top" : "Double Bottom") + (DisplayRiskPerReward ? " RR - "+tostring(riskPerReward) : "") baseLabel = label.new(x=index, y=value, text=labelText, yloc=doubleTop?yloc.abovebar:yloc.belowbar, color=doubleTop?bearishColor:bullishColor, style=doubleTop?label.style_label_down:label.style_label_up, textcolor=textColor, size=size.normal) if not (doubleTop or doubleBottom) line.delete(base) line.delete(l1) line.delete(l2) label.delete(baseLabel) var doubleTopCount = 0 var doubleBottomCount = 0 doubleTopCount := doubleTop? nz(doubleTopCount[1],0) + 1 : nz(doubleTopCount[1],0) doubleBottomCount := doubleBottom? nz(doubleBottomCount[1],0) + 1 : nz(doubleBottomCount[1],0) if(line.get_x2(base) == line.get_x2(base[1])) line.delete(base[1]) line.delete(l1[1]) line.delete(l2[1]) label.delete(baseLabel[1]) doubleTopCount := doubleTop? doubleTopCount -1 : doubleTopCount doubleBottomCount := doubleBottom? doubleBottomCount -1 : doubleBottomCount if(barstate.islast) lres = line.new(x1=bar_index, y1=lvalue, x2 = lindex, y2=lvalue, color=isBullish? bearishColor : bullishColor, width=2, style=line.style_dashed, extend=extend.left) lsup = line.new(x1=bar_index, y1=llvalue, x2 = llindex, y2=llvalue, color=isBullish? bullishColor : bearishColor , width=2, style=line.style_dashed, extend=extend.left) doubleTopConfirmationCount = doubleTopConfirmation>0? 1 : 0 doubleBottomConfirmationCount = doubleBottomConfirmation>0? 1:0 doubleTopInvalidationCount = doubleTopConfirmation<0? 1 : 0 doubleBottomInvalidationCount = doubleBottomConfirmation<0? 1:0 if(doubleTopConfirmation != 0 or doubleBottomConfirmation !=0) if(doubleTopConfirmation>0 or doubleBottomConfirmation > 0) lresbreak = line.new(x1=lindex, y1=lvalue, x2 = bar_index, y2=lvalue, color=isBullish? bearishColor : bullishColor, width=2, style=line.style_dashed) if(line.get_x1(lresbreak[1]) == line.get_x1(lresbreak)) doubleTopConfirmationCount := 0 doubleBottomConfirmationCount := 0 doubleTopInvalidationCount := 0 doubleBottomInvalidationCount := 0 line.delete(lresbreak) lresbreak := lresbreak[1] else if(doubleTopConfirmation<0 or doubleBottomConfirmation < 0) lsupbreak = line.new(x1=llindex, y1=llvalue, x2 = bar_index, y2=llvalue, color=isBullish? bullishColor : bearishColor , width=2, style=line.style_dashed) if(line.get_x1(lsupbreak[1]) == line.get_x1(lsupbreak)) doubleTopInvalidationCount := 0 doubleBottomInvalidationCount := 0 doubleTopConfirmationCount := 0 doubleBottomConfirmationCount := 0 line.delete(lsupbreak) lsupbreak := lsupbreak[1] doubleTopConfirmationCount := nz(doubleTopConfirmationCount[1],0)+doubleTopConfirmationCount doubleBottomConfirmationCount := nz(doubleBottomConfirmationCount[1],0)+doubleBottomConfirmationCount doubleTopInvalidationCount := nz(doubleTopInvalidationCount[1],0)+doubleTopInvalidationCount doubleBottomInvalidationCount := nz(doubleBottomInvalidationCount[1],0)+doubleBottomInvalidationCount [doubleTopCount, doubleBottomCount, doubleTopConfirmationCount, doubleBottomConfirmationCount, doubleTopInvalidationCount, doubleBottomInvalidationCount] zigzag(length) [doubleTop, doubleBottom] = calculate_double_pattern() [doubleTopConfirmation, doubleBottomConfirmation] = get_crossover_info(doubleTop, doubleBottom) [doubleTopCount, doubleBottomCount, doubleTopConfirmationCount, doubleBottomConfirmationCount, doubleTopInvalidationCount, doubleBottomInvalidationCount] = draw_double_pattern(doubleTop,doubleBottom,doubleTopConfirmation,doubleBottomConfirmation) var stats = table.new(position = position.top_right, columns = 5, rows = 5, border_width = 1) if(barstate.islast and showStats) colorWorst = color.rgb(255, 153, 51) colorBest = color.rgb(51, 204, 51) colorBad = color.rgb(255, 204, 153) colorGood = color.rgb(204, 255, 204) colorNeutral = color.rgb(255, 255, 204) dtConfirmationPercent = doubleTopConfirmationCount+doubleTopInvalidationCount == 0? 0.5 : doubleTopConfirmationCount/(doubleTopConfirmationCount+doubleTopInvalidationCount) dbConfirmationPercent = (doubleBottomConfirmationCount+doubleBottomInvalidationCount) == 0? 0.5 : doubleBottomConfirmationCount/(doubleBottomConfirmationCount+doubleBottomInvalidationCount) dtColor = dtConfirmationPercent >= 0.8? colorBest : dtConfirmationPercent >= 0.6? colorGood : dtConfirmationPercent >= 0.4? colorNeutral : dtConfirmationPercent >= 0.2? colorBad : colorWorst dbColor = dbConfirmationPercent >= 0.8? colorBest : dbConfirmationPercent >= 0.6? colorGood : dbConfirmationPercent >= 0.4? colorNeutral : dbConfirmationPercent >= 0.2? colorBad : colorWorst table.cell(table_id = stats, column = 0, row = 0 , text = "", bgcolor=color.black, text_color=color.white) table.cell(table_id = stats, column = 0, row = 1 , text = "Double Top", bgcolor=color.black, text_color=color.white) table.cell(table_id = stats, column = 0, row = 2 , text = "Double Bottom", bgcolor=color.black, text_color=color.white) table.cell(table_id = stats, column = 1, row = 0 , text = "Count", bgcolor=color.black, text_color=color.white) table.cell(table_id = stats, column = 2, row = 0 , text = "Confirmation", bgcolor=color.black, text_color=color.white) table.cell(table_id = stats, column = 3, row = 0 , text = "Invalidation", bgcolor=color.black, text_color=color.white) table.cell(table_id = stats, column = 1, row = 1, text = tostring(doubleTopCount), bgcolor=dtColor) table.cell(table_id = stats, column = 1, row = 2, text = tostring(doubleBottomCount), bgcolor=dbColor) table.cell(table_id = stats, column = 2, row = 1, text = tostring(doubleTopConfirmationCount), bgcolor=dtColor) table.cell(table_id = stats, column = 3, row = 1, text = tostring(doubleTopInvalidationCount), bgcolor=dtColor) table.cell(table_id = stats, column = 2, row = 2, text = tostring(doubleBottomConfirmationCount), bgcolor=dbColor) table.cell(table_id = stats, column = 3, row = 2, text = tostring(doubleBottomInvalidationCount), bgcolor=dbColor) if(barstate.islast and array.size(zigzagindexes) > 1) lastHigh = 0.0 lastLow = 0.0 for x = 0 to array.size(zigzagindexes)-1 i = array.size(zigzagindexes)-1-x index = array.get(zigzagindexes, i) value = array.get(zigzagvalues, i) highLow = array.get(zigzagdir, i) index_offset = bar_index-index labelText = highLow ==2 ? "HH" : highLow == 1? "LH" : highLow == -1? "HL" : "LL" labelColor = highLow ==2 ? bullishColor : highLow == 1? bullTrapColor : highLow == -1? bearTrapColor : bearishColor labelStyle = highLow > 0? label.style_label_down : label.style_label_up // labelLocation = highLow > 0? yloc.abovebar : yloc.belowbar labelLocation = yloc.price if(showPivots) l = label.new(x=index, y = value, text=labelText, xloc=xloc.bar_index, yloc=labelLocation, style=labelStyle, size=size.tiny, color=labelColor, textcolor=textColor) array.unshift(labelArray, l) if(array.size(labelArray) > 100) label.delete(array.pop(labelArray)) if(i < array.size(zigzagindexes)-1 and showZigzag) indexLast = array.get(zigzagindexes, i+1) valueLast = array.get(zigzagvalues, i+1) l = line.new(x1=index, y1=value, x2 = indexLast, y2=valueLast, color=labelColor, width=2, style=line.style_solid) array.unshift(lineArray, l) if(array.size(lineArray) > 100) line.delete(array.pop(lineArray)) alertcondition(doubleBottom, "Double Bottom", "Probable double bottom observed for {{ticker}} on {{interval}} timeframe") alertcondition(doubleBottomConfirmation > 0, "Double Bottom Confirmation", "Double bottom confirmation observed for {{ticker}} on {{interval}} timeframe") alertcondition(doubleBottomConfirmation < 0, "Double Bottom Invalidation", "Double bottom invalidation observed for {{ticker}} on {{interval}} timeframe") alertcondition(doubleTop, "Double Top", "Probable double top observed for {{ticker}} on {{interval}} timeframe") alertcondition(doubleTopConfirmation > 0, "Double Top Confirmation", "Double top confirmation observed for {{ticker}} on {{interval}} timeframe") alertcondition(doubleTopConfirmation < 0, "Double Top Invalidation", "Double top invalidation observed for {{ticker}} on {{interval}} timeframe")