Three Bar Gap (Simple Price Action - with 1 line plot)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © DojiEmoji

// This script is tailored towards experienced traders who prefer to view raw price charts during live execution. 
// It searches for a three-bar pattern of what is colloquially called "fair value gap", or "imbalance" and uses 
// a single line to plot the results. The goal is to display price in a way that is as simple as possible so that 
// chart readers who don't prefer to add indicators on their screen will still find this indicator as an 
// acceptable option to consider for.

//@version=5
indicator("3 Bar Gap [DojiEmoji]", overlay=true)

//---------------------
// Settings - Adjustable:
var int atr_filter_len = input.int(20, minval=2, inline="ln1", title="Threshold: ATR")
var float atr_filter_multi = input.float(0.5, minval=0.1, maxval=1, step=0.1, inline="ln1", title="x",
 tooltip="The minimum distance that candles [over 3 bars] must 'gap' by in order for move to be"+
 "considered significant.\n\n-Higher Multi. => Less lines \n-Lower Multi. => More lines (might be noisy)"
 )
var color col_up    = input.color(color.new(color.blue,0), title="Colors for Up move / Down move:", inline="ln2", group="Plots")
var color col_dn    = input.color(color.new(color.red,0), title="/", inline="ln2", group="Plots")
var int linewidth   = input.int(2, minval=1, maxval=9,title="Width of the line", group="Plots")
//---------------------
// Data structure for gaps. Will remember their characteristics. Will also store them according to order of occurrence. 
// {
type gap                                                        // Attributes to describe gap 
    float pivot_upper   = na
    float pivot_lower   = na
    float pivot_mid     = na
    color linecolor     = na
    int barindex        = na

var gap[] _history = array.new<gap>()                           // Array of gaps, to keep track of them.
if barstate.isfirst
    array.push(_history, gap.new())                             // to prevent none type errors at the beginning when size==0

// @function insert_gap(price[2], price[0]) returns void
insert_gap(gap g, float price_t_minustwo, float price_t_zero) =>
    g.pivot_upper := math.max(price_t_minustwo, price_t_zero)
    g.pivot_lower := math.min(price_t_minustwo, price_t_zero)
    g.pivot_mid := (g.pivot_upper+g.pivot_lower)/2
    g.linecolor := price_t_minustwo < price_t_zero ? col_up : price_t_minustwo > price_t_zero ? col_dn : na
    g.barindex := bar_index
    array.unshift(_history, g)

// @function get_recent_gap() returns an instance of gap obj.
get_recent_gap() =>
    if array.size(_history) == 0
        runtime.error(message="")      
    array.get(_history,0)
//}

//---------------------
// Tests for FVGs:
// Requirement 1: Displacement test;                            upward FVG must > 0, and downward FVG must < 0
// Requirement 2: Threshold test;                               distance of FVG must > ATR200 x 0.5

float displacement_up = low[0] - high[2]                        // Test for FVG-up
float displacement_dn = high[0] - low[2]                        // Test for FVG-down

// FVG is valid iff both requirements (1 & 2) are met:
float _threshold = ta.atr(atr_filter_len) * atr_filter_multi
if math.abs(displacement_up) > _threshold and displacement_up > 0
    insert_gap(gap.new(), high[2], low[0])
if math.abs(displacement_dn) > _threshold and displacement_dn < 0
    insert_gap(gap.new(), low[2], high[0])

//---------------------
// Plotting:
var float main_pivot = na
main_pivot := get_recent_gap().pivot_mid[0] == get_recent_gap().pivot_mid[1] ? get_recent_gap().pivot_mid[0] : na
plot(main_pivot, color=get_recent_gap().linecolor, linewidth=linewidth, title="Pivot", offset=-1, editable=false, style=plot.style_linebr)
