Relative Average Extrapolation
Updated
April 15, 2024
TradingView

For free use on the TradingView platform

Relative Average Extrapolation (ChartPrime) is a new take on session averages, like the famous vwap. This indicator leverages patterns in the market by leveraging average-at-time to get a footprint of the average market conditions for the current time. This allows for a great estimate of market conditions throughout the day allowing for predictive forecasting. If we know what the market conditions are at a given time of day we can use this information to make assumptions about future market conditions. This is what allows us to estimate an entire session with fair accuracy. This indicator works on any intra-day time frame and will not work on time frames less than a minute, or time frames that are a day or greater in length. A unique aspect of this indicator is that it allows for analysis of pre and post market sessions independently from regular hours. This results in a cleaner and more usable vwap for each individual session. One drawback of this is that the indicator utilizes an average for the length of a session. Because of this, some after hour sessions will only have a partial estimation. The average and deviation bands will work past the point where it has been extrapolated to in this instance however. On low time frames due to the limited number of data points, the indicator can appear noisy.

Generally crypto doesn't have a consistent footprint making this indicator less suitable in crypto markets. Because of this we have implemented other weighting schemes to allow for more flexibility in the number of use cases for this indicator. Besides volume weighting we have also included time, volatility, and linear (none) weighting. Using any one of these weighting schemes will transform the vwap into a wma, volatility adjusted ma, or a simple moving average. All of the style are still session period and will become longer as the session progresses.

Relative Average Extrapolation (ChartPrime) works by storing data for each time step throughout the day by utilizing a custom indexing system. It takes the a key, ie hour/minute, and transforms it into an array index to stor the current data point in its unique array. From there we can take the current time of day and advance it by one step to retrieve the data point for the next bar index. This allows us to utilize the footprint the extrapolate into the future. We use the relative rate of change for the average, the relative deviation, and relative price position to extrapolate from the current point to the end of the session. This process is fast and effective and possibly easier to use than the built in map feature.

If you have used vwap before you should be familiar with the general settings for this indicator. We have made a point to make it as intuitive for anyone who is already used to using the standard vwap. You can pick the source for the average and adjust/enable the deviation bands multipliers in the settings group. The average period is what determines the number of days to use for the average-at-time. When it is set to 0 it will use all available data. Under "Extrapolation" you will find the settings for the estimation. "Direction Sensitivity" adjusts how sensitive the indicator is to the direction of the vwap. A higher number will allow it to change directions faster, where a lower number will make it more stable throughout the session. Under the "Style" section you will find all of the color and style adjustments to customize the appearance of this indicator.

Relative Average Extrapolation (ChartPrime) is an advanced and customizable session average indicator with the ability to estimate the direction and volatility of intra-day sessions. We hope you will find this script fascinating and useful in your trading and decision making. With its unique take on session weighting and forecasting, we believe it will be a secret weapon for traders for years to come.

Enjoy


// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ChartPrime

//@version=5 
indicator("Relative Average Extrapolation [ChartPrime]", overlay = true, max_bars_back = 1000)

if not timeframe.isminutes
    runtime.error("Timeframe must be intera day.")

type key
    int m = minute
    int h = hour

type tod
    key TIME
    float[] v

type data
    float source
    tod[] rel

type settings
    data relative_rstdev
    data relative_delta
    data relative_position
    key TIME
    float multiplier_1
    bool enable_1
    float multiplier_2
    bool enable_2
    float multiplier_3
    bool enable_3
    bool extrapolate
    string project_price
    int sensitivity
    int max_length
    string center_style
    int center_width
    color center_color
    string project_style
    int projection_width
    color projection_color
    string dev_1_style
    int dev_color_1_width
    color dev_color_1
    string dev_2_style
    int dev_color_2_width
    color dev_color_2
    string dev_3_style
    int dev_color_3_width
    color dev_color_3
    int alpha

key_to_hash(key TIME)=>
    int hash = TIME.h * 60 + TIME.m

method advance_key(key self, int forward)=>
    int period = forward * timeframe.multiplier
    int advanced_minute = self.m + period
    int minute_rotations = math.floor(advanced_minute / 60)
    int new_minute = advanced_minute % 60
    int hour_rotations = self.h + minute_rotations
    int new_hour = hour_rotations % 24
    key.new(new_minute, new_hour)

local_vol(float source)=> 
    if session.isfirstbar or session.isfirstbar_regular or session.islastbar_regular[1]
        1
    else
        math.sqrt(math.pow(source - source[1], 2.0)) + 1 

clamp(float source, float top = 1, float bottom = 0)=>
    math.min(1, math.max(0, source))

abs_range(float source)=>
    math.abs(clamp(source) - 0.5) * 2

abs_percent(float end, float start)=>
    math.abs(end - start) / math.min(end, start)

add_percent(float source, float percent, bool sign)=>
    if sign
        source + source * percent
    else
        source - source * percent

trash(self, int length)=>
    if self.size() >= length and length > 0
        self.pop()

dump(source)=>
    if source.size() > 0
        for i = source.size() - 1 to 0
            source.remove(i).delete()

method add_element(tod[] self, float source, int period)=>
    if not na(source)
        if not na(self.get(key_to_hash(key.new())))
            tod key_value = self.get(key_to_hash(key.new()))
            key t = key_value.TIME
            float[] v = key_value.v
            trash(v, period)
            v.unshift(source)
        else
            float[] v = array.new()
            v.unshift(source)
            self.set(key_to_hash(key.new()), tod.new(key.new(), v))

method get_average(data self, key TIME)=>
    float vols = na
    if not na(self.rel.get(key_to_hash(TIME)))
        vols := self.rel.get(key_to_hash(TIME)).v.avg()
    else
        vols := self.source

relative(float source = volume, int period = 0)=>
    var tod[] minute_time_frame = array.new(1440)
    minute_time_frame.add_element(source, period)
    data.new(source, minute_time_frame)

line_style(string style)=>
    switch style
        "Solid" => line.style_solid
        "Dashed" => line.style_dashed
        "Dotted" => line.style_dotted
        => line.style_solid

make_projection(float source, int current_sample, int length, bool session, settings s)=>
    chart.point[] middle = array.new()
    chart.point[] top_1 = array.new()
    chart.point[] bottom_1 = array.new()
    chart.point[] top_2 = array.new()
    chart.point[] bottom_2 = array.new()
    chart.point[] top_3 = array.new()
    chart.point[] bottom_3 = array.new()
    chart.point[] projection_points = array.new()
    chart.point[] projection_points_2 = array.new()
    chart.point[] confidence_points = array.new()

    var polyline[] extrapolations = array.new()

    int remaining = length - current_sample + 1

    dump(extrapolations)

    if remaining >= 0 and s.extrapolate and session and barstate.islast
        bool direction = source > source[math.ceil(current_sample / s.sensitivity)]
        int projection_range = math.min(s.max_length, remaining)
        float prev = source

        for i = 0 to projection_range
            key new_key = advance_key(s.TIME, i)
            int idx = bar_index + i

            if current_sample == 0
                float dev = s.relative_rstdev.get_average(new_key)

                middle.push(chart.point.new(na, bar_index + i, source))

                if s.enable_1
                    top_1.push(chart.point.new(na, idx, source + dev * s.multiplier_1))
                    bottom_1.unshift(chart.point.new(na, idx, source - dev * s.multiplier_1))
                if s.enable_2
                    top_2.push(chart.point.new(na, idx, source + dev * s.multiplier_2))
                    bottom_2.unshift(chart.point.new(na, idx, source - dev * s.multiplier_2))
                if s.enable_3
                    top_3.push(chart.point.new(na, idx, source + dev * s.multiplier_3))
                    bottom_3.unshift(chart.point.new(na, idx, source - dev * s.multiplier_3))

                if s.project_price != "None"
                    float project = s.relative_position.get_average(new_key)
                    bool polarity = open < close
                    if s.project_price == "Polar"
                        projection_points.push(chart.point.new(na, idx, source + nz((polarity ? 1 : -1) * project * dev * math.max(s.multiplier_3, s.multiplier_2, s.multiplier_1))))
                    else
                        projection_points.push(chart.point.new(na, idx, source + nz(project * dev * math.max(s.multiplier_3, s.multiplier_2, s.multiplier_1))))
                        projection_points_2.push(chart.point.new(na, idx, source - nz(project * dev * math.max(s.multiplier_3, s.multiplier_2, s.multiplier_1))))
            else
                float percent = s.relative_delta.get_average(new_key)
                float dev = s.relative_rstdev.get_average(new_key)

                middle.push(chart.point.new(na, idx, prev))

                if s.enable_1
                    top_1.push(chart.point.new(na, idx, prev + dev * s.multiplier_1))
                    bottom_1.unshift(chart.point.new(na, idx, prev - dev * s.multiplier_1))
                if s.enable_2
                    top_2.push(chart.point.new(na, idx, prev + dev * s.multiplier_2))
                    bottom_2.unshift(chart.point.new(na, idx, prev - dev * s.multiplier_2))
                if s.enable_3
                    top_3.push(chart.point.new(na, idx, prev + dev * s.multiplier_3))
                    bottom_3.unshift(chart.point.new(na, idx, prev - dev * s.multiplier_3))

                if s.project_price != "None"
                    float project = s.relative_position.get_average(new_key)
                    if s.project_price == "Polar"
                        projection_points.push(chart.point.new(na, idx, prev + nz((direction ? 1 : -1) * project * dev * math.max(s.multiplier_3, s.multiplier_2, s.multiplier_1))))
                    else
                        projection_points.push(chart.point.new(na, idx, prev + nz(project * dev * math.max(s.multiplier_3, s.multiplier_2, s.multiplier_1))))
                        projection_points_2.push(chart.point.new(na, idx, prev - nz(project * dev * math.max(s.multiplier_3, s.multiplier_2, s.multiplier_1))))

                prev := add_percent(prev, percent, direction)

        extrapolations.push(polyline.new(middle, false, false, xloc.bar_index, s.center_color, line_style = line_style(s.center_style), line_width = s.center_width))

        bool curved = s.project_style == "Solid"

        if s.enable_1
            extrapolations.push(polyline.new(top_1.concat(bottom_1), false, false, xloc.bar_index, s.dev_color_1, color.new(s.dev_color_1, s.alpha), line_style(s.dev_1_style), s.dev_color_1_width))
        if s.enable_2
            extrapolations.push(polyline.new(top_2.concat(bottom_2), false, false, xloc.bar_index, s.dev_color_2, color.new(s.dev_color_2, s.alpha), line_style(s.dev_2_style), s.dev_color_2_width))
        if s.enable_3
            extrapolations.push(polyline.new(top_3.concat(bottom_3), false, false, xloc.bar_index, s.dev_color_3, color.new(s.dev_color_3, s.alpha), line_style(s.dev_3_style), s.dev_color_3_width))

        if s.project_price != "None"
            extrapolations.push(polyline.new(projection_points, curved, false, xloc.bar_index, s.projection_color, line_style = line_style(s.project_style), line_width = s.projection_width))

            if s.project_price == "Both"
                extrapolations.push(polyline.new(projection_points_2, curved, false, xloc.bar_index, s.projection_color, line_style = line_style(s.project_style), line_width = s.projection_width))

const string group_1 = "Settings"
float source = input.source(hlc3, "Source", group = group_1)
string weight = input.string("Volume", "Weighting Style", ["Volume", "Time", "Volatility", "None"], group = group_1)
int max_size = input.int(0, "Average Period", minval = 0, tooltip = "The number of days you want to take the average at time over. When set to 0 it will use all of the available data.", group = group_1)
float multiplier_1 = input.float(1, "Multiplier 1          ", minval = 0, step = 0.125, inline = "1", group = group_1)
bool enable_1 = input.bool(true, "", inline = "1", group = group_1)
float multiplier_2 = input.float(2, "Multiplier 2          ", minval = 0, step = 0.125, inline = "2", group = group_1)
bool enable_2 = input.bool(true, "", inline = "2", group = group_1)
float multiplier_3 = input.float(3, "Multiplier 3          ", minval = 0, step = 0.125, inline = "3", group = group_1)
bool enable_3 = input.bool(true, "", inline = "3", group = group_1)
const string group_2 = "Extrapolation"
bool extrapolate = input.bool(true, "Extrapolate     ", inline = "EXT", group = group_2)
int max_length = input.int(500, "", minval = 1, maxval = 500, inline = "EXT", tooltip = "Adjust the number of candles to extrapolate into the future.", group = group_2)
int sensitivity = input.int(3, "Direction Sensitivity", minval = 1, maxval = 10, tooltip = "Adjust this to make the estimation more sensitive to price movents. A higher number will force the estimation to change directions easier.", group = group_2)
string project_price = input.string("Both", "Project Price", ["Polar", "Both", "None"], tooltip = "Display the average location of the price at a given time. Polar will display the projection in the direction of the average while both will display both sides.", group = group_2)
const string group_3 = "Style"
string center_style = input.string("Solid", "Center Style    ", ["Solid", "Dashed", "Dotted"], inline = "cen", group = group_3)
int center_width = input.int(1, "", minval = 1, maxval = 5, inline = "cen", group = group_3)
color center_color = input.color(color.blue, "", inline = "cen", group = group_3)
string project_style = input.string("Dashed", "Projection Style ", ["Solid", "Dashed", "Dotted"], inline = "pro", group = group_3)
int projection_width = input.int(3, "", minval = 1, maxval = 5, inline = "pro", group = group_3)
color projection_color = input.color(#8c3bf5, "", inline = "pro", group = group_3)
string dev_1_style = input.string("Solid", "Deviation 1 Style", ["Solid", "Dashed", "Dotted"], inline = "d1", group = group_3)
int dev_color_1_width = input.int(1, "", minval = 1, maxval = 5, inline = "d1", group = group_3)
color dev_color_1 = input.color(color.green, "", inline = "d1", group = group_3)
string dev_2_style = input.string("Solid", "Deviation 2 Style", ["Solid", "Dashed", "Dotted"], inline = "d2", group = group_3)
int dev_color_2_width = input.int(1, "", minval = 1, maxval = 5, inline = "d2", group = group_3)
color dev_color_2 = input.color(color.olive, "", inline = "d2", group = group_3)
string dev_3_style = input.string("Solid", "Deviation 3 Style", ["Solid", "Dashed", "Dotted"], inline = "d3", group = group_3)
int dev_color_3_width = input.int(1, "", minval = 1, maxval = 5, inline = "d3", group = group_3)
color dev_color_3 = input.color(color.teal, "", inline = "d3", group = group_3)
int alpha = input.int(95, "Fill Alpha", minval = 0, maxval = 100, group = group_3)

key TIME = key.new()

var float rvwap = na
var float up_1 = na
var float down_1 = na
var float up_2 = na
var float down_2 = na
var float up_3 = na
var float down_3 = na
var float sum = 0
var float w = 0
var float vari = 0
var float delta = 0
var int samples = -1
var int[] day_length = array.new()
var int[] premarket_length = array.new()
var int[] postmarket_length = array.new()

float lvol = local_vol(source)

float weights = switch weight
    "Volume" => volume
    "Time" => samples + 2
    "Volatility" => lvol
    "None" => 1

data relative_volume = relative(weights, max_size)
float average_volume = relative_volume.get_average(TIME)

if (session.islastbar_regular or session.isfirstbar or session.islastbar) and samples > -1 and not na(rvwap)
    float max_range = math.max(up_1, up_2, up_3)
    float min_range = math.min(down_1, down_2, down_3)

    if session.ismarket
        trash(day_length, max_size)
        day_length.unshift(samples)

    if session.ispremarket
        trash(premarket_length, max_size)
        premarket_length.unshift(samples)

    if session.ispostmarket
        trash(postmarket_length, max_size)
        postmarket_length.unshift(samples)

if session.isfirstbar or session.isfirstbar_regular or session.islastbar_regular[1]
    sum := 0
    w := 0
    vari := 0
    samples := -1

sum += source * average_volume
w += average_volume

rvwap := sum / w

vari += math.pow(source - rvwap, 2)
samples += 1

float stdev = nz(math.sqrt(vari / samples))
data relative_rstdev = relative(stdev, max_size)
float rstdev = relative_rstdev.get_average(TIME)

up_1 := rvwap + rstdev * multiplier_1
down_1 := rvwap - rstdev * multiplier_1
up_2 := rvwap + rstdev * multiplier_2
down_2 := rvwap - rstdev * multiplier_2
up_3 := rvwap + rstdev * multiplier_3
down_3 := rvwap - rstdev * multiplier_3

float max_range = math.max(up_1, up_2, up_3)
float min_range = math.min(down_1, down_2, down_3)
data relative_position = relative(abs_range((source - min_range) / (max_range - min_range)), max_size)

delta := abs_percent(rvwap, rvwap[1])

if session.isfirstbar or session.isfirstbar_regular or session.islastbar_regular[1]
    delta := 0

data relative_delta = relative(delta, max_size)

int average_day_length = math.floor(day_length.avg())
int average_pre_length = math.floor(premarket_length.avg())
int average_post_length = math.floor(postmarket_length.avg())

settings s = settings.new(
   relative_rstdev
 , relative_delta
 , relative_position
 , TIME
 , multiplier_1
 , enable_1
 , multiplier_2
 , enable_2
 , multiplier_3
 , enable_3
 , extrapolate
 , project_price
 , sensitivity
 , max_length
 , center_style
 , center_width
 , center_color
 , project_style
 , projection_width
 , projection_color
 , dev_1_style
 , dev_color_1_width
 , dev_color_1
 , dev_2_style
 , dev_color_2_width
 , dev_color_2
 , dev_3_style
 , dev_color_3_width
 , dev_color_3
 , alpha)

make_projection( // regular market
   rvwap
 , samples
 , average_day_length
 , session.ismarket
 , s
 )

make_projection( // premarket
   rvwap
 , samples
 , average_pre_length
 , session.ispremarket
 , s
 )

make_projection( // postmarket
   rvwap
 , samples
 , average_post_length
 , session.ispostmarket
 , s
 )

vwap = plot(rvwap, "RVWAP", center_color, center_width)
um1 = plot(enable_1 ? up_1 : na, "RSTDEV 1", dev_color_1, dev_color_1_width, display = enable_1 ? display.all : display.none)
dm1 = plot(enable_1 ? down_1 : na, "RSTDEV 1", dev_color_1, dev_color_1_width, display = enable_1 ? display.all : display.none)
um2 = plot(enable_2 ? up_2 : na, "RSTDEV 2", dev_color_2, dev_color_2_width, display = enable_2 ? display.all : display.none)
dm2 = plot(enable_2 ? down_2 : na, "RSTDEV 2", dev_color_2, dev_color_2_width, display = enable_2 ? display.all : display.none)
um3 = plot(enable_3 ? up_3 : na, "RSTDEV 3", dev_color_3, dev_color_3_width, display = enable_3 ? display.all : display.none)
dm3 = plot(enable_3 ? down_3 : na, "RSTDEV 3", dev_color_3, dev_color_3_width, display = enable_3 ? display.all : display.none)

fill(vwap, um1, color.new(dev_color_1, alpha), "RSTDEV 1")
fill(vwap, dm1, color.new(dev_color_1, alpha), "RSTDEV 1")
fill(vwap, um2, color.new(dev_color_2, alpha), "RSTDEV 2")
fill(vwap, dm2, color.new(dev_color_2, alpha), "RSTDEV 2")
fill(vwap, um3, color.new(dev_color_3, alpha), "RSTDEV 3")
fill(vwap, dm3, color.new(dev_color_3, alpha), "RSTDEV 3")

Get access to our Exclusive
premium indicators