karlwancl / Trady

Trady is a handy library for computing technical indicators, and it targets to be an automated trading system that provides stock data feeding, indicator computing, strategy building and automatic trading. It is built based on .NET Standard 2.0.

Home Page:https://lppkarl.github.io/Trady

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add VWAP indicator

codebeaulieu opened this issue · comments

I was very suprised to see that VWAP is not included in the Indicator list. What would it take to add this widely used indicator? Was it left out for a specific reason?

There are five steps in calculating VWAP:

  1. Calculate the Typical Price for the period. [(High + Low + Close)/3)]
  2. Multiply the Typical Price by the period Volume (Typical Price x Volume)
  3. Create a Cumulative Total of Typical Price. Cumulative(Typical Price x Volume)
  4. Create a Cumulative Total of Volume. Cumulative(Volume)
  5. Divide the Cumulative Totals.

VWAP = Cumulative(Typical Price x Volume) / Cumulative(Volume)

source: https://www.tradingview.com/wiki/Volume_Weighted_Average_Price_(VWAP)

@lppkarl Could you provide some high-level guidance on this by chance?

I'm attempting to wire this indicator up on my own but it looks like the closest base class to what I need is CumulativeNumericAnalyzableBase, which provides a single means of accumulating data. It appears what I am after is a CumulativeNumericAnalyzableBase /DualCumulativeAnalyzableBase pair since I need to accumulate both Typical Price and Volume separately in order to divide it later.

Am I thinking about this the correct way?

Any guidance would be greatly appriciated and I'll be happy to submit a pull request once the indicator is complete.

@codebeaulieu sorry for the late reply as I was very busy for my job. You may think it like this:
If you want to get the vwap, you have to get the Cumulative(Typical Price x Volume) & Cumulative(Volume) first, this will be treated as 2 different Cumulative indicators.

The vwap indicator should be implemented like this:

public class VWAP<TInput, TOutput>: NumericAnalyzableBase<TInput, (decimal High, decimal Low, decimal Close, decimal Volume), TOutput>
{
    private CumulativeTypicalPriceVolumeProduct _indicator1;
    private CumulativeVolume _indicator2;

    public VWAP(some parameters here)
    {
        _indicator1 = new CumulativeTypicalPriceVolumeProduct(...);
        _indicator2 = new CumulativeVolume(...);
    }

    protected override decimal? ComputeByIndexImpl(IReadOnlyList<...> list, int index)
    {
        return _indicator1[index]/_indicator2[index];
    }
} 

And the 2 cumulative indicator should be implemented using the cumulativeNumericAnalyzableBase like this:

public class CumulativeVolume <TInput, TOutput>: CumulativeNumericAnalyzableBase<TInput, decimal?, TOutput>
{
    public CumulativeVolume(some parameters here)
    {
    }

    protected override decimal? ComputeCumulativeValue(IReadOnlyList<...> list, int index, decimal? prevOutputToMap)
    {
        return prevOutputToMap + list[index];
    }
}

Of course, the above 2 only shows a draft of the indicators, the central idea is to divide the indicator into different manageable sub indicators and solve the individual indicators. i.e. VWAP consists of 2 cumulative indicators, then we make VWAP a normal indicator which calls the 2 cumulative indicators.

@lppkarl Alright, I believe I may have this correct. The data I have at my disposal is IEX data, which, if I understand correctly doesn't reflect volume accurately. I'll seek out a better source here this evening so I can test properly. But from my current unit tests, this is definitely returning reasonable values.

Could you review my implementation and let me know if this looks correct to you.

VolumeWeightedAveragePrice

 public class VolumeWeightedAveragePrice<TInput, TOutput> : NumericAnalyzableBase<TInput, (decimal High, decimal Low, decimal Close, decimal Volume), TOutput>
    {
        private CumulativeTypicalPriceVolumeProductByTuple _indicator1;
        private CumulativeVolumeByTuple _indicator2;

        public VolumeWeightedAveragePrice(IEnumerable<TInput> inputs, Func<TInput, (decimal High, decimal Low, decimal Close, decimal Volume)> inputMapper) : base(inputs, inputMapper)
        {
            _indicator1 = new CumulativeTypicalPriceVolumeProductByTuple(inputs.Select(inputMapper));
            _indicator2 = new CumulativeVolumeByTuple(inputs.Select(inputMapper));
        } 

        protected override decimal? ComputeByIndexImpl(IReadOnlyList<(decimal High, decimal Low, decimal Close, decimal Volume)> mappedInputs, int index)
        {
            if (_indicator1[index] == 0M || _indicator2[index] == 0M)
                return 0M;

            var result = _indicator1[index] / _indicator2[index];
            System.Diagnostics.Debug.WriteLine($"{result}");
            return result;
        }
    }

    public class VolumeWeightedAveragePriceByTuple : VolumeWeightedAveragePrice<(decimal High, decimal Low, decimal Close, decimal Volume), decimal?>
    {
        public VolumeWeightedAveragePriceByTuple(IEnumerable<(decimal High, decimal Low, decimal Close, decimal Volume)> inputs)
            : base(inputs, i => i)
        {
        }
    }

    public class VolumeWeightedAveragePrice : VolumeWeightedAveragePrice<IOhlcv, AnalyzableTick<decimal?>>
    {
        public VolumeWeightedAveragePrice(IEnumerable<IOhlcv> inputs)
            : base(inputs, c => (c.High, c.Low, c.Close, c.Volume))
        {
        }
    }
public class CumulativeVolume<TInput, TOutput>
       : CumulativeNumericAnalyzableBase<TInput, (decimal High, decimal Low, decimal Close, decimal Volume), TOutput>
    {
        public CumulativeVolume(IEnumerable<TInput> inputs, Func<TInput, (decimal High, decimal Low, decimal Close, decimal Volume)> inputMapper) : base(inputs, inputMapper)
        {
         
        }
 
        protected override decimal? ComputeNullValue(IReadOnlyList<(decimal High, decimal Low, decimal Close, decimal Volume)> mappedInputs, int index) => null;

        protected override decimal? ComputeCumulativeValue(IReadOnlyList<(decimal High, decimal Low, decimal Close, decimal Volume)> mappedInputs, int index, decimal? prevOutputToMap)
        { 
            return prevOutputToMap + mappedInputs[index].Volume;
        }

        protected override decimal? ComputeInitialValue(IReadOnlyList<(decimal High, decimal Low, decimal Close, decimal Volume)> mappedInputs, int index) => mappedInputs.ElementAt(index).Volume;
 
    }

    public class CumulativeVolumeByTuple : CumulativeVolume<(decimal High, decimal Low, decimal Close, decimal Volume), decimal?>
    {
        public CumulativeVolumeByTuple(IEnumerable<(decimal High, decimal Low, decimal Close, decimal Volume)> inputs)
            : base(inputs, i => i)
        {
        }
    }

    public class CumulativeVolume : CumulativeVolume<IOhlcv, AnalyzableTick<decimal?>>
    {
        public CumulativeVolume(IEnumerable<IOhlcv> inputs)
            : base(inputs, c => (c.High, c.Low, c.Close, c.Volume))
        {
        }
    }
    public class CumulativeTypicalPriceVolumeProduct<TInput, TOutput>
        : CumulativeNumericAnalyzableBase<TInput, (decimal High, decimal Low, decimal Close, decimal Volume), TOutput>
    {
        public CumulativeTypicalPriceVolumeProduct(IEnumerable<TInput> inputs, Func<TInput, (decimal High, decimal Low, decimal Close, decimal Volume)> inputMapper)
            : base(inputs, inputMapper)
        {
    
        }
  
        protected override decimal? ComputeNullValue(IReadOnlyList<(decimal High, decimal Low, decimal Close, decimal Volume)> mappedInputs, int index) => null;

        protected override decimal? ComputeCumulativeValue(IReadOnlyList<(decimal High, decimal Low, decimal Close, decimal Volume)> mappedInputs, int index, decimal? prevOutputToMap)
        {
            var (High, Low, Close, Volume) = mappedInputs[index]; 
  
            var typicalPrice = (High + Low + Close) / 3;
 
            return prevOutputToMap + (typicalPrice * Volume); 
        }

        protected override decimal? ComputeInitialValue(IReadOnlyList<(decimal High, decimal Low, decimal Close, decimal Volume)> mappedInputs, int index) => mappedInputs.ElementAt(index).Volume;


    }

    public class CumulativeTypicalPriceVolumeProductByTuple : CumulativeTypicalPriceVolumeProduct<(decimal High, decimal Low, decimal Close, decimal Volume), decimal?>
    {
        public CumulativeTypicalPriceVolumeProductByTuple(IEnumerable<(decimal High, decimal Low, decimal Close, decimal Volume)> inputs)
            : base(inputs, i => i)
        {
        }
    }

    public class CumulativeTypicalPriceVolumeProduct : CumulativeTypicalPriceVolumeProduct<IOhlcv, AnalyzableTick<decimal?>>
    {
        public CumulativeTypicalPriceVolumeProduct(IEnumerable<IOhlcv> inputs)
            : base(inputs, c => (c.High, c.Low, c.Close, c.Volume))
        {
        }
    }

One thing that I defnitely need to implement is the ability to start fresh each day, which is what VWAP typically does. Do you have an idea or an example on how to do this?

Thanks!

@codebeaulieu I will review it at this weekend. For your 2nd question, i don't really understand what you mean by "start fresh each day", does it mean for each day, it would be a new set of data and the data of old days would be ignored? If it's the case, just pass the minutes data in input per each day.

@lppkarl The magenta line is VWAP. Notice how it basically resets or normalizes at the beginning of the day?

screen_shot_2018-12-18_at_7_20_41_pm

By defauly, VWAP only shows you the current days data, which is why its an intraday indicator and ususally is omitted from daily charts.

I have some ideas on how to implement this, but I was wondering what your thoughts were.

Thanks! I know you are busy, I appriciate your time.

Whoops, just noticed you said: If it's the case, just pass the minutes data in input per each day.

I'll give this a shot

@lppkarl I was about to ask if you wanted me to submit a pull request. However, I noticed you actually already have a branch with a VWAP implementation. Is there any reason why it has never been merged in?

https://github.com/lppkarl/Trady/blob/vwap/Trady.Analysis/Indicator/VolumeWeightedAveragePrice.cs

Let me know.

@codebeaulieu it's because I haven't created the unit test case on the indicator, and i couldn't find a third party site that has the indicator for me to verify my algorithm. That's the main reason.

@lppkarl I understand.

How do you feel about testing against market data and a chart? That's how I've been testing your indicators and it's been working very well.

Basically I pull data into a CSV file, say NFLX for example. I'll store the 1 minute bars and aggregate them to suit the needs of my tests if needed.

A positive VWAP test would be

  • Observe VWAP on NFLX chart at 320.50 at 10:00AM ET, Assert that VWAP is approx equal to 320.50 at the exact same time in my data.

Do a few negative tests

  • VWAP is not 320.60
  • VWAP is not 320.40

Is this an approach you are open to? If so I'll write a few unit tests if it helps validate the indicator.

@codebeaulieu Does it mean you have a third party chart that can give the VWAP value for you? If that's the case, it's okay to have the test case like yours.

Yes, for example in the image I shared above (a few posts back) I have VWAP (magenta).

In my unit tests I ensure my indicator settings are exactly the same and then perform the tests. For what its worth, I am trading live with your indicators and they perform exactly how I'd expect them to from my unit tests. So I believe this to be a good approach.

If you are cool with this approach to testing, and to having a few extra CSV files in your Unit Test project, I will definitely write unit tests against that VWAP indicator and issue a pull request.

@codebeaulieu okay, i'm okay with the approach, let's do it in this way. Thanks.

Merged master into the VWAP branch and added 3 passing unit tests with fresh data.
Pull Request #83
@lppkarl