// This Pine Script™ code is propriatery software. It is NOT freeware. // © 2024 Ferry Petnga //@version=5 strategy("Order Flow Navigator", "Order Flow Navigator", overlay=true, max_bars_back=600, initial_capital=1000, currency='USD', default_qty_value=100, default_qty_type=strategy.percent_of_equity, commission_value=0.0, max_labels_count=500, calc_on_every_tick=true)//true, max_labels_count = 100, max_lines_count = 50, max_bars_back = 4999) truncate(number, decimals) => factor = math.pow(10, decimals) int(number * factor) / factor inLong = strategy.position_size < 0 inShort = strategy.position_size > 0 flat = strategy.position_size == 0 // ————— Date range filtering DateFilter = input(false, 'Use Date Range Filter') i_startTime = input.time(defval=timestamp('01 Jan 2000 00:00 +0000'), title='⏰ Start Time') i_endTime = input.time(defval=timestamp('30 Dec 2050 00:00 +0000'), title='⏰ End Time') inDateRange() => DateFilter ? time >= i_startTime and time <= i_endTime : true // Input for enabling/disabling signals long_on = input.bool(title='🟩 Use Long Signals ', defval=true, group='━━━━━ ↓ Signal mode ↓ ━━━━━ ', inline='1') short_on = input.bool(title='🟥 Use Short Signals', defval=true, group='━━━━━ ↓ Signal mode ↓ ━━━━━ ', inline='2') entry_type = input.string(defval = "SB" , title = "Select Entry Type", options = ["SB", "TC", "Both"]) // Get the current time in EST turn_time_filter = input(true, ' ', inline = '1') tradingSession = input.session("0900-1200", title="Trading Times", inline = '1') // Convert the time to EST var timezone = "GMT+2" //"America/New_York" inTradingSession = na(time(timeframe.period, tradingSession, timezone)) == false bgcolor(inTradingSession ? #afa54c45 : na) //===============================================================================================================================================================================================================// // Strat -> StopLoss and Take Profit Calculation //===============================================================================================================================================================================================================// sl_group = '━━━━━ ↓ StopLoss & Take Profit ↓ ━━━━━ ' ATR = 'ATR' Per = 'Fixed Percentange' Swing = 'Swing High/Low' SL_type = input.string(defval= Swing, title='Stoploss & Take Profit Type', options=[Swing], group=sl_group) ratio = input.float(1, '⚠️Risk️ :💰Reward = 1 ', tooltip='Recommended 1:1', group=sl_group) //Swing High/Low Based stoploss swing_stop_group = 'Swing High / Low Based Stop Loss' swingLow = input.int(5, 'Swing Low', tooltip='Long Stoploss based on Last N candle Swing Low', group=swing_stop_group) swingHigh = input.int(5, 'Swing High', tooltip='Short Stoploss based on Last N candle Swing High', group=swing_stop_group) // longStop_swing = ta.valuewhen(long_last, ta.lowest(swingLow), 0) // shortStop_swing = ta.valuewhen(short_last, ta.highest(swingHigh), 0) // Input for Pip Buffer pip_buffer_pips = input.float(1.0, 'Pip Buffer (in pips)', minval= 0.1, step = 0.1, group=swing_stop_group) //* 10 import PineCoders/LibraryStopwatch/1 as stopwatch import SimpleCryptoLife/LibraryTimeframeHelper/1 as timeframeHelper import RozaniGhani-RG/TimeframeComparison/1 as timeframeComparison // ————— Constants // Inputs const string STYLE = "Style" const string GROUP_TIMEFRAMES = "Timeframes" const string GROUP_LTF_CALCULATION = "LTF Calculation" const string GROUP_HTF_CALCULATION = "HTF Calculation" const string GROUP_HIGHS_LOWS = "Highs and Lows" const string GROUP_VALID_RANGES = "Valid Range" const string GROUP_STRUCTURE_BREAKS = "Structure Breaks" const string GROUP_TREND_CHANGES = "Trend Changes" const string GROUP_SHITWICKS = "Shitwicks" const string GROUP_INFOPANEL = "Info Panel" const string GROUP_ALERTS = "Alerts" const string GROUP_LOGGER = "Debugging" const string INLINE_SHOW_HIGHS_LOWS = "Show Highs and Lows" const string INLINE_HIGHS_LOWS_STYLE = "HL Style" const string INLINE_VALID_RANGE_LINE_STYLE = "VR Line Style" const string INLINE_VALID_RANGE_LABEL_STYLE = "VR Label Style" const string INLINE_STRUCTURE_BREAK_LINE_STYLE = "SB Line Style" const string INLINE_STRUCTURE_BREAK_LABEL_STYLE = "SB Label Style" const string INLINE_TREND_CHANGE_LINE_STYLE = "TC Line Style" const string INLINE_TREND_CHANGES_LABEL_STYLE = "TC Label Style" const string INLINE_SHITWICKS = "Shitwicks" const string INLINE_INFOPANEL_POSITION = "Position" const string INLINE_INFOPANEL_STYLE = "Infopanel Style" const string INLINE_LOGGER_BREAKS = "Shitwicks" // Enums const string HIGH = "HIGH" const string LOW = "LOW" const string UP = "Up" const string DOWN = "Down" const string TC = "Trend Change" const string SB = "Structure Break" const string ARROW_UP = "↑" const string ARROW_DOWN = "↓" // Values const string LOG_SEPARATOR = "\n" const int MAX_ARRAY_SIZE = 100 // ————— Inputs borderStyle1 = '────' borderStyle2 = '- - - - - -' borderStyle3 = '•••••••••' borderStyle (_style) => switch _style borderStyle1 => line.style_solid borderStyle2 => line.style_dashed borderStyle3 => line.style_dotted => _style // Timeframes var timeframeThresholdInput = input.timeframe("60", "HTF threshold", group = GROUP_TIMEFRAMES, tooltip = "Everything above this setting including the setting itself is considered a HTF, everything below a LTF. If it is set to 'Chart' it will use the LTF calculation for all timeframes.") // Calculation Logic var ltfOppositeCandlesInput = input.int(1, "Opposite Candles", group = GROUP_LTF_CALCULATION, tooltip = "Number of Opposite Candles on the lower timeframes needed to form a new Valid Range") var ltfPullbackCandlesInput = input.int(1, "Pullback Candles", group = GROUP_LTF_CALCULATION, tooltip = "Number of Pullback Candles on the lower timeframes needed to form a new Valid Range") var htfOppositeCandlesInput = input.int(1, "Opposite Candles", group = GROUP_HTF_CALCULATION, tooltip = "Number of Opposite Candles on the higher timeframes needed to form a new Valid Range") var htfPullbackCandlesInput = input.int(2, "Pullback Candles", group = GROUP_HTF_CALCULATION, tooltip = "Number of Pullback Candles on the higher timeframes needed to form a new Valid Range") // Highs and Lows var hlShowInput = input.bool(true, "Show Highs and Lows", group = GROUP_HIGHS_LOWS, inline = INLINE_SHOW_HIGHS_LOWS) var hlShowLastVrOnly = input.bool(false, "Last Valid Range only", group = GROUP_HIGHS_LOWS, inline = INLINE_SHOW_HIGHS_LOWS) var hlUpTrendColorInput = input.color(color.rgb(41, 98, 255, 80), "Uptrend", group = GROUP_HIGHS_LOWS, inline = INLINE_HIGHS_LOWS_STYLE) var hlDownTrendColorInput = input.color(color.rgb(242, 54, 69, 80), "Downtrend", group = GROUP_HIGHS_LOWS, inline = INLINE_HIGHS_LOWS_STYLE) var hlMaxInput = input.int(20, "Max Highs and Lows to display", minval = 4, maxval = MAX_ARRAY_SIZE, group = GROUP_HIGHS_LOWS) // Structure Breaks var sbShowInput = input.bool(true, "Lines", group = GROUP_STRUCTURE_BREAKS, inline = INLINE_STRUCTURE_BREAK_LINE_STYLE) var sbBorderColorInput = input.color(color.rgb(0, 0, 0), '', group = GROUP_STRUCTURE_BREAKS, inline = INLINE_STRUCTURE_BREAK_LINE_STYLE) var sbBorderStyleInput = borderStyle(input.string(borderStyle1, '', options=[borderStyle1, borderStyle2, borderStyle3], group = GROUP_STRUCTURE_BREAKS, inline = INLINE_STRUCTURE_BREAK_LINE_STYLE)) var sbBorderWidthInput = input.int(1, '', minval=0, group = GROUP_STRUCTURE_BREAKS, inline = INLINE_STRUCTURE_BREAK_LINE_STYLE) var sbShowLabelsInput = input.bool(true, "Labels", group = GROUP_STRUCTURE_BREAKS, inline = INLINE_STRUCTURE_BREAK_LABEL_STYLE) var sbLabelColorInput = input.color(color.black, "", group = GROUP_STRUCTURE_BREAKS, inline = INLINE_STRUCTURE_BREAK_LABEL_STYLE) var sbMaxSbInput = input.int(5, "Max SB's to display", minval = 1, maxval = 20, group = GROUP_STRUCTURE_BREAKS) // Trend Changes var tcShowInput = input.bool(true, "Lines", group = GROUP_TREND_CHANGES, inline = INLINE_TREND_CHANGE_LINE_STYLE) var tcBorderColorInput = input.color(color.rgb(255, 0, 0), '', group = GROUP_TREND_CHANGES, inline = INLINE_TREND_CHANGE_LINE_STYLE) var tcBorderStyleInput = borderStyle(input.string(borderStyle1, '', options=[borderStyle1, borderStyle2, borderStyle3], group = GROUP_TREND_CHANGES, inline = INLINE_TREND_CHANGE_LINE_STYLE)) var tcBorderWidthInput = input.int(1, '', minval=0, group = GROUP_TREND_CHANGES, inline = INLINE_TREND_CHANGE_LINE_STYLE) var tcShowLabelsInput = input.bool(true, "Labels", group = GROUP_TREND_CHANGES, inline = INLINE_TREND_CHANGES_LABEL_STYLE) var tcLabelColorInput = input.color(color.black, "", group = GROUP_TREND_CHANGES, inline = INLINE_TREND_CHANGES_LABEL_STYLE) var tcMaxTcInput = input.int(5, "Max TC's to display", minval = 1, maxval = 20, group = GROUP_TREND_CHANGES) // Valid Ranges var vrShowLastInput = input.bool(true, "Valid Range", group = GROUP_VALID_RANGES, inline = INLINE_VALID_RANGE_LINE_STYLE) var vrBorderColorInput = input.color(color.rgb(0, 0, 0), '', group = GROUP_VALID_RANGES, inline = INLINE_VALID_RANGE_LINE_STYLE) var vrBorderStyleInput = borderStyle(input.string(borderStyle2, '', options=[borderStyle1, borderStyle2, borderStyle3], group = GROUP_VALID_RANGES, inline = INLINE_VALID_RANGE_LINE_STYLE)) var vrBorderWidthInput = input.int(1, '', minval=0, group = GROUP_VALID_RANGES, inline = INLINE_VALID_RANGE_LINE_STYLE) var vrShowLabelsInput = input.bool(true, "Labels", group = GROUP_VALID_RANGES, inline = INLINE_VALID_RANGE_LABEL_STYLE) var vrLabelColorInput = input.color(color.black, "", group = GROUP_VALID_RANGES, inline = INLINE_VALID_RANGE_LABEL_STYLE) var vrLabelBgColorInput = input.color(color.white, "", group = GROUP_VALID_RANGES, inline = INLINE_VALID_RANGE_LABEL_STYLE) // Shitwicks var swShowInput = input.bool(false, "Highlight Shitwicks", group = GROUP_SHITWICKS, inline = INLINE_SHITWICKS) var swColorInput = input.color(color.yellow, "", group = GROUP_SHITWICKS, inline = INLINE_SHITWICKS) // Info Panel var infoPanelShowInput = input.bool(true, "Info Panel", group = GROUP_INFOPANEL, inline = INLINE_INFOPANEL_POSITION) var infoPanelYposInput = input.string("top", "", inline = INLINE_INFOPANEL_POSITION, options = ["top", "middle", "bottom"], group = GROUP_INFOPANEL) var infoPanelXposInput = input.string("right", "", inline = INLINE_INFOPANEL_POSITION, options = ["left", "center", "right"], group = GROUP_INFOPANEL) var infoPanelHeaderBgColorInput = input.color(color.rgb(0, 0, 0, 90), "Header Background", inline = INLINE_INFOPANEL_STYLE, group = GROUP_INFOPANEL) var infoPanelBodyBgColorInput = input.color(color.white, "Body Background", inline = INLINE_INFOPANEL_STYLE, group = GROUP_INFOPANEL) var infoPanelTextColorInput = input.color(color.black, "Text Color", inline = INLINE_INFOPANEL_STYLE, group = GROUP_INFOPANEL) var infoPanelNotesInput = input.text_area("", "User Notes", "Custom notes to display in the info panel", group = GROUP_INFOPANEL) // logger var loggerMessageInput = input.bool(false, "Enable Pine Log", group = GROUP_LOGGER) var loggerShowBarIndexInput = input.bool(false, "Show Bar Index", group = GROUP_LOGGER) var loggerShowInternalValidSwsInput = input.bool(false, "Internal Valid SW", group = GROUP_LOGGER, inline = INLINE_LOGGER_BREAKS) var loggerShowInternalInvalidSwsInput = input.bool(false, "Internal Invalid SW", group = GROUP_LOGGER, inline = INLINE_LOGGER_BREAKS) var loggerPerformanceInput = input.bool(false, "Performance Stats", group = GROUP_LOGGER) // ————— Extends // @function request security for timeframe multiplier in minutes // @param TF string value // @param _exp float value // @returns request.security() method minRequest(string _timeframe = na, float _exp = na) => ref = _timeframe == '' ? str.tostring(timeframe.multiplier) : _timeframe val = request.security(syminfo.tickerid, ref, _exp) // return val // ————— Types // @type representing the current trend // @field direction contains the direction of the trend, can pe UP or DOWN type trend string direction // @type representing a valid point, either a high or a low // @field price the price of the high or low // @field barIndex the bar index of the pivot // @field valid is the pivot currently valid // @field plot should the pivot be plotted // @field plotted has the pivot been plotted type pivot float price int barIndex bool valid = false bool new = false bool plot = true bool plotted = false string plotDirection = "UP" string side = "HIGH" label pivotPlot = na method setPlotting(pivot this, _plot = true) => this.plot := _plot this.plotted := _plot ? false : true // @function limit array size to defined max size // @param this array of valid pivots // @param _maxSize array max size // @returns array method limitSize(array this, int _maxSize) => if this.size() > _maxSize vp = this.shift() if not na(vp.pivotPlot) label.delete(vp.pivotPlot) // return this // @function add pivot for plotting // @param this array of plot pivots // @param _pivot the pivot to be plotted // @param _plotDirection the plot direction // @returns array method addPlotPivot(array this, pivot _pivot, string _plotDirection = "UP") => addPivot = true if this.size() > 2 for i = 0 to 2 lastPivot = array.get(this, array.size(this) - 1 - i) if lastPivot.barIndex == _pivot.barIndex and lastPivot.price == _pivot.price addPivot := false if addPivot vp = _pivot.copy() vp.plotDirection := _plotDirection this.push(vp) // return this // @type containing the valid range break // @field barIndex bar index of the current break // @field active is the break active // @field type is it a structure break or a trend change // @field up is the current break up // @field down is the current break down type validRangeBreak int barIndex bool active = false string type = na bool up = false bool down = false // @function set valid range break // @field this valid range break // @field _type SB or TC // @returns validRangeBreak method setBreak(validRangeBreak this, _type = "SB") => this.type := _type this.active := true this.barIndex := bar_index // return this // @function reset valid range break // @param this valid range break // @returns validRangeBreak method resetBreak(validRangeBreak this) => this.active := false // return this // @function check if valid range break is active // @param this valid range break // @param _high high price // @param _low low price // @returns bool method determineBreak(validRangeBreak this, float _high, float _low) => this.up := high > _high this.down := low < _low this.active := this.down or this.up if this.active this.barIndex := bar_index // return this.active // @type cointaining the current valid range // @field h the valid high // @field l the valdi low // @field br the current break // @field valid is the current range valid // @field new has the break occured within this cycle type validRange pivot h pivot l validRangeBreak br bool valid = false bool new = false // @function check if valid range break has occured // @param this validRange // @returns bool method isBroken(validRange this) => active = this.br.determineBreak(this.h.price, this.l.price) // return active // @function validate or invalidate valid range // @param this valid range // @param _vh the current valid high // @param _vl the current valdi low // @param _valid the flag to either validate or invalidate the valid range // @returns valid range method validate(validRange this, pivot _vh, pivot _vl, bool _valid = true) => this.h := pivot.copy(_vh) this.l := pivot.copy(_vl) this.valid := _valid this.new := _valid // return this // @type shitwick // @field barIndex the bar index at which the shitwick occured // @field vh the high of the shitwick // @field vl the low of the shitwick // @field active is the shitwick active // @field new is the shitwick new // @field internal is it an internal shitwick type shitwick int barIndex pivot vh pivot vl pivot h pivot l active = false new = false internal = false // @function activate the shitwick // @param this shitwick // @param _internal is the shitwick internal method activate(shitwick this, bool _internal = false) => this.active := true this.new := true this.internal := _internal this.barIndex := bar_index this.vh := pivot.new(high, bar_index, false) this.vl := pivot.new(low, bar_index, false) this.l := pivot.new(high, bar_index, false) this.h := pivot.new(low, bar_index, false) // @type the break plot // @field breakLine the line plot // @field breakLabel the label plot type breakPlot line breakLine = na label breakLabel = na // @function limit array size to defined max size // @param this array of valid pivots // @param _maxSize array max size // @returns array method limitSize(array this, int _maxSize) => if this.size() > _maxSize firstBreakPlot = this.shift() line.delete(firstBreakPlot.breakLine) if not na(firstBreakPlot.breakLabel) label.delete(firstBreakPlot.breakLabel) // return this // @type containing params and methods to execute the application logic // @field vh the last internal valid high // @field vl the last internal valid low // @field vhs a list to keep track of the valid highs // @field vls a list to keep track of the valid lows // @field pullbackCandleCount the amount of pullback candles needed to form a valid range // @field oppositeCandleCount the amount of opposite candles needed to form a valid range type processor pivot vh pivot vl array vhs array vls int pullbackCandleCount int oppositeCandleCount // @function check if this bar is a valid high // @param this processor // @param _validHighPullbackCandleCount pullback candles since last valid low // @param _validHighOppositeCandleCount opposite candles since last valid low // @returns bool method isValidHigh(processor this, int _validHighPullbackCandleCount, float _validHighOppositeCandleCount) => this.vl.valid and _validHighPullbackCandleCount >= this.pullbackCandleCount and _validHighOppositeCandleCount >= this.oppositeCandleCount // @function check if this bar is a valid low // @param this processor // @param _validLowPullbackCandleCount pullback candles since last valid high // @param _validLowOppositeCandleCount opposite candles since last valid high // @returns bool method isValidLow(processor this, int _validLowPullbackCandleCount, float _validLowOppositeCandleCount) => this.vh.valid and _validLowPullbackCandleCount >= this.pullbackCandleCount and _validLowOppositeCandleCount >= this.oppositeCandleCount // @function set valid high // @param this processor // @param _barIndex the bar index of the valid high // @param _invalidateLow should the low be invalidated // @returns processor method setValidHigh(processor this, int _barIndex, bool _invalidateLow = true) => this.vh.new := true this.vh.side := HIGH this.vh.valid := true this.vh.price := high[bar_index - _barIndex] this.vh.barIndex := _barIndex this.vhs.push(this.vh.copy()) // invalidate valid low this.vl.valid := _invalidateLow ? false : true // return this // @function set valid low // @param this processor // @param _barIndex the bar index of the valid low // @param _invalidateHigh should the high be invalidated // @returns processor method setValidLow(processor this, int _barIndex, bool _invalidateHigh = true) => this.vl.new := true this.vl.side := LOW this.vl.valid := true this.vl.price := low[bar_index - _barIndex] this.vl.barIndex := _barIndex this.vls.push(this.vl.copy()) // invalidate valid high this.vh.valid := _invalidateHigh ? false : true // return this // @function check for new valid high // @param this processor // @returns bool method isNewValidHigh(processor this) => // return this.vh.new // @function check for new valid low // @param this processor // @returns bool method isNewValidLow(processor this) => // return this.vl.new // @function enter shitwick mode // @param this shitwick // @param _sw the current shitwick // @param _vr the current valid range // @param _t trend // @returns processor method enterShitwickMode(processor this, shitwick _sw, validRange _vr, trend _t) => this.setValidHigh(bar_index, false) this.setValidLow(bar_index, false) _sw.activate() _vr.br.setBreak(TC) _t.direction := _t.direction == UP ? DOWN : UP // return this // @function enter shitwick mode // @param this shitwick // @param _sw the current shitwick // @param _vr the current valid range // @param _t trend // @returns processor method enterInternalShitwickMode(processor this, shitwick _sw, validRange _vr, trend _t) => this.setValidHigh(bar_index, false) this.setValidLow(bar_index, false) _sw.activate(true) if _t.direction == UP and _vr.br.down _t.direction := DOWN _vr.br.setBreak(TC) else if _t.direction == DOWN and _vr.br.up _t.direction := UP _vr.br.setBreak(TC) else _vr.br.setBreak(SB) // return this // @type containing output and state of the log message // @field message the message to be logged // @field type the info level // @field output should the message be logged type logMessage string message = "" string type = "message" bool output = false // @function append message to be logged // @param logMessage logger // @param multi message // @returns logMessage method appendLogMessage(logMessage this, _msg) => this.output := true this.message += str.tostring(_msg) + LOG_SEPARATOR // return this // @function reset logger // @param logMessage logger // @returns logMessage method resetLog(logMessage this) => this.output := false this.message := LOG_SEPARATOR + "Bar " + str.tostring(bar_index) + LOG_SEPARATOR // return this // ————— Globals // calculation var timeframeThreshold = timeframeThresholdInput.minRequest(timeframeComparison.minMult()) var oppositeCandleCount = timeframeComparison.minMult() < timeframeThreshold ? ltfOppositeCandlesInput : htfOppositeCandlesInput var pullbackCandleCount = timeframeComparison.minMult() < timeframeThreshold ? ltfPullbackCandlesInput : htfPullbackCandlesInput // trend var trend t = trend.new(UP) // processor var processor engine = processor.new( vh = pivot.new(price = high, barIndex = bar_index, valid = true), vl = pivot.new(price = low, barIndex = bar_index), vhs = array.new(1, pivot.new(price = high, barIndex = bar_index, valid = true)), vls = array.new(1, pivot.new(price = low, barIndex = bar_index)), pullbackCandleCount = pullbackCandleCount, oppositeCandleCount = oppositeCandleCount) // valid range var validRange vr = validRange.new(engine.vh.copy(), engine.vl.copy(), validRangeBreak.new(barIndex = 0)) // valid highs and lows var array vps = array.new(1, pivot.new(price = high, barIndex = bar_index, valid = true)) var array sbs = array.new(0) var array tcs = array.new(0) // Shitwick var shitwick sw = shitwick.new() // styles and formatting var timeframePeriodFormatted = str.upper(timeframeHelper.f_prettifyTF(timeframe.period)) var line linePd000 = na, var line linePd050 = na, var line linePd100 = na var label labelPd000 = na, var label labelPd050 = na, var label labelPd100 = na var label labelVrHigh = na, var label labelVrLow = na // logger var logger = logMessage.new() // ————— Functions garbageCollect() => engine.vhs.limitSize(MAX_ARRAY_SIZE) engine.vls.limitSize(MAX_ARRAY_SIZE) vps.limitSize(hlMaxInput) sbs.limitSize(sbMaxSbInput) tcs.limitSize(tcMaxTcInput) resetRuntimeFlags() => engine.vl.new := false engine.vh.new := false vr.new := false sw.new := false vr.br.resetBreak() logger.resetLog() // ————— Calculations garbageCollect() resetRuntimeFlags() // Debugging internalValidSw = false internalInvalidSw = false // Valid Highs validHighLookbackIndex = sw.active ? sw.barIndex - 1 : engine.vls.last().barIndex validHighLookback = bar_index > 0 ? bar_index - validHighLookbackIndex : 1 validHighPullbackCandleCount = math.abs(ta.highestbars(validHighLookback)) validHighOppositeCandleCount = math.sum(close < open ? 1 : 0, validHighPullbackCandleCount + 1) // Valid Lows validLowLookbackIndex = sw.active ? sw.barIndex - 1 : engine.vhs.last().barIndex validLowLookback = bar_index > 0 ? bar_index - engine.vhs.last().barIndex : 1 validLowPullbackCandleCount = math.abs(ta.lowestbars(validLowLookback)) validLowOppositeCandleCount = math.sum(close > open ? 1 : 0, validLowPullbackCandleCount + 1) // if Shitwick active if engine.isValidHigh(validHighPullbackCandleCount, validHighOppositeCandleCount) engine.setValidHigh(bar_index - validHighPullbackCandleCount) else if engine.isValidLow(validLowPullbackCandleCount, validLowOppositeCandleCount) engine.setValidLow(bar_index - validLowPullbackCandleCount) if sw.active // if a new valid high has formed that's not shitwick internal if engine.isNewValidHigh() and engine.vh.price >= sw.vh.price vps.addPlotPivot(sw.vh, sw.internal ? t.direction : UP) // set a valid low at the shitwick bar index if it has not been broken yet if sw.internal and (sw.vl.barIndex > sw.l.barIndex or engine.vh.barIndex == sw.vl.barIndex) vr.validate(sw.vh, sw.vl, false) else vps.addPlotPivot(sw.vl, sw.internal ? t.direction : DOWN) vr.validate(sw.vh, sw.vl, true) sw.active := false // if a new valid low has formed that's not shitwick internal else if engine.isNewValidLow() and engine.vl.price <= sw.vl.price vps.addPlotPivot(sw.vl, sw.internal ? t.direction : DOWN) // set a valid high at the shitwick bar index if it has not been broken yet if sw.internal and (sw.vh.barIndex > sw.h.barIndex or engine.vl.barIndex == sw.vh.barIndex) vr.validate(sw.vh, sw.vl, false) else vps.addPlotPivot(sw.vh, sw.internal ? t.direction : UP) vr.validate(sw.vh, sw.vl, true) sw.active := false // extend Shitwick Range if broken without new valid pivot else //@todo if now vr break go into real shitwick mode if high > sw.vh.price sw.vh.price := high sw.vh.barIndex := bar_index if low < sw.vl.price sw.vl.price := low sw.vl.barIndex := bar_index if vr.valid == false and not sw.active if (engine.isNewValidHigh() and engine.vhs.last().price > vr.h.price) vr.validate(engine.vhs.last(), engine.vls.last(), true) vps.addPlotPivot(engine.vhs.last(), t.direction) else if (engine.isNewValidLow() and engine.vls.last().price < vr.l.price) vr.validate(engine.vhs.last(), engine.vls.last(), true) vps.addPlotPivot(engine.vls.last(), t.direction) // Early Break down else if t.direction == UP and low < vr.l.price internalInvalidSw := true engine.setValidHigh(bar_index - validHighPullbackCandleCount) vps.addPlotPivot(engine.vhs.last()) vr.h := engine.vhs.last().copy() vr.br.setBreak(TC) t.direction := DOWN // Early break up else if t.direction == DOWN and high > vr.h.price internalInvalidSw := true engine.setValidLow(bar_index - validLowPullbackCandleCount) vps.addPlotPivot(engine.vls.last()) vr.l := engine.vls.last().copy() vr.br.setBreak(TC) t.direction := UP if vr.valid == true and not sw.active if vr.isBroken() vr.valid := false // if BR down and BR up (= Shitwick) if vr.br.up == true and vr.br.down == true //logger.appendLogMessage("VR Break Shitwick") engine.enterShitwickMode(sw, vr, t) // Early Break Up else if vr.br.up == true and validLowPullbackCandleCount == 0 internalValidSw := true engine.enterInternalShitwickMode(sw, vr, t) // Early Break Down else if vr.br.down == true and validHighPullbackCandleCount == 0 internalValidSw := true engine.enterInternalShitwickMode(sw, vr, t) else if vr.br.up == true // determine SB / TC vr.br.type := t.direction == UP ? SB : TC t.direction := UP // if no valid low since last high just take the last low OR any lower pivot thereafter if engine.vhs.last().barIndex > engine.vls.last().barIndex or (bar_index - validLowPullbackCandleCount > engine.vls.last().barIndex and low[validLowPullbackCandleCount] < engine.vls.last().price) engine.setValidLow(bar_index - validLowPullbackCandleCount) // determine last valid low / fallback low vps.addPlotPivot(engine.vls.last(), t.direction) // @todo check if new vr low needed vr.l := engine.vls.last().copy() // if BR down else if vr.br.down == true // determine SB / TC vr.br.type := t.direction == DOWN ? SB : TC t.direction := DOWN // if no valid high since last low just take the last high OR any higher pivot thereafter if engine.vls.last().barIndex > engine.vhs.last().barIndex or (bar_index - validHighPullbackCandleCount > engine.vhs.last().barIndex and high[validHighPullbackCandleCount] > engine.vhs.last().price) engine.setValidHigh(bar_index - validHighPullbackCandleCount) // determine last valid high / fallback high vps.addPlotPivot(engine.vhs.last(), t.direction) // @todo check if new vr high needed vr.h := engine.vhs.last().copy() // ————— Visuals // Plot Highs and Lows if hlShowInput // plot all highs and lows if hlShowLastVrOnly == false if vps.size() > 2 for i = 0 to 2 vp = array.get(vps, array.size(vps) - 1 - i) if not vp.plotted vp.pivotPlot := label.new(vp.barIndex, vp.price, style = label.style_circle, size = size.tiny, color = vp.plotDirection == UP ? hlUpTrendColorInput : hlDownTrendColorInput) vp.plotted := true // plot the high and low of every new valid range else if vr.new label.delete(labelVrLow), label.delete(labelVrHigh) labelVrLow := label.new(vr.l.barIndex, vr.l.price, style = label.style_circle, size = size.tiny, color = t.direction == UP ? hlUpTrendColorInput : hlDownTrendColorInput) labelVrHigh := label.new(vr.h.barIndex, vr.h.price, style = label.style_circle, size = size.tiny, color = t.direction == UP ? hlUpTrendColorInput : hlDownTrendColorInput) if vrShowLastInput and barstate.islast line.delete(linePd000), line.delete(linePd050), line.delete(linePd100) label.delete(labelPd000), label.delete(labelPd050), label.delete(labelPd100) if vr.valid pd000 = t.direction == UP ? vr.l.price : vr.h.price pd050 = vr.h.price - ((vr.h.price - vr.l.price) / 2) pd100 = t.direction == UP ? vr.h.price : vr.l.price pdOffset = vr.l.barIndex < vr.h.barIndex ? vr.l.barIndex : vr.h.barIndex linePd000 := line.new(pdOffset, pd000, bar_index, pd000, extend = extend.right, color = vrBorderColorInput, style = vrBorderStyleInput, width = vrBorderWidthInput) linePd050 := line.new(pdOffset, pd050, bar_index, pd050, extend = extend.right, color = vrBorderColorInput, style = vrBorderStyleInput, width = vrBorderWidthInput) linePd100 := line.new(pdOffset, pd100, bar_index, pd100, extend = extend.right, color = vrBorderColorInput, style = vrBorderStyleInput, width = vrBorderWidthInput) if vrShowLabelsInput labelPd000 := label.new(bar_index + 8, pd000, "0%", xloc = xloc.bar_index, color = vrLabelBgColorInput, style = label.style_label_center, textcolor = vrLabelColorInput) labelPd050 := label.new(bar_index + 8, pd050, "50%", xloc = xloc.bar_index, color = vrLabelBgColorInput, style = label.style_label_center, textcolor = vrLabelColorInput) labelPd100 := label.new(bar_index + 8, pd100, "100%", xloc = xloc.bar_index, color = vrLabelBgColorInput, style = label.style_label_center, textcolor = vrLabelColorInput) // Draw Structure Breaks if sbShowInput and vr.br.active and vr.br.type == SB sbPlot = breakPlot.new() if t.direction == UP sbPlot.breakLine := line.new(vr.h.barIndex, vr.h.price, bar_index, vr.h.price, color = sbBorderColorInput, style = sbBorderStyleInput, width = sbBorderWidthInput) if sbShowLabelsInput sbPlot.breakLabel := label.new(vr.h.barIndex - 1, vr.h.price, timeframePeriodFormatted + " " + ARROW_UP, style = label.style_label_lower_left, textcolor = sbLabelColorInput, color = color.rgb(0, 0, 0, 100)) else sbPlot.breakLine := line.new(vr.l.barIndex, vr.l.price, bar_index, vr.l.price, color = sbBorderColorInput, style = sbBorderStyleInput, width = sbBorderWidthInput) if sbShowLabelsInput sbPlot.breakLabel := label.new(vr.l.barIndex - 1, vr.l.price, timeframePeriodFormatted + " " + ARROW_DOWN, style = label.style_label_upper_left, textcolor = sbLabelColorInput, color = color.rgb(0, 0, 0, 100)) sbs.push(sbPlot) // Draw Trend Changes if tcShowInput and vr.br.active and vr.br.type == TC tcPlot = breakPlot.new() if t.direction == UP tcPlot.breakLine := line.new(vr.h.barIndex, vr.h.price, bar_index, vr.h.price, color = tcBorderColorInput, style = tcBorderStyleInput, width = tcBorderWidthInput) if tcShowLabelsInput tcPlot.breakLabel := label.new(vr.h.barIndex - 1, vr.h.price, timeframePeriodFormatted + " TC " + ARROW_UP, style = label.style_label_lower_left, textcolor = tcLabelColorInput, color = color.rgb(0, 0, 0, 100)) else tcPlot.breakLine := line.new(vr.l.barIndex, vr.l.price, bar_index, vr.l.price, color = tcBorderColorInput, style = tcBorderStyleInput, width = tcBorderWidthInput) if tcShowLabelsInput tcPlot.breakLabel := label.new(vr.l.barIndex - 1, vr.l.price, timeframePeriodFormatted + " TC " + ARROW_DOWN, style = label.style_label_upper_left, textcolor = tcLabelColorInput, color = color.rgb(0, 0, 0, 100)) tcs.push(tcPlot) // Shitwicks barcolor(swShowInput and sw.active and sw.new ? swColorInput : na) // Draw Info Panel if infoPanelShowInput and barstate.islast var table infoPanel = table.new(infoPanelYposInput + "_" + infoPanelXposInput, 2, 6, bgcolor = infoPanelHeaderBgColorInput, frame_width = 0, frame_color = color.black, border_width = 0, border_color = color.black) // Style table.set_frame_width(infoPanel, 2) table.set_border_width(infoPanel, 1) // Header table.cell(infoPanel, 0, 0, "Parameter", text_color = infoPanelTextColorInput, text_halign = text.align_left) table.cell(infoPanel, 1, 0, "Value", text_color = infoPanelTextColorInput, text_halign = text.align_left) // Body table.cell(infoPanel, 0, 1, "Trend Direction", text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) table.cell(infoPanel, 1, 1, t.direction == UP ? UP + " ⇧" : DOWN + " ⇩", text_color = t.direction == UP ? color.green : color.red, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) table.cell(infoPanel, 0, 2, "Last Break", text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) table.cell(infoPanel, 1, 2, vr.br.type, text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) table.cell(infoPanel, 0, 3, "Valid Range", text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) table.cell(infoPanel, 1, 3, vr.valid ? "✔" : "✘", text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) priceInfo = vr.valid ? (close > vr.h.price - ((vr.h.price - vr.l.price) / 2) ? "Above 50%, in Premium" : "Below 50%, in Discount") : "No VR → no PD" table.cell(infoPanel, 0, 4, "Price", text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) table.cell(infoPanel, 1, 4, priceInfo, text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) if infoPanelNotesInput != "" table.cell(infoPanel, 0, 5, "Notes", text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) table.cell(infoPanel, 1, 5, infoPanelNotesInput, text_color = infoPanelTextColorInput, bgcolor = infoPanelBodyBgColorInput, text_halign = text.align_left) // ————— Alerts alertcondition(vr.br.active and vr.br.type == SB, "Structure Break", "Structure Break, Price: {{close}}") alertcondition(vr.br.active and vr.br.type == TC, "Trend Change", "Trend Change, Price: {{close}}") alertcondition(vr.new, "New Valid Range", "New Valid Range, Price: {{close}}") sb_long = vr.br.active and vr.br.type == SB and t.direction == UP and inTradingSession and inDateRange() and long_on sb_short = vr.br.active and vr.br.type == SB and t.direction == DOWN and inTradingSession and inDateRange() and short_on tc_long = vr.br.active and vr.br.type == TC and t.direction == UP and inTradingSession and inDateRange() and long_on tc_short = vr.br.active and vr.br.type == TC and t.direction == DOWN and inTradingSession and inDateRange() and short_on long = entry_type == "SB" ? sb_long : entry_type == "TC" ? tc_long : entry_type == "Both" ? (sb_long or tc_long) : na short = entry_type == "SB" ? sb_short : entry_type == "TC" ? tc_short : entry_type == "Both" ? (sb_short or tc_short) : na // Detect what was the last signal (long or short) var float entryPrice = na var bool inPosition = false var int direction = 0 // 1 for long, -1 for short // Entry logic if not inPosition and long direction := 1 entryPrice := close inPosition := true strategy.entry('LONG', strategy.long) else if not inPosition and short direction := -1 entryPrice := close inPosition := true strategy.entry('SHORT', strategy.short) long_last = direction[1] == 0 and direction == 1 plotshape(long_last, style=shape.labelup, location=location.belowbar, color=color.new(color.green, 0), size=size.tiny, title='Long', text=' Long', textcolor=color.new(color.white, 0)) short_last = direction[1] == 0 and direction == -1 plotshape(short_last, style=shape.labeldown, location=location.abovebar, color=color.new(color.red, 0), size=size.tiny, title='Short', text=' Short', textcolor=color.new(color.white, 0)) //Plot entryPrice longPrice = ta.valuewhen(long_last, close, 0) Long_entry_plot = plot(direction == 1 ? longPrice : na, style=plot.style_linebr, color=color.new(color.blue, 0), linewidth=2, title='Long Entry') shortPrice = ta.valuewhen(short_last, close, 0) Short_entry_plot = plot(direction == -1 ? shortPrice : na, style=plot.style_linebr, color=color.new(color.blue, 0), linewidth=2, title='Short Entry') //================================================================================================================================================// //ATR Based SL atr_based_sl_group = 'ATR Based SL & TP' atrPeriod = 14 //input.int(title='ATR Period', defval=14, minval=1, group=atr_based_sl_group) atrMultiplier = 4 //input.int(title='ATR Multiplier', defval=4, group=atr_based_sl_group) // ATR atrSL = ta.atr(atrPeriod) LongATR = close - atrSL * atrMultiplier ShortATR = close + atrSL * atrMultiplier longStop_ATR = ta.valuewhen(long_last, LongATR, 0) shortStop_ATR = ta.valuewhen(short_last, ShortATR, 0) //ATR target longSLdist_atr = 1 * (longPrice - longStop_ATR) longTakeCalc_atr = longPrice + ratio * longSLdist_atr longTake_atr = ta.valuewhen(long_last, longTakeCalc_atr, 0) shortSLdist_atr = 1 * (shortStop_ATR - shortPrice) shortTakeCalc_atr = shortPrice - ratio * shortSLdist_atr shortTake_atr = ta.valuewhen(short_last, shortTakeCalc_atr, 0) //==================================================================================================================================================// // SL Based on % per_based_sl_group = 'Percentage Based StopLoss and Take Profit' // User Options to Change Inputs (%) stopPer = 5 / 100//input.float(5.0, title='Stop Loss %', group=per_based_sl_group) / 100 takePer = 5 / 100 //input.float(5.0, title='Take Profit %', group=per_based_sl_group) / 100 // Determine where you've entered and in what direction longStop_per = longPrice * (1 - stopPer) shortStop_per = shortPrice * (1 + stopPer) shortTake_per = shortPrice * (1 - takePer) longTake_per = longPrice * (1 + takePer) //==================================================================================================================================================// pip_size = 0.0001 // Calculate Pip Value for the Current Symbol pip_value = pip_buffer_pips * pip_size // Calculate Swing Low and Swing High Stop Loss Levels longStop_swing = ta.valuewhen(long_last, ta.lowest(swingLow), 0) - pip_value // Subtract pip buffer for long stop loss shortStop_swing = ta.valuewhen(short_last, ta.highest(swingHigh), 0) + pip_value // Add pip buffer for short stop loss //Swing Based Stoploss target with pip buffer longSLdist = 1 * (longPrice - longStop_swing) longTakeCalc = longPrice + ratio * longSLdist longTake_swing = ta.valuewhen(long_last, longTakeCalc, 0) shortSLdist = 1 * (shortStop_swing - shortPrice) shortTakeCalc = shortPrice - ratio * shortSLdist shortTake_swing = ta.valuewhen(short_last, shortTakeCalc, 0) //Swing Based Stoploss target // longSLdist = 1 * (longPrice - ta.lowest(swingLow)) // longTakeCalc = longPrice + ratio * longSLdist // longTake_swing = ta.valuewhen(long_last, longTakeCalc, 0) // shortSLdist = 1 * (ta.highest(swingHigh) - shortPrice) // shortTakeCalc = shortPrice - ratio * shortSLdist // shortTake_swing = ta.valuewhen(short_last, shortTakeCalc, 0) //===================================================================================================================================================// //StopLoss Selection float Long_Stop_Loss = na if SL_type == ATR Long_Stop_Loss := longStop_ATR Long_Stop_Loss if SL_type == Per Long_Stop_Loss := longStop_per Long_Stop_Loss if SL_type == Swing Long_Stop_Loss := longStop_swing Long_Stop_Loss float Short_Stop_Loss = na if SL_type == ATR Short_Stop_Loss := shortStop_ATR Short_Stop_Loss if SL_type == Per Short_Stop_Loss := shortStop_per Short_Stop_Loss if SL_type == Swing Short_Stop_Loss := shortStop_swing Short_Stop_Loss //TP Selection float Long_Take_Profit = na if SL_type == ATR Long_Take_Profit := longTake_atr Long_Take_Profit if SL_type == Per Long_Take_Profit := longTake_per Long_Take_Profit if SL_type == Swing Long_Take_Profit := longTake_swing Long_Take_Profit float Short_Take_Profit = na if SL_type == ATR Short_Take_Profit := shortTake_atr Short_Take_Profit if SL_type == Per Short_Take_Profit := shortTake_per Short_Take_Profit if SL_type == Swing Short_Take_Profit := shortTake_swing Short_Take_Profit // Plot SL and TP lines longSLLine = plot(direction == 1 and inPosition ? Long_Stop_Loss : na, style=plot.style_linebr, color=color.new(color.red, 0), linewidth=2, title='Long Fixed SL') shortSLLine = plot(direction == -1 and inPosition ? Short_Stop_Loss : na, style=plot.style_linebr, color=color.new(color.red, 0), linewidth=2, title='Short Fixed SL') longTPLine = plot(direction == 1 and inPosition ? Long_Take_Profit : na, style=plot.style_linebr, color=color.new(color.green, 0), linewidth=2, title='Long Fixed TP') shortTPLine = plot(direction == -1 and inPosition ? Short_Take_Profit : na, style=plot.style_linebr, color=color.new(color.green, 0), linewidth=2, title='Short Fixed TP') // Check if SL or TP is hit longSLhit = direction == 1 and inPosition and low < Long_Stop_Loss shortSLhit = direction == -1 and inPosition and high > Short_Stop_Loss longTPhit = direction == 1 and inPosition and high > Long_Take_Profit shortTPhit = direction == -1 and inPosition and low < Short_Take_Profit // Fill the areas between the entry and SL/TP lines fill(Long_entry_plot, longTPLine, color=color.new(color.green, 90)) fill(Short_entry_plot, shortTPLine, color=color.new(color.green, 90)) fill(Long_entry_plot, longSLLine, color=color.new(color.red, 90)) fill(Short_entry_plot, shortSLLine, color=color.new(color.red, 90)) // Plot SL and TP hit markers plotshape(longSLhit, style=shape.labelup, location=location.belowbar, color=color.new(color.gray, 0), size=size.tiny, title='Long SL', text=' SL', textcolor=color.new(color.white, 0)) plotshape(shortSLhit, style=shape.labeldown, location=location.abovebar, color=color.new(color.gray, 0), size=size.tiny, title='Short SL', text=' SL', textcolor=color.new(color.white, 0)) plotshape(longTPhit, style=shape.labeldown, location=location.abovebar, color=color.new(color.green, 0), size=size.tiny, title='Long TP', text='TP', textcolor=color.new(color.white, 0)) plotshape(shortTPhit, style=shape.labelup, location=location.belowbar, color=color.new(color.green, 0), size=size.tiny, title='Short TP', text='TP', textcolor=color.new(color.white, 0)) // Reset state if SL or TP is hit if longSLhit or shortSLhit or longTPhit or shortTPhit inPosition := false direction := 0 entryPrice := na entryPrice //==================================================================================================================================================// //Fixed Amout Per trade Calculation fixed_qty_group = 'Risk Management : Fixed QTY per trade' turn_custom_initial = input.bool(title='Turn On/Off - Fixed Amount per Trade', defval=false, tooltip='If you want to trade with fixed amount on each trade then turn on this and give initial value.\n Example : If initial balanace is 1000$ then it will open each trade with 1000$. \n If your balance goes down then 1000$ , it will open trade with the remaning money and if your balance increase than the initial balance , it will still open each trade with the 1000$.', group=fixed_qty_group) initial_balance = input.int(title='💵 Initial Capital :', defval=1000, group=fixed_qty_group) custom_qty = turn_custom_initial and strategy.equity > initial_balance ? initial_balance / close : turn_custom_initial and strategy.equity < initial_balance ? strategy.equity / close : initial_balance / close final_qty = turn_custom_initial ? custom_qty : na if long_on and inDateRange() ? strategy.position_size == 0 or not inShort : na strategy.entry('LONG', strategy.long, when=long_last, qty=final_qty, comment = " ") if short_on and inDateRange() ? strategy.position_size == 0 or not inLong : na strategy.entry('SHORT', strategy.short, when=short_last, qty=final_qty, comment = " ") if strategy.position_size > 0 strategy.exit(id='Close Long', stop=Long_Stop_Loss, limit=Long_Take_Profit, comment=' ') if strategy.position_size < 0 strategy.exit(id='Close Short', stop=Short_Stop_Loss, limit=Short_Take_Profit, comment=' ') // plotshape(long_last, title='Buy', text='Long', style=shape.labelup, location=location.belowbar, color=color.new(color.green, 0), textcolor=color.new(color.white, 0), size=size.tiny) // plotshape(short_last, title='Sell', text='Short', style=shape.labeldown, location=location.abovebar, color=color.new(color.red, 0), textcolor=color.new(color.white, 0), size=size.tiny) // plotshape(long , text = "Buy", color = #2962ff, location = location.belowbar, style = shape.labelup, textcolor = color.white) // plotshape(short , text = "Sell", color = #434651, location = location.abovebar, style = shape.labeldown, textcolor = color.white) // ————— Debugging barcolor(loggerShowInternalValidSwsInput and internalValidSw ? color.aqua : na) barcolor(loggerShowInternalInvalidSwsInput and internalInvalidSw ? color.lime : na) if loggerMessageInput and logger.output == true log.info(logger.message) if loggerShowBarIndexInput label.new(bar_index, 0, yloc = yloc.belowbar, style=label.style_none, text = str.tostring(bar_index), textcolor = color.gray, size = size.tiny) if loggerPerformanceInput [timePerBarInMs, totalTimeInMs, barsTimed, barsNotTimed] = stopwatch.stopwatchStats() if barstate.islastconfirmedhistory var stats = str.tostring(timePerBarInMs, "\nms/bar: #.######\n") + str.tostring(totalTimeInMs, "Total time (ms): #,###.######\n") + str.tostring(barsTimed + barsNotTimed, "Bars analyzed: #") log.info(stats) //===============================================================================================================================================================================================================// // Start -> Tables //===============================================================================================================================================================================================================// green_color = input.color(color.new(#1b5e20, 0), title='Bull Colour', group='━━━━━ ↓ Apperance ↓ ━━━━━ ', inline='8') red_color = input.color(color.new(#b22833, 0), title='Bear Colour', group='━━━━━ ↓ Apperance ↓ ━━━━━ ', inline='8') drawTester = input.bool(title='Table | Version :', defval=true, group='━━━━━ ↓ Apperance ↓ ━━━━━ ', inline='11') table_option = input.string(defval='Mobile', title=' ', options=['PC', 'Mobile'], group='━━━━━ ↓ Apperance ↓ ━━━━━ ', inline='11') pc_table = table_option == 'PC' ? true : false mobile_table = table_option == 'Mobile' ? true : false table_position_option = input.string(defval='Bottom Right', title='Position : ', options=['Bottom Center', 'Bottom Left', 'Bottom Right', 'Top Center', 'Top Left', 'Top Right', 'Middle Left', 'Middle Right'], group='━━━━━ ↓ Apperance ↓ ━━━━━ ', inline='11') table_position = table_position_option == 'Bottom Center' ? position.bottom_center : table_position_option == 'Bottom Left' ? position.bottom_left : table_position_option == 'Bottom Right' ? position.bottom_right : table_position_option == 'Top Center' ? position.top_center : table_position_option == 'Top Left' ? position.top_left : table_position_option == 'Top Right' ? position.top_right : table_position_option == 'Middle Left' ? position.middle_left : table_position_option == 'Middle Right' ? position.middle_right : na // Prepare stats table f_fillCell(_table, _column, _row, _title, _value, _bgcolor, _txtcolor) => _cellText = _title + '\n' + _value table.cell(_table, _column, _row, _cellText, bgcolor=_bgcolor, text_color=_txtcolor) newWin = strategy.wintrades > strategy.wintrades[1] and strategy.losstrades == strategy.losstrades[1] and strategy.eventrades == strategy.eventrades[1] newLoss = strategy.wintrades == strategy.wintrades[1] and strategy.losstrades > strategy.losstrades[1] and strategy.eventrades == strategy.eventrades[1] varip int winRow = 0 varip int lossRow = 0 varip int maxWinRow = 0 varip int maxLossRow = 0 if newWin lossRow := 0 winRow += 1 if winRow > maxWinRow maxWinRow := winRow maxWinRow if newLoss winRow := 0 lossRow += 1 if lossRow > maxLossRow maxLossRow := lossRow maxLossRow net_profit_per = (strategy.netprofit + strategy.initial_capital) * 100 / strategy.initial_capital - 100 // Draw stats table var table testTable = table.new(table_position, 10, 1, border_width=2) var bgcolor = green_color if drawTester and pc_table if barstate.islastconfirmedhistory f_fillCell(testTable, 0, 0, 'Total Trades:', str.tostring(strategy.closedtrades), bgcolor, color.white) f_fillCell(testTable, 1, 0, 'Win Rate:', str.tostring(truncate(strategy.wintrades / strategy.closedtrades * 100, 2)) + '%', bgcolor, color.white) f_fillCell(testTable, 2, 0, 'Net profit:', str.tostring(truncate(strategy.netprofit, 2)) + '%', strategy.netprofit > 0 ? green_color : red_color, color.white) f_fillCell(testTable, 3, 0, 'Total Win:', str.tostring(strategy.wintrades), green_color, color.white) f_fillCell(testTable, 4, 0, 'Total Loss:', str.tostring(strategy.losstrades), red_color, color.white) f_fillCell(testTable, 5, 0, 'Profit Factor:', str.tostring(truncate(strategy.grossprofit / strategy.grossloss, 2)), green_color, color.white) f_fillCell(testTable, 6, 0, 'Win in a raw:', str.tostring(maxWinRow), green_color, color.white) f_fillCell(testTable, 7, 0, 'Loss in a raw:', str.tostring(maxLossRow), green_color, color.white) var table m_testTable = table.new(table_position, 1, 10, border_width=2) if drawTester and mobile_table if barstate.islastconfirmedhistory f_fillCell(m_testTable, 0, 0, 'Total Trades:', str.tostring(strategy.closedtrades), bgcolor, color.white) f_fillCell(m_testTable, 0, 1, 'Win Rate:', str.tostring(truncate(strategy.wintrades / strategy.closedtrades * 100, 2)) + '%', bgcolor, color.white) f_fillCell(m_testTable, 0, 2, 'Net profit:', str.tostring(truncate(net_profit_per, 2)) + '%', strategy.netprofit > 0 ? green_color : red_color, color.white) f_fillCell(m_testTable, 0, 3, 'Total Win:', str.tostring(strategy.wintrades), green_color, color.white) f_fillCell(m_testTable, 0, 4, 'Total Loss:', str.tostring(strategy.losstrades), red_color, color.white) f_fillCell(m_testTable, 0, 5, 'Profit Factor:', str.tostring(truncate(strategy.grossprofit / strategy.grossloss, 2)), green_color, color.white) f_fillCell(m_testTable, 0, 6, 'Win in a raw:', str.tostring(maxWinRow), green_color, color.white) f_fillCell(m_testTable, 0, 7, 'Loss in a raw:', str.tostring(maxLossRow), green_color, color.white) /////////////////// // MONTHLY TABLE // turn_monthly_table = input.bool(title='Monthly Table | ', defval=true, group='━━━━━ ↓ Appearance ↓ ━━━━━ ', inline='12') monthly_table_position_option = input.string(defval='Bottom Left', title='Position : ', options=['Bottom Center', 'Bottom Left', 'Bottom Right', 'Top Center', 'Top Left', 'Top Right', 'Middle Left', 'Middle Right'], group='━━━━━ ↓ Appearance ↓ ━━━━━ ', inline='12') prec = input.int(2, title=' Fraction : ', group='━━━━━ ↓ Appearance ↓ ━━━━━ ', inline='12') // Input options for colors bgcolor_bg = input.color(color.new(#025ca5, 0), title='Monthly Table Background : ', group='━━━━━ ↓ Appearance ↓ ━━━━━ ', inline='13') text_color = input.color(color.new(#ffffff, 0), title=' - Text Colour : ', group='━━━━━ ↓ Appearance ↓ ━━━━━ ', inline='13') dev_mode = input.bool(false, "Developer Mode ", group='━━━━━ ↓ Appearance ↓ ━━━━━ ', inline='14') // Position the table monthy_table_position = monthly_table_position_option == 'Bottom Center' ? position.bottom_center : monthly_table_position_option == 'Bottom Left' ? position.bottom_left : monthly_table_position_option == 'Bottom Right' ? position.bottom_right : monthly_table_position_option == 'Top Center' ? position.top_center : monthly_table_position_option == 'Top Left' ? position.top_left : monthly_table_position_option == 'Top Right' ? position.top_right : monthly_table_position_option == 'Middle Left' ? position.middle_left : monthly_table_position_option == 'Middle Right' ? position.middle_right : na new_month = month(time) != month(time[1]) new_year = year(time) != year(time[1]) // Variables to store number of wins and losses per month var win_count = 0 var loss_count = 0 if longTPhit or shortTPhit win_count := win_count + 1 if dev_mode label.new(bar_index, high, 'Win: ' + str.tostring(win_count), color=color.green, textcolor = color.white, style = label.style_label_left) else if longSLhit or shortSLhit loss_count := loss_count + 1 if dev_mode label.new(bar_index, low, 'Loss: ' + str.tostring(loss_count), color=color.red, textcolor = color.white, style = label.style_label_left) // Arrays to store monthly wins and losses var month_wins = array.new_int(0) var month_losses = array.new_int(0) var month_time = array.new_int(0) if new_month and not na(time[1]) // At the start of a new month and ensure time is valid array.push(month_wins, win_count) // Store the wins for the previous month array.push(month_losses, loss_count) // Store the losses for the previous month array.push(month_time, time[1]) // Store the time for reference if dev_mode // Debugging labels for previous month label.new(bar_index, close, 'Time: ' + str.tostring(array.get(month_time, array.size(month_time) - 1)), color=#5552ff, textcolor=color.white, style = label.style_label_right) label.new(bar_index, low, 'Total Monthly Win: ' + str.tostring(array.get(month_wins, array.size(month_wins) - 1)), color=color.green, textcolor=color.white, style = label.style_label_left) label.new(bar_index, high, 'Total Monthly Loss: ' + str.tostring(array.get(month_losses, array.size(month_losses) - 1)), color=color.red, textcolor=color.white, style = label.style_label_left) // Reset the win/loss counters for the new month win_count := 0 loss_count := 0 //============================================================================= // // Initialize arrays to store yearly totals // Arrays to store yearly wins and losses var yearly_wins = array.new_int(0) var yearly_losses = array.new_int(0) // Initialize arrays to store yearly totals if new_year and not na(time[1]) yearly_wins_sum = 0 yearly_losses_sum = 0 // Sum wins and losses for the past year for mi = 0 to array.size(month_time) - 1 if year(array.get(month_time, mi)) == year(time[1]) yearly_wins_sum := yearly_wins_sum + array.get(month_wins, mi) yearly_losses_sum := yearly_losses_sum + array.get(month_losses, mi) // Store the yearly totals in arrays array.push(yearly_wins, yearly_wins_sum) array.push(yearly_losses, yearly_losses_sum) // // Reset monthly counters for the new year win_count := 0 loss_count := 0 //============================================================================== yearly_wins_sum_c= 0 yearly_losses_sum_c= 0 // Only calculate totals if there are months in the array if array.size(month_time) > 0 // Loop through monthly data and sum wins and losses for the current year for mi = 0 to array.size(month_time) - 1 if year(array.get(month_time, mi)) == year(time) yearly_wins_sum_c:= yearly_wins_sum_c+ array.get(month_wins, mi) yearly_losses_sum_c:= yearly_losses_sum_c+ array.get(month_losses, mi) // Include the current running month's wins and losses yearly_wins_sum_c:= yearly_wins_sum_c+ win_count yearly_losses_sum_c:= yearly_losses_sum_c+ loss_count // Store the yearly totals in arrays (if required) if array.size(yearly_wins) > 0 array.set(yearly_wins, array.size(yearly_wins) - 1, yearly_wins_sum_c) // Update the current year's total array.set(yearly_losses, array.size(yearly_losses) - 1, yearly_losses_sum_c) else array.push(yearly_wins, yearly_wins_sum_c) // First time adding the yearly totals array.push(yearly_losses, yearly_losses_sum_c) if barstate.islastconfirmedhistory and dev_mode// close > open //or close < open label.new(bar_index, close, 'Time: ' + str.tostring( yearly_wins), color=#5552ff, textcolor=color.white, style = label.style_label_right) label.new(bar_index, open, 'Time: ' + str.tostring( yearly_wins_sum_c), color=#5552ff, textcolor=color.white, style = label.style_label_right) //================================================================================== // Monthly Wins & Losses Table var monthly_table = table(na) if barstate.islast and turn_monthly_table // Adjust the number of rows to match the number of months monthly_table := table.new(monthy_table_position, columns=15, rows=array.size(month_time) + 1, border_width=1) // +1 to account for headers and the current month // Plot the table headers table.cell(monthly_table, 0, 0, 'Year', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 1, 0, 'Jan', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 2, 0, 'Feb', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 3, 0, 'Mar', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 4, 0, 'Apr', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 5, 0, 'May', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 6, 0, 'Jun', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 7, 0, 'Jul', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 8, 0, 'Aug', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 9, 0, 'Sep', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 10, 0, 'Oct', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 11, 0, 'Nov', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 12, 0, 'Dec', bgcolor=bgcolor_bg, text_color=text_color) table.cell(monthly_table, 13, 0, 'Yearly Wins / Losses', bgcolor=bgcolor_bg, text_color=text_color) // Populate the table with the number of wins and losses for each month for mi = 0 to array.size(month_time) - 1 by 1 if not na(array.get(month_time, mi)) m_row = year(array.get(month_time, mi)) - year(array.get(month_time, 0)) + 1 m_col = month(array.get(month_time, mi)) wins = array.get(month_wins, mi) losses = array.get(month_losses, mi) // Add monthly data to the table table.cell(monthly_table, m_col, m_row, text=str.tostring(wins) + ' / ' + str.tostring(losses), bgcolor= wins > losses ? green_color : red_color, text_color=text_color) // Add the year to the "Year" column table.cell(monthly_table, 0, m_row, text=str.tostring(year(array.get(month_time, mi))), bgcolor=bgcolor_bg, text_color=text_color) // Add the yearly totals to the "Yearly Wins / Losses" column for yr = 0 to array.size(yearly_wins) - 1 by 1 table.cell(monthly_table, 13, yr + 1, text=str.tostring(array.get(yearly_wins, yr)) + ' / ' + str.tostring(array.get(yearly_losses, yr)), bgcolor=array.get(yearly_wins, yr) > array.get(yearly_losses, yr) ? green_color : red_color, text_color=text_color) // Show current running month's data in the existing row current_year = year(time) current_month = month(time) current_row = current_year - year(array.get(month_time, 0)) + 1 table.cell(monthly_table, current_month, current_row, text=str.tostring(win_count) + ' / ' + str.tostring(loss_count), bgcolor= win_count > loss_count ? green_color : red_color, text_color=text_color) //=============================================== Alert Function call ===================================================================================// long_alert = input(title="Turn On/Off : Long Alert",defval= true , group= "━━━ ↓ Alert Setting (alert() function calls only) ↓ ━━━") short_alert = input(title="Turn On/Off : Short Alert",defval= true , group= "━━━ ↓ Alert Setting (alert() function calls only) ↓ ━━━") Long_text = "#" + syminfo.ticker + " - 🤖 | 📈 Long Alert :" + "\n " + "\n ▶️Entry Price : "+str.tostring(truncate(close,4)) +"\n " +"\n ⏰Time : "+str.tostring(dayofmonth) +"/"+str.tostring(month) +"/"+str.tostring(year) + " | " +str.tostring(hour) + ":" +str.tostring(minute) + " UTC" +"\n " Short_text = "#" + syminfo.ticker + " - 🤖 | 📉 Short Alert :" + "\n " +"\n ▶️Entry Price : "+str.tostring(truncate(close,4)) +"\n " +"\n ⏰Time : "+str.tostring(dayofmonth) +"/"+str.tostring(month) +"/"+str.tostring(year) + " | " +str.tostring(hour) + ":" +str.tostring(minute) + " UTC" +"\n " //Long Short Alert if long_last and long_alert and long_on and strategy.position_size == 0 alert(Long_text, alert.freq_once_per_bar_close) if short_last and short_alert and short_on and strategy.position_size == 0 alert(Short_text, alert.freq_once_per_bar_close) //-----------------------------------------------------------------------------------------------------------------------------------------------------------// long_tp_alert = input(title="Turn On/Off : Long TP Alert",defval= true , group= "━━━ ↓ Alert Setting (alert() function calls only) ↓ ━━━") short_tp_alert = input(title="Turn On/Off : Short TP Alert",defval= true , group= "━━━ ↓ Alert Setting (alert() function calls only) ↓ ━━━") Long_tp_text = "#" + syminfo.ticker + " - 🤖 | 📈 Long TP Hit :" +"\n " +"\n ⏰Time : "+str.tostring(dayofmonth) +"/"+str.tostring(month) +"/"+str.tostring(year) + " | " +str.tostring(hour) + ":" +str.tostring(minute) + " UTC" +"\n " Short_tp_text = "#" + syminfo.ticker + " - 🤖 | 📉 Short TP Hit :" +"\n " +"\n ⏰Time : "+str.tostring(dayofmonth) +"/"+str.tostring(month) +"/"+str.tostring(year) + " | " +str.tostring(hour) + ":" +str.tostring(minute) + " UTC" +"\n " if longTPhit and long_tp_alert and long_on alert(Long_tp_text, alert.freq_once_per_bar_close) if shortTPhit and short_tp_alert and short_on alert(Short_tp_text, alert.freq_once_per_bar_close) //-----------------------------------------------------------------------------------------------------------------------------------------------------------// long_sl_alert = input(title="Turn On/Off : Long SL Alert",defval= true , group= "━━━ ↓ Alert Setting (alert() function calls only) ↓ ━━━") short_sl_alert = input(title="Turn On/Off : Short SL Alert",defval= true , group= "━━━ ↓ Alert Setting (alert() function calls only) ↓ ━━━") Long_sl_text = "#" + syminfo.ticker + " - 🤖 | 📈 Long SL Hit:" +"\n " +"\n ⏰Time : "+str.tostring(dayofmonth) +"/"+str.tostring(month) +"/"+str.tostring(year) + " | " +str.tostring(hour) + ":" +str.tostring(minute) + " UTC" +"\n " Short_sl_text = "#" + syminfo.ticker + " - 🤖 | 📉 Short SL Hit :" +"\n " +"\n ⏰Time : "+str.tostring(dayofmonth) +"/"+str.tostring(month) +"/"+str.tostring(year) + " | " +str.tostring(hour) + ":" +str.tostring(minute) + " UTC" +"\n " if long_on and longSLhit alert(Long_sl_text, alert.freq_once_per_bar_close) if short_on and shortSLhit alert(Short_sl_text, alert.freq_once_per_bar_close)