Last active
May 14, 2025 16:09
-
-
Save jasonseney/d768933489ec5fad92f2aabd8ec70031 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //@version=5 | |
| // Strategy Version 0.01 | |
| strategy('Resonance Range Strategy') | |
| IDEAL_TIMEFRAME = 15 | |
| TIME_FRAME_RATIO = timeframe.in_seconds() / 60 / IDEAL_TIMEFRAME | |
| ATR_DIFF_THRESHOLD = 0 // The range in which we decide to take trades or not | |
| // TODO Use this to adjust certain parameters for other timeframes | |
| // *** Settings | |
| timeBoxed = input(defval=false, title='Use Start Date', group = 'date') | |
| startDate = input.time(defval = timestamp('2023-01-01'), title = 'Start Date', group = 'date') | |
| timeMultiplier = input(defval = 2, title = 'Time Multiplier', tooltip = 'Adjusts the overall sensitivity to wave trends') | |
| //timeMultiplier = math.min(2 / TIME_FRAME_RATIO, 1) | |
| channelLength = input(10, 'Channel Length') | |
| stochasticRatio = input(21, 'Stochastic Ratio Filter') | |
| crossoverSmaLength = input(3, 'Crossover Lag (>1)') | |
| obLevel = input(75, 'Overbought %') | |
| osLevel = -obLevel | |
| obExtremeLevel = input(100, 'Extreme Overbought %') | |
| osExtremeLevel = -obExtremeLevel | |
| atrFastBars = input(defval=5, title='ATR Fast Bars', group = 'ATR Shift') | |
| atrSlowBars = input(defval=10, title='ATR Slow Bars') | |
| // TODO: Adjust this with time frame ratio! | |
| normalizedRangeBars = input(defval=96, title='ATR Normalization Bars') | |
| // *** Wave Trends | |
| waveTrend(priceSeries, baseLength, stochasticRatioLength, crossoverLength, timeRatio) => | |
| // Calculate the Exponential Moving Average of the input price series | |
| // using the length multiplied by the time ratio | |
| baseMA = ta.ema(priceSeries, baseLength * timeRatio) | |
| // Calculate the absolute difference between the input price series and the previously computed EMA | |
| // Then, compute the EMA of this absolute difference using the same period length | |
| diffMA = ta.ema(math.abs(priceSeries - baseMA), baseLength * timeRatio) | |
| // Compute the Composite Index by dividing the difference between the input price series and ema by | |
| // the EMA of the absolute difference. Multiply the result by 100 to obtain the CI, stored in the variable ci. | |
| compositeIndesx = 100 * (priceSeries - baseMA) / diffMA | |
| // Calculate the EMA of the Composite Index | |
| wtA = ta.ema(compositeIndesx, stochasticRatioLength * timeRatio) | |
| // Calculate the Simple Moving Average (SMA) of wave trend A | |
| wtB = ta.sma(wtA, crossoverLength * timeRatio) | |
| wtDiff = wtA - wtB | |
| [wtA, wtB, wtDiff] | |
| // Normalized average true range over a range of bars and length of atr calculation | |
| atrNorm(normRange, length) => | |
| atr = ta.atr(length) | |
| atrCompare = ta.highest(atr, normRange) - ta.lowest(atr, normRange) | |
| normalized = ((atr - ta.lowest(atr, normRange)) / atrCompare - 0.5) | |
| normalized * 100 | |
| // *** ATR Trends | |
| atrTrendNorm(normRange, lengthfast, lengthslow) => | |
| atrNormF = atrNorm(normRange, lengthfast) | |
| atrNormS = atrNorm(normRange, lengthslow) | |
| atrNormDiff = atrNormF - atrNormS | |
| // TODO: Actually return if it's trending up or down instead of just the diff ********* | |
| // Return the ATR trend as a string | |
| [atrNormDiff, atrNormF, atrNormS] | |
| atrTrendLegacy(lengthfast, lengthslow) => | |
| atrFast = ta.atr(lengthfast) / close * 100 | |
| atrSlow = ta.atr(lengthslow) / close * 100 | |
| ma_diff = atrFast - atrSlow | |
| [ma_diff, atrFast, atrSlow] | |
| // *** RSI Calculations (NOT USED RN) | |
| getRSI(length) => | |
| rmaUp = ta.rma(math.max(ta.change(close), 0), length) | |
| rmaDown = ta.rma(-math.min(ta.change(close), 0), length) | |
| rmaDown == 0 ? 100 : rmaUp == 0 ? 0 : 100 - (100 / (1 + rmaUp / rmaDown)) | |
| // Generate the wave trend | |
| [wta, wtb, wtdiff] = waveTrend(hlc3, channelLength, stochasticRatio, crossoverSmaLength, timeMultiplier) | |
| [wta2, wtb2, wtdiff2] = waveTrend(hlc3, channelLength, stochasticRatio, crossoverSmaLength, timeMultiplier / 2) | |
| isCrossing = ta.cross(wta, wtb) | |
| isBull = wtb - wta < 0 | |
| isBear = not isBull | |
| isCrossingFast = ta.cross(wta2, wtb2) | |
| isBullFast = wtb2 - wta2 < 0 | |
| isBearFast = not isBullFast | |
| // Calc cross types based on levels | |
| extreme_cross = (wta > obExtremeLevel or wta < osExtremeLevel) and isCrossing | |
| significant_cross = (wta > obLevel or wta < osLevel) and isCrossing | |
| // Generate the ATR trends | |
| [atrDiff, atrFast, atrSlow] = atrTrendNorm(normalizedRangeBars, atrFastBars, atrSlowBars) | |
| //[atrDiff, atrFast, atrSlow] = atrTrendLegacy(atrFastBars, atrSlowBars) | |
| // *** Strategy Execution *** | |
| // ENTER position if: | |
| // - Crossing on wave trend | |
| // - and ATR Norm is less than threshold (generally less than average) | |
| // - OR ATR Diff is low | |
| var order_count = 0 | |
| var openLong = false | |
| var openShort = false | |
| enteringLong = false | |
| enteringShort = false | |
| openLongConditions = isBull and (atrFast < 0 or atrDiff < ATR_DIFF_THRESHOLD) //and wta < 0 //Low range vs range ma, wave trend might be over sold | |
| openShortConditions = isBear and (atrFast < 0 or atrDiff < ATR_DIFF_THRESHOLD) //and wta > 0 //Low range vs range ma, wave trend might be over bought | |
| dateCondition = timeBoxed and time >= startDate | |
| if isCrossing and dateCondition | |
| // LONG | |
| if openLongConditions | |
| strategy.entry('Long', strategy.long) | |
| label.new(bar_index, wta, style=label.style_diamond, color=color.green, size=size.tiny, text = 'LONG', textcolor = color.green) | |
| order_count += 1 | |
| openLong := true | |
| enteringLong := true | |
| // SHORT | |
| else if openShortConditions | |
| strategy.entry('Short', strategy.short) | |
| label.new(bar_index, wta, style=label.style_diamond, color=color.red, size=size.tiny, text = 'SHORT', textcolor = color.red) | |
| openShort := true | |
| enteringShort := true | |
| order_count += 1 | |
| // *** Strategy Closes *** | |
| // CLOSE position if: | |
| // - Opening an opposite position | |
| // OR | |
| // - Moving in opposite direction | |
| // - and ATR Diff is more than threshold (trending up) | |
| closeLongConditions = isBear and (atrDiff > ATR_DIFF_THRESHOLD or atrFast > 0) | |
| closeShortConditions = isBull and (atrDiff > ATR_DIFF_THRESHOLD or atrFast > 0) | |
| if enteringShort or closeLongConditions | |
| strategy.close('Long') | |
| if openLong | |
| openLong := false | |
| //label.new(bar_index, wta, style=label.style_xcross, color=color.orange, size=size.tiny, text = 'CLOSE LONG', textcolor = color.orange) | |
| if enteringLong or closeShortConditions | |
| strategy.close('Short') | |
| if openShort | |
| openShort := false | |
| //label.new(bar_index, wta, style=label.style_xcross, color=color.orange, size=size.tiny, text = 'CLOSE SHORT', textcolor = color.orange) | |
| // **** PLOT STRATEGY ***** | |
| hasPosition = strategy.opentrades > 0 | |
| // Debug Info | |
| plot(isCrossing ? 1 : 0, title= 'Crossing', display = display.data_window) | |
| plot(wta, title= 'Resonance Trend', display = display.data_window) | |
| plot(order_count, title= 'Order #', display = display.data_window) | |
| plot(getRSI(IDEAL_TIMEFRAME), title= 'RSI', display = display.data_window) | |
| plot(atrFast, title= 'ATR Fast', display = display.data_window) | |
| plot(atrSlow, title= 'ATR Slow', display = display.data_window) | |
| plot(atrDiff, title= 'ATR Diff', display = display.data_window) | |
| plot(hasPosition ? strategy.openprofit : na, title= 'Open Profit', display = display.data_window, color = strategy.openprofit > 0 ? color.green : color.red) | |
| plot(strategy.opentrades, title= 'Open Trades', display = display.data_window, color = color.fuchsia) | |
| // Fill overbought | |
| obE = hline(obExtremeLevel, color=color.silver, linestyle=hline.style_dashed, linewidth=1) | |
| ob = hline(obLevel, color=color.silver, linestyle=hline.style_dotted, linewidth=1) | |
| fill(ob, obE, color=color.new(color.maroon, 90)) | |
| // Fill oversold | |
| os = hline(osLevel, color=color.silver, linestyle=hline.style_dotted, linewidth=1) | |
| osE = hline(osExtremeLevel, color=color.silver, linestyle=hline.style_dashed, linewidth=1) | |
| fill(os, osE, color=color.new(color.olive, 90)) | |
| // Plot out wave trend | |
| cross_circle_color = isBull ? color.green : color.red | |
| plot(wta, title = 'Wave Trend A', color= color.new(cross_circle_color, transp = 0.8), display = display.pane + display.data_window) | |
| plot(wtb, title = 'Wave Trend B', color= color.new(cross_circle_color, transp = 0.8), display = display.pane + display.data_window) | |
| // Plot ATR | |
| plot(atrDiff, title='ATR Diff', color=color.new(color.silver,40), style=plot.style_histogram, display = display.pane) | |
| plot(atrFast, title='ATR Norm F', color=color.new(color.purple,40), style=plot.style_line, display = display.pane) | |
| plot(atrSlow, title='ATR Norm S', color=color.new(color.blue,40), style=plot.style_line, display = display.pane) | |
| // Plot out crosses | |
| plot(significant_cross ? wta : na, color=color.new(color.white, 0.5), style=plot.style_circles, linewidth=3, display = display.pane) | |
| plot(extreme_cross ? wta : na, color=color.new(color.gray, 0.5), style=plot.style_cross, linewidth=3, display = display.pane) | |
| // Use this to color extremes | |
| //cross_circle_color = extreme_cross ? (isBull ? lime : red) : (isBull ? aqua : #FF9900) | |
| // Plot cross dots | |
| plot(isCrossing ? wta : na, color=cross_circle_color, style=plot.style_circles, linewidth=2, display = display.pane) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment