Indicator · Pine Script v6 · On the chart

CandelaCharts - Value Area Reversals

by iamc1oud· code credited to CandelaCharts

Calculates higher-timeframe volume profiles to identify Value Area High (VAH), Value Area Low (VAL), and Point of Control (POC) levels directly on the price chart.

View on GitHub

GitHub stars56on the repository
Pine versionv6current
Lines of code861
Settings27inputs
LicenceNone statedfile: MPL-2.0
Last update2 months ago20 Aug 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

861 lines of Pine Script v6 from 2027-Setup (1 To Master)/volume-profile-area-reversal.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

volume-profile-area-reversal.pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © CandelaCharts

//@version=6
indicator("CandelaCharts - Value Area Reversals", shorttitle = "CandelaCharts - Value Area Reversals", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 5000, max_polylines_count = 100)




 


 


 

// # ========================================================================= #
// # |   Colors   |
// # ========================================================================= #

//#region

colors_white                        = color.white
colors_black                        = color.black
colors_green                        = color.green
colors_orange                       = color.orange
colors_blue                         = color.blue
colors_aqua                         = color.aqua
colors_fuchsia                      = color.fuchsia
colors_lime                         = color.lime
colors_maroon                       = color.maroon
colors_navy                         = color.navy
colors_olive                        = color.olive
colors_purple                       = color.purple
colors_teal                         = color.teal
colors_yellow                       = color.yellow
colors_red                          = color.red
colors_gray                         = color.gray
colors_silver                       = color.silver
colors_transparent                  = color.new(color.white, 100)

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   Inputs   |
// # ========================================================================= #

//#region

general_font                        = input.string("Monospace", "Text                  ", options = ["Default", "Monospace"], inline = "1.0", group = "General")
general_text                        = input.string("Tiny", "", options = ["Tiny", "Small", "Normal", "Large", "Huge", "Auto"], inline = "1.0", group = "General", tooltip = "Font family and text size for all on-chart labels.")
general_brand_show                  = input.bool(false, "Hide brand", group = "General")

prof_tf                             = input.timeframe("D", "Timeframe       ", group = "Volume Profile", tooltip = "Higher timeframe used to determine when a new profile session begins.")
prof_rows                           = input.int(50, "Rows             ", minval = 10, maxval = 200, group = "Volume Profile", tooltip = "Number of horizontal bins in the volume profile histogram.")
prof_va_pct                         = input.int(70, "Value area    ", minval = 50, maxval = 95, group = "Volume Profile", tooltip = "Percentage of total volume included within the Value Area.")
prof_max_width                      = input.int(30, "Max width       ", minval = 5, maxval = 100, group = "Volume Profile", tooltip = "Maximum horizontal extent of the profile in bars.")
prof_insig_pct                      = input.int(10, "Threshold       ", minval = 0, maxval = 50, group = "Volume Profile", tooltip = "Volume level (%) below which profile rows are considered insignificant.")
prof_style                          = input.string("Histogram", "Style            ", options = ["Curved", "Histogram"], group = "Volume Profile", tooltip = "Curved draws a smooth outline, Histogram draws stepped volume bars.")
prof_hist                           = input.int(1, "History           ", minval = 1, maxval = 10, group = "Volume Profile", tooltip = "Number of historical profiles to display (including the current period).")

style_cisd_show                     = input.bool(true, "CISD          ", inline = "8.0", group = "Change In State Of Delivery", tooltip = "Detect and display Change in State of Delivery (CISD) lines.")
style_cisd_style                    = input.string('⎯⎯⎯', "", options = ['⎯⎯⎯', '----', '····'], inline = "8.0", group = "Change In State Of Delivery")
style_cisd_bull_color               = input.color(color.teal, "", inline = "8.0", group = "Change In State Of Delivery")
style_cisd_bear_color               = input.color(color.orange, "", inline = "8.0", group = "Change In State Of Delivery")

sig_show_signals                    = input.bool(true, "Lookback   ", inline = "3.0", group = "Signals", tooltip = "Enable or disable Value Area rejection signals.")
sig_sibling_count                   = input.int(20, "", minval = 1, maxval = 60, inline = "3.0", group = "Signals", tooltip = "Number of previous candles whose volume must be exceeded for a signal to fire.")
sig_algo                            = input.string("Algorithm 1 (Same Type)", "Algorithm ", options = ["Algorithm 1 (Same Type)", "Algorithm 2 (All Types)"], group = "Signals", tooltip = "Algorithm 1 compares volume to the last N candles of the same type. Algorithm 2 compares volume to the last N candles regardless of type.")
sig_bias                            = input.string("Neutral", "Bias              ", options = ["Bullish", "Bearish", "Neutral"], group = "Signals", tooltip = "Filter signals by bias. Neutral shows all signals.")

style_vah_css                       = input.color(#787b86, "Levels               ", inline = "4.0", group = "Style", tooltip = "Colors for VAH, VAL, and POC horizontal levels.")
style_val_css                       = input.color(#787b86, "VAL", inline = "4.0", group = "Style")
style_poc_css                       = input.color(color.black, "POC", inline = "4.0", group = "Style")
style_up_vol_css                    = input.color(color.teal, "Volume             ", inline = "5.0", group = "Style", tooltip = "Fill colors for up-volume and down-volume profile areas.")
style_dn_vol_css                    = input.color(color.orange, "Down", inline = "5.0", group = "Style")
style_bull_signal_css               = input.color(color.teal, "Signal               ", inline = "6.0", group = "Style", tooltip = "Colors for bullish and bearish reclaim signal markers.")
style_bear_signal_css               = input.color(color.orange, "Bearish", inline = "6.0", group = "Style")
style_va_style                      = input.string('⎯⎯⎯', "VAH/L Line ", options = ['⎯⎯⎯', '----', '····'], group = "Style")
style_color_candles                 = input.bool(true, "Color Candles", group = "Style", tooltip = "Color candles that have higher volume than their recent same-type siblings.")

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   UDT   |
// # ========================================================================= #

//#region

type ColoredCandle
    int b_idx
    int t
    int t_step
    float o
    float h
    float l
    float c
    color col

type SessionProfile
    int     start_idx
    int     end_idx
    float   high_p
    float   low_p
    array<float>  bar_h
    array<float>  bar_l
    array<float>  bar_c
    array<float>  bar_v
    array<bool>   bar_b

type SignalMarker
    int     b_idx          // bar_index where signal fired
    bool    bull           // true = bullish reclaim, false = bearish reclaim
    float   price_h        // high of signal bar
    float   price_l        // low of signal bar

type CISDTracker
    int     b_idx
    int     start_t
    float   level
    bool    bull
    bool    confirmed
    int     conf_t
    bool    locked

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   Functions   |
// # ========================================================================= #

//#region

method text_size(string size) =>
    out = switch size
        "Tiny"   => size.tiny
        "Small"  => size.small
        "Normal" => size.normal
        "Large"  => size.large
        "Huge"   => size.huge
        "Auto"   => size.auto
    out

method font_style(string font) =>
    out = switch font
        'Default'   => font.family_default
        'Monospace' => font.family_monospace

method set_opacity(color css, int opacity) =>
    blue  = color.b(css)
    red   = color.r(css)
    green = color.g(css)
    color.rgb(red, green, blue, opacity)

// Compute anchored volume profile bins, POC, VAH, VAL from bar data arrays
// Returns [poc_price, vah_price, val_price, buy_bins, sell_bins, total_bins, max_bin_vol, poc_bin, va_high_bin, va_low_bin, p_high, p_low, p_step]
method compute_profile(SessionProfile p, int rows, int va_pct) =>
    float p_high = p.high_p
    float p_low  = p.low_p
    float p_step = (p_high - p_low) / rows

    float out_poc  = na
    float out_vah  = na
    float out_val  = na
    int   out_poc_bin     = 0
    int   out_va_high_bin = 0
    int   out_va_low_bin  = 0
    float out_max_bin_vol = 0.0

    array<float> buy_bins   = array.new<float>(rows, 0.0)
    array<float> sell_bins  = array.new<float>(rows, 0.0)
    array<float> total_bins = array.new<float>(rows, 0.0)

    if p_step > 0 and p.bar_v.size() > 0
        float total_vol = 0.0

        for i = 0 to p.bar_v.size() - 1
            float bh = p.bar_h.get(i)
            float bl = p.bar_l.get(i)
            float bc = p.bar_c.get(i)
            float bv = p.bar_v.get(i)
            bool  bb = p.bar_b.get(i)

            float bar_range = bh - bl
            if bar_range == 0
                int bin_idx = math.floor((bc - p_low) / p_step)
                bin_idx := math.max(0, math.min(rows - 1, bin_idx))
                if bb
                    buy_bins.set(bin_idx, buy_bins.get(bin_idx) + bv)
                else
                    sell_bins.set(bin_idx, sell_bins.get(bin_idx) + bv)
                total_bins.set(bin_idx, total_bins.get(bin_idx) + bv)
                total_vol += bv
            else
                for j = 0 to rows - 1
                    float bin_low  = p_low + j * p_step
                    float bin_high = bin_low + p_step
                    float overlap_low  = math.max(bl, bin_low)
                    float overlap_high = math.min(bh, bin_high)
                    float overlap = math.max(0.0, overlap_high - overlap_low)

                    if overlap > 0
                        float vol_fraction = bv * (overlap / bar_range)
                        if bb
                            buy_bins.set(j, buy_bins.get(j) + vol_fraction)
                        else
                            sell_bins.set(j, sell_bins.get(j) + vol_fraction)
                        total_bins.set(j, total_bins.get(j) + vol_fraction)
                        total_vol += vol_fraction

        // Find POC bin
        int poc_bin = 0
        float max_bin_vol = 0.0
        for j = 0 to rows - 1
            float v = total_bins.get(j)
            if v > max_bin_vol
                max_bin_vol := v
                poc_bin := j

        // Expand VA from POC
        float va_target_vol = total_vol * (va_pct / 100.0)
        float current_va_vol = total_bins.get(poc_bin)
        int va_high_bin = poc_bin
        int va_low_bin  = poc_bin

        while current_va_vol < va_target_vol and (va_high_bin < rows - 1 or va_low_bin > 0)
            float upper_vol = va_high_bin < rows - 1 ? total_bins.get(va_high_bin + 1) : -1.0
            float lower_vol = va_low_bin > 0 ? total_bins.get(va_low_bin - 1) : -1.0

            if upper_vol >= lower_vol and upper_vol >= 0
                va_high_bin += 1
                current_va_vol += upper_vol
            else if lower_vol >= 0
                va_low_bin -= 1
                current_va_vol += lower_vol
            else
                break

        out_poc  := p_low + poc_bin * p_step + p_step / 2
        out_vah  := p_low + va_high_bin * p_step + p_step
        out_val  := p_low + va_low_bin * p_step
        out_poc_bin     := poc_bin
        out_va_high_bin := va_high_bin
        out_va_low_bin  := va_low_bin
        out_max_bin_vol := max_bin_vol

    [out_poc, out_vah, out_val, buy_bins, sell_bins, total_bins, out_max_bin_vol, out_poc_bin, out_va_high_bin, out_va_low_bin, p_high, p_low, p_step]

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   Variables   |
// # ========================================================================= #

//#region

var colored_candles = array.new<ColoredCandle>()
var draw_polys    = array.new<polyline>()
var draw_lines    = array.new<line>()
var draw_boxes    = array.new<box>()
var draw_labels   = array.new<label>()
var sig_labels    = array.new<label>()

var array<SignalMarker> signal_markers = array.new<SignalMarker>()
var array<SessionProfile> stored_profiles = array.new<SessionProfile>()
var SessionProfile current_profile = na

// Previous session profile levels (finalized)
var float prev_vah = na
var float prev_val = na
var float prev_poc = na

var array<CISDTracker> cisd_trackers = array.new<CISDTracker>()

find_cisd_data(bool look_for_bullish, int start_idx) =>
    int streak_start = start_idx
    for i = start_idx to start_idx + 20
        bool is_bull_candle = close[i] > open[i]
        if is_bull_candle == look_for_bullish
            streak_start := i
            break
            
    int idx = streak_start
    for i = streak_start to streak_start + 20
        bool is_bull_candle = close[i] > open[i]
        if is_bull_candle != look_for_bullish
            break
        idx := i
        
    [open[idx], close[idx], high[idx], low[idx], look_for_bullish, time[idx]]

get_line_style(string str) =>
    str == '----' ? line.style_dashed : str == '····' ? line.style_dotted : line.style_solid

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   Profile Engine   |
// # ========================================================================= #

//#region

// Detect new session
bool is_new_session = ta.change(time(prof_tf)) != 0

// Manage session profiles
if is_new_session or na(current_profile)
    if not na(current_profile)
        current_profile.end_idx := bar_index - 1

        // Compute and cache previous session's finalized levels
        [f_poc, f_vah, f_val, _b1, _s1, _t1, _m1, _p1, _vh1, _vl1, _ph1, _pl1, _ps1] = current_profile.compute_profile(prof_rows, prof_va_pct)
        prev_poc := f_poc
        prev_vah := f_vah
        prev_val := f_val

        // Store for rendering
        int max_prev = math.max(0, prof_hist - 1)
        while stored_profiles.size() > 0 and stored_profiles.size() >= max_prev
            stored_profiles.shift()
        if max_prev > 0
            stored_profiles.push(current_profile)

    current_profile := SessionProfile.new(bar_index, bar_index, high, low, array.new<float>(), array.new<float>(), array.new<float>(), array.new<float>(), array.new<bool>())

    // Trim signal markers dynamically to prevent memory overflow
    int min_start = na
    if stored_profiles.size() > 0
        min_start := stored_profiles.get(0).start_idx
    else if not na(current_profile)
        min_start := current_profile.start_idx
        
    if not na(min_start)
        while signal_markers.size() > 0 and signal_markers.get(0).b_idx < min_start
            signal_markers.shift()
        while cisd_trackers.size() > 0 and cisd_trackers.get(0).b_idx < min_start
            cisd_trackers.shift()

if not na(current_profile)
    current_profile.high_p  := math.max(current_profile.high_p, high)
    current_profile.low_p   := math.min(current_profile.low_p, low)
    current_profile.end_idx := bar_index

    current_profile.bar_h.push(high)
    current_profile.bar_l.push(low)
    current_profile.bar_c.push(close)
    current_profile.bar_v.push(volume)
    current_profile.bar_b.push(close >= open)

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   Signal Engine   |
// # ========================================================================= #

//#region

// Bar-by-bar VA computation for signal detection
float curr_vah = na
float curr_val = na
float curr_poc = na

if not na(current_profile) and current_profile.bar_v.size() >= 2
    [s_poc, s_vah, s_val, _b2, _s2, _t2, _m2, _p2, _vh2, _vl2, _ph2, _pl2, _ps2] = current_profile.compute_profile(prof_rows, prof_va_pct)
    curr_vah := s_vah
    curr_val := s_val
    curr_poc := s_poc

// Determine which VA levels to use for signals
// Previous day signals use stored previous session's finalized VA

// Volume sibling tracking arrays
var array<float> bull_vols = array.new<float>()
var array<float> bear_vols = array.new<float>()

// Check if volume exceeds the last N entries in a sibling array
f_vol_above_siblings(array<float> sibling_vols, float vol, int count) =>
    if sibling_vols.size() < count
        false
    else
        bool result = true
        for i = 0 to count - 1
            if vol <= sibling_vols.get(sibling_vols.size() - 1 - i)
                result := false
                break
        result

bool bull_reclaim_signal = false
bool bear_reclaim_signal = false

// Compute volume spikes for both signal and coloring logic
bool is_bullish      = close > open
bool is_bearish      = close < open

bool algo1_bull_vol_spike = f_vol_above_siblings(bull_vols, volume, sig_sibling_count)
bool algo1_bear_vol_spike = f_vol_above_siblings(bear_vols, volume, sig_sibling_count)

bool algo2_vol_spike = true
for i = 1 to sig_sibling_count
    if volume <= nz(volume[i])
        algo2_vol_spike := false
        break

bool bull_vol_spike  = sig_algo == "Algorithm 1 (Same Type)" ? algo1_bull_vol_spike : algo2_vol_spike
bool bear_vol_spike  = sig_algo == "Algorithm 1 (Same Type)" ? algo1_bear_vol_spike : algo2_vol_spike

if sig_show_signals and not na(prev_vah) and not na(prev_val) and barstate.isconfirmed
    // Bearish rejection from VAH: candle reached VAH, closed bearish, volume stands out
    bool touched_vah     = high >= prev_vah
    if touched_vah and is_bearish and bear_vol_spike and (sig_bias == "Bearish" or sig_bias == "Neutral")
        bear_reclaim_signal := true
        signal_markers.push(SignalMarker.new(bar_index, false, high, low))
        if style_cisd_show
            cisd_trackers.push(CISDTracker.new(bar_index, na, na, false, false, na, false))

    // Bullish rejection from VAL: candle reached VAL, closed bullish, volume stands out
    bool touched_val     = low <= prev_val
    if touched_val and is_bullish and bull_vol_spike and (sig_bias == "Bullish" or sig_bias == "Neutral")
        bull_reclaim_signal := true
        signal_markers.push(SignalMarker.new(bar_index, true, high, low))
        if style_cisd_show
            cisd_trackers.push(CISDTracker.new(bar_index, na, na, true, false, na, false))

// Push current bar's volume to the appropriate sibling array (after signal check)
if is_bullish
    bull_vols.push(volume)
else if is_bearish
    bear_vols.push(volume)

bool cisd_bull_alert = false
bool cisd_bear_alert = false

// Final signals (based only on previous day)
if style_cisd_show
    for tracker in cisd_trackers
        if not tracker.locked
            int look_left = bar_index - tracker.b_idx + 1
            if tracker.bull
                [o, c, h, l, bull, t] = find_cisd_data(false, look_left)
                tracker.level := o
                tracker.start_t := t
                tracker.locked := true
            else
                [o, c, h, l, bull, t] = find_cisd_data(true, look_left)
                tracker.level := o
                tracker.start_t := t
                tracker.locked := true
        else if not tracker.confirmed
            if tracker.bull and close > tracker.level
                tracker.confirmed := true
                tracker.conf_t := time
                cisd_bull_alert := true
            else if not tracker.bull and close < tracker.level
                tracker.confirmed := true
                tracker.conf_t := time
                cisd_bear_alert := true

bool final_bull_signal = bull_reclaim_signal
bool final_bear_signal = bear_reclaim_signal

// Candle Coloring based on volume spikes
if style_color_candles
    int step = time - nz(time[1], time)
    if is_bullish and bull_vol_spike
        colored_candles.push(ColoredCandle.new(bar_index, time, step, open, high, low, close, style_up_vol_css))
    else if is_bearish and bear_vol_spike
        colored_candles.push(ColoredCandle.new(bar_index, time, step, open, high, low, close, style_dn_vol_css))

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   Visuals   |
// # ========================================================================= #

//#region

if barstate.islast
    // Clean previous drawings
    if draw_polys.size() > 0
        for py in draw_polys
            py.delete()
    if draw_lines.size() > 0
        for ln in draw_lines
            ln.delete()
    if draw_boxes.size() > 0
        for bx in draw_boxes
            bx.delete()
    if draw_labels.size() > 0
        for lb in draw_labels
            lb.delete()
    if sig_labels.size() > 0
        for lb in sig_labels
            lb.delete()

    draw_polys.clear()
    draw_lines.clear()
    draw_boxes.clear()
    draw_labels.clear()
    sig_labels.clear()

    // Collect profiles to render
    array<SessionProfile> render_list = array.new<SessionProfile>()

    if stored_profiles.size() > 0
        for sp in stored_profiles
            render_list.push(sp)

    if not na(current_profile)
        render_list.push(current_profile)

    for p in render_list
        if p.bar_v.size() == 0
            continue

        [r_poc, r_vah, r_val, r_buy, r_sell, r_total, r_max, r_poc_bin, r_va_hi, r_va_lo, r_p_hi, r_p_lo, r_p_step] = p.compute_profile(prof_rows, prof_va_pct)

        if na(r_poc) or r_p_step == 0
            continue

        int prof_width = math.min(prof_max_width, math.max(10, int((p.end_idx - p.start_idx) / 3)))

        // Draw profile shape
        float tot_b = 0.0
        float tot_s = 0.0
        for j = 0 to prof_rows - 1
            tot_b += r_buy.get(j)
            tot_s += r_sell.get(j)
        color poly_color = tot_b > tot_s ? style_up_vol_css : style_dn_vol_css

        if prof_style == "Curved"
            // Full profile
            array<chart.point> cp_full_fill = array.new<chart.point>()
            array<chart.point> cp_full_stroke = array.new<chart.point>()
            cp_full_fill.push(chart.point.from_index(p.start_idx, r_p_lo))
            cp_full_fill.push(chart.point.from_index(p.start_idx, r_p_lo))
            cp_full_fill.push(chart.point.from_index(p.start_idx, r_p_lo))
            for j = 0 to prof_rows - 1
                float t_vol = r_total.get(j)
                float mid = r_p_lo + j * r_p_step + r_p_step / 2
                int value = 0
                if r_max > 0 and t_vol > 0
                    value := math.max(1, math.round((t_vol / r_max) * prof_width))
                chart.point pt = chart.point.from_index(p.start_idx + value, mid)
                cp_full_fill.push(pt)
                cp_full_stroke.push(pt)
            cp_full_fill.push(chart.point.from_index(p.start_idx, r_p_hi))
            cp_full_fill.push(chart.point.from_index(p.start_idx, r_p_hi))
            cp_full_fill.push(chart.point.from_index(p.start_idx, r_p_hi))
            for j = prof_rows - 1 to 0
                float mid = r_p_lo + j * r_p_step + r_p_step / 2
                cp_full_fill.push(chart.point.from_index(p.start_idx, mid))
                
            draw_polys.push(polyline.new(cp_full_fill, curved = true, closed = true, line_color = na, fill_color = color.new(poly_color, 90)))
            draw_polys.push(polyline.new(cp_full_stroke, curved = true, closed = false, line_color = color.new(poly_color, 70), fill_color = na))

            // VA overlay
            array<chart.point> cp_va_fill = array.new<chart.point>()
            array<chart.point> cp_va_stroke = array.new<chart.point>()
            float va_bot = r_p_lo + r_va_lo * r_p_step
            float va_top = r_p_lo + (r_va_hi + 1) * r_p_step
            cp_va_fill.push(chart.point.from_index(p.start_idx, va_bot))
            cp_va_fill.push(chart.point.from_index(p.start_idx, va_bot))
            cp_va_fill.push(chart.point.from_index(p.start_idx, va_bot))
            for j = r_va_lo to r_va_hi
                float t_vol = r_total.get(j)
                float mid = r_p_lo + j * r_p_step + r_p_step / 2
                int value = 0
                if r_max > 0 and t_vol > 0
                    value := math.max(1, math.round((t_vol / r_max) * prof_width))
                chart.point pt = chart.point.from_index(p.start_idx + value, mid)
                cp_va_fill.push(pt)
                cp_va_stroke.push(pt)
            cp_va_fill.push(chart.point.from_index(p.start_idx, va_top))
            cp_va_fill.push(chart.point.from_index(p.start_idx, va_top))
            cp_va_fill.push(chart.point.from_index(p.start_idx, va_top))
            for j = r_va_hi to r_va_lo
                float mid = r_p_lo + j * r_p_step + r_p_step / 2
                cp_va_fill.push(chart.point.from_index(p.start_idx, mid))
                
            draw_polys.push(polyline.new(cp_va_fill, curved = true, closed = true, line_color = na, fill_color = color.new(poly_color, 70)))
            draw_polys.push(polyline.new(cp_va_stroke, curved = true, closed = false, line_color = color.new(poly_color, 30), fill_color = na))

        else
            // Full histogram (faded — outside VA appearance)
            array<chart.point> hp_full = array.new<chart.point>()
            for j = 0 to prof_rows - 1
                float t_vol = r_total.get(j)
                float bin_low  = r_p_lo + j * r_p_step
                float bin_high = bin_low + r_p_step
                int bar_w = 0
                if r_max > 0 and t_vol > 0
                    bar_w := math.max(1, math.round((t_vol / r_max) * prof_width))
                hp_full.push(chart.point.from_index(p.start_idx + bar_w, bin_low))
                hp_full.push(chart.point.from_index(p.start_idx + bar_w, bin_high))
            hp_full.push(chart.point.from_index(p.start_idx, r_p_hi))
            hp_full.push(chart.point.from_index(p.start_idx, r_p_lo))
            draw_polys.push(polyline.new(hp_full, curved = false, closed = true, line_color = color.new(poly_color, 70), fill_color = color.new(poly_color, 90)))

            // VA histogram overlay (opaque — inside VA)
            array<chart.point> hp_va = array.new<chart.point>()
            float va_bot = r_p_lo + r_va_lo * r_p_step
            float va_top = r_p_lo + (r_va_hi + 1) * r_p_step
            for j = r_va_lo to r_va_hi
                float t_vol = r_total.get(j)
                float bin_low  = r_p_lo + j * r_p_step
                float bin_high = bin_low + r_p_step
                int bar_w = 0
                if r_max > 0 and t_vol > 0
                    bar_w := math.max(1, math.round((t_vol / r_max) * prof_width))
                hp_va.push(chart.point.from_index(p.start_idx + bar_w, bin_low))
                hp_va.push(chart.point.from_index(p.start_idx + bar_w, bin_high))
            hp_va.push(chart.point.from_index(p.start_idx, va_top))
            hp_va.push(chart.point.from_index(p.start_idx, va_bot))
            draw_polys.push(polyline.new(hp_va, curved = false, closed = true, line_color = color.new(poly_color, 30), fill_color = color.new(poly_color, 70)))

        // Draw POC line
        draw_lines.push(line.new(p.start_idx, r_poc, p.end_idx, r_poc, color = style_poc_css, width = 3))

        // Draw VAH / VAL lines
        string line_style_va = get_line_style(style_va_style)
        draw_lines.push(line.new(p.start_idx, r_vah, p.end_idx, r_vah, color = style_vah_css, style = line_style_va, width = 1))
        draw_lines.push(line.new(p.start_idx, r_val, p.end_idx, r_val, color = style_val_css, style = line_style_va, width = 1))

        // Labels for levels
        draw_labels.push(label.new(p.end_idx, r_vah, text = "VAH", color = color(na), textcolor = style_vah_css, style = label.style_label_left, size = size.tiny, text_font_family = font_style(general_font)))
        draw_labels.push(label.new(p.end_idx, r_val, text = "VAL", color = color(na), textcolor = style_val_css, style = label.style_label_left, size = size.tiny, text_font_family = font_style(general_font)))
        draw_labels.push(label.new(p.end_idx, r_poc, text = "POC", color = color(na), textcolor = style_poc_css, style = label.style_label_left, size = size.tiny, text_font_family = font_style(general_font)))

    // ── Colored Candles (all enabled profiles) ───────────────────────────────── //

    if style_color_candles and colored_candles.size() > 0
        int min_start_idx = na
        if stored_profiles.size() > 0
            min_start_idx := stored_profiles.get(0).start_idx
        else if not na(current_profile)
            min_start_idx := current_profile.start_idx

        for c in colored_candles
            if na(min_start_idx) or c.b_idx >= min_start_idx
                color solid_color = color.new(c.col, 0)
                // Draw thin line for wick
                draw_lines.push(line.new(c.t, c.h, c.t, c.l, color = solid_color, width = 1, xloc = xloc.bar_time))
                // Draw box for body
                if c.o == c.c
                    draw_lines.push(line.new(c.t, c.c, c.t, c.c + syminfo.mintick, color = solid_color, width = 4, xloc = xloc.bar_time))
                else
                    int half_step = int(c.t_step * 0.35)
                    draw_boxes.push(box.new(left = c.t - half_step, top = math.max(c.o, c.c), right = c.t + half_step, bottom = math.min(c.o, c.c), border_color = solid_color, border_width = 1, bgcolor = solid_color, xloc = xloc.bar_time))

    // ── CISD Lines ───────────────────────────────────────────────────────────── //

    if style_cisd_show and cisd_trackers.size() > 0
        int min_start_idx = na
        if stored_profiles.size() > 0
            min_start_idx := stored_profiles.get(0).start_idx
        else if not na(current_profile)
            min_start_idx := current_profile.start_idx

        for tracker in cisd_trackers
            if tracker.locked and (na(min_start_idx) or tracker.b_idx >= min_start_idx)
                int end_t = tracker.confirmed ? tracker.conf_t : time
                color c_color = tracker.bull ? style_cisd_bull_color : style_cisd_bear_color
                draw_lines.push(line.new(tracker.start_t, tracker.level, end_t, tracker.level, xloc=xloc.bar_time, color=c_color, style=get_line_style(style_cisd_style), width=2))
                draw_labels.push(label.new(end_t, tracker.level, text="CISD", xloc=xloc.bar_time, color=color(na), style=label.style_label_left, textcolor=c_color, size=size.tiny))

    // ── Signal Labels (all enabled profiles) ─────────────────────────────────── //

    if sig_show_signals and signal_markers.size() > 0
        int min_start_idx = na
        if stored_profiles.size() > 0
            min_start_idx := stored_profiles.get(0).start_idx
        else if not na(current_profile)
            min_start_idx := current_profile.start_idx

        for sig in signal_markers
            if na(min_start_idx) or sig.b_idx >= min_start_idx
                if sig.bull
                    sig_labels.push(label.new(sig.b_idx, sig.price_l, text = "▲", color = color(na), textcolor = style_bull_signal_css, style = label.style_label_up, size = size.large, tooltip = "Bullish VA Reclaim"))
                else
                    sig_labels.push(label.new(sig.b_idx, sig.price_h, text = "▼", color = color(na), textcolor = style_bear_signal_css, style = label.style_label_down, size = size.large, tooltip = "Bearish VA Reclaim"))



//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   Alerts   |
// # ========================================================================= #

//#region

if final_bull_signal
    alert(str.format("Bullish VA Reclaim on {0} at {1}", syminfo.ticker, str.tostring(close)), alert.freq_once_per_bar_close)

if final_bear_signal
    alert(str.format("Bearish VA Reclaim on {0} at {1}", syminfo.ticker, str.tostring(close)), alert.freq_once_per_bar_close)

if cisd_bull_alert
    alert(str.format("Bullish CISD Confirmed on {0} at {1}", syminfo.ticker, str.tostring(close)), alert.freq_once_per_bar_close)

if cisd_bear_alert
    alert(str.format("Bearish CISD Confirmed on {0} at {1}", syminfo.ticker, str.tostring(close)), alert.freq_once_per_bar_close)

alertcondition(final_bull_signal, "Bullish VA Reclaim", "Bullish Value Area Reclaim Signal")
alertcondition(final_bear_signal, "Bearish VA Reclaim", "Bearish Value Area Reclaim Signal")
alertcondition(cisd_bull_alert, "Bullish CISD", "Bullish Change in State of Delivery Confirmed")
alertcondition(cisd_bear_alert, "Bearish CISD", "Bearish Change in State of Delivery Confirmed")

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #




 


 


 

// # ========================================================================= #
// # |   Brand   |
// # ========================================================================= #

//#region

if barstate.isfirst and not general_brand_show
    var table brand = table.new(position.bottom_right, 1, 1, bgcolor = chart.bg_color)
    table.cell(brand, 0, 0, "© CandelaCharts", text_color = color.gray, text_halign = text.align_center, text_size = text_size(general_text), text_font_family = font_style(general_font))

//#endregion

// # ========================================================================= #
// # |   End   |
// # ========================================================================= #

Overview

CandelaCharts - Value Area Reversals flags potential reversal setups and Change in State of Delivery (CISD) patterns relative to value area boundaries based on selectable algorithm types and lookback parameters. Users can adjust profile resolution, value area percentage thresholds, level line styles, and bar coloring.

Details

Published by

iamc1oud on GitHub

Code credit

CandelaCharts, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Volume, Support & resistance, SMC / ICT / price action

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Text                  stringMonospacegeneral_font
general_textstringTinygeneral_text
Hide brandboolfalsegeneral_brand_show
Timeframe       timeframeDprof_tf
Rows             int50prof_rows
Value area    int70prof_va_pct
Max width       int30prof_max_width
Threshold       int10prof_insig_pct
Style            stringHistogramprof_style
History           int1prof_hist
CISD          booltruestyle_cisd_show
style_cisd_stylestring⎯⎯⎯style_cisd_style
Show all 27 settings
SettingTypeDefaultVariable
style_cisd_bull_colorcolorcolor.tealstyle_cisd_bull_color
style_cisd_bear_colorcolorcolor.orangestyle_cisd_bear_color
Lookback   booltruesig_show_signals
sig_sibling_countint20sig_sibling_count
Algorithm stringAlgorithm 1 (Same Type)sig_algo
Bias              stringNeutralsig_bias
Levels               color#787b86style_vah_css
VALcolor#787b86style_val_css
POCcolorcolor.blackstyle_poc_css
Volume             colorcolor.tealstyle_up_vol_css
Downcolorcolor.orangestyle_dn_vol_css
Signal               colorcolor.tealstyle_bull_signal_css
Bearishcolorcolor.orangestyle_bear_signal_css
VAH/L Line string⎯⎯⎯style_va_style
Color Candlesbooltruestyle_color_candles
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Moon Dev GEX Sweep & CVD

by moondevonyt
92 stars

The indicator detects liquidity sweeps across key price levels and calculates a cumulative volume delta proxy. It uses pivot high and low lookbacks to identify swing points, marking sweeps when price breaks beyond a level and quickly closes back inside. Featur

Indicator v5 Support & resistanceSMC / ICT / price action
Updated 8 weeks ago · Custom licence GitHub repository of Moon Dev GEX Sweep & CVD

Copilot

by milocaetano
48 stars

Copilot is an order flow range-reversal indicator designed to overlay trading setups directly on the price chart. It evaluates four criteria: market context (sideways vs trending regime based on ATR height), structure (double top or double bottom retests), abs

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated yesterday · MIT GitHub repository of Copilot

Supply and Demand Visible Range identifies supply, demand, and equilibrium zones across the visible chart range. It accumulates high, low, and volume data starting from the leftmost visible bar, optionally requesting lower timeframe intrabar data for finer res

Indicator v5 Support & resistanceVolume Repaints
Updated 2 months ago · AGPL-3.0 GitHub repository of Supply and Demand Visible Range [LuxAlgo]
13 stars

This script identifies and visualizes bullish and bearish order blocks on the price chart. It detects low-volatility consolidation ranges followed by breakout moves accompanied by high trading volume. Users can select between Simple, Relative, or Weighted volu

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 6 months ago · MIT GitHub repository of Advanced Order Blocks with Volume

This indicator identifies and visualizes key support and resistance heatmap zones directly on the price chart. Using closed one-hour price data, it evaluates multi-scale pivots, liquidity clusters, relative volume, and round-number resonances to score structur

Indicator v6 Support & resistanceSMC / ICT / price action
Updated 7 weeks ago · MIT GitHub repository of 加密货币1小时关键价位热力图 V2

This indicator estimates crypto liquidation levels by tracking Open Interest changes from Binance USDT perpetual contracts. It calculates Open Interest delta spikes relative to a simple moving average and projects estimated liquidation price levels across mult

Indicator v5 Support & resistanceSMC / ICT / price action

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers