anchoredVWAP {code}

After using RMB to initiate label move with my latency indicator I liked the RMB control and figured it could work well with indicators that are interactive, like anchored VWAP

#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.NinjaScript;
#endregion

// This namespace holds Indicators in this folder and is required. Do not change it.
namespace NinjaTrader.NinjaScript.Indicators
{
    public class AnchoredVwap : Indicator
    {
        // =====================================================================
        // Inner types
        // =====================================================================

        private sealed class VwapAnchor
        {
            /// <summary>Absolute bar index at which this VWAP is anchored.</summary>
            public int      BarIndex;

            /// <summary>
            /// VWAP value per bar index.
            ///   β€’ Indices below BarIndex          β†’ NaN (before anchor)
            ///   β€’ Indices BarIndex..lastClosed     β†’ computed VWAP
            ///   β€’ Indices beyond lastClosed        β†’ NaN (data thread fills on close)
            ///
            /// Threading contract:
            ///   Writer β€” OnBarUpdate (data thread):  writes index = CurrentBar.
            ///   Writer β€” AddAnchorInternal (UI thread): writes all indices while the
            ///             object is still private (not yet in the shared list).
            ///   Reader β€” RebuildGeometries (render thread): reads indices ≀ _lastClosedBar.
            ///
            ///   CurrentBar (data write) and _lastClosedBar (render read ceiling) advance
            ///   together: the volatile write to _lastClosedBar in OnBarUpdate acts as a
            ///   release fence that makes anchor.Values[CurrentBar] visible to any thread
            ///   that subsequently reads _lastClosedBar with an acquire fence.  Because
            ///   the render thread caps its reads at _lastClosedBar, data and render
            ///   accesses never touch the same index concurrently β€” no per-element lock
            ///   is required.
            ///
            ///   The _anchorLock guards only list-structure mutations (Add/Remove) and
            ///   the _arrayCapacity field.
            /// </summary>
            public double[] Values;

            /// <summary>Index into Palette[] (0–MaxAnchors-1), determines line color.</summary>
            public int      ColorIndex;
        }

        // Render-thread-only β€” not shared with data or UI threads.
        private sealed class RenderItem
        {
            public SharpDX.Direct2D1.PathGeometry Geometry;
            public int                            ColorIndex;
        }

        // =====================================================================
        // Constants
        // =====================================================================

        private const int MaxAnchors = 8;

        // One distinct color per anchor slot.  Cycles if anchors are added and removed.
        private static readonly SharpDX.Color4[] Palette =
        {
            new SharpDX.Color4(0.0f, 1.0f, 1.0f, 1.0f),   // 0 – cyan
            new SharpDX.Color4(1.0f, 1.0f, 0.0f, 1.0f),   // 1 – yellow
            new SharpDX.Color4(0.2f, 1.0f, 0.2f, 1.0f),   // 2 – lime
            new SharpDX.Color4(1.0f, 0.5f, 0.0f, 1.0f),   // 3 – orange
            new SharpDX.Color4(0.6f, 0.4f, 1.0f, 1.0f),   // 4 – violet
            new SharpDX.Color4(1.0f, 0.4f, 0.7f, 1.0f),   // 5 – pink
            new SharpDX.Color4(0.4f, 0.8f, 1.0f, 1.0f),   // 6 – sky blue
            new SharpDX.Color4(1.0f, 1.0f, 1.0f, 1.0f),   // 7 – white
        };

        // =====================================================================
        // Member variables β€” data thread (OnBarUpdate)
        // =====================================================================

        // Prefix-sum arrays indexed by absolute bar index:
        //   _cumPV[i]  = Ξ£(TP Γ— Vol) for bars 0..i   (TP = (H+L+C)/3)
        //   _cumVol[i] = Ξ£(Vol)       for bars 0..i
        //
        // VWAP for anchor A over range [A, i]:
        //   (_cumPV[i] - _cumPV[A-1]) / (_cumVol[i] - _cumVol[A-1])
        //
        // Written exclusively by OnBarUpdate.
        // Read by the UI thread only at indices ≀ _lastClosedBar (see volatile note below).
        private double[] _cumPV;
        private double[] _cumVol;

        // Guarded by _anchorLock when written from EnsureCapacity so that
        // AddAnchorInternal always reads a value consistent with the actual array sizes.
        private int _arrayCapacity;

        // Volatile: written as the LAST step in OnBarUpdate (release fence) so all
        // preceding array writes are visible to any thread that reads this value
        // (acquire fence).  The render thread uses it as the draw ceiling;
        // the UI thread uses it to bound the historical fill in AddAnchorInternal.
        private volatile int _lastClosedBar = -1;

        // =====================================================================
        // Member variables β€” shared (lock _anchorLock)
        // =====================================================================

        private readonly List<VwapAnchor> _anchors    = new List<VwapAnchor>(MaxAnchors);
        private readonly object           _anchorLock = new object();

        // Monotonically increasing; mod Palette.Length when assigning a color.
        // Ensures each new anchor gets the next color in the cycle regardless of
        // which slots were previously freed.
        private int _nextColorIdx = 0;

        // =====================================================================
        // Member variables β€” render thread
        // =====================================================================

        private SharpDX.Direct2D1.SolidColorBrush[] _paletteBrushes;
        private RenderItem[]                         _renderItems   = new RenderItem[0];
        private volatile bool                        _geometryDirty = true;
        private int                                  _lastFirstBar  = -1;
        private int                                  _lastLastBar   = -1;
        private double                               _lastScaleMin  = double.NaN;
        private double                               _lastScaleMax  = double.NaN;

        // =====================================================================
        // Member variables β€” UI thread (mouse state)
        // =====================================================================

        private bool            _rmbDown;
        private Point           _rmbDownPos;
        private bool            _rmbTriggered; // true once the timer fires and places/removes anchor
        private DispatcherTimer _rmbTimer;     // fires after RmbHoldSeconds while RMB is still held

        // =====================================================================

        public override string DisplayName => string.Empty;

        // =====================================================================
        // OnStateChange
        // =====================================================================

        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Description  = "Anchored VWAP β€” hold right mouse button on any candle for the " +
                               "configured delay to place a VWAP anchor.  Hold again on the same " +
                               "candle to remove it.  Supports up to 8 simultaneous anchors, " +
                               "each drawn in a distinct color.";
                Name         = "AnchoredVwap";
                Calculate    = Calculate.OnBarClose;   // we only draw through last closed bar
                IsOverlay    = true;
                DisplayInDataBox         = false;
                DrawOnPricePanel         = true;
                IsAutoScale              = false;
                IsSuspendedWhileInactive = true;
                BarsRequiredToPlot       = 0;          // allow anchoring at bar 0

                LineWidth      = 1.5f;
                RmbHoldSeconds = 1.0;
            }
            else if (State == State.DataLoaded)
            {
                int initCap    = Math.Max(Bars.Count + 512, 2048);
                _cumPV         = new double[initCap];
                _cumVol        = new double[initCap];
                _arrayCapacity = initCap;
                SubscribeMouseEvents();
            }
            else if (State == State.Terminated)
            {
                UnsubscribeMouseEvents();
                DisposeRenderResources();
            }
        }

        // =====================================================================
        // DirectX resource management (render thread)
        // =====================================================================

        public override void OnRenderTargetChanged()
        {
            DisposeRenderResources();
            _geometryDirty = true;

            if (RenderTarget == null) return;

            _paletteBrushes = new SharpDX.Direct2D1.SolidColorBrush[Palette.Length];
            for (int i = 0; i < Palette.Length; i++)
                _paletteBrushes[i] = new SharpDX.Direct2D1.SolidColorBrush(RenderTarget, Palette[i]);
        }

        private void DisposeRenderResources()
        {
            if (_paletteBrushes != null)
            {
                foreach (var b in _paletteBrushes)
                    b?.Dispose();
                _paletteBrushes = null;
            }
            DisposeGeometries();
        }

        private void DisposeGeometries()
        {
            foreach (var item in _renderItems)
                item?.Geometry?.Dispose();
            _renderItems = new RenderItem[0];
        }

        // =====================================================================
        // Data processing β€” OnBarUpdate (data thread, Calculate.OnBarClose)
        // =====================================================================

        protected override void OnBarUpdate()
        {
            EnsureCapacity(CurrentBar + 1);

            // Extend prefix sums to include this newly-closed bar.
            double tp      = (High[0] + Low[0] + Close[0]) / 3.0;
            double prevPV  = CurrentBar > 0 ? _cumPV[CurrentBar - 1]  : 0.0;
            double prevVol = CurrentBar > 0 ? _cumVol[CurrentBar - 1] : 0.0;
            _cumPV[CurrentBar]  = prevPV  + tp * Volume[0];
            _cumVol[CurrentBar] = prevVol + Volume[0];

            // Append this bar's VWAP to every active anchor.
            // ALL writes here must precede the volatile write to _lastClosedBar below
            // so the render thread sees them after its acquire read.
            lock (_anchorLock)
            {
                foreach (var anchor in _anchors)
                {
                    if (CurrentBar < anchor.BarIndex) continue;

                    // Safety: anchor may have been added between our last EnsureCapacity
                    // call and now, giving it a Values array that is one bar too short.
                    if (CurrentBar >= anchor.Values.Length)
                        ExpandAnchorValues(anchor, _arrayCapacity);

                    double oPV   = anchor.BarIndex > 0 ? _cumPV[anchor.BarIndex - 1]  : 0.0;
                    double oVol  = anchor.BarIndex > 0 ? _cumVol[anchor.BarIndex - 1] : 0.0;
                    double denom = _cumVol[CurrentBar] - oVol;
                    anchor.Values[CurrentBar] = denom > 0
                        ? (_cumPV[CurrentBar] - oPV) / denom
                        : double.NaN;
                }
            }

            // Volatile write (release fence) β€” must be the final operation so the render
            // thread's acquire read of _lastClosedBar guarantees it sees all array writes above.
            _lastClosedBar = CurrentBar;

            // Only flag geometry dirty in realtime; historical OnRender is suppressed anyway.
            if (State == State.Realtime)
                _geometryDirty = true;
        }

        private void EnsureCapacity(int required)
        {
            if (required <= _arrayCapacity) return;

            int newCap = Math.Max(_arrayCapacity * 2, required + 256);
            Array.Resize(ref _cumPV,  newCap);
            Array.Resize(ref _cumVol, newCap);

            lock (_anchorLock)
            {
                foreach (var anchor in _anchors)
                    ExpandAnchorValues(anchor, newCap);

                // Commit inside the lock: AddAnchorInternal reads _arrayCapacity under the
                // same lock, so it always sees the value that matches the actual array sizes.
                _arrayCapacity = newCap;
            }
        }

        /// <summary>
        /// Grows <paramref name="anchor"/>.Values to <paramref name="newCap"/> elements,
        /// filling every new slot with NaN.
        /// </summary>
        /// <remarks>
        /// Must be called with _anchorLock held when invoked from EnsureCapacity.
        /// No lock is required when called from AddAnchorInternal because the anchor
        /// object is not yet in the shared list at that point.
        /// </remarks>
        private static void ExpandAnchorValues(VwapAnchor anchor, int newCap)
        {
            int old = anchor.Values.Length;
            if (newCap <= old) return;
            Array.Resize(ref anchor.Values, newCap);
            for (int i = old; i < newCap; i++)
                anchor.Values[i] = double.NaN;
        }

        // =====================================================================
        // Rendering (render thread)
        // =====================================================================

        protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
        {
            if (State == State.Historical || RenderTarget == null) return;
            if (_paletteBrushes == null) OnRenderTargetChanged();

            int lastClosed = _lastClosedBar;   // volatile read β€” acquire fence
            if (lastClosed < 0) return;

            int firstBar = ChartBars.FromIndex;
            int lastBar  = Math.Min(ChartBars.ToIndex, lastClosed);
            if (lastBar < firstBar) return;

            // Viewport change (scroll / zoom) or price-scale change requires a geometry rebuild.
            if (firstBar != _lastFirstBar || lastBar != _lastLastBar
                || chartScale.MinValue != _lastScaleMin || chartScale.MaxValue != _lastScaleMax)
            {
                _lastFirstBar  = firstBar;
                _lastLastBar   = lastBar;
                _lastScaleMin  = chartScale.MinValue;
                _lastScaleMax  = chartScale.MaxValue;
                _geometryDirty = true;
            }

            if (_geometryDirty)
            {
                RebuildGeometries(chartControl, chartScale, firstBar, lastBar);
                _geometryDirty = false;
            }

            // Draw β€” one call per anchor, no math.
            foreach (var item in _renderItems)
            {
                if (item?.Geometry == null || item.Geometry.IsDisposed) continue;
                RenderTarget.DrawGeometry(item.Geometry, _paletteBrushes[item.ColorIndex], LineWidth);
            }
        }

        /// <summary>
        /// Rebuilds the Direct2D path geometry for each active anchor, covering
        /// only the currently visible bar range.  Called from OnRender when dirty.
        /// </summary>
        private void RebuildGeometries(ChartControl chartControl, ChartScale chartScale,
                                       int firstBar, int lastBar)
        {
            DisposeGeometries();

            if (ChartBars == null) return;

            // Take a snapshot of the anchor list under the lock.  The (potentially slow)
            // geometry-building loop runs outside the lock so we don't stall OnBarUpdate
            // or the UI mouse handlers.
            VwapAnchor[] snapshot;
            lock (_anchorLock)
            {
                snapshot = _anchors.Count > 0 ? _anchors.ToArray() : new VwapAnchor[0];
            }

            _renderItems = new RenderItem[snapshot.Length];

            for (int i = 0; i < snapshot.Length; i++)
            {
                VwapAnchor anchor  = snapshot[i];
                double[]   values = anchor.Values;  // capture ref β€” safe; see VwapAnchor threading contract
                int        start  = Math.Max(firstBar, anchor.BarIndex);

                // Always create the item so the draw loop has a valid (possibly
                // geometry-less) entry for every anchor.
                _renderItems[i] = new RenderItem { ColorIndex = anchor.ColorIndex };

                if (start > lastBar) continue;  // anchor scrolled off the left edge, or no data yet

                SharpDX.Direct2D1.PathGeometry geo = null;
                bool figureOpen = false;

                try
                {
                    geo = new SharpDX.Direct2D1.PathGeometry(RenderTarget.Factory);

                    // GeometrySink must be Close()d before the geometry can be drawn,
                    // and Dispose()d to release the underlying COM reference.
                    var sink = geo.Open();
                    try
                    {
                        for (int b = start; b <= lastBar; b++)
                        {
                            if (b >= values.Length) break;      // guard: in-flight resize

                            double val = values[b];
                            if (double.IsNaN(val)) continue;    // gap (zero-volume bar, pre-anchor)

                            float x = (float)chartControl.GetXByBarIndex(ChartBars, b);
                            float y = (float)chartScale.GetYByValue(val);

                            if (!figureOpen)
                            {
                                sink.BeginFigure(
                                    new SharpDX.Vector2(x, y),
                                    SharpDX.Direct2D1.FigureBegin.Hollow);
                                figureOpen = true;
                            }
                            else
                            {
                                sink.AddLine(new SharpDX.Vector2(x, y));
                            }
                        }

                        if (figureOpen)
                            sink.EndFigure(SharpDX.Direct2D1.FigureEnd.Open);

                        sink.Close();
                    }
                    finally
                    {
                        sink.Dispose();
                    }

                    if (figureOpen)
                        _renderItems[i].Geometry = geo;
                    else
                    {
                        geo.Dispose();
                        geo = null;
                    }
                }
                catch (Exception ex)
                {
                    Print("AnchoredVwap.RebuildGeometries: " + ex.Message);
                    geo?.Dispose();
                }
            }
        }

        // =====================================================================
        // Mouse interaction (UI / WPF dispatcher thread)
        // =====================================================================

        private void SubscribeMouseEvents()
        {
            if (ChartControl == null) return;
            ChartControl.MouseRightButtonDown += OnRmbDown;
            ChartControl.MouseRightButtonUp   += OnRmbUp;
            ChartControl.MouseMove            += OnRmbMouseMove;
        }

        private void UnsubscribeMouseEvents()
        {
            if (ChartControl == null) return;
            ChartControl.MouseRightButtonDown -= OnRmbDown;
            ChartControl.MouseRightButtonUp   -= OnRmbUp;
            ChartControl.MouseMove            -= OnRmbMouseMove;
            StopRmbTimer();
        }

        private void OnRmbDown(object sender, MouseButtonEventArgs e)
        {
            _rmbDown      = true;
            _rmbTriggered = false;
            _rmbDownPos   = e.GetPosition(ChartControl);

            // Start a one-shot timer.  When it fires (on the UI thread) the anchor
            // is placed immediately β€” no need to wait for button release.
            StopRmbTimer();
            _rmbTimer          = new DispatcherTimer(DispatcherPriority.Input);
            _rmbTimer.Interval = TimeSpan.FromSeconds(RmbHoldSeconds);
            _rmbTimer.Tick    += OnRmbTimerTick;
            _rmbTimer.Start();
        }

        private void OnRmbTimerTick(object sender, EventArgs e)
        {
            StopRmbTimer();
            if (!_rmbDown) return;  // button released before timer fired

            _rmbTriggered = true;
            ToggleAnchorAt(_rmbDownPos.X);
        }

        private void OnRmbUp(object sender, MouseButtonEventArgs e)
        {
            if (!_rmbDown) return;
            _rmbDown = false;
            StopRmbTimer();

            // If the timer already placed/removed an anchor, just suppress the
            // context menu.  If the timer hadn't fired yet (short press), do nothing
            // and let NinjaTrader's normal context menu appear.
            if (_rmbTriggered)
            {
                _rmbTriggered = false;
                e.Handled     = true;
            }
        }

        // Cancels the pan gesture: if the cursor travels more than ~5 px while
        // RMB is held, the user is panning β€” kill the timer before it fires.
        private void OnRmbMouseMove(object sender, MouseEventArgs e)
        {
            if (_rmbTimer == null) return;

            Point  pos = e.GetPosition(ChartControl);
            double dx  = pos.X - _rmbDownPos.X;
            double dy  = pos.Y - _rmbDownPos.Y;
            if (dx * dx + dy * dy > 25.0)   // 5 px radius
            {
                _rmbDown = false;
                StopRmbTimer();
            }
        }

        private void StopRmbTimer()
        {
            if (_rmbTimer == null) return;
            _rmbTimer.Stop();
            _rmbTimer.Tick -= OnRmbTimerTick;
            _rmbTimer       = null;
        }

        // Shared toggle logic β€” called by the timer tick (immediate) and nothing else.
        private void ToggleAnchorAt(double chartX)
        {
            int lcb = _lastClosedBar;   // volatile read
            if (lcb < 0) return;

            int barIdx = BarIdxFromX(chartX);
            barIdx = Math.Max(0, Math.Min(barIdx, lcb));

            bool removed = false;
            lock (_anchorLock)
            {
                for (int i = 0; i < _anchors.Count; i++)
                {
                    if (_anchors[i].BarIndex != barIdx) continue;
                    _anchors.RemoveAt(i);
                    removed = true;
                    break;
                }
            }

            if (!removed)
                AddAnchorInternal(barIdx);

            _geometryDirty = true;
            ChartControl.InvalidateVisual();
        }

        /// <summary>
        /// Converts a chart-space X pixel coordinate to the nearest bar index via
        /// linear interpolation across the currently visible bar range.
        /// </summary>
        private int BarIdxFromX(double x)
        {
            if (ChartBars == null) return 0;

            int first = ChartBars.FromIndex;
            int last  = ChartBars.ToIndex;
            if (first >= last) return first;

            double x0 = ChartControl.GetXByBarIndex(ChartBars, first);
            double x1 = ChartControl.GetXByBarIndex(ChartBars, last);
            if (Math.Abs(x1 - x0) < 1.0) return first;

            double t = (x - x0) / (x1 - x0);
            int    b = (int)Math.Round(first + t * (last - first));
            return Math.Max(first, Math.Min(last, b));
        }

        /// <summary>
        /// Fills the full VWAP series for a new anchor at <paramref name="barIdx"/>
        /// using the pre-computed prefix sums, then adds the anchor to the shared list.
        /// </summary>
        /// <remarks>
        /// Called on the UI thread.  Safe to read _cumPV / _cumVol at indices ≀ upTo
        /// because:
        ///   (a) upTo is obtained via a volatile read of _lastClosedBar (acquire fence),
        ///       which guarantees all OnBarUpdate writes up to that bar are visible.
        ///   (b) The data thread only writes index = CurrentBar; we only read ≀ upTo
        ///       which equals the previous CurrentBar, so no index overlaps.
        /// The anchor object is fully populated before being inserted into _anchors, so
        /// the render and data threads never observe a partially-initialised Values array.
        /// </remarks>
        private void AddAnchorInternal(int barIdx)
        {
            int upTo = _lastClosedBar;   // volatile read β€” acquire fence
            if (upTo < 0 || barIdx > upTo) return;

            // Read _arrayCapacity under the lock so we always see the value last committed
            // by EnsureCapacity, even if it ran concurrently on the data thread.
            int cap;
            lock (_anchorLock)
            {
                if (_anchors.Count >= MaxAnchors)
                    _anchors.RemoveAt(0);   // FIFO eviction β€” drop the oldest anchor
                cap = _arrayCapacity;
            }

            // Capture array references (stable for indices we will read: 0..upTo).
            double[] pv  = _cumPV;
            double[] vol = _cumVol;

            double offsetPV  = barIdx > 0 ? pv[barIdx - 1]  : 0.0;
            double offsetVol = barIdx > 0 ? vol[barIdx - 1] : 0.0;

            double[] values = new double[cap];

            // Bars before anchor β†’ NaN.
            for (int i = 0; i < barIdx && i < cap; i++)
                values[i] = double.NaN;

            // Bars from anchor to last closed bar β†’ VWAP via prefix subtraction.
            for (int i = barIdx; i <= upTo && i < cap; i++)
            {
                double d = vol[i] - offsetVol;
                values[i] = d > 0 ? (pv[i] - offsetPV) / d : double.NaN;
            }

            // Bars beyond upTo β†’ NaN; OnBarUpdate fills these as new bars close.
            for (int i = upTo + 1; i < cap; i++)
                values[i] = double.NaN;

            // Insert under lock, with a final capacity sync in case EnsureCapacity
            // ran on the data thread between the two lock acquisitions above.
            lock (_anchorLock)
            {
                if (_anchors.Count >= MaxAnchors)
                    _anchors.RemoveAt(0);   // FIFO eviction β€” re-check in case another was added concurrently

                int currentCap = _arrayCapacity;
                if (cap < currentCap)
                {
                    // Capacity grew while we were filling; extend to match.
                    Array.Resize(ref values, currentCap);
                    for (int i = cap; i < currentCap; i++)
                        values[i] = double.NaN;
                }

                _anchors.Add(new VwapAnchor
                {
                    BarIndex   = barIdx,
                    Values     = values,
                    ColorIndex = _nextColorIdx % Palette.Length,
                });
                _nextColorIdx++;
            }
        }

        // =====================================================================
        // Properties
        // =====================================================================

        [Range(0.5f, 5f)]
        [Display(Name        = "Line Width",
                 Description = "VWAP line stroke width in pixels",
                 GroupName   = "Parameters", Order = 1)]
        public float LineWidth { get; set; }

        [Range(0.5, 5.0)]
        [Display(Name        = "RMB Hold (s)",
                 Description = "Seconds the right mouse button must be held to toggle an anchor",
                 GroupName   = "Parameters", Order = 2)]
        public double RmbHoldSeconds { get; set; }
    }
}

anchoredVWAP

Point at the anchor candle and hold RMB for 1s

1 Like

what is this V Wap based on?

Current code is using chart bars to calculate VWAP so smaller timeframes result in more accurate VWAP. It’s using cumulative sums in order to accelerate VWAP calculations.

            _cumPV[CurrentBar]  = prevPV  + tp * Volume[0];
            _cumVol[CurrentBar] = prevVol + Volume[0];

I am still contemplating what is the best anchor point placement. Currently it is:

double tp = (High[0] + Low[0] + Close[0]) / 3.0;

2 Likes

Now with Live Previewβ„’

#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.NinjaScript;
#endregion

// This namespace holds Indicators in this folder and is required. Do not change it.
namespace NinjaTrader.NinjaScript.Indicators
{
    public class AnchoredVwap : Indicator
    {
        // =====================================================================
        // Inner types
        // =====================================================================

        private sealed class VwapAnchor
        {
            /// <summary>Absolute bar index at which this VWAP is anchored.</summary>
            public int      BarIndex;

            /// <summary>
            /// VWAP value per bar index.
            ///   β€’ Indices below BarIndex       β†’ NaN (before anchor)
            ///   β€’ Indices BarIndex..lastClosed β†’ computed VWAP
            ///   β€’ Indices beyond lastClosed    β†’ NaN (data thread fills on close)
            ///
            /// Threading contract (committed anchors in _anchors list):
            ///   Writer β€” OnBarUpdate (data thread):      writes index = CurrentBar.
            ///   Writer β€” BuildVwapAnchor (UI thread):    writes all indices while the
            ///            object is still private.
            ///   Reader β€” RebuildGeometries (render):     reads indices ≀ _lastClosedBar.
            ///   The data-thread write index (CurrentBar) and the render-thread read
            ///   ceiling (_lastClosedBar) always differ by β‰₯ 1, so accesses are disjoint.
            ///
            /// Threading contract (_previewAnchor):
            ///   Written via volatile reference write by the UI thread.
            ///   Read    via volatile reference read  by the render thread.
            ///   The Values array inside is fully populated before the volatile write,
            ///   so the render thread always sees a complete object.
            ///
            /// _anchorLock guards only list mutations (Add/Remove) and _arrayCapacity.
            /// </summary>
            public double[] Values;

            /// <summary>Index into Palette[] (0–MaxAnchors-1), determines line color.</summary>
            public int      ColorIndex;
        }

        // Render-thread-only β€” never shared with data or UI threads.
        private sealed class RenderItem
        {
            public SharpDX.Direct2D1.PathGeometry Geometry;
            public int                            ColorIndex;
        }

        // =====================================================================
        // Constants
        // =====================================================================

        private const int MaxAnchors = 8;

        private static readonly SharpDX.Color4[] Palette =
        {
            new SharpDX.Color4(0.0f, 1.0f, 1.0f, 1.0f),   // 0 – cyan
            new SharpDX.Color4(1.0f, 1.0f, 0.0f, 1.0f),   // 1 – yellow
            new SharpDX.Color4(0.2f, 1.0f, 0.2f, 1.0f),   // 2 – lime
            new SharpDX.Color4(1.0f, 0.5f, 0.0f, 1.0f),   // 3 – orange
            new SharpDX.Color4(0.6f, 0.4f, 1.0f, 1.0f),   // 4 – violet
            new SharpDX.Color4(1.0f, 0.4f, 0.7f, 1.0f),   // 5 – pink
            new SharpDX.Color4(0.4f, 0.8f, 1.0f, 1.0f),   // 6 – sky blue
            new SharpDX.Color4(1.0f, 1.0f, 1.0f, 1.0f),   // 7 – white
        };

        // =====================================================================
        // Member variables β€” data thread (OnBarUpdate)
        // =====================================================================

        // Prefix-sum arrays:
        //   _cumPV[i]  = Ξ£(TP Γ— Vol) for bars 0..i   (TP = (H+L+C)/3)
        //   _cumVol[i] = Ξ£(Vol)       for bars 0..i
        // Written exclusively by OnBarUpdate.
        // Safe to read on the UI thread at indices ≀ _lastClosedBar after a volatile read.
        private double[] _cumPV;
        private double[] _cumVol;

        // Updated as the LAST step in OnBarUpdate (release fence).
        // Reading it (acquire fence) guarantees visibility of all preceding _cumPV/_cumVol writes.
        private volatile int _lastClosedBar = -1;

        // Guarded by _anchorLock; committed inside EnsureCapacity so AddAnchorInternal
        // always reads a value consistent with the actual array sizes.
        private int _arrayCapacity;

        // =====================================================================
        // Member variables β€” shared (lock _anchorLock)
        // =====================================================================

        private readonly List<VwapAnchor> _anchors    = new List<VwapAnchor>(MaxAnchors);
        private readonly object           _anchorLock = new object();
        private          int              _nextColorIdx = 0; // monotonically increasing; mod Palette.Length on use

        // =====================================================================
        // Member variables β€” UI ↔ render thread (volatile references)
        // =====================================================================

        // Live preview anchor built while RMB is held after the time threshold.
        // Written by the UI thread (volatile), read by the render thread (volatile).
        // null = no preview active.
        private volatile VwapAnchor _previewAnchor;

        // =====================================================================
        // Member variables β€” render thread only
        // =====================================================================

        private SharpDX.Direct2D1.SolidColorBrush[] _paletteBrushes;
        private RenderItem[]                         _renderItems        = new RenderItem[0];
        private RenderItem                           _previewRenderItem;
        private volatile bool                        _geometryDirty      = true;
        private int                                  _lastFirstBar       = -1;
        private int                                  _lastLastBar        = -1;
        private double                               _lastScaleMin       = double.NaN;
        private double                               _lastScaleMax       = double.NaN;

        // =====================================================================
        // Member variables β€” UI thread only (mouse state)
        // =====================================================================

        private bool            _rmbDown;
        private Point           _rmbDownPos;
        private bool            _isPreviewing; // true from timer-tick until RMB release
        private DispatcherTimer _rmbTimer;

        // =====================================================================

        public override string DisplayName => string.Empty;

        // =====================================================================
        // OnStateChange
        // =====================================================================

        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Description  = "Anchored VWAP β€” hold right mouse button on any candle for the " +
                               "configured delay to begin dragging a preview VWAP.  Release to " +
                               "commit the anchor; release within Β±1 bar of an existing anchor " +
                               "to erase it instead.  Supports up to 8 anchors (FIFO).";
                Name         = "AnchoredVwap";
                Calculate    = Calculate.OnBarClose;
                IsOverlay    = true;
                DisplayInDataBox         = false;
                DrawOnPricePanel         = true;
                IsAutoScale              = false;
                IsSuspendedWhileInactive = true;
                BarsRequiredToPlot       = 0;

                LineWidth      = 1.5f;
                RmbHoldSeconds = 1.0;
            }
            else if (State == State.DataLoaded)
            {
                int initCap    = Math.Max(Bars.Count + 512, 2048);
                _cumPV         = new double[initCap];
                _cumVol        = new double[initCap];
                _arrayCapacity = initCap;
                SubscribeMouseEvents();
            }
            else if (State == State.Terminated)
            {
                UnsubscribeMouseEvents();
                DisposeRenderResources();
            }
        }

        // =====================================================================
        // DirectX resource management (render thread)
        // =====================================================================

        public override void OnRenderTargetChanged()
        {
            DisposeRenderResources();
            _geometryDirty = true;

            if (RenderTarget == null) return;

            _paletteBrushes = new SharpDX.Direct2D1.SolidColorBrush[Palette.Length];
            for (int i = 0; i < Palette.Length; i++)
                _paletteBrushes[i] = new SharpDX.Direct2D1.SolidColorBrush(RenderTarget, Palette[i]);
        }

        private void DisposeRenderResources()
        {
            if (_paletteBrushes != null)
            {
                foreach (var b in _paletteBrushes)
                    b?.Dispose();
                _paletteBrushes = null;
            }
            DisposeGeometries();
        }

        private void DisposeGeometries()
        {
            foreach (var item in _renderItems)
                item?.Geometry?.Dispose();
            _renderItems = new RenderItem[0];

            _previewRenderItem?.Geometry?.Dispose();
            _previewRenderItem = null;
        }

        // =====================================================================
        // Data processing β€” OnBarUpdate (data thread, Calculate.OnBarClose)
        // =====================================================================

        protected override void OnBarUpdate()
        {
            EnsureCapacity(CurrentBar + 1);

            double tp      = (High[0] + Low[0] + Close[0]) / 3.0;
            double prevPV  = CurrentBar > 0 ? _cumPV[CurrentBar - 1]  : 0.0;
            double prevVol = CurrentBar > 0 ? _cumVol[CurrentBar - 1] : 0.0;
            _cumPV[CurrentBar]  = prevPV  + tp * Volume[0];
            _cumVol[CurrentBar] = prevVol + Volume[0];

            // Append this bar's VWAP to every committed anchor.
            // All writes here MUST precede the volatile write to _lastClosedBar.
            lock (_anchorLock)
            {
                foreach (var anchor in _anchors)
                {
                    if (CurrentBar < anchor.BarIndex) continue;

                    if (CurrentBar >= anchor.Values.Length)
                        ExpandAnchorValues(anchor, _arrayCapacity);

                    double oPV   = anchor.BarIndex > 0 ? _cumPV[anchor.BarIndex - 1]  : 0.0;
                    double oVol  = anchor.BarIndex > 0 ? _cumVol[anchor.BarIndex - 1] : 0.0;
                    double denom = _cumVol[CurrentBar] - oVol;
                    anchor.Values[CurrentBar] = denom > 0
                        ? (_cumPV[CurrentBar] - oPV) / denom
                        : double.NaN;
                }
            }

            // Volatile write (release fence) β€” must be last.
            _lastClosedBar = CurrentBar;

            if (State == State.Realtime)
                _geometryDirty = true;
        }

        private void EnsureCapacity(int required)
        {
            if (required <= _arrayCapacity) return;

            int newCap = Math.Max(_arrayCapacity * 2, required + 256);
            Array.Resize(ref _cumPV,  newCap);
            Array.Resize(ref _cumVol, newCap);

            lock (_anchorLock)
            {
                foreach (var anchor in _anchors)
                    ExpandAnchorValues(anchor, newCap);
                _arrayCapacity = newCap;
            }
        }

        private static void ExpandAnchorValues(VwapAnchor anchor, int newCap)
        {
            int old = anchor.Values.Length;
            if (newCap <= old) return;
            Array.Resize(ref anchor.Values, newCap);
            for (int i = old; i < newCap; i++)
                anchor.Values[i] = double.NaN;
        }

        // =====================================================================
        // Rendering (render thread)
        // =====================================================================

        protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
        {
            if (State == State.Historical || RenderTarget == null) return;
            if (_paletteBrushes == null) OnRenderTargetChanged();

            int lastClosed = _lastClosedBar;   // volatile read β€” acquire fence
            if (lastClosed < 0) return;

            int firstBar = ChartBars.FromIndex;
            int lastBar  = Math.Min(ChartBars.ToIndex, lastClosed);
            if (lastBar < firstBar) return;

            if (firstBar != _lastFirstBar || lastBar != _lastLastBar
                || chartScale.MinValue != _lastScaleMin || chartScale.MaxValue != _lastScaleMax)
            {
                _lastFirstBar  = firstBar;
                _lastLastBar   = lastBar;
                _lastScaleMin  = chartScale.MinValue;
                _lastScaleMax  = chartScale.MaxValue;
                _geometryDirty = true;
            }

            if (_geometryDirty)
            {
                RebuildGeometries(chartControl, chartScale, firstBar, lastBar);
                _geometryDirty = false;
            }

            // Draw committed anchors β€” full opacity.
            foreach (var item in _renderItems)
            {
                if (item?.Geometry == null || item.Geometry.IsDisposed) continue;
                RenderTarget.DrawGeometry(item.Geometry, _paletteBrushes[item.ColorIndex], LineWidth);
            }

            // Draw preview anchor β€” same color as it will have on commit, at reduced opacity.
            if (_previewRenderItem?.Geometry != null && !_previewRenderItem.Geometry.IsDisposed)
            {
                var brush        = _paletteBrushes[_previewRenderItem.ColorIndex];
                float savedOpacity = brush.Opacity;
                brush.Opacity    = 0.45f;
                RenderTarget.DrawGeometry(_previewRenderItem.Geometry, brush, LineWidth);
                brush.Opacity    = savedOpacity;
            }
        }

        private void RebuildGeometries(ChartControl chartControl, ChartScale chartScale,
                                       int firstBar, int lastBar)
        {
            DisposeGeometries();

            if (ChartBars == null) return;

            // Snapshot committed anchors under the lock.
            VwapAnchor[] snapshot;
            lock (_anchorLock)
            {
                snapshot = _anchors.Count > 0 ? _anchors.ToArray() : new VwapAnchor[0];
            }

            _renderItems = new RenderItem[snapshot.Length];
            for (int i = 0; i < snapshot.Length; i++)
                _renderItems[i] = BuildSingleGeometry(snapshot[i], chartControl, chartScale, firstBar, lastBar);

            // Build preview geometry (volatile read of the reference β€” safe).
            VwapAnchor preview = _previewAnchor;
            if (preview != null)
                _previewRenderItem = BuildSingleGeometry(preview, chartControl, chartScale, firstBar, lastBar);
        }

        /// <summary>
        /// Builds a <see cref="RenderItem"/> for one anchor covering the visible bar range.
        /// The geometry is null when the anchor has no visible data points.
        /// </summary>
        private RenderItem BuildSingleGeometry(VwapAnchor anchor, ChartControl chartControl,
                                               ChartScale chartScale, int firstBar, int lastBar)
        {
            var item   = new RenderItem { ColorIndex = anchor.ColorIndex };
            int start  = Math.Max(firstBar, anchor.BarIndex);
            if (start > lastBar) return item;

            double[] values = anchor.Values;   // capture ref β€” safe; see VwapAnchor doc

            SharpDX.Direct2D1.PathGeometry geo = null;
            bool figureOpen = false;

            try
            {
                geo = new SharpDX.Direct2D1.PathGeometry(RenderTarget.Factory);
                var sink = geo.Open();
                try
                {
                    for (int b = start; b <= lastBar; b++)
                    {
                        if (b >= values.Length) break;

                        double val = values[b];
                        if (double.IsNaN(val)) continue;

                        float x = (float)chartControl.GetXByBarIndex(ChartBars, b);
                        float y = (float)chartScale.GetYByValue(val);

                        if (!figureOpen)
                        {
                            sink.BeginFigure(new SharpDX.Vector2(x, y),
                                             SharpDX.Direct2D1.FigureBegin.Hollow);
                            figureOpen = true;
                        }
                        else
                        {
                            sink.AddLine(new SharpDX.Vector2(x, y));
                        }
                    }

                    if (figureOpen)
                        sink.EndFigure(SharpDX.Direct2D1.FigureEnd.Open);

                    sink.Close();
                }
                finally
                {
                    sink.Dispose();
                }

                if (figureOpen)
                    item.Geometry = geo;
                else
                { geo.Dispose(); geo = null; }
            }
            catch (Exception ex)
            {
                Print("AnchoredVwap.BuildSingleGeometry: " + ex.Message);
                geo?.Dispose();
            }

            return item;
        }

        // =====================================================================
        // Mouse interaction (UI / WPF dispatcher thread)
        // =====================================================================

        private void SubscribeMouseEvents()
        {
            if (ChartControl == null) return;
            ChartControl.MouseRightButtonDown += OnRmbDown;
            ChartControl.MouseRightButtonUp   += OnRmbUp;
            ChartControl.MouseMove            += OnRmbMouseMove;
        }

        private void UnsubscribeMouseEvents()
        {
            if (ChartControl == null) return;
            ChartControl.MouseRightButtonDown -= OnRmbDown;
            ChartControl.MouseRightButtonUp   -= OnRmbUp;
            ChartControl.MouseMove            -= OnRmbMouseMove;
            StopRmbTimer();
        }

        private void OnRmbDown(object sender, MouseButtonEventArgs e)
        {
            _rmbDown      = true;
            _isPreviewing = false;
            _rmbDownPos   = e.GetPosition(ChartControl);

            StopRmbTimer();
            _rmbTimer          = new DispatcherTimer(DispatcherPriority.Input);
            _rmbTimer.Interval = TimeSpan.FromSeconds(RmbHoldSeconds);
            _rmbTimer.Tick    += OnRmbTimerTick;
            _rmbTimer.Start();
        }

        private void OnRmbTimerTick(object sender, EventArgs e)
        {
            StopRmbTimer();
            if (!_rmbDown) return;

            int lcb = _lastClosedBar;
            if (lcb < 0) return;

            int barIdx = BarIdxFromX(_rmbDownPos.X);
            barIdx = Math.Max(0, Math.Min(barIdx, lcb));

            BuildPreviewAnchor(barIdx);     // writes _previewAnchor (volatile)
            _isPreviewing  = true;
            _geometryDirty = true;
            ChartControl.InvalidateVisual();
        }

        private void OnRmbUp(object sender, MouseButtonEventArgs e)
        {
            if (!_rmbDown) return;
            _rmbDown = false;
            StopRmbTimer();

            // Short press (timer never fired) β€” let NinjaTrader's context menu appear.
            if (!_isPreviewing) return;

            _isPreviewing = false;
            e.Handled     = true;   // suppress context menu

            int lcb = _lastClosedBar;
            if (lcb >= 0)
            {
                Point pos          = e.GetPosition(ChartControl);
                int   releaseBarIdx = BarIdxFromX(pos.X);
                releaseBarIdx      = Math.Max(0, Math.Min(releaseBarIdx, lcb));

                // Release within Β±1 bar of an existing anchor β†’ erase it.
                // Release anywhere else β†’ commit the preview as a new anchor.
                bool erased = false;
                lock (_anchorLock)
                {
                    for (int i = 0; i < _anchors.Count; i++)
                    {
                        if (Math.Abs(_anchors[i].BarIndex - releaseBarIdx) > 1) continue;
                        _anchors.RemoveAt(i);
                        erased = true;
                        break;
                    }
                }

                if (!erased)
                    CommitPreviewAnchor();
            }

            _previewAnchor = null;      // volatile write β€” clears preview from render thread
            _geometryDirty = true;
            ChartControl.InvalidateVisual();
        }

        private void OnRmbMouseMove(object sender, MouseEventArgs e)
        {
            if (!_rmbDown) return;

            Point pos = e.GetPosition(ChartControl);

            if (_isPreviewing)
            {
                // Update the preview anchor bar as the cursor drags.
                int lcb = _lastClosedBar;
                if (lcb < 0) return;

                int newBarIdx = BarIdxFromX(pos.X);
                newBarIdx = Math.Max(0, Math.Min(newBarIdx, lcb));

                VwapAnchor current = _previewAnchor;    // volatile read
                if (current != null && current.BarIndex == newBarIdx) return;

                BuildPreviewAnchor(newBarIdx);
                _geometryDirty = true;
                ChartControl.InvalidateVisual();
            }
            else
            {
                // Timer not yet fired β€” cancel if this turns into a pan gesture.
                double dx = pos.X - _rmbDownPos.X;
                double dy = pos.Y - _rmbDownPos.Y;
                if (dx * dx + dy * dy > 25.0)   // 5 px radius
                {
                    _rmbDown = false;
                    StopRmbTimer();
                }
            }
        }

        private void StopRmbTimer()
        {
            if (_rmbTimer == null) return;
            _rmbTimer.Stop();
            _rmbTimer.Tick -= OnRmbTimerTick;
            _rmbTimer       = null;
        }

        // =====================================================================
        // Anchor building helpers (UI thread)
        // =====================================================================

        /// <summary>
        /// Builds a fully-populated <see cref="VwapAnchor"/> for <paramref name="barIdx"/>
        /// from the current prefix-sum arrays and writes it to <see cref="_previewAnchor"/>
        /// via a volatile reference write (release fence).
        /// </summary>
        private void BuildPreviewAnchor(int barIdx)
        {
            VwapAnchor anchor = BuildVwapAnchor(barIdx);
            _previewAnchor    = anchor;   // volatile write β€” render thread sees complete object
        }

        /// <summary>
        /// Moves the current <see cref="_previewAnchor"/> into the committed
        /// <see cref="_anchors"/> list, applying FIFO eviction and a final capacity sync.
        /// </summary>
        private void CommitPreviewAnchor()
        {
            VwapAnchor preview = _previewAnchor;   // volatile read
            if (preview == null) return;

            lock (_anchorLock)
            {
                if (_anchors.Count >= MaxAnchors)
                    _anchors.RemoveAt(0);   // FIFO eviction

                // Sync capacity in case EnsureCapacity ran while the preview was being built.
                int      currentCap = _arrayCapacity;
                double[] values     = preview.Values;
                int      oldLen     = values.Length;
                if (oldLen < currentCap)
                {
                    Array.Resize(ref values, currentCap);
                    for (int i = oldLen; i < currentCap; i++)
                        values[i] = double.NaN;
                    preview.Values = values;
                }

                // Assign the definitive color and add to the list.
                preview.ColorIndex = _nextColorIdx % Palette.Length;
                _nextColorIdx++;
                _anchors.Add(preview);
            }
        }

        /// <summary>
        /// Allocates and fills a <see cref="VwapAnchor"/> for <paramref name="barIdx"/>
        /// using the prefix-sum arrays.  Returns null if no data is available.
        /// Called on the UI thread; reads _cumPV/_cumVol at indices ≀ _lastClosedBar,
        /// which is safe after the volatile read of _lastClosedBar (acquire fence).
        /// </summary>
        private VwapAnchor BuildVwapAnchor(int barIdx)
        {
            int upTo = _lastClosedBar;   // volatile read β€” acquire fence
            if (upTo < 0 || barIdx > upTo) return null;

            int cap;
            lock (_anchorLock)
            {
                cap = _arrayCapacity;
            }

            double[] pv  = _cumPV;
            double[] vol = _cumVol;

            double offsetPV  = barIdx > 0 ? pv[barIdx - 1]  : 0.0;
            double offsetVol = barIdx > 0 ? vol[barIdx - 1] : 0.0;

            double[] values = new double[cap];

            for (int i = 0; i < barIdx && i < cap; i++)
                values[i] = double.NaN;

            for (int i = barIdx; i <= upTo && i < cap; i++)
            {
                double d = vol[i] - offsetVol;
                values[i] = d > 0 ? (pv[i] - offsetPV) / d : double.NaN;
            }

            for (int i = upTo + 1; i < cap; i++)
                values[i] = double.NaN;

            // Peek at the next color index without incrementing β€” CommitPreviewAnchor
            // will assign the final color and increment when the anchor is committed.
            int colorIdx;
            lock (_anchorLock)
            {
                colorIdx = _nextColorIdx % Palette.Length;
            }

            return new VwapAnchor { BarIndex = barIdx, Values = values, ColorIndex = colorIdx };
        }

        // =====================================================================
        // Coordinate helpers
        // =====================================================================

        /// <summary>
        /// Converts a chart-space X pixel coordinate to the nearest bar index via
        /// linear interpolation across the currently visible bar range.
        /// </summary>
        private int BarIdxFromX(double x)
        {
            if (ChartBars == null) return 0;

            int first = ChartBars.FromIndex;
            int last  = ChartBars.ToIndex;
            if (first >= last) return first;

            double x0 = ChartControl.GetXByBarIndex(ChartBars, first);
            double x1 = ChartControl.GetXByBarIndex(ChartBars, last);
            if (Math.Abs(x1 - x0) < 1.0) return first;

            double t = (x - x0) / (x1 - x0);
            int    b = (int)Math.Round(first + t * (last - first));
            return Math.Max(first, Math.Min(last, b));
        }

        // =====================================================================
        // Properties
        // =====================================================================

        [Range(0.5f, 5f)]
        [Display(Name        = "Line Width",
                 Description = "VWAP line stroke width in pixels",
                 GroupName   = "Parameters", Order = 1)]
        public float LineWidth { get; set; }

        [Range(0.5, 5.0)]
        [Display(Name        = "RMB Hold (s)",
                 Description = "Seconds the right mouse button must be held to begin previewing",
                 GroupName   = "Parameters", Order = 2)]
        public double RmbHoldSeconds { get; set; }
    }
}

Dragging RMB results in AVWAP preview. Releasing RMB anchors it. RMB in the vicinity of existing anchor cancels it.

AVWAP_livePreview

3 Likes