//@version=6 strategy('ICT Killzones Toolkit [LuxAlgo]', overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 500, margin_long = 0, margin_short = 0) //------------------------------------------------------------------------------ //Settings //-----------------------------------------------------------------------------{ disp = display.all - display.status_line kzGR = 'Killzones' asSH = input(true, '', inline = 'asia', group = kzGR) asST = input.string('Asian', '', inline = 'asia', group = kzGR, display = disp) asS = input.session('2000-0000', '', inline = 'asia', group = kzGR, display = disp) asC = input.color(color.new(#e91e63, 90), '', inline = 'asia', group = kzGR) ldnOSH = input(true, '', inline = 'ldno', group = kzGR) ldnOST = input.string('London', '', inline = 'ldno', group = kzGR, display = disp) ldnOS = input.session('0200-0500', '', inline = 'ldno', group = kzGR, display = disp) ldnOC = input.color(color.new(#00bcd4, 90), '', inline = 'ldno', group = kzGR) nySH = input(true, '', inline = 'nyam', group = kzGR) nyST = input.string('New York AM', '', inline = 'nyam', group = kzGR, display = disp) nyS = input.session('0830-1100', '', inline = 'nyam', group = kzGR, display = disp) nyC = input.color(color.new(#ff5d00, 90), '', inline = 'nyam', group = kzGR) ldnCSH = input(true, '', inline = 'nypm', group = kzGR) ldnCST = input.string('New York PM', '', inline = 'nypm', group = kzGR, display = disp) ldnCS = input.session('1330-1600', '', inline = 'nypm', group = kzGR, display = disp) ldnCC = input.color(color.new(#2157f3, 90), '', inline = 'nypm', group = kzGR) kzMML = input(true, 'Killzone Lines : Top/Bottom', inline = 'LN', group = kzGR) kzML = input(false, 'Mean', inline = 'LN', group = kzGR) kzLE = input(true, 'Extend Top/Bottom', inline = 'LN', group = kzGR) kzLB = input(true, 'Killzone Labels', group = kzGR) kzSH = input.int(15, 'Display Killzones within Timeframes Up To', options = [1, 3, 5, 15, 30, 45, 60], group = kzGR, display = disp) dwmO = input.string('None', 'Open Price of', options = ['Killzones', 'the Day', 'the Week', 'the Month', 'None'], inline = 'OP', group = kzGR, display = disp) dwmS = input.bool(true, 'Separator', inline = 'OP', group = kzGR) dwmC = input(color.new(color.gray, 89), '', inline = 'OP', group = kzGR) dwmL = input.bool(true, 'Label', inline = 'OP', group = kzGR) obbGR = 'Order Blocks & Breaker Blocks' obSH = input.bool(true, 'Order Blocks | Breaker Blocks', inline = 'OB', group = obbGR) bbSH = input.bool(false, '', inline = 'OB', group = obbGR) obbLN = input.int(5, 'Swing Detection Length', minval = 3, group = obbGR, display = disp) obbMT = input.string('Closing Price', 'Mitigation Price', options = ['Closing Price', 'Wick'], group = obbGR, display = disp) obbMP = obbMT == 'Closing Price' useBody = input(false, 'Use Candle Body in Detection', group = obbGR) obbR = input.bool(true, 'Remove Mitigated Order Blocks & Breaker Blocks', group = obbGR) extTT = 'In this context, "extend" refers to the action of projecting or elongating the visual objects beyond the boundaries of the killzones.' obbEX = input.bool(true, 'Extend Order Blocks & Breaker Blocks', group = obbGR, tooltip = extTT) obbSH = input.string('First', 'Display Order Blocks & Breaker Blocks', options = ['All', 'First', 'Last'], group = obbGR, display = disp) bullOC = input(color.new(#2157f3, 80), 'Order Blocks : Bullish', inline = 'OBC', group = obbGR) bearOC = input(color.new(#ff5d00, 80), 'Bearish', inline = 'OBC', group = obbGR) bullBC = input(color.new(#ff1100, 80), 'Breaker Blocks : Bullish', inline = 'BBC', group = obbGR) bearBC = input(color.new(#0cb51a, 80), 'Bearish', inline = 'BBC', group = obbGR) obbTX = input.bool(true, 'Show Order Blocks & Breaker Blocks Text', group = obbGR) mssGR = 'Market Structure Shifts' mssSH = input.bool(false, 'Market Structure Shifts', group = mssGR) mssLN = input.int(7, 'Detection Length', minval = 1, group = mssGR, display = disp) mssDO = input.string('First', 'Display Market Structure Shifts', options = ['All', 'First', 'Last'], group = mssGR, display = disp) ppLCB = input.color(color.new(color.teal, 0), 'Market Structure Shifts : Bullish', inline = 'MSS', group = mssGR) ppLCS = input.color(color.new(color.red, 0), 'Bearish', inline = 'MSS', group = mssGR) mssTX = input.bool(true, 'Show Market Structure Shifts Text', group = mssGR) fvgGR = 'Fair Value Gaps' fvgSH = input.bool(true, 'Fair Value Gaps', group = fvgGR) fvgTT = 'The script showcases fair value gaps that exceed a predetermined length calculated by multiplying the fixed-average true range (ATR) value by the option\'s value.\n\n' + 'The option value set to 0 means no filtering is applied.\n\n' + 'Remark: No filtering will be implemented for the initial 144 candles based on the fixed-length ATR, as the ATR value won\'t be available during this period.' fvgTH = input.float(1.2, 'Fair Value Gap Width Filter', minval = 0, step = .1, tooltip = fvgTT, group = fvgGR, display = disp) fvgR = input.bool(true, 'Remove Mitigated Fair Value Gaps', group = fvgGR) fvgE = input.bool(true, 'Extend Fair Value Gaps', group = fvgGR, tooltip = extTT) fvgDO = input.string('First', 'Display Fair Value Gaps', options = ['All', 'First', 'Last'], group = fvgGR, display = disp) fvgBC = input.color(color.new(#4caf50, 80), 'Bullish Imbalance', group = fvgGR) fvgSC = input.color(color.new(color.red, 80), 'Bearish Imbalance', group = fvgGR) fvgTX = input.bool(true, 'Show Fair Value Gaps Text', group = fvgGR) //-----------------------------------------------------------------------------} // User Defined Types //-----------------------------------------------------------------------------{ type bar float o = open float h = high float l = low float c = close int t = time int i = bar_index type OB float top = na float btm = na int obI = bar_index box bxOB bool ext = true type BB box bxOB box bxBB bool ext = true bool bb = false int bbI = na int lst = na type swing float y = na int i = na bool x = false type pivotPoint float h int hi bool hx float l int li bool lx type KZ line lnT line lnM line lnB line lnO label lb label lbO type DWM line ln label lb type MSS line ln box bx type FVG box bx bool e //-----------------------------------------------------------------------------} // Variables //-----------------------------------------------------------------------------{ bar b = bar.new() tfM = timeframe.multiplier nyam = not na(time(timeframe.period, nyS, 'UTC-5')) and nySH and tfM <= kzSH ldnO = not na(time(timeframe.period, ldnOS, 'UTC-5')) and ldnOSH and tfM <= kzSH ldnC = not na(time(timeframe.period, ldnCS, 'UTC-5')) and ldnCSH and tfM <= kzSH asian = not na(time(timeframe.period, asS, 'UTC-5')) and asSH and tfM <= kzSH inKZ = nyam or ldnO or ldnC or asian var KZ kz = KZ.new() var DWM dwm = DWM.new() var bOB = array.new(0) var aOB = array.new(0) var bBB = array.new(0) var aBB = array.new(0) var bLS = array.new_int() var aLS = array.new_int() var bMSS = array.new(0) var aMSS = array.new(0) var pivotPoint pp = pivotPoint.new() var shift = 0 var bFVG = array.new(0) //FVG.new(array.new (na)) var aFVG = array.new(0) //FVG.new(array.new (na)) fvgATR = nz(ta.atr(144)) * fvgTH //var bool last = na //-----------------------------------------------------------------------------} // Functions / Methods //-----------------------------------------------------------------------------{ swings(_l) => var os = 0 var swing top = swing.new(na, na) var swing btm = swing.new(na, na) upper = ta.highest(_l) lower = ta.lowest(_l) os := high[_l] > upper ? 0 : low[_l] < lower ? 1 : os if os == 0 and os[1] != 0 top := swing.new(high[_l], bar_index[_l]) top if os == 1 and os[1] != 1 btm := swing.new(low[_l], bar_index[_l]) btm [top, btm] method killzones(KZ _id, _s, _kz, _o, _h, _l, _c, _t, _cr, _tx, _mml, _ml, _lb, _le, _ol, _olC, _olL, areaCss) => var float max = na var float mid = na var float min = na var int sbT = na var bool xt = false var bool xb = false var box area = na var tC = color.rgb(color.r(_cr), color.g(_cr), color.b(_cr)) if _s and not _s[1] max := _h sbT := _t min := _l mid := math.avg(max, min) area := box.new(bar_index, max, bar_index, min, na, bgcolor = areaCss) if _mml _id.lnT := line.new(sbT, max, sbT, max, xloc.bar_time, color = tC) //, xt := true _id.lnB := line.new(sbT, min, sbT, min, xloc.bar_time, color = tC) //, xb := true _id.lnB if _ml _id.lnM := line.new(sbT, mid, sbT, mid, xloc.bar_time, color = tC, style = line.style_dotted) _id.lnM if _ol _id.lnO := line.new(sbT, _o, sbT, _o, xloc.bar_time, color = color.new(_olC, 0), style = line.style_dotted) if _olL _id.lbO := label.new(sbT, _o, 'KZO(' + str.tostring(_o, format.mintick) + ')', xloc.bar_time, color = color(na), style = label.style_label_left, textcolor = color.new(_olC, 0), size = size.tiny) _id.lbO if _lb _id.lb := label.new(sbT, max, _tx, xloc.bar_time, color = #ffffff00, style = label.style_label_down, textcolor = tC, size = size.small) _id.lb if _s max := math.max(_h, max) min := math.min(_l, min) mid := math.avg(max, min) xt := true xb := true area.set_top(max) area.set_rightbottom(bar_index, min) if _lb label.set_x(_id.lb, int(math.avg(_t, sbT))) label.set_y(_id.lb, max) if _mml _id.lnT.set_y1(max) _id.lnT.set_xy2(_t, max) _id.lnB.set_y1(min) _id.lnB.set_xy2(_t, min) if _ml _id.lnM.set_y1(mid) _id.lnM.set_xy2(_t, mid) if _ol _id.lnO.set_x2(_t) if _olL _id.lbO.set_x(_t) if not _s and _le and not _kz if _mml if xt if _h < _id.lnT.get_y1() _id.lnT.set_x2(_t) else _id.lnT.set_x2(_t) xt := false xt if xb if _l > _id.lnB.get_y1() _id.lnB.set_x2(_t) else _id.lnB.set_x2(_t) xb := false xb if _ml _id.lnM.set_x2(_t) method pDWM(DWM _id, _tC, _t, _o, _cl, _lbTX, _olL) => if _tC _id.lb.delete() _id.ln := line.new(_t, _o, _t, _o, xloc.bar_time, extend.none, color.new(_cl, 0), line.style_dotted, 1) if _olL _id.lb := label.new(_t, _o, _lbTX + '(' + str.tostring(_o, format.mintick) + ')', xloc.bar_time, yloc.price, color(na), label.style_label_left, color.new(_cl, 0), size.tiny) _id.lb else _id.ln.set_x2(_t) if _olL _id.lb.set_x(_t) na pOBB(_s, _o, _h, _l, _c, _i, _t, _st, _sb, _mx, _mn, _cOBb, _cBBb, _cOBa, _cBBa, _obS, _bbS, _obbM, _obbTX, _obbE, _obbD, _obbR) => var int sbI = 0 //, var bool zb = na var bool xb = false var bool xa = false if _s and not _s[1] sbI := _i //, zb := true xb := true xa := true if _obbE if bOB.size() > 0 for i = bOB.size() - 1 to 0 by 1 bOB.remove(i) if aOB.size() > 0 for i = aOB.size() - 1 to 0 by 1 aOB.remove(i) if bBB.size() > 0 for i = bBB.size() - 1 to 0 by 1 bBB.remove(i) if aBB.size() > 0 for i = aBB.size() - 1 to 0 by 1 aBB.remove(i) if _s if _c[1] > _st.y and not _st.x and _st.i >= sbI _st.x := true minima = _mx[1] maxima = _mn[1] sBT = _t[1] for i = 1 to _i - _st.i - 1 by 1 minima := math.min(_mn[i], minima) maxima := minima == _mn[i] ? _mx[i] : maxima sBT := minima == _mn[i] ? _t[i] : sBT sBT bOB.unshift(OB.new(maxima, minima, sBT, box.new(na, na, na, na, color(na), xloc = xloc.bar_time, text = _obbTX ? 'OB' : '', text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center, text_color = color.new(_cOBb, 0)))) bBB.unshift(BB.new(box.new(na, na, na, na, color(na), xloc = xloc.bar_time), box.new(na, na, na, na, color(na), xloc = xloc.bar_time, text = _obbTX ? 'BB' : '', text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center, text_color = color.new(_cBBb, 0)))) if _c[1] < _sb.y and not _sb.x and _st.i >= sbI _sb.x := true minima = _mn[1] maxima = _mx[1] sBT = _t[1] for i = 1 to _i - _sb.i - 1 by 1 maxima := math.max(_mx[i], maxima) minima := maxima == _mx[i] ? _mn[i] : minima sBT := maxima == _mx[i] ? _t[i] : sBT sBT aOB.unshift(OB.new(maxima, minima, sBT, box.new(na, na, na, na, color(na), xloc = xloc.bar_time, text = _obbTX ? 'OB' : '', text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center, text_color = color.new(_cOBa, 0)))) aBB.unshift(BB.new(box.new(na, na, na, na, color(na), xloc = xloc.bar_time), box.new(na, na, na, na, color(na), xloc = xloc.bar_time, text = _obbTX ? 'BB' : '', text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center, text_color = color.new(_cBBa, 0)))) if _obbE ? true : _s if bOB.size() > 0 for i = bOB.size() - 1 to 0 by 1 ob = bOB.get(i) bb = bBB.get(i) if _obbR if not ob.ext ob.bxOB.delete() if not bb.ext bb.bxOB.delete() bb.bxBB.delete() if not bb.bb if _obS and (_obbD == 'First' ? i == bOB.size() - 1 : true) ob.bxOB.set_lefttop(ob.obI, ob.top) ob.bxOB.set_rightbottom(_t, ob.btm) ob.bxOB.set_bgcolor(_obbD == 'Last' ? i == 0 ? _cOBb : color(na) : _cOBb) ob.bxOB.set_text_color(_obbD == 'Last' ? i == 0 ? color.new(_cOBb, 0) : color(na) : color.new(_cOBb, 0)) if math.min(_obbM ? _c[1] : _l[1], _o[1]) < ob.btm bb.bb := true if _obS ob.bxOB.set_right(_t[1]) ob.ext := false ob.ext if _bbS and xb bb.bbI := _t[1] bb.bxBB.set_lefttop(bb.bbI, ob.top) bb.bxBB.set_rightbottom(_t, ob.btm) bb.bxBB.set_bgcolor(_cBBb) if not _obS or _obbR or not(_obbD == 'First' ? i == bOB.size() - 1 : true) bb.bxOB.set_lefttop(ob.obI, ob.top) bb.bxOB.set_rightbottom(bb.bbI, ob.btm) bb.bxOB.set_bgcolor(color(na)) bb.bxOB.set_border_color(_cBBb) if _obbD == 'First' xb := false xb if _obbD == 'Last' bLS.push(bb.bbI) else if (_obbM ? _c[1] : _h[1]) > ob.top bb.ext := false bb.ext if _obS ob.bxOB.set_bgcolor(_obbD == 'Last' ? i == 0 ? _cOBb : color(na) : _cOBb) ob.bxOB.set_text_color(_obbD == 'Last' ? i == 0 ? color.new(_cOBb, 0) : color(na) : color.new(_cOBb, 0)) if _bbS and bb.ext bb.bxBB.set_right(_t) if _bbS if _obbD == 'Last' //and zb if i != 0 bb.bxOB.set_lefttop(ob.obI, ob.top) bb.bxOB.set_rightbottom(bb.bbI, ob.btm) bb.bxOB.set_bgcolor(color(na)) bb.bxOB.set_border_color(_cBBb) if bLS.max() != bb.bxBB.get_left() bb.bxBB.set_bgcolor(color(na)) bb.bxBB.set_text_color(color(na)) bb.bxOB.set_border_color(color(na)) //bLS.clear() if aOB.size() > 0 for i = aOB.size() - 1 to 0 by 1 ob = aOB.get(i) bb = aBB.get(i) if _obbR if not ob.ext ob.bxOB.delete() if not bb.ext bb.bxOB.delete() bb.bxBB.delete() if not bb.bb if _obS and (_obbD == 'First' ? i == aOB.size() - 1 : true) ob.bxOB.set_lefttop(ob.obI, ob.top) ob.bxOB.set_rightbottom(_t, ob.btm) ob.bxOB.set_bgcolor(_obbD == 'Last' ? i == 0 ? _cOBa : color(na) : _cOBa) ob.bxOB.set_text_color(_obbD == 'Last' ? i == 0 ? color.new(_cOBa, 0) : color(na) : color.new(_cOBa, 0)) if math.max(_obbM ? _c[1] : _h[1], _o[1]) > ob.top bb.bb := true if _obS ob.bxOB.set_right(_t[1]) ob.ext := false ob.ext if _bbS and xa bb.bbI := _t[1] bb.bxBB.set_lefttop(bb.bbI, ob.top) bb.bxBB.set_rightbottom(_t, ob.btm) bb.bxBB.set_bgcolor(_cBBa) if not _obS or _obbR or not(_obbD == 'First' ? i == aOB.size() - 1 : true) bb.bxOB.set_lefttop(ob.obI, ob.top) bb.bxOB.set_rightbottom(bb.bbI, ob.btm) bb.bxOB.set_bgcolor(color(na)) bb.bxOB.set_border_color(_cBBa) if _obbD == 'First' xa := false xa if _obbD == 'Last' aLS.push(bb.bbI) else if (_obbM ? _c[1] : _l[1]) < ob.btm bb.ext := false bb.ext if _obS ob.bxOB.set_bgcolor(_obbD == 'Last' ? i == 0 ? _cOBa : color(na) : _cOBa) ob.bxOB.set_text_color(_obbD == 'Last' ? i == 0 ? color.new(_cOBa, 0) : color(na) : color.new(_cOBa, 0)) if _bbS and bb.ext bb.bxBB.set_right(_t) if _bbS if _obbD == 'Last' if i != 0 bb.bxOB.set_lefttop(ob.obI, ob.top) bb.bxOB.set_rightbottom(bb.bbI, ob.btm) bb.bxOB.set_bgcolor(color(na)) bb.bxOB.set_border_color(_cBBa) if aLS.max() != bb.bxBB.get_left() bb.bxBB.set_bgcolor(color(na)) bb.bxBB.set_text_color(color(na)) bb.bxOB.set_border_color(color(na)) else if not _obbE if bOB.size() > 0 for i = bOB.size() - 1 to 0 by 1 bOB.remove(i) if aOB.size() > 0 for i = aOB.size() - 1 to 0 by 1 aOB.remove(i) if bBB.size() > 0 for i = bBB.size() - 1 to 0 by 1 bBB.remove(i) if aBB.size() > 0 for i = aBB.size() - 1 to 0 by 1 aBB.remove(i) na pFVG(_s, _h, _l, _c, _i, _atr, _fR, _fE, _fD, _fTX, _fbC, _faC) => var bool xb = false var bool xa = false if _s and not _s[1] xb := true xa := true if _fE if bFVG.size() > 0 for i = bFVG.size() - 1 to 0 by 1 bFVG.remove(i) if aFVG.size() > 0 for i = aFVG.size() - 1 to 0 by 1 aFVG.remove(i) bullG = _l > _h[1] bearG = _h < _l[1] if _s bull = _l - _h[2] > _atr and _l > _h[2] and _c[1] > _h[2] and not(bullG or bullG[1]) if bull and xb bFVG.unshift(FVG.new(box.new(_i - 1, _l, _i, _h[2], na, bgcolor = _fbC, text = _fTX ? 'FVG' : '', text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center, text_color = color.new(_fbC, 0)), true)) if _fD == 'First' xb := false xb if bFVG.size() > 1 if _fD == 'Last' fvg = bFVG.pop() fvg.bx.delete() bear = _l[2] - _h > _atr and _h < _l[2] and _c[1] < _l[2] and not(bearG or bearG[1]) if bear and xa aFVG.unshift(FVG.new(box.new(_i - 1, _l[2], _i, _h, na, bgcolor = _faC, text = _fTX ? 'FVG' : '', text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center, text_color = color.new(_faC, 0)), true)) if _fD == 'First' xa := false xa if aFVG.size() > 1 if _fD == 'Last' fvg = aFVG.pop() fvg.bx.delete() if _fE ? true : _s if bFVG.size() > 0 for i = bFVG.size() - 1 to 0 by 1 fvg = bFVG.get(i) fvgB = fvg.bx.get_bottom() if fvg.e if _l < fvgB fvg.bx.set_right(_i) if _fR fvg.bx.delete() fvg.e := false fvg.e else fvg.bx.set_right(_i) if aFVG.size() > 0 for i = aFVG.size() - 1 to 0 by 1 fvg = aFVG.get(i) fvgT = fvg.bx.get_top() if fvg.e if _h > fvgT fvg.bx.set_right(_i) if _fR fvg.bx.delete() fvg.e := false na else fvg.bx.set_right(_i) else if not _fE if bFVG.size() > 0 for i = bFVG.size() - 1 to 0 by 1 bFVG.remove(i) if aFVG.size() > 0 for i = aFVG.size() - 1 to 0 by 1 aFVG.remove(i) na //-----------------------------------------------------------------------------} // Calculations - Killzones //-----------------------------------------------------------------------------{ kzO = dwmO == 'Killzones' kz.killzones(nyam and timeframe.isintraday, inKZ, b.o, b.h, b.l, b.c, b.t, nyC, nyST, kzMML, kzML, kzLB, kzLE, kzO, dwmC, dwmL, nyC) kz.killzones(ldnO and timeframe.isintraday, inKZ, b.o, b.h, b.l, b.c, b.t, ldnOC, ldnOST, kzMML, kzML, kzLB, kzLE, kzO, dwmC, dwmL, ldnOC) kz.killzones(ldnC and timeframe.isintraday, inKZ, b.o, b.h, b.l, b.c, b.t, ldnCC, ldnCST, kzMML, kzML, kzLB, kzLE, kzO, dwmC, dwmL, ldnCC) kz.killzones(asian and timeframe.isintraday, inKZ, b.o, b.h, b.l, b.c, b.t, asC, asST, kzMML, kzML, kzLB, kzLE, kzO, dwmC, dwmL, asC) [xChg, xTxt] = switch dwmO 'the Day' => [timeframe.change('D'), 'DO'] 'the Week' => [timeframe.change('W'), 'WO'] 'the Month' => [timeframe.change('M'), 'MO'] if not kzO and timeframe.isintraday and tfM <= kzSH dwm.pDWM(xChg, b.t, b.o, dwmC, xTxt, dwmL) bgcolor(dwmS and not kzO and timeframe.isintraday and tfM <= kzSH ? xChg ? dwmC : na : na) //-----------------------------------------------------------------------------} // Calculations - Order Blocks & Breaker Blocks //-----------------------------------------------------------------------------{ [top, btm] = swings(obbLN) max = useBody ? math.max(b.c, b.o) : b.h min = useBody ? math.min(b.c, b.o) : b.l stratGR = 'Strategy & Risk Settings' riskPct = input.float(1.0, title = 'Risk Per Trade (%)', group = stratGR) rrTP1 = input.float(2.0, title = 'Risk:Reward Ratio for TP1 (e.g. 2 = 1:2)', group = stratGR) rrTP2 = input.float(4.0, title = 'Risk:Reward Ratio for TP2 (e.g. 2 = 1:2)', group = stratGR) tp1Pct = input.float(25.0, title = 'Percentage of Position to Close at TP1 (%)', minval = 0, maxval = 100, group = stratGR) minObSize = input.int(30, title = 'Min. Order Block Size (Ticks/Points)', group = stratGR, tooltip = 'Filter out tiny OBs. (E.g.: 30 = at least 3 pips wide).') maxObSize = input.int(10000, title = 'Max. Order Block Size (Ticks/Points)', group = stratGR) maxExtendBars = input.int(10, title = 'Max. Extend Untapped OBs (bars)', minval = 1, group = stratGR) exitMode = input.string('Break-even After TP1', title = 'Runner Exit Mode', options = ['Break-even After TP1', 'Trailing Stop After TP1', 'Fixed TP2'], group = stratGR, tooltip = 'How the remaining position is managed after TP1: move SL to break-even instantly, trail the stop, or keep the fixed TP2 target.') trailMult = input.float(1.0, title = 'Trailing Stop Distance (x SL Distance)', minval = 0.1, step = 0.1, group = stratGR) licenseID = input.string('9501415768277', title = 'PineConnector License ID', group = stratGR) var bOBx = array.new(0) var aOBx = array.new(0) var bTradedM = map.new() var aTradedM = map.new() var bExtM = map.new() var bExtTM = map.new() var aExtM = map.new() var aExtTM = map.new() var bPlacedM = map.new() var aPlacedM = map.new() var bBoxM = map.new() var aBoxM = map.new() var bBeDoneM = map.new() var aBeDoneM = map.new() var openBM = map.new() var openSM = map.new() var int closedSeen = 0 kzStartT = inKZ and not inKZ[1] kzEndT = not inKZ and inKZ[1] if obbEX ? kzStartT : kzEndT if bOB.size() > 0 for itm in bOB sz = math.abs(itm.top - itm.btm) / syminfo.mintick if itm.ext and sz >= minObSize and sz <= maxObSize bOBx.push(itm) if aOB.size() > 0 for itm in aOB sz = math.abs(itm.top - itm.btm) / syminfo.mintick if itm.ext and sz >= minObSize and sz <= maxObSize aOBx.push(itm) if obSH or bbSH pOBB(inKZ, b.o, b.h, b.l, b.c, b.i, b.t, top, btm, max, min, bullOC, bullBC, bearOC, bearBC, obSH, bbSH, obbMP, obbTX, obbEX, obbSH, obbR) //-----------------------------------------------------------------------------} // Calculations - Market Structure Shifts //-----------------------------------------------------------------------------{ pp_h = ta.pivothigh(mssLN, mssLN) pp_l = ta.pivotlow(mssLN, mssLN) if not na(pp_h) pp.h := pp_h pp.hx := false pp.hi := b.i - mssLN pp.hi if not na(pp_l) pp.l := pp_l pp.lx := false pp.li := b.i - mssLN pp.li if not inKZ pp.l := 0 pp.h := 10e8 shift := 0 if bMSS.size() > 0 for i = bMSS.size() - 1 to 0 by 1 bMSS.remove(i) if aMSS.size() > 0 for i = aMSS.size() - 1 to 0 by 1 aMSS.remove(i) if mssSH and inKZ if (b[1]).c > pp.h and not pp.hx pp.hx := true if shift == -1 or shift == 0 if mssDO == 'First' ? bMSS.size() < 1 : true bMSS.unshift(MSS.new(line.new(pp.hi, pp.h, b.i - 1, pp.h, color = ppLCB), box.new(pp.hi, pp.h, b.i - 1, pp.h, text = 'CHoCH', text_size = size.tiny, text_halign = text.align_left, text_valign = text.align_bottom, text_color = mssTX ? ppLCB : color(na), bgcolor = color(na), border_color = color(na)))) if bMSS.size() > 1 if mssDO == 'Last' mss = bMSS.pop() mss.ln.delete() mss.bx.delete() shift := 1 shift if (b[1]).c < pp.l and not pp.lx pp.lx := true if shift == 1 or shift == 0 if mssDO == 'First' ? aMSS.size() < 1 : true aMSS.unshift(MSS.new(line.new(pp.li, pp.l, b.i - 1, pp.l, color = ppLCS), box.new(pp.li, pp.l, b.i - 1, pp.l, text = 'CHoCH', text_size = size.tiny, text_halign = text.align_left, text_valign = text.align_top, text_color = mssTX ? ppLCS : color(na), bgcolor = color(na), border_color = color(na)))) if aMSS.size() > 1 if mssDO == 'Last' mss = aMSS.pop() mss.ln.delete() mss.bx.delete() shift := -1 shift //-----------------------------------------------------------------------------} // Calculations - Fair Value Gaps //-----------------------------------------------------------------------------{ if fvgSH pFVG(inKZ, b.h, b.l, b.c, b.i, fvgATR, fvgR, fvgE, fvgDO, fvgTX, fvgBC, fvgSC) // detect fills once per bar: open trades + only NEW closed trades openBM.clear() openSM.clear() if strategy.opentrades > 0 for j = 0 to strategy.opentrades - 1 eId = strategy.opentrades.entry_id(j) eObI = int(str.tonumber(str.substring(eId, 2))) if str.substring(eId, 0, 1) == 'L' openBM.put(eObI, 1) if not bTradedM.contains(eObI) bTradedM.put(eObI, time) else openSM.put(eObI, 1) if not aTradedM.contains(eObI) aTradedM.put(eObI, time) if strategy.closedtrades > closedSeen for j = closedSeen to strategy.closedtrades - 1 by 1 eId = strategy.closedtrades.entry_id(j) eObI = int(str.tonumber(str.substring(eId, 2))) if str.substring(eId, 0, 1) == 'L' if not bTradedM.contains(eObI) bTradedM.put(eObI, time) else if not aTradedM.contains(eObI) aTradedM.put(eObI, time) closedSeen := strategy.closedtrades closedSeen // 1. Process all active Bullish Order Blocks (Longs) if bOB.size() + bOBx.size() > 0 for i = 0 to bOB.size() + bOBx.size() - 1 by 1 ob = i < bOB.size() ? bOB.get(i) : bOBx.get(i - bOB.size()) float slDistRaw = math.abs(ob.top - ob.btm) float ObTickSize = slDistRaw / syminfo.mintick bool szOK = ObTickSize >= minObSize and ObTickSize <= maxObSize bool alreadyTraded = bTradedM.contains(ob.obI) // hide the core's visual box ob.bxOB.set_bgcolor(color(na)) ob.bxOB.set_text_color(color(na)) ob.bxOB.set_border_color(color(na)) if ob.ext and szOK or alreadyTraded if not bBoxM.contains(ob.obI) bBoxM.put(ob.obI, box.new(ob.obI, ob.top, time, ob.btm, xloc = xloc.bar_time, border_color = color(na), bgcolor = bullOC, text = obbTX ? 'OB' : '', text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center, text_color = color.new(bullOC, 0))) myB = bBoxM.get(ob.obI) int rT = time if alreadyTraded rT := bTradedM.get(ob.obI) rT else if not bExtM.contains(ob.obI) bExtM.put(ob.obI, 1) bExtTM.put(ob.obI, time) else if bExtM.get(ob.obI) < maxExtendBars bExtM.put(ob.obI, bExtM.get(ob.obI) + 1) bExtTM.put(ob.obI, time) rT := bExtTM.get(ob.obI) rT myB.set_lefttop(ob.obI, ob.top) myB.set_rightbottom(rT, ob.btm) if ob.ext and close > ob.top and szOK if not alreadyTraded string longId = 'L_' + str.tostring(ob.obI) float longEntry = math.max(ob.top, ob.btm) float longSL = math.min(ob.top, ob.btm) float longTP1 = longEntry + slDistRaw * rrTP1 float longTP2 = longEntry + slDistRaw * rrTP2 float riskAmt = strategy.equity * (riskPct / 100) float qty = slDistRaw > 0 ? riskAmt / (slDistRaw * syminfo.pointvalue) : 0 if qty > 0 float tp1_qty = qty * (tp1Pct / 100) float trail_qty = qty - tp1_qty string longEntryMsg = licenseID + ',buylimit,' + syminfo.ticker + ',price=' + str.tostring(longEntry) + ',sl=' + str.tostring(longSL) + ',tp=' + str.tostring(longTP2) + ',risk=' + str.tostring(riskPct) strategy.entry(longId, strategy.long, qty = qty, limit = longEntry, alert_message = longEntryMsg) bPlacedM.put(ob.obI, 1) if tp1_qty > 0 string longTP1Msg = licenseID + ',closelong,' + syminfo.ticker + ',50' strategy.exit('TP1_' + longId, from_entry = longId, qty = tp1_qty, limit = longTP1, stop = longSL, alert_message = longTP1Msg) if trail_qty > 0 if exitMode == 'Trailing Stop After TP1' strategy.exit('TP2_' + longId, from_entry = longId, stop = longSL, trail_price = longTP1, trail_offset = math.max(1, math.round(trailMult * slDistRaw / syminfo.mintick))) else if exitMode == 'Break-even After TP1' strategy.exit('TP2_' + longId, from_entry = longId, stop = longSL) else strategy.exit('TP2_' + longId, from_entry = longId, limit = longTP2, stop = longSL) else if bPlacedM.contains(ob.obI) strategy.cancel('L_' + str.tostring(ob.obI)) bPlacedM.remove(ob.obI) // move SL to break-even instantly once TP1 is hit if exitMode == 'Break-even After TP1' and openBM.contains(ob.obI) and not bBeDoneM.contains(ob.obI) if high >= ob.top + slDistRaw * rrTP1 strategy.exit('TP2_L_' + str.tostring(ob.obI), from_entry = 'L_' + str.tostring(ob.obI), stop = ob.top, alert_message = licenseID + ',slbe,' + syminfo.ticker) bBeDoneM.put(ob.obI, 1) // prune archived long OBs that are finished if bOBx.size() > 0 for k = bOBx.size() - 1 to 0 by 1 obX = bOBx.get(k) if bTradedM.contains(obX.obI) bool stillOpen = false if strategy.opentrades > 0 for j = 0 to strategy.opentrades - 1 by 1 if strategy.opentrades.entry_id(j) == 'L_' + str.tostring(obX.obI) stillOpen := true break if not stillOpen bOBx.remove(k) bBoxM.remove(obX.obI) bExtM.remove(obX.obI) bExtTM.remove(obX.obI) bBeDoneM.remove(obX.obI) // 2. Process all active Bearish Order Blocks (Shorts) if aOB.size() + aOBx.size() > 0 for i = 0 to aOB.size() + aOBx.size() - 1 by 1 ob = i < aOB.size() ? aOB.get(i) : aOBx.get(i - aOB.size()) // Calculate the order block size in Ticks (Points) float slDistRaw = math.abs(ob.top - ob.btm) float ObTickSize = slDistRaw / syminfo.mintick bool szOK = ObTickSize >= minObSize and ObTickSize <= maxObSize bool alreadyTraded = aTradedM.contains(ob.obI) // hide the core's visual box ob.bxOB.set_bgcolor(color(na)) ob.bxOB.set_text_color(color(na)) ob.bxOB.set_border_color(color(na)) if ob.ext and szOK or alreadyTraded if not aBoxM.contains(ob.obI) aBoxM.put(ob.obI, box.new(ob.obI, ob.top, time, ob.btm, xloc = xloc.bar_time, border_color = color(na), bgcolor = bearOC, text = obbTX ? 'OB' : '', text_size = size.tiny, text_halign = text.align_center, text_valign = text.align_center, text_color = color.new(bearOC, 0))) myB = aBoxM.get(ob.obI) int rT = time if alreadyTraded rT := aTradedM.get(ob.obI) rT else if not aExtM.contains(ob.obI) aExtM.put(ob.obI, 1) aExtTM.put(ob.obI, time) else if aExtM.get(ob.obI) < maxExtendBars aExtM.put(ob.obI, aExtM.get(ob.obI) + 1) aExtTM.put(ob.obI, time) rT := aExtTM.get(ob.obI) rT myB.set_lefttop(ob.obI, ob.top) myB.set_rightbottom(rT, ob.btm) // Ensure the block is active, price is below it, and the block is large enough if ob.ext and close < ob.btm and szOK if not alreadyTraded string shortId = 'S_' + str.tostring(ob.obI) float shortEntry = math.min(ob.top, ob.btm) float shortSL = math.max(ob.top, ob.btm) float shortTP1 = shortEntry - slDistRaw * rrTP1 float shortTP2 = shortEntry - slDistRaw * rrTP2 float riskAmt = strategy.equity * (riskPct / 100) float qty = slDistRaw > 0 ? riskAmt / (slDistRaw * syminfo.pointvalue) : 0 if qty > 0 float tp1_qty = qty * (tp1Pct / 100) float trail_qty = qty - tp1_qty string shortEntryMsg = licenseID + ',selllimit,' + syminfo.ticker + ',price=' + str.tostring(shortEntry) + ',sl=' + str.tostring(shortSL) + ',tp=' + str.tostring(shortTP2) + ',risk=' + str.tostring(riskPct) strategy.entry(shortId, strategy.short, qty = qty, limit = shortEntry, alert_message = shortEntryMsg) aPlacedM.put(ob.obI, 1) if tp1_qty > 0 string shortTP1Msg = licenseID + ',closeshort,' + syminfo.ticker + ',50' strategy.exit('TP1_' + shortId, from_entry = shortId, qty = tp1_qty, limit = shortTP1, stop = shortSL, alert_message = shortTP1Msg) if trail_qty > 0 if exitMode == 'Trailing Stop After TP1' strategy.exit('TP2_' + shortId, from_entry = shortId, stop = shortSL, trail_price = shortTP1, trail_offset = math.max(1, math.round(trailMult * slDistRaw / syminfo.mintick))) else if exitMode == 'Break-even After TP1' strategy.exit('TP2_' + shortId, from_entry = shortId, stop = shortSL) else strategy.exit('TP2_' + shortId, from_entry = shortId, limit = shortTP2, stop = shortSL) else if aPlacedM.contains(ob.obI) strategy.cancel('S_' + str.tostring(ob.obI)) aPlacedM.remove(ob.obI) // move SL to break-even instantly once TP1 is hit if exitMode == 'Break-even After TP1' and openSM.contains(ob.obI) and not aBeDoneM.contains(ob.obI) if low <= ob.btm - slDistRaw * rrTP1 strategy.exit('TP2_S_' + str.tostring(ob.obI), from_entry = 'S_' + str.tostring(ob.obI), stop = ob.btm, alert_message = licenseID + ',slbe,' + syminfo.ticker) aBeDoneM.put(ob.obI, 1) // prune archived short OBs that are finished if aOBx.size() > 0 for k = aOBx.size() - 1 to 0 by 1 obX = aOBx.get(k) if aTradedM.contains(obX.obI) bool stillOpen = false if strategy.opentrades > 0 for j = 0 to strategy.opentrades - 1 by 1 if strategy.opentrades.entry_id(j) == 'S_' + str.tostring(obX.obI) stillOpen := true break if not stillOpen aOBx.remove(k) aBoxM.remove(obX.obI) aExtM.remove(obX.obI) aExtTM.remove(obX.obI) aBeDoneM.remove(obX.obI) // handle invalidated OBs (cancel orders whose OB no longer exists) if strategy.opentrades > 0 for j = 0 to strategy.opentrades - 1 by 1 entryId = strategy.opentrades.entry_id(j) entryBidx = int(str.tonumber(str.substring(entryId, 2))) bOK = false for itm in aOB if itm.obI == entryBidx bOK := true break if not bOK for itm in bOB if itm.obI == entryBidx bOK := true break if not bOK for itm in aOBx if itm.obI == entryBidx bOK := true break if not bOK for itm in bOBx if itm.obI == entryBidx bOK := true break if not bOK strategy.cancel(entryId) strategy.cancel('TP1_' + entryId) strategy.cancel('TP2_' + entryId) //-----------------------------------------------------------------------------}