Skip to main content

Indicator request: Rolling VWAP

One of my most used indicator is the daily, weekly and monthly RVWAP, It would be nice to have built-in RVWAP indicator.

I will make this easy and provide the source I've made for RVWAP.

This code works and draws the RVWAP correctly but might not be most optimized solution as this was my first indicator ever written.

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Drawing;
using System.Windows.Media;
using ATAS.Indicators;
using ATAS.Indicators.Technical.Properties;
using Microsoft.VisualBasic;
using Utils.Common.Logging;
using Color = System.Windows.Media.Color;

public class RVWAP7 : Indicator
{
    #region Properties

    [DisplayName("Period")]
    public RVWAPPeriodType PeriodType
    {
        get => _periodType;
        set { _periodType = value; RecalculateValues(); }
    }

    [DisplayName("FirstDev")]
    public decimal StDev1
    {
        get => _stdev1;
        set { _stdev1 = Math.Max(value, 0); RecalculateValues(); }
    }

    [DisplayName("SecondDev")]
    public decimal StDev2
    {
        get => _stdev2;
        set { _stdev2 = Math.Max(value, 0); RecalculateValues(); }
    }

    [DisplayName("ThirdDev")]
    public decimal StDev3
    {
        get => _stdev3;
        set { _stdev3 = Math.Max(value, 0); RecalculateValues(); }
    }

    [DisplayName("Period")]
    public int Period
    {
        get => _period;
        set
        {
            _period = Math.Max(value, 1);
            RecalculateValues();
        }
    }

    [Display(ResourceType = typeof(Resources), Name = "AletName", GroupName = "Alerts", Order = 320)]
    public string AlertName { get; set; } = "";

    [Display(ResourceType = typeof(Resources), Name = "Line Touch Alerts", GroupName = "Alerts", Order = 320)]
    public bool UseAlerts { get; set; }

    [Display(ResourceType = typeof(Resources), Name = "ApproximationAlert", GroupName = "Alerts", Order = 300)]
    public bool UseApproximationAlert { get; set; }

    [Display(ResourceType = typeof(Resources), Name = "ApproximationFilter", GroupName = "Alerts", Order = 310)]
    public int ApproximationFilter { get; set; } = 3;

    [Display(ResourceType = typeof(Resources), Name = "AlertFile", GroupName = "Alerts", Order = 360)]
    public string AlertFile { get; set; } = "alert1";

    [Display(ResourceType = typeof(Resources), Name = "FontColor", GroupName = "Alerts", Order = 370)]
    public Color AlertForeColor { get; set; } = Color.FromArgb(255, 247, 249, 249);

    [Display(ResourceType = typeof(Resources), Name = "BackGround", GroupName = "Alerts", Order = 380)]
    public Color AlertBGColor { get; set; } = Color.FromArgb(255, 75, 72, 72);

    #endregion

    #region private variables

    private int _previous24hPeriodStart = 0;
    private int _lastbar = -1;
    private decimal _prevClose;
    private int _lastAlert = -1;
    private decimal _stdev1 = 1;
    private decimal _stdev2 = 1.5m;
    private decimal _stdev3 = 2;
    public int _period = 1;
    private int _candleCount = 0;
    private decimal _rval = 0;
    private decimal _rvah = 0;
    private RVWAPPeriodType _periodType = RVWAPPeriodType.Days;
    private List<string> StringForAlert = new List<string>() { "RVWAP", "UPPER1", "UPPER2", "UPPER3", "LOWER1", "LOWER2", "LOWER3"};

    #endregion

    #region Dataserieces

    ValueDataSeries _sqrt = new ValueDataSeries("sqrt");
    ValueDataSeries _upper3 = new ValueDataSeries("Upper std3") { Color = Color.FromArgb(255, 0, 255, 0) };
    ValueDataSeries _upper2 = new ValueDataSeries("Upper std2") { Color = Color.FromArgb(255, 0, 128, 0) };
    ValueDataSeries _upper1 = new ValueDataSeries("Upper std1") { Color = Color.FromArgb(255, 128, 0, 0) };
    ValueDataSeries _lower1 = new ValueDataSeries("Lower std1") { Color = Color.FromArgb(255, 0, 128, 0) };
    ValueDataSeries _lower2 = new ValueDataSeries("Lower std2") { Color = Color.FromArgb(255, 128, 0, 0) };
    ValueDataSeries _lower3 = new ValueDataSeries("Lower std3") { Color = Color.FromArgb(255, 255, 0, 0) };
    ValueDataSeries _rvals = new ValueDataSeries("RVALS") { Color = Colors.LightBlue, Width = 0, LineDashStyle = OFT.Rendering.Settings.LineDashStyle.Dash };
    ValueDataSeries _rvahs = new ValueDataSeries("RVAHS") { Color = Colors.LightBlue, Width = 0, LineDashStyle = OFT.Rendering.Settings.LineDashStyle.Dash };
    ValueDataSeries _typicalPrices = new ValueDataSeries("typicalPrices");
    ValueDataSeries _totalVolume = new ValueDataSeries("totalVolume");
    ValueDataSeries _totalVolumeVariance = new ValueDataSeries("totalVolumeVariance");
    ValueDataSeries _totalVolToClose = new ValueDataSeries("volToClose");


    private readonly RangeDataSeries _upper2Background = new RangeDataSeries("UpperFill2")
    {
        RangeColor = Color.FromArgb(75, 0, 255, 0),
        DrawAbovePrice = false
    };
    private readonly RangeDataSeries _upper1Background = new RangeDataSeries("UpperFill1")
    {
        RangeColor = Color.FromArgb(0, 0, 0, 0),
        DrawAbovePrice = false
    };

    private readonly RangeDataSeries _midDownBackground = new RangeDataSeries("MiddleFillDown")
    {
        RangeColor = Color.FromArgb(75, 75, 75, 75),
        DrawAbovePrice = false
    };

    private readonly RangeDataSeries _midUpBackground = new RangeDataSeries("MiddleFillUp")
    {
        RangeColor = Color.FromArgb(75, 75, 75, 75),
        DrawAbovePrice = false

    };

    private readonly RangeDataSeries _lower1Background = new RangeDataSeries("LowerFill1")
    {
        RangeColor = Color.FromArgb(0, 0, 255, 0),
        DrawAbovePrice = false
    };

    private readonly RangeDataSeries _lower2Background = new RangeDataSeries("LowerFill2")
    {
        RangeColor = Color.FromArgb(75, 255, 0, 0),
        DrawAbovePrice = false
    };


    #endregion

    public RVWAP7()
    {
        var series = (ValueDataSeries)DataSeries[0];
        series.Color = Colors.White;
        DataSeries.Add(_lower1);
        DataSeries.Add(_upper1);
        DataSeries.Add(_lower2);
        DataSeries.Add(_upper2);
        DataSeries.Add(_lower3);
        DataSeries.Add(_upper3);
        DataSeries.Add(_upper2Background);
        DataSeries.Add(_upper1Background);
        DataSeries.Add(_midUpBackground);
        DataSeries.Add(_midDownBackground);
        DataSeries.Add(_lower1Background);
        DataSeries.Add(_lower2Background);
        DataSeries.Add(_rvahs);
        DataSeries.Add(_rvals);
    }

    protected override void OnCalculate(int bar, decimal value)
    {

        if (_previous24hPeriodStart > bar)
            _previous24hPeriodStart = 0;

        if (bar == 0)
        {
            _rval = 0;
            _rvah = 0;
            _lastAlert = -1;
            _previous24hPeriodStart = bar;
            _prevClose = GetCandle(CurrentBar - 1).Close;
            this[bar] = _upper1[bar] = _lower1[bar] = _upper2[bar] = _lower2[bar] = _upper3[bar] = _lower3[bar] = GetCandle(bar).Close;
            return;
        }
        if (GetCandle(bar).Volume == 0) return;

        var candle = GetCandle(bar);


        if (bar != _lastbar)
        {
            _lastbar = bar;

            #region real 24h... 
            TimeSpan timeToFind = new TimeSpan(0, 0, 0);

            switch (PeriodType)
            {
                case RVWAPPeriodType.Minutes:
                    timeToFind = new TimeSpan(0, _period, 0);
                    break;
                case RVWAPPeriodType.Hours:
                    timeToFind = new TimeSpan(_period, 0, 0);
                    break;
                case RVWAPPeriodType.Days:
                    timeToFind = new TimeSpan(_period,0, 0, 0);
                    break;
                case RVWAPPeriodType.Weeks:
                    timeToFind = new TimeSpan(_period*7, 0,0, 0, 0);
                    break;
            }

            var ThisBarcandle = GetCandle(bar);
            var BarTime = ThisBarcandle.Time;
            var barTimeTimeToFind = BarTime.Subtract(timeToFind);

            int iterationMax = 10000;
            int iteration = 0;

            while (_candleCount == 0)
            {
                if (iteration > iterationMax)
                {
                    this.LogError($"ITERATION MAX");
                    break;
                }
                iteration++;

                int firstBar = 0;
                int lastBar = bar;

                if (_previous24hPeriodStart != 0)
                    firstBar = _previous24hPeriodStart;

                var firstCandle = GetCandle(firstBar);
                var lastCandle = GetCandle(lastBar);

                var firstTime = firstCandle.Time;
                var lastTime = lastCandle.Time;


                if (barTimeTimeToFind < firstTime || barTimeTimeToFind > lastTime)
                {
                    //this.LogWarn($"Date not valid");
                    break;
                }

                for (int i = firstBar; i < lastBar; i++)
                {
                    var localCandle = GetCandle(i);
                    var nextCandle = GetCandle(i + 1);

                    if (barTimeTimeToFind >= localCandle.Time && barTimeTimeToFind <= nextCandle.Time)
                    {
                        //var startOfFirstBar = ChartInfo.PriceChartContainer.GetXByBar(i);
                        //var startOfSecondBar = ChartInfo.PriceChartContainer.GetXByBar(i + 1);
                        //var candlesTimeDiff = (nextCandle.Time - candle.Time).TotalMilliseconds;
                        //var timeDiff = (barTimeYesterday - candle.Time).TotalMilliseconds;
                        //var xOffset = timeDiff * (startOfSecondBar - startOfFirstBar) / candlesTimeDiff;

                        //this.LogWarn($"We should have a date {startOfFirstBar} :: {xOffset}");
                        //var barToReturn = i + xOffset;

                        _candleCount = Convert.ToInt32(bar - i);
                    }
                }
            }


            #endregion
        }


        if (_candleCount > 0)
        {

            _typicalPrices.Clear();
            _totalVolume.Clear();
            _totalVolumeVariance.Clear();

            int BarsToInclude = bar - _candleCount;
            if (BarsToInclude > 0)
            {
                for (int i = 0; i < _candleCount; i++)
                {
                    var localCandle = GetCandle(Math.Max(0, i + BarsToInclude));

                    var typicalPrice = (localCandle.High + localCandle.Low + localCandle.Close) / 3;
                    _typicalPrices[bar] += typicalPrice * localCandle.Volume;
                    _totalVolume[bar] += localCandle.Volume;
                    _totalVolumeVariance[bar] += localCandle.Volume * (typicalPrice * typicalPrice);
                }
            }

            decimal rvwap = _typicalPrices[bar] / _totalVolume[bar];
            this[bar] = rvwap;
            decimal variance = _totalVolumeVariance[bar] / _totalVolume[bar] - (decimal)Math.Pow((double)rvwap, 2);
            variance = Math.Max(0, variance);
            var stdDev = (decimal)Math.Sqrt((double)variance);
            _sqrt[bar] = stdDev;
            //this.LogWarn($"RVAP {rvwap} \n TypicalPrices {_typicalPrices[bar]} \n TotalVolume {_totalVolume} \n TotalVolumeVar {_totalVolumeVariance[bar]} \n Variance {variance} \n STDEV {stdDev}");
            //this.LogWarn($"apply");


            var std = stdDev * _stdev1;
            var std1 = stdDev * _stdev2;
            var std2 = stdDev * _stdev3;

            _upper1[bar] = rvwap + std;
            _lower1[bar] = rvwap - std;
            _upper2[bar] = rvwap + std1;
            _lower2[bar] = rvwap - std1;
            _upper3[bar] = rvwap + std2;
            _lower3[bar] = rvwap - std2;

            _upper2Background[bar].Upper = _upper3[bar];
            _upper2Background[bar].Lower = _upper2[bar];

            _upper1Background[bar].Upper = _upper2[bar];
            _upper1Background[bar].Lower = _upper1[bar];

            _midUpBackground[bar].Upper = _upper1[bar];
            _midUpBackground[bar].Lower = rvwap;

            _midDownBackground[bar].Upper = rvwap;
            _midDownBackground[bar].Lower = _lower1[bar];

            _lower1Background[bar].Upper = _lower1[bar];
            _lower1Background[bar].Lower = _lower2[bar];

            _lower2Background[bar].Upper = _lower2[bar];
            _lower2Background[bar].Lower = _lower3[bar];

            _candleCount = 0;
            _previous24hPeriodStart = _candleCount;



            if (UseAlerts && _lastAlert != CurrentBar)
            {

                //alerts
                List<decimal> valuesToTestForAlert = new List<decimal>();

                valuesToTestForAlert.Add(rvwap);
                valuesToTestForAlert.Add(_upper1[bar]);
                valuesToTestForAlert.Add(_upper2[bar]);
                valuesToTestForAlert.Add(_upper3[bar]);
                valuesToTestForAlert.Add(_lower1[bar]);
                valuesToTestForAlert.Add(_lower2[bar]);
                valuesToTestForAlert.Add(_lower3[bar]);

                foreach (decimal item in valuesToTestForAlert)
                {
                    if ((candle.Close >= item && _prevClose < item) || (candle.Close <= item && _prevClose > item))
                    {
                        string AlertLine = StringForAlert[valuesToTestForAlert.IndexOf(item)];
                        AddAlert(AlertFile, InstrumentInfo.Instrument, $"RV: {AlertName} {AlertLine}", AlertBGColor, AlertForeColor);
                        _lastAlert = CurrentBar;
                    }
                }
            }
        }


        if(IsNewSession(bar))
        {
            _rvah = _upper1[bar];
            _rval = _lower1[bar];
        }

        if (_rvah > 0 && _rval > 0)
        {
            _rvahs[bar] = _rvah;
            _rvals[bar] = _rval;
        }

        _prevClose = candle.Close;

    }

    public enum RVWAPPeriodType
    {
        Minutes,
        Hours,
        Days,
        Weeks,
    }
}
12 comments

Log in to comment and vote

Comments12

  • Veri

    •

    Nov 22, 2023

    Hi you can already use the normal VWAP/TWAP indicator as a Rolling VWAP:

    -Set the Period to "Custom"
    -Enable "Custom starting point mode"
    -Set the "Days look back" to the rolling VWAP value

    Here is an example of my 7 day rolling VWAP settings:

    Hope this helps :)

  • Danny

    •

    Nov 27, 2023

    More important to me than a rolling vwap is one that is auto-anchored to a pre-determined time. Specifically, I always use 2 day vwap. ATAS will show you where yesterday's VWAP closed, but will not show you a continuation of the prior day's VWAP.