//@version=4 // ══════════════════════════════════════════════════════════════════════════════════════════════════ // //# * ══════════════════════════════════════════════════════════════════════════════════════════════ //# * //# * Study : Signals from Elliott Wave Oscillator (EWO-S) //# * - with Backtest Framework Adaptation //# * - and Keltner Channels Add-On //# * Author : © dgtrd //# * //# * Revision History //# * Release : Jul 1 , 2020 //# * Update : Sep 6 , 2020 : Alert addition //# * Update : Nov 26, 2020 : Backtest framework adaptation //# * Update : Dec 3 , 2020 : Backtest framework bug fix //# * Update : Mar 15, 2021 : Enchanced backtest framework, enriched Keltner Channels //# * Update : Mar 17, 2021 : Separated Alerts //# * //# * ══════════════════════════════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════════════════════════════════ // study("Elliott Wave Oscillator Signals by DGT", "EWO-S ʙʏ DGT ☼☾", true, max_labels_count = 500) // ══════════════════════════════════════════════════════════════════════════════════════════════════ // //# * ══════════════════════════════════════════════════════════════════════════════════════════════ //# * //# * Study : Elliott Wave Oscillator Signals //# * Author : © dgtrd //# * //# * Revision History //# * Release : Jul 1 , 2020 //# * //# * ══════════════════════════════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════════════════════════════════ // // -Inputs ══════════════════════════════════════════════════════════════════════════════════════ // e = input(true, "Use Exponential MA" , group = "Elliott Wave Oscillator Settings") d = input(5 , "Signal : Delay" , minval=2, inline = "EWO", group = "Elliott Wave Oscillator Settings") t = input(13, "    Strength Threshold" , minval=1, inline = "EWO", group = "Elliott Wave Oscillator Settings") h = input(false, "Display EWO Histogram (on Linear Scale)" , group = "Elliott Wave Oscillator Settings") p = input(233, "Plotting Length", group = "Display Settings") // -Calculations ════════════════════════════════════════════════════════════════════════════════ // source = close ewo = e ? (ema(source, 5)/ema(source, 34) - 1) * 100 : (sma(source, 5)/sma(source, 34) - 1) * 100 ewoSignal = e ? ema(ewo,d) : sma(ewo,d) ewoHistogram = h and timeframe.isdwm ? e ? ema(source, 5) - ema(source, 34) : sma(source, 5) - sma(source, 34) : na ewoColor = h and timeframe.isdwm ? ewoHistogram >=0 ? (ewoHistogram[1] < ewoHistogram ? #006400 : color.green) : (ewoHistogram[1] < ewoHistogram ? color.red : #910000) : na // -Plotting ════════════════════════════════════════════════════════════════════════════════════ // plotshape(crossover (ewo, ewoSignal) and ewo < -t, title = "Strong Long" , color=#006400 , style=shape.labelup , location=location.belowbar, size=size.small, show_last = p) //, text="↑", textcolor=color.white) plotshape(crossover (ewo, ewoSignal) and ewo > -t, title = "Long" , color=color.green, style=shape.labelup , location=location.belowbar, size=size.tiny , show_last = p) plotshape(crossunder(ewo, ewoSignal) and ewo > t, title = "Strong Short", color=#910000 , style=shape.labeldown, location=location.abovebar, size=size.small, show_last = p) //, text="↓", textcolor=color.white) plotshape(crossunder(ewo, ewoSignal) and ewo < t, title = "Short" , color=color.red , style=shape.labeldown, location=location.abovebar, size=size.tiny , show_last = p) if h and not timeframe.isdwm if barstate.islast label ewoLabel = label.new(bar_index, .97*low, text="warning ...", textcolor=color.white, textalign=text.align_left, style=label.style_label_up, tooltip="histogram plotting is supported for daily, weekly and monthly timeframe\nTo ged rid of this warning message disable hisrogram display") label.delete(ewoLabel[1]) plot(ewoHistogram, title = "EWO Histogram", color=ewoColor, style=plot.style_histogram, show_last = p) // -Alerts ══════════════════════════════════════════════════════════════════════════════════════ // longAlertCondition = crossover(ewo, ewoSignal) alertcondition(longAlertCondition , "Long : Early Warning" , "EWO-S - Not Confirmed Probable Long Trade Opportunity\n{{exchange}}:{{ticker}}->\nPrice = {{close}},\nTime = {{time}}") alertcondition(longAlertCondition[1], "Long : Trading Opportunity" , "EWO-S - Probable Long Trade Opportunity\n{{exchange}}:{{ticker}}->\nPrice = {{close}},\nTime = {{time}}") shortAlertCondition = crossunder(ewo, ewoSignal) alertcondition(shortAlertCondition , "Short : Early Warning" , "EWO-S - Not Confirmed Probable Short Trade Opportunity\n{{exchange}}:{{ticker}}->\nPrice = {{close}},\nTime = {{time}}") alertcondition(shortAlertCondition[1], "Short : Trading Opportunity", "EWO-S - Probable Short Trade Opportunity\n{{exchange}}:{{ticker}}->\nPrice = {{close}},\nTime = {{time}}") // ══════════════════════════════════════════════════════════════════════════════════════════════════ // //# * ══════════════════════════════════════════════════════════════════════════════════════════════ //# * //# * Study : Backtest Framework //# * Author : © dgtrd //# * Purpose : Ability to optimize a study and observe trade simulation statistics accordingly //# * //# * Revision History //# * Release : Nov 21, 2020 : Initial Release //# * Update : Mar 13, 2021 : Enchanced Backtest Framework //# * - long/short/stoploss conditions enchaced //# * - early warning ability added (label + alert) //# * //# * ══════════════════════════════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════════════════════════════════ // // -Inputs ══════════════════════════════════════════════════════════════════════════════════════════ // isBackTest = input(false, "Backtest On/Off" , group = "Backtest Framework") dasCapital = input(1000., "Initial Capital" , inline = "BT1", group = "Backtest Framework") lenBckTst = input(1, "Period (Year)", minval=0, step = .1 , inline = "BT1", group = "Backtest Framework") isStopLoss = input(false, "Apply Stop Loss, with Stop Loss Set To %" , inline = "BT2", group = "Backtest Framework") stopLoss = input(1., "", step=.1, minval = 0 , inline = "BT2", group = "Backtest Framework") / 100 isBull = input(false, "Long : Candle Direction as Confirmation : Short" , inline = "BT3", group = "Backtest Framework") isBear = input(false, "" , inline = "BT3", group = "Backtest Framework") isSudden = input(true, "Avoid Sudden Price Changes" , group = "Backtest Framework") lblInOutSL = input(true, "Trade Entry/Exit Labels  Trade Statistics Label" , inline = "BT4", group = "Backtest Framework") lblTrdStat = input(true, "" , inline = "BT4", group = "Backtest Framework") // -Calculations ════════════════════════════════════════════════════════════════════════════════════ // startBckTst = time > timenow - lenBckTst * 31556952000 var inTrade = false var entryPrice = 0. var exitPrice = 0. if isBackTest var capital = dasCapital var trades = 0 var win = 0 var loss = 0 bullCandle = close > open bearCandle = close < open stopLossTrigger = crossunder(close, entryPrice * (1 - stopLoss)) longCondition = isBull ? isSudden ? longAlertCondition [1] and not shortAlertCondition and bullCandle : longAlertCondition [1] and bullCandle : isSudden ? longAlertCondition [1] and not shortAlertCondition : longAlertCondition [1] shortCondition = isBear ? isSudden ? shortAlertCondition[1] and not longAlertCondition and bearCandle : shortAlertCondition[1] and bearCandle : isSudden ? shortAlertCondition[1] and not longAlertCondition : shortAlertCondition[1] stopLossCondition = isStopLoss ? inTrade and not shortCondition ? stopLossTrigger : 0 : 0 if startBckTst and longCondition and not inTrade entryPrice := open inTrade := true trades := trades + 1 if lblInOutSL label longLabel = label.new(bar_index, low, text="L" ,tooltip="entry price : " + tostring(entryPrice) + "\nentry value : " + tostring(capital, "#.##") ,color=color.green, style=label.style_label_up, textcolor=color.white, textalign=text.align_center, size=size.tiny) alert("long : probable trading opportunity, price " + tostring(close), alert.freq_once_per_bar) if (shortCondition or stopLossCondition) and inTrade exitPrice := stopLossCondition ? close : open inTrade := false capital := capital * (exitPrice / entryPrice) if exitPrice > entryPrice win := win + 1 else loss := loss + 1 if lblInOutSL text = stopLossCondition ? "SL" : "TP" label shortLabel = label.new(bar_index, high, text = text ,tooltip="change .......... : " + tostring((exitPrice / entryPrice - 1) * 100, "#.##") + "%\nentry/exit price : " + tostring(entryPrice) + " / " + tostring(exitPrice) + "\nnew capital ..... : " + tostring(capital, "#.##") ,color=color.red, style=label.style_label_down, textcolor=color.white, textalign=text.align_center, size=size.tiny) alert("short : probable trading opportunity, price " + tostring(close), alert.freq_once_per_bar) var label wLabel = na if not inTrade and longAlertCondition wLabel := label.new(bar_index, low, text="⚠️" ,tooltip="probable long trading opportunity \nawaiting confirmation (next candle)\nif confirmed, backtest tool will execute trade with open price of the canlde" ,color=color.green, style=label.style_none, textcolor=color.white, textalign=text.align_center, size=size.huge) label.delete(wLabel[1]) alert("long : early warning : probable trading opportunity, awaiting confirmation (next candle), price " + tostring(close), alert.freq_once_per_bar) if inTrade and shortAlertCondition wLabel := label.new(bar_index, high, text="⚠️" ,tooltip="probable short/take profit trading opportunity \nawaiting confirmation (next candle)\nif confirmed, backtest tool will execute trade with open price of the canlde" ,color=color.green, style=label.style_none, textcolor=color.white, textalign=text.align_center, size=size.huge) label.delete(wLabel[1]) alert("short : early warning : probable trading opportunity, awaiting confirmation (next candle), price " + tostring(close), alert.freq_once_per_bar) if change(time) label.delete(wLabel[1]) if stopLossCondition alert("stop loss condition, price " + tostring(close), alert.freq_once_per_bar) if lblTrdStat var years = (timenow - time) / 31556952000 var yearsTxt = "" var remarks = "" if years < lenBckTst lenBckTst := years yearsTxt := tostring(lenBckTst, "#.##") + " Years***" remarks := "\n\n*longs only\n**final value, if trade active displays estimated final value\n***max available data for selected timeframe : # of bars - " + tostring(bar_index) else yearsTxt := tostring(lenBckTst, "#.##") + " Year(s)" remarks := "\n\n*longs only\n**final value - if in trade, displays estimated final value" inTradeTxt = inTrade ? "inTrade" : "not inTrade" estimated = inTrade ? capital * (close / entryPrice) : capital entryTxt = inTrade ? tostring(entryPrice) : "not inTrade" lastTrdTxt = inTrade ? ", Gain/Loss " + tostring((estimated/capital - 1) * 100, "#.##") + "%, Stop Loss " + tostring(isStopLoss ? entryPrice * (1 - stopLoss) : na) : "" stopLossTxt = isStopLoss ? "if last value falls by " + tostring(stopLoss * 100) + "% of entry price" : "not applied" tooltipTxt = "entires/exit caclulations\n" + "-long entry , on next bar when ewo crosses above its signal line (green labels up)\n" + "-take profit, on next bar when ewo crosses below its signal line (red labels down)\n" + "-stop loss " + stopLossTxt + remarks label indiLabel = label.new(time, close ,text="☼☾ Trade Statistics*, Trade Period - " + yearsTxt + "\n═════════════════════════════════════" + "\nSuccess Ratio ...... : " + tostring((win/trades)*100, "#") + "%" + ", # of Trades - " + tostring(trades) + ", Win/Loss - " + tostring(win) + "/" + tostring(loss) + "\nGain/Loss % ........ : " + tostring((estimated/dasCapital - 1) * 100, "#") + "%" + ", Initial/Final Value** - " + tostring(dasCapital) + " / " + tostring(estimated, "#") + "\n\nCurrent TradeStatus - " + inTradeTxt + lastTrdTxt + "\n═════════════════════════════════════" + "\nEntry Price/Value . : " + entryTxt + " / " + tostring(capital, "#.##") + " " + inTradeTxt + "\nLast Price/Value ... : " + tostring(close) + " / " + tostring(estimated , "#.##") + " " + inTradeTxt ,tooltip=tooltipTxt ,color=inTrade ? estimated/dasCapital > 1 ? color.teal : color.maroon : color.gray, xloc=xloc.bar_time, style=label.style_label_left, textcolor=color.white, textalign=text.align_left) label.set_x(indiLabel, label.get_x(indiLabel) + round(change(time) * 5)) label.delete(indiLabel[1]) // -Plotting ════════════════════════════════════════════════════════════════════════════════════ // bgcolor(isBackTest and startBckTst and startBckTst != startBckTst[1] ? color.blue : na) plot(inTrade ? entryPrice : exitPrice > 0 ? exitPrice : na, title="Entry/Exit Price Line", color=inTrade ? color.green : color.red, style = plot.style_circles) // ══════════════════════════════════════════════════════════════════════════════════════════════════ // //# * ══════════════════════════════════════════════════════════════════════════════════════════════ //# * //# * Study : Keltner Channels //# * Author : © dgtrd //# * //# * Revision History //# * Release : Jul 1 , 2020 //# * Update : Mar 15, 2021 : Multi Band Customizable Keltner Channels //# * //# * ══════════════════════════════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════════════════════════════════ // // -Input ═══════════════════════════════════════════════════════════════════════════════════════ // k = input(false, "Display Keltner Channel's Clouds" , group = "Keltner Channels Settings") kcSource = input(close, "MA : Source" , inline = "KC1" , group = "Keltner Channels Settings") kcMaType = input("EMA", "Type", options=["DEMA", "EMA", "HMA", "SMA", "WMA"] , inline = "KC1" , group = "Keltner Channels Settings") kcLength = input(20, "Length", minval=1 , inline = "KC1" , group = "Keltner Channels Settings") bandsStyle = input("Average True Range", "Bands : Style", options = ["Average True Range", "True Range", "Range"] , inline = "BAND0", group = "Keltner Channels Settings") atrlength = input(10, "ATR Length" , inline = "BAND0", group = "Keltner Channels Settings") bandsFill = input(false, "Fill Background" , inline = "BAND0", group = "Keltner Channels Settings") isBand1 = input(false, "" , inline = "BAND1", group = "Keltner Channels Settings") kcMult1 = input(1., "1st Band : Multiplier", minval=1, step = .1, inline = "BAND1", group = "Keltner Channels Settings") colorK1 = input(color.silver, "Color" , inline = "BAND1", group = "Keltner Channels Settings") widthK1 = input(1, "Width" , inline = "BAND1", group = "Keltner Channels Settings") isBand2 = input(true, "" , inline = "BAND2", group = "Keltner Channels Settings") kcMult2 = input(2., "2nd Band : Multiplier", minval=1, step = .1, inline = "BAND2", group = "Keltner Channels Settings") colorK2 = input(color.orange, "Color" , inline = "BAND2", group = "Keltner Channels Settings") widthK2 = input(2, "Width" , inline = "BAND2", group = "Keltner Channels Settings") isBand3 = input(true, "" , inline = "BAND3", group = "Keltner Channels Settings") kcMult3 = input(3., "3rd Band : Multiplier", minval=1, step = .1, inline = "BAND3", group = "Keltner Channels Settings") colorK3 = input(color.red, "Color" , inline = "BAND3", group = "Keltner Channels Settings") widthK3 = input(3, "Width" , inline = "BAND3", group = "Keltner Channels Settings") // -Calculations ════════════════════════════════════════════════════════════════════════════════ // f_kc(_src, _len, _mult, _maType, _style, _atrlen) => range = _style == "True Range" ? rma(tr(true) , _len) : _style == "Average True Range" ? atr(_atrlen) : _style == "Range" ? rma(high - low, _len) : 0 basis = if _maType == "DEMA" ema1 = ema(_src, _len) ema2 = ema(ema1, _len) 2 * ema1 - ema2 else if _maType == "EMA" ema(_src, _len) else if _maType == "HMA" hma(_src, _len) else if _maType == "SMA" sma(_src, _len) else if _maType == "WMA" wma(_src, _len) [basis, basis + _mult * range, basis - _mult * range] [middle, _ , _ ] = f_kc(kcSource, kcLength, 0 , kcMaType, "" , 0) [_ , upper1, lower1] = f_kc(kcSource, kcLength, kcMult1, kcMaType, bandsStyle, atrlength) [_ , upper2, lower2] = f_kc(kcSource, kcLength, kcMult2, kcMaType, bandsStyle, atrlength) [_ , upper3, lower3] = f_kc(kcSource, kcLength, kcMult3, kcMaType, bandsStyle, atrlength) // -Plotting ════════════════════════════════════════════════════════════════════════════════════ // plot(k ? middle : na, "Keltner Channels Middle Line", show_last = p) pu3 = plot(k and isBand3 ? upper3 : na, "Keltner Channels Upper Volatility Factor Mult3", colorK3, widthK3, transp = 73, show_last = p) pu2 = plot(k and isBand2 ? upper2 : na, "Keltner Channels Upper Volatility Factor Mult2", colorK2, widthK2, transp = 73, show_last = p) pu1 = plot(k and isBand1 ? upper1 : na, "Keltner Channels Upper Volatility Factor Mult1", colorK1, widthK1, transp = 73, show_last = p) pl1 = plot(k and isBand1 ? lower1 : na, "Keltner Channels Lower Volatility Factor Mult1", colorK1, widthK1, transp = 73, show_last = p) pl2 = plot(k and isBand2 ? lower2 : na, "Keltner Channels Lower Volatility Factor Mult2", colorK2, widthK2, transp = 73, show_last = p) pl3 = plot(k and isBand3 ? lower3 : na, "Keltner Channels Lower Volatility Factor Mult3", colorK3, widthK3, transp = 73, show_last = p) fill(pl2, pl3, bandsFill ? color.green : na, 89, "Keltner Channel's Clouds Lowest" , show_last = p) fill(pl1, pl2, bandsFill ? #9ef2e8 : na, 89, "Keltner Channel's Clouds Lower" , show_last = p) fill(pu1, pu2, bandsFill ? #fa8072 : na, 89, "Keltner Channel's Clouds Higher" , show_last = p) fill(pu2, pu3, bandsFill ? color.red : na, 89, "Keltner Channel's Clouds Highest", show_last = p) // -Alerts ══════════════════════════════════════════════════════════════════════════════════════ // alertcondition(crossover (close, upper3), "Crossover Upper High KC", "Crossover Upper High KC Band\n{{exchange}}:{{ticker}}->\nPrice = {{close}},\nTime = {{time}}") alertcondition(crossover (close, upper2), "Crossover Upper Mid KC" , "Crossover Upper Mid KC Band\n{{exchange}}:{{ticker}}->\nPrice = {{close}},\nTime = {{time}}") alertcondition(crossunder(close, lower2), "Crossunder Lower Mid KC", "Crossunder Lower Mid KC Band\n{{exchange}}:{{ticker}}->\nPrice = {{close}},\nTime = {{time}}") alertcondition(crossunder(close, lower3), "Crossunder Lower Low KC", "Crossunder Lower Low KC Band\n{{exchange}}:{{ticker}}->\nPrice = {{close}},\nTime = {{time}}")