- ago
Any chance on seeing an "Anchored VWAP" Indicator, where we can set the anchor to a specific date (along with the time as is currently available)?

I've coded the Anchored VWAP as an indicator within ThinkorSwim successfully, but C# is still a challenge for me.
1
457
Solved
12 Replies

Reply

Bookmark

Sort
- ago
#1
C++ is a challenge for me, too. Perhaps someone could code it for you in C# if there's description of the indicator, and following your thinkScript example code might help.
0
Cone8
 ( 25.44% )
- ago
#2
Here's a good start for you to turn it into a custom indicator -

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; namespace WealthScript72 { public class MyStrategy : UserStrategyBase { public override void Initialize(BarHistory bars) {          DateTime startDateTime = new DateTime(2023, 11, 1, 13, 30, 0);          TimeSeries myvwap = VWAP2(bars, startDateTime);          PlotTimeSeries(myvwap, "VWAP2", "Price", WLColor.NeonYellow); }       TimeSeries VWAP2(BarHistory bars, DateTime startDateTime)       {                   TimeSeries vwap2 = new TimeSeries(bars.DateTimes, true);          int startIdx = bars.IndexOf(startDateTime);          if (startIdx > -1)          {             double d = 0, cvol = 0;             for (int bar = startIdx; bar < bars.Count; bar++)             {                d += (bars.AveragePriceHLC[bar] * bars.Volume[bar]);                cvol += bars.Volume[bar];                vwap2[bar] = d / cvol;             }          }          return vwap2;       }        public override void Execute(BarHistory bars, int idx) { } } }


1
- ago
#3
Thank you very much, Cone!

But, I'm having difficulty saving this indicator even while in Windows Admin mode as required, and even after the code compiles OK, I still receive an "Error populating indicator: "Object reference not set to an instance of an object."
I believe that I did provide the necessary Indicator Properties.
0
Cone8
 ( 25.44% )
- ago
#4
You have to make it into an indicator. This code doesn't do that.
0
- ago
#5
Oh, well, THAT's a disappointment. I thought that the code you were offering was a "starting point" toward making a custom indicator, so I carefully read the F1 help page on creating a custom indicator which I could accomplish, using your code.

I'm not a coder. I suspect many of us are not coders on this platform. There should be a much easier way to accomplish something like this, IMO.
0
- ago
#6
QUOTE:
There should be a much easier way to accomplish something like this, IMO.

A much easier way to code a custom logic in C#? Yes, it's called ChatGPT.

0
Cone8
 ( 25.44% )
- ago
#7
What ideas do you have to program a new indicator in an easier way?

It will only take about 5 minutes to turn that into an indicator, so I'll do it since this gives me another idea for another indicator that could use this one.

Also, we have the Concierge Service for custom programming jobs and consulting.
0
- ago
#8
Here you go. With this anchored VWAP indicator you can anchor by date/time or by length (sliding window of n bars). Note that the date dropdown is kind of hard to read if you're using WL8 dark mode...

CODE:
using System; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { /// <summary> /// Anchored VWAP indicator that allows anchoring VWAP by date/time or by an n-bar sliding window. /// </summary> public class AnchoredVwap : IndicatorBase { private const int ParamIndexSource = 0; private const int ParamIndexAnchorDate = 1; private const int ParamIndexUseLength = 2; private const int ParamIndexLength = 3; public AnchoredVwap() { // for WL 8 - do not remove } public AnchoredVwap(BarHistory source, DateTime anchorDate, bool useLength, int length) { Parameters[ParamIndexSource].Value = source; Parameters[ParamIndexAnchorDate].Value = anchorDate; Parameters[ParamIndexUseLength].Value = useLength; Parameters[ParamIndexLength].Value = length; Populate(); } private BarHistory Source => Parameters[ParamIndexSource].AsBarHistory; private DateTime AnchorDateTime => Parameters[ParamIndexAnchorDate].AsDate; private bool UseLength => Parameters[ParamIndexUseLength].AsBoolean; private int Length => Parameters[ParamIndexLength].AsInt; public override string Name => "Anchored VWAP"; public override string Abbreviation => "Anchored VWAP"; public override string HelpDescription => "Anchored VWAP by date or bar count"; public override string PaneTag => "Price"; public override WLColor DefaultColor { get; } = WLColor.Purple; public override void Populate() { DateTimes = Source.DateTimes; double cumulativePriceVolume = 0, cumulativeVolume = 0; // be speedy var source = Source; var hlc = source.AveragePriceHLC; var volume = source.Volume; if (UseLength) { // sliding window... var length = Length; if (length < 1) { // For an invalid length don't throw an exception because it will cause a problem when using building blocks // as of WL 8 Build 54. // When running a strategy or dragged to a chart, all values will be NaN. // So, dont' use an invalid length. return; } for (var idx = 0; idx < length - 1; idx++) { cumulativePriceVolume += hlc[idx] * volume[idx]; cumulativeVolume += volume[idx]; } var removeIdx = 0; for (var idx = length - 1; idx < source.Count; idx++) { cumulativePriceVolume += hlc[idx] * volume[idx]; cumulativeVolume += volume[idx]; Values[idx] = cumulativePriceVolume / cumulativeVolume; // remove oldest from accumulators cumulativePriceVolume -= hlc[removeIdx] * volume[removeIdx]; cumulativeVolume -= volume[removeIdx]; removeIdx++; } } else { // fixed date/time... var startIdx = Source.IndexOf(AnchorDateTime); if (startIdx > -1) { for (var idx = startIdx; idx < source.Count; idx++) { cumulativePriceVolume += hlc[idx] * volume[idx]; cumulativeVolume += volume[idx]; Values[idx] = cumulativePriceVolume / cumulativeVolume; } } } } public static AnchoredVwap Series(BarHistory source, DateTime anchorDate, bool useLength, int length) { var key = CacheKey("AnchoredVWAP", anchorDate, useLength, length); if (source.Cache.TryGetValue(key, out var vwap)) { return (AnchoredVwap) vwap; } var result = new AnchoredVwap(source, anchorDate, useLength, length); source.Cache[key] = result; return result; } protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); AddParameter("Anchor Date", ParameterType.Date, DateTime.Now.Date); AddParameter("Use length instead of anchor date?", ParameterType.Boolean, false); AddParameter("Length", ParameterType.Int32, 20); } } }
1
- ago
#9
Thank you, Paul1986!! That worked like a charm!
0
- ago
#10
I improved the indicator by allowing a TimeSeries to be specified for the price component (the P) in VWAP. This way you can use any TimeSeries for the price instead of the previously hardcoded AveragePriceHLC. Of course, the TimeSeries should ideally be derived from and synchronized with the source bars.

The TimeSeries defaults to AveragePriceHLC of the bars. This is a breaking change to the code of Post #8 above because of the TimeSeries parameter was added...

CODE:
using System; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { /// <summary> /// Anchored VWAP indicator that allows anchoring VWAP by date/time or by an n-bar sliding window. /// The price (the P in VWAP) is specified by a TimeSeries. This defaults to AveragePriceHLC. /// </summary> public class AnchoredVwap : IndicatorBase { private const int ParamIndexSource = 0; private const int ParamIndexTimeSeries = 1; private const int ParamIndexAnchorDate = 2; private const int ParamIndexUseLength = 3; private const int ParamIndexLength = 4; public AnchoredVwap() { // for WL 8 - do not remove } public AnchoredVwap(BarHistory source, TimeSeries timeSeries, DateTime anchorDate, bool useLength, int length) { Parameters[ParamIndexSource].Value = source; Parameters[ParamIndexTimeSeries].Value = timeSeries; Parameters[ParamIndexAnchorDate].Value = anchorDate; Parameters[ParamIndexUseLength].Value = useLength; Parameters[ParamIndexLength].Value = length; Populate(); } private BarHistory Source => Parameters[ParamIndexSource].AsBarHistory; private TimeSeries TimeSeries => Parameters[ParamIndexTimeSeries].AsTimeSeries; private DateTime AnchorDateTime => Parameters[ParamIndexAnchorDate].AsDate; private bool UseLength => Parameters[ParamIndexUseLength].AsBoolean; private int Length => Parameters[ParamIndexLength].AsInt; public override string Name => "Anchored VWAP"; public override string Abbreviation => "Anchored VWAP"; public override string HelpDescription => "Anchored VWAP by date or bar count"; public override string PaneTag => "Price"; public override WLColor DefaultColor { get; } = WLColor.Purple; public override void Populate() { double cumulativePriceVolume = 0, cumulativeVolume = 0; // be speedy var source = Source; var ts = TimeSeries; var volume = source.Volume; var tsFirstValidIndex = ts.FirstValidIndex; DateTimes = source.DateTimes; if (UseLength) { // sliding window... var length = Length; if (length < 1) { // For an invalid length don't throw an exception because it will cause a problem when using building blocks // as of WL 8 Build 54. // When running a strategy or dragged to a chart, all values will be NaN. // So, dont' use an invalid length. return; } var startIdx = tsFirstValidIndex + length - 1; // accumulate volume and price * volume to use for the first result value that can be generated for (var idx = tsFirstValidIndex; idx < startIdx && idx < ts.Count; idx++) { cumulativePriceVolume += ts[idx] * volume[idx]; cumulativeVolume += volume[idx]; } var removeIdx = tsFirstValidIndex; for (var idx = startIdx; idx < ts.Count; idx++) { cumulativePriceVolume += ts[idx] * volume[idx]; cumulativeVolume += volume[idx]; Values[idx] = cumulativeVolume == 0 ? double.NaN : cumulativePriceVolume / cumulativeVolume; // remove oldest from accumulators cumulativePriceVolume -= ts[removeIdx] * volume[removeIdx]; cumulativeVolume -= volume[removeIdx]; removeIdx++; } // if no valid values, just set to one past end of the time series FirstValidIndex = Math.Min(startIdx, ts.Count); } else { // fixed date/time... var startIdx = source.IndexOf(AnchorDateTime); if (startIdx > -1) { // if starting in an invalid index into the time series then // move up to the first valid index if (startIdx < tsFirstValidIndex) { startIdx = tsFirstValidIndex; } for (var idx = startIdx; idx < source.Count; idx++) { cumulativePriceVolume += ts[idx] * volume[idx]; cumulativeVolume += volume[idx]; Values[idx] = cumulativeVolume == 0 ? double.NaN : cumulativePriceVolume / cumulativeVolume; } FirstValidIndex = startIdx; } else { // no valid values, just set to one past end of the time series FirstValidIndex = ts.Count; } } } public static AnchoredVwap Series(BarHistory source, TimeSeries timeSeries, DateTime anchorDate, bool useLength, int length) { var key = CacheKey("AnchoredVWAP", timeSeries, anchorDate, useLength, length); if (source.Cache.TryGetValue(key, out var vwap)) { return (AnchoredVwap) vwap; } var result = new AnchoredVwap(source, timeSeries, anchorDate, useLength, length); source.Cache[key] = result; return result; } protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); AddParameter("Price", ParameterType.TimeSeries, PriceComponent.AveragePriceHLC); AddParameter("Anchor Date", ParameterType.Date, DateTime.Now.Date); AddParameter("Use length instead of anchor date?", ParameterType.Boolean, false); AddParameter("Length", ParameterType.Int32, 20); } } }
1
Best Answer
- ago
#11
Many Thanks, Paul! It works beautifully, and the WL8 Optimizer reveals some very interesting preliminary results which seem to contradict some popular web-based opinions re: anchor placement.

BTW, have you ever seen the Auto-Anchored VWAP at work?
Trading view has it in their platform.
Below is the technical definition in case you are interested.

https://www.tradingview.com/support/solutions/43000652199-vwap-auto-anchored/

Thanks, again, for your coding help.
2
- ago
#12
@Neverlong - thanks for the reference.
0

Reply

Bookmark

Sort