# Bullish 3 Bar Play & 4 Bar Play v1.5 by Len20
# based on Live Traders video https://www.youtube.com/watch?v=xEjUd82NVVg
# v1.1 fixed small mistake
# v1.2 individual lookback customization, ignitor bar height now includes wicks, can now customize pause bars minimum low (was ignitor midpoint),
# non-uptrend and no resistance requirements can be disabled, better uptrend detection, inputs more consistent, tweaks, fixes, more comments
# v1.3 trigger bar need not be green = more potential pattern matches
# v1.4 "Show Potential" now includes possible pattern setups during middle ("pause") bars, bug fix, trigger bar min low can now be same as pause bars min low ("Relaxed"), instead of pause bars actual low (official)
# v1.5 "Show potential" now includes ignitor bar. Can now color bars. Background color change on signals. Pattern will signal when forming live, disappears if it fails. Uptrend detection lookback now won't overlap pre-market, reg hours, or afterhours time zones. Recoded some logic. Other fixes, tweaks.
# Magenta arrow = potential pattern forming
# White arrow = confirmed (trigger bar high broke the high of ignitor and pause bars)
# Official Criteria:
# 1st bar ("ignitor") spike in price, range, maybe volume & 1st or 2nd bar of a move
# 1st bar not in middle of an uptrend
# 1st bar should be above resistance (closes above recent high)
# middle bar/bars ("pause") low is above midpoint of 1st bar
# middle bar/bars high is near 1st bar high
# middle bar/bars can be green or red
# trigger bar (potential) low is greater than low of middle bar/bars
# trigger bar (confirmed) high is above the high of the ingnitor and middle bars
input Show_Arrows_Potential = yes; # ignitor and pause bars match pattern, trigger has potential
input Show_Arrows_Confirmed = yes; # trigger bar successfully crossed previous bars high
input Color_Bars_Potential = yes; # ignitor and pause bars match pattern, trigger has potential
input Color_Bars_Confirmed = yes; # trigger bar successfully crossed previous bars high
input Flash_Background_Start = {default "Off", "Ignitor", "Pause Bars", "Trigger"}; # BG color change starting with selected bar match
input trigger_Bar_Low_Min = {default "Official", "Relaxed"}; # Trigger bar low min based on pause bars low (official), or pause bars minimum
input Show_During_Uptrend = yes; # Strict pattern not supposed to be in middle of uptrend
input uptrend_Limit_Perc_Inc = 1.0; # Only used if Show_During_Uptrend = no (Still signal if uptrend is below % entered)
input uptrend_LookBack = 10; # Only used if Show_During_Uptrend = no
input prev_High_LookBack = 5; # Ignitor bar must break previous period high
input avg_Vol_LookBack = 10; # for qualifying ignitor bar
input avg_Range_LookBack = 10; # for qualifying ignitor bar
input ignitor_Range_Spike_Perc = 200; # Minimum ignitor range % relative to avg range (200% = 2x avg range)
input ignitor_Vol_Spike_Perc = 100; # Minimum volume % relative to avg volume
input pause_Low_Min_Perc = 45; # Pause bars low min % of ignitor bar range (50% = middle of ingitor bar)
input pause_High_Min_Perc = 80; # Pause bars high min % of ignitor bar range
input pause_High_max_Perc = 110; # Pause bars high max % of ignitor bar range
Assert(prev_High_LookBack >= 1, "prev_High_LookBack must be greater than zero");
Assert(uptrend_LookBack >= 1, "uptrend_LookBack must be greater than zero");
Assert(avg_Vol_LookBack >= 1, "avg_Vol_LookBack must be greater than zero");
Assert(avg_Range_LookBack >= 1, "avg_Range_LookBack must be greater than zero");
Assert(ignitor_Range_Spike_Perc >= 110, "ignitor_Range_Spike_Perc must be greater than 110%");
Assert(pause_Low_Min_Perc >= 0, "pause_Low_Min_Perc must be greater than zero");
Assert(pause_High_Min_Perc >= pause_Low_Min_Perc, "pause_High_Min_Perc must be greater than pause_Low_Min_Perc");
Assert(pause_High_max_Perc >= 100, "pause_High_max_Perc must be greater than or equal to 100%");
def triggerMode;
switch (trigger_Bar_Low_Min) {
case "Official": triggerMode = 1;
case "Relaxed": triggerMode = 2; }
def flashBGMode;
switch (Flash_Background_Start) {
case "Off": flashBGMode = 1;
case "Ignitor": flashBGMode = 2;
case "Pause Bars": flashBGMode = 3;
case "Trigger": flashBGMode = 4; }
def start = 0930;
def end = 1600;
def pre = secondsTillTime(start) > 0;
def regHrs = secondsfromtime(start) >= 0 and secondsTillTime(end) > 0;
def AH = secondsfromtime(end) >= 0;
def utLookback = max(1, rounddown(uptrend_LookBack / 2, 0));
def utLookbackRecent = max(1, rounddown(uptrend_LookBack / 4, 0));
def uptrendLimMult = 1 + (uptrend_Limit_Perc_Inc * .01);
def h = high;
def l = low;
def o = open;
def c = close;
def v = volume;
def liveBar = isNaN(c[-1]);
def upBar = o < c;
def range = h - l;
def avgRange = ATR(avg_Range_LookBack);
def avgVol = Average(v, avg_Vol_LookBack);
def hh = Highest(h, prev_High_LookBack);
def movAvg = MovingAverage(AverageType.SIMPLE, c, utLookback);
def movAvgRecent = MovingAverage(AverageType.SIMPLE, c, utLookbackRecent);
def diffTimeZone1 = (pre and !pre[utLookback * 2]) or (regHrs and !regHrs[utLookback * 2]) or (AH and !AH[utLookback * 2]);
def diffTimeZone2 = (pre and !pre[utLookbackRecent * 2]) or (regHrs and !regHrs[utLookbackRecent * 2]) or (AH and !AH[utLookbackRecent * 2]);
def notUptrend1 = diffTimeZone1 or (movAvg <= movAvg[utLookback] * uptrendLimMult);
def notUptrend2 = diffTimeZone2 or (movAvgRecent <= movAvgRecent[utLookbackRecent] * uptrendLimMult);
def notUptrend = Show_During_Uptrend or (notUptrend1 and notUptrend2);
def ignitorMinHigh = max(hh[1] , h[1] + (range[1] * (ignitor_Range_Spike_Perc - 100) * .01 * .7));
def ignitor_bar = upBar and notUptrend[1] and h >= ignitorMinHigh and range > range[1] and range >= avgRange[1] * ignitor_Range_Spike_Perc * .01 and v >= avgVol[1] * ignitor_Vol_Spike_Perc *.01;
def pauseLowMin = l + (range * pause_Low_Min_Perc * .01);
def pauseHighMin = l + (range * pause_High_Min_Perc * .01);
def pauseHighMax = l + (range * pause_High_max_Perc * .01);
def pause_bar1 = ignitor_bar[1] and l >= pauseLowMin[1] and h <= pauseHighMax[1] and h >= pauseHighMin[1];
def pause_bar2 = pause_bar1[1] and l >= pauseLowMin[2] and h <= pauseHighMax[2] and h >= pauseHighMin[2];
def potential_3BP = pause_bar1[1] and range > 0 and if triggerMode == 1 then l >= l[1] else l >= pauseLowMin[2];
def confirmed_3BP = potential_3BP and h > Max(h[1], h[2]);
def potential_4BP = pause_bar2[1] and range > 0 and if triggerMode == 1 then l >= min(l[1], l[2]) else l >= pauseLowMin[3];
def confirmed_4BP = potential_4BP and h > Max(Max(h[1], h[2]), h[3]);
# The 3rd bar can be both pause_bar2 and 3BP trigger bar at same time
def show_confirmed = confirmed_3BP or confirmed_4BP;
def show_potential = (potential_3BP or potential_4BP) and liveBar;
#def show_pause2 = pause_bar2 and !potential_3BP and !confirmed_3BP and (if liveBar then 1 else (potential_4BP[-1] and liveBar[-1]) or confirmed_4BP[-1]);
def show_pause2 = pause_bar2 and (if liveBar then 1 else (potential_4BP[-1] and liveBar[-1]) or confirmed_4BP[-1]);
def show_pause1 = pause_bar1 and (if liveBar then 1 else show_pause2[-1] or (potential_3BP[-1] and liveBar[-1]) or confirmed_3BP[-1]);
def show_ignitor = ignitor_bar and (if liveBar then 1 else show_pause1[-1]);
plot confirmed_arrows = Show_Arrows_Confirmed and show_confirmed;
plot potential_arrows = Show_Arrows_Potential and (show_ignitor or show_pause1 or show_pause2 or show_potential);
def paintPotential = Color_Bars_Potential and (show_ignitor or show_pause1 or show_pause2 or show_potential);
def paintConfirmed = Color_Bars_Confirmed and show_confirmed;
def flashBG = if flashBGMode == 2 then (show_ignitor or show_pause1 or show_pause2 or show_potential or show_confirmed) else
if flashBGMode == 3 then (show_pause1 or show_pause2 or show_potential or show_confirmed) else
if flashBGMode == 4 then (show_potential or show_confirmed) else 0;
# STYLE #
confirmed_arrows.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
confirmed_arrows.SetDefaultColor(Color.WHITE);
confirmed_arrows.SetLineWeight(3);
potential_arrows.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
potential_arrows.SetDefaultColor(Color.MAGENTA);
potential_arrows.SetLineWeight(3);
assignPriceColor(if paintPotential then if c < o then createColor(255,0,20) else createColor(200,255,20) else color.CURRENT);
assignPriceColor(if paintConfirmed then if c < o then createColor(255,0,20) else createColor(200,255,20) else color.CURRENT);
assignBackgroundColor(if flashBG then color.LIGHT_GRAY else color.current);