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,
    }
}

Please authenticate to join the conversation.

Upvoters
Status

In review

Board

💡 Feature Request

Date

Over 2 years ago

Author

Hannes Vaisanen

Subscribe to post

Get notified by email when there are changes.