1+ /*
2+ * FofAnchoredVwap.cs
3+ * Copyright (c) 2025 Roldão Rego Jr.
4+ *
5+ * Modified by https://github.com/Linus404, 2025
6+ *
7+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8+ * of this software and associated documentation files (the "Software"), to deal
9+ * in the Software without restriction, including without limitation the rights
10+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+ * copies of the Software, and to permit persons to whom the Software is
12+ * furnished to do so, subject to the following conditions:
13+ *
14+ * The above copyright notice and this permission notice shall be included in all
15+ * copies or substantial portions of the Software.
16+ *
17+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+ * SOFTWARE.
24+ */
125#region Using declarations
226using System ;
327using System . Collections . Generic ;
@@ -42,9 +66,18 @@ public override object Icon {
4266 }
4367 #endregion
4468
69+ public enum PriceSource
70+ {
71+ Close ,
72+ HLC3 ,
73+ HL2 ,
74+ OHLC4 ,
75+ HLCC4
76+ }
77+
4578 #region Private properties
4679 private static float MinimumSize = 5f ;
47- private InputPriceType calculatedInputPrice = InputPriceType . Median ;
80+ private PriceSource calculatedPriceSource = PriceSource . HLC3 ;
4881
4982 private double BarWidth
5083 {
@@ -73,18 +106,31 @@ private ChartBars ChartBars
73106 private Dictionary < int , double > vwap = new Dictionary < int , double > ( ) ;
74107 private double cumVol ;
75108 private double cumPV ;
109+ private double cumPV2 ;
76110 private int StartBar = - 1 ;
77111 private int EndBar = - 1 ;
112+ private Dictionary < int , double > upperBand1 = new Dictionary < int , double > ( ) ;
113+ private Dictionary < int , double > lowerBand1 = new Dictionary < int , double > ( ) ;
114+ private Dictionary < int , double > upperBand2 = new Dictionary < int , double > ( ) ;
115+ private Dictionary < int , double > lowerBand2 = new Dictionary < int , double > ( ) ;
78116 #endregion
79117
80118 protected override void OnStateChange ( )
81119 {
82120 if ( State == State . SetDefaults )
83121 {
84- Name = "Anchored VWAP (Free Order Flow)" ;
85- Description = @"Plot VWAP from anchored bar" ;
86- InputPrice = InputPriceType . Median ;
87- Stroke = new Stroke ( Brushes . DodgerBlue , 1 ) ;
122+ Name = "Anchored VWAP (Fof)" ;
123+ Description = @"Plot VWAP from anchored bar with Standard Deviation Bands" ;
124+ PriceSourceInput = PriceSource . HLC3 ;
125+ Stroke = new Stroke ( Brushes . DeepSkyBlue , 1 ) ;
126+ UpperBand1Stroke = new Stroke ( Brushes . LimeGreen , 1 ) ;
127+ LowerBand1Stroke = new Stroke ( Brushes . LimeGreen , 1 ) ;
128+ UpperBand2Stroke = new Stroke ( Brushes . Orange , 1 ) ;
129+ LowerBand2Stroke = new Stroke ( Brushes . Orange , 1 ) ;
130+ ShowFirstBands = true ;
131+ ShowSecondBands = true ;
132+ FirstStdDev = 1.0 ;
133+ SecondStdDev = 2.0 ;
88134 Anchor = new ChartAnchor
89135 {
90136 DisplayName = Custom . Resource . NinjaScriptDrawingToolAnchor ,
@@ -135,7 +181,7 @@ public override void OnMouseDown(ChartControl chartControl, ChartPanel chartPane
135181 Anchor . IsEditing = false ;
136182 DrawingState = DrawingState . Normal ;
137183 IsSelected = false ;
138- DetectPriceInput ( ) ;
184+ DetectPriceSource ( ) ;
139185 break ;
140186 case DrawingState . Normal :
141187 // make sure they clicked near us. use GetCursor incase something has more than one point, like arrows
@@ -160,7 +206,7 @@ public override void OnMouseUp(ChartControl control, ChartPanel chartPanel, Char
160206 {
161207 if ( DrawingState == DrawingState . Editing || DrawingState == DrawingState . Moving ) {
162208 DrawingState = DrawingState . Normal ;
163- DetectPriceInput ( ) ;
209+ DetectPriceSource ( ) ;
164210 ForceRefresh ( ) ;
165211 }
166212 }
@@ -197,40 +243,50 @@ public override IEnumerable<ChartAnchor> Anchors
197243 #endregion
198244
199245 #region Calculations
200- private double GetInputPrice ( int currentBar )
246+ private double GetPrice ( int currentBar )
201247 {
202- switch ( InputPrice )
248+ switch ( PriceSourceInput )
203249 {
204- case InputPriceType . High :
205- return ChartBars . Bars . GetHigh ( currentBar ) ;
206- case InputPriceType . Low :
207- return ChartBars . Bars . GetLow ( currentBar ) ;
250+ case PriceSource . Close :
251+ return ChartBars . Bars . GetClose ( currentBar ) ;
252+ case PriceSource . HLC3 :
253+ return ( ChartBars . Bars . GetHigh ( currentBar ) + ChartBars . Bars . GetLow ( currentBar ) + ChartBars . Bars . GetClose ( currentBar ) ) / 3.0 ;
254+ case PriceSource . HL2 :
255+ return ( ChartBars . Bars . GetHigh ( currentBar ) + ChartBars . Bars . GetLow ( currentBar ) ) / 2.0 ;
256+ case PriceSource . OHLC4 :
257+ return ( ChartBars . Bars . GetOpen ( currentBar ) + ChartBars . Bars . GetHigh ( currentBar ) + ChartBars . Bars . GetLow ( currentBar ) + ChartBars . Bars . GetClose ( currentBar ) ) / 4.0 ;
258+ case PriceSource . HLCC4 :
259+ return ( ChartBars . Bars . GetHigh ( currentBar ) + ChartBars . Bars . GetLow ( currentBar ) + ChartBars . Bars . GetClose ( currentBar ) + ChartBars . Bars . GetClose ( currentBar ) ) / 4.0 ;
208260 default :
209- return ( ChartBars . Bars . GetHigh ( currentBar ) + ChartBars . Bars . GetLow ( currentBar ) ) / 2 ;
261+ return ( ChartBars . Bars . GetHigh ( currentBar ) + ChartBars . Bars . GetLow ( currentBar ) + ChartBars . Bars . GetClose ( currentBar ) ) / 3.0 ;
210262 }
211263 }
212264
213- private void DetectPriceInput ( ) {
265+ private void DetectPriceSource ( ) {
214266 var high = ChartBars . Bars . GetHigh ( ( int ) Anchor . SlotIndex ) ;
215267 var low = ChartBars . Bars . GetLow ( ( int ) Anchor . SlotIndex ) ;
268+ var close = ChartBars . Bars . GetClose ( ( int ) Anchor . SlotIndex ) ;
216269 var tickSize = AttachedTo . Instrument . MasterInstrument . TickSize ;
217270
218- if ( Anchor . Price < low + tickSize ) {
219- InputPrice = InputPriceType . Low ;
220- } else if ( Anchor . Price > high - tickSize ) {
221- InputPrice = InputPriceType . High ;
271+ if ( Math . Abs ( Anchor . Price - close ) < tickSize ) {
272+ PriceSourceInput = PriceSource . Close ;
273+ } else if ( Math . Abs ( Anchor . Price - low ) < tickSize ) {
274+ PriceSourceInput = PriceSource . HL2 ;
275+ } else if ( Math . Abs ( Anchor . Price - high ) < tickSize ) {
276+ PriceSourceInput = PriceSource . HL2 ;
222277 } else {
223- InputPrice = InputPriceType . Median ;
278+ PriceSourceInput = PriceSource . HLC3 ;
224279 }
225280 }
226281
227282 private void CalculateVWAP ( ) {
228283 int startIndex = - 1 ;
229284
230- if ( calculatedInputPrice != InputPrice || StartBar != ( int ) Anchor . SlotIndex ) {
285+ if ( calculatedPriceSource != PriceSourceInput || StartBar != ( int ) Anchor . SlotIndex ) {
231286 // recalculate
232287 cumVol = 0 ;
233288 cumPV = 0 ;
289+ cumPV2 = 0 ;
234290 startIndex = ( int ) Anchor . SlotIndex ;
235291 } else if ( EndBar < ChartBars . Bars . Count - 1 ) {
236292 // new bars added
@@ -239,23 +295,47 @@ private void CalculateVWAP() {
239295
240296 if ( startIndex >= 0 ) {
241297 for ( int i = startIndex ; i < ChartBars . Bars . Count ; i ++ ) {
242- cumPV += GetInputPrice ( i ) * ChartBars . Bars . GetVolume ( i ) ;
243- cumVol += ChartBars . Bars . GetVolume ( i ) ;
298+ double price = GetPrice ( i ) ;
299+ double volume = ChartBars . Bars . GetVolume ( i ) ;
300+ cumPV += price * volume ;
301+ cumPV2 += price * price * volume ;
302+ cumVol += volume ;
244303 vwap [ i ] = cumPV / ( cumVol == 0 ? 1 : cumVol ) ;
304+
305+ if ( ShowFirstBands || ShowSecondBands )
306+ {
307+ double variance = ( cumPV2 / ( cumVol == 0 ? 1 : cumVol ) ) - ( vwap [ i ] * vwap [ i ] ) ;
308+ double stdDev = variance > 0 ? Math . Sqrt ( variance ) : 0 ;
309+
310+ if ( ShowFirstBands )
311+ {
312+ upperBand1 [ i ] = vwap [ i ] + ( stdDev * FirstStdDev ) ;
313+ lowerBand1 [ i ] = vwap [ i ] - ( stdDev * FirstStdDev ) ;
314+ }
315+
316+ if ( ShowSecondBands )
317+ {
318+ upperBand2 [ i ] = vwap [ i ] + ( stdDev * SecondStdDev ) ;
319+ lowerBand2 [ i ] = vwap [ i ] - ( stdDev * SecondStdDev ) ;
320+ }
321+ }
245322 }
246323 StartBar = ( int ) Anchor . SlotIndex ;
247324 EndBar = ChartBars . Bars . Count - 1 ;
248325 }
249326
250- calculatedInputPrice = InputPrice ;
327+ calculatedPriceSource = PriceSourceInput ;
251328 }
252329 #endregion
253330
254331 #region Rendering
255332 public override void OnRenderTargetChanged ( )
256333 {
257- if ( Stroke == null ) return ;
258- if ( RenderTarget != null ) Stroke . RenderTarget = RenderTarget ;
334+ if ( Stroke != null && RenderTarget != null ) Stroke . RenderTarget = RenderTarget ;
335+ if ( UpperBand1Stroke != null && RenderTarget != null ) UpperBand1Stroke . RenderTarget = RenderTarget ;
336+ if ( LowerBand1Stroke != null && RenderTarget != null ) LowerBand1Stroke . RenderTarget = RenderTarget ;
337+ if ( UpperBand2Stroke != null && RenderTarget != null ) UpperBand2Stroke . RenderTarget = RenderTarget ;
338+ if ( LowerBand2Stroke != null && RenderTarget != null ) LowerBand2Stroke . RenderTarget = RenderTarget ;
259339 }
260340
261341 public override void OnRender ( ChartControl chartControl , ChartScale chartScale )
@@ -265,7 +345,7 @@ public override void OnRender(ChartControl chartControl, ChartScale chartScale)
265345 RenderTarget . AntialiasMode = SharpDX . Direct2D1 . AntialiasMode . PerPrimitive ;
266346
267347 if ( DrawingState != DrawingState . Moving ) {
268- Anchor . Price = GetInputPrice ( ( int ) Anchor . SlotIndex ) ;
348+ Anchor . Price = GetPrice ( ( int ) Anchor . SlotIndex ) ;
269349 }
270350
271351 ChartPanel panel = chartControl . ChartPanels [ chartScale . PanelIndex ] ;
@@ -279,7 +359,7 @@ public override void OnRender(ChartControl chartControl, ChartScale chartScale)
279359 ) ;
280360
281361 if ( DrawingState == DrawingState . Normal && (
282- InputPrice != calculatedInputPrice ||
362+ PriceSourceInput != calculatedPriceSource ||
283363 StartBar != ( int ) Anchor . SlotIndex ||
284364 EndBar != ChartBars . ToIndex
285365 ) ) {
@@ -297,6 +377,8 @@ private void RenderVWAP(ChartControl chartControl, ChartScale chartScale) {
297377 for ( int i = StartBar + 1 ; i < EndBar ; i ++ ) {
298378 if ( ! vwap . ContainsKey ( i - 1 ) ) continue ;
299379 if ( i > ChartBars . ToIndex ) break ;
380+
381+ // Render VWAP line
300382 SharpDX . Vector2 startPoint = new SharpDX . Vector2 (
301383 chartControl . GetXByBarIndex ( ChartBars , i - 1 ) ,
302384 chartScale . GetYByValue ( vwap [ i - 1 ] )
@@ -306,19 +388,92 @@ private void RenderVWAP(ChartControl chartControl, ChartScale chartScale) {
306388 chartScale . GetYByValue ( vwap [ i ] )
307389 ) ;
308390 RenderTarget . DrawLine ( startPoint , endPoint , Stroke . BrushDX , Stroke . Width , Stroke . StrokeStyle ) ;
391+
392+ // Render First Standard Deviation Bands
393+ if ( ShowFirstBands && upperBand1 . ContainsKey ( i - 1 ) && upperBand1 . ContainsKey ( i ) && lowerBand1 . ContainsKey ( i - 1 ) && lowerBand1 . ContainsKey ( i ) ) {
394+ // Upper Band 1
395+ SharpDX . Vector2 startUpperBand1 = new SharpDX . Vector2 (
396+ chartControl . GetXByBarIndex ( ChartBars , i - 1 ) ,
397+ chartScale . GetYByValue ( upperBand1 [ i - 1 ] )
398+ ) ;
399+ SharpDX . Vector2 endUpperBand1 = new SharpDX . Vector2 (
400+ chartControl . GetXByBarIndex ( ChartBars , i ) ,
401+ chartScale . GetYByValue ( upperBand1 [ i ] )
402+ ) ;
403+ RenderTarget . DrawLine ( startUpperBand1 , endUpperBand1 , UpperBand1Stroke . BrushDX , UpperBand1Stroke . Width , UpperBand1Stroke . StrokeStyle ) ;
404+
405+ // Lower Band 1
406+ SharpDX . Vector2 startLowerBand1 = new SharpDX . Vector2 (
407+ chartControl . GetXByBarIndex ( ChartBars , i - 1 ) ,
408+ chartScale . GetYByValue ( lowerBand1 [ i - 1 ] )
409+ ) ;
410+ SharpDX . Vector2 endLowerBand1 = new SharpDX . Vector2 (
411+ chartControl . GetXByBarIndex ( ChartBars , i ) ,
412+ chartScale . GetYByValue ( lowerBand1 [ i ] )
413+ ) ;
414+ RenderTarget . DrawLine ( startLowerBand1 , endLowerBand1 , LowerBand1Stroke . BrushDX , LowerBand1Stroke . Width , LowerBand1Stroke . StrokeStyle ) ;
415+ }
416+
417+ // Render Second Standard Deviation Bands
418+ if ( ShowSecondBands && upperBand2 . ContainsKey ( i - 1 ) && upperBand2 . ContainsKey ( i ) && lowerBand2 . ContainsKey ( i - 1 ) && lowerBand2 . ContainsKey ( i ) ) {
419+ // Upper Band 2
420+ SharpDX . Vector2 startUpperBand2 = new SharpDX . Vector2 (
421+ chartControl . GetXByBarIndex ( ChartBars , i - 1 ) ,
422+ chartScale . GetYByValue ( upperBand2 [ i - 1 ] )
423+ ) ;
424+ SharpDX . Vector2 endUpperBand2 = new SharpDX . Vector2 (
425+ chartControl . GetXByBarIndex ( ChartBars , i ) ,
426+ chartScale . GetYByValue ( upperBand2 [ i ] )
427+ ) ;
428+ RenderTarget . DrawLine ( startUpperBand2 , endUpperBand2 , UpperBand2Stroke . BrushDX , UpperBand2Stroke . Width , UpperBand2Stroke . StrokeStyle ) ;
429+
430+ // Lower Band 2
431+ SharpDX . Vector2 startLowerBand2 = new SharpDX . Vector2 (
432+ chartControl . GetXByBarIndex ( ChartBars , i - 1 ) ,
433+ chartScale . GetYByValue ( lowerBand2 [ i - 1 ] )
434+ ) ;
435+ SharpDX . Vector2 endLowerBand2 = new SharpDX . Vector2 (
436+ chartControl . GetXByBarIndex ( ChartBars , i ) ,
437+ chartScale . GetYByValue ( lowerBand2 [ i ] )
438+ ) ;
439+ RenderTarget . DrawLine ( startLowerBand2 , endLowerBand2 , LowerBand2Stroke . BrushDX , LowerBand2Stroke . Width , LowerBand2Stroke . StrokeStyle ) ;
440+ }
309441 }
310442 }
311443 #endregion
312444
313445 #region Properties
314446 public ChartAnchor Anchor { get ; set ; }
315447
316- [ Display ( ResourceType = typeof ( Custom . Resource ) , Name = "Input Price Type " , GroupName = "NinjaScriptGeneral " , Order = 1 ) ]
317- public InputPriceType InputPrice { get ; set ; }
448+ [ Display ( Name = "Price Source " , Description = "Source for price calculation " , Order = 1 , GroupName = "Parameters" ) ]
449+ public PriceSource PriceSourceInput { get ; set ; }
318450
319- [ Display ( ResourceType = typeof ( Custom . Resource ) , Name = "Line" , GroupName = "NinjaScriptGeneral" , Order = 2 ) ]
451+ [ Display ( Name = "Show First Std Dev Bands" , Description = "Show first standard deviation bands" , Order = 2 , GroupName = "Parameters" ) ]
452+ public bool ShowFirstBands { get ; set ; }
453+
454+ [ Display ( Name = "Show Second Std Dev Bands" , Description = "Show second standard deviation bands" , Order = 3 , GroupName = "Parameters" ) ]
455+ public bool ShowSecondBands { get ; set ; }
456+
457+ [ Display ( Name = "First Std Dev Multiplier" , Description = "Multiplier for first standard deviation" , Order = 4 , GroupName = "Parameters" ) ]
458+ public double FirstStdDev { get ; set ; }
459+
460+ [ Display ( Name = "Second Std Dev Multiplier" , Description = "Multiplier for second standard deviation" , Order = 5 , GroupName = "Parameters" ) ]
461+ public double SecondStdDev { get ; set ; }
462+
463+ [ Display ( ResourceType = typeof ( Custom . Resource ) , Name = "Line" , GroupName = "NinjaScriptGeneral" , Order = 10 ) ]
320464 public Stroke Stroke { get ; set ; }
465+
466+ [ Display ( Name = "Upper Band 1 Stroke" , GroupName = "NinjaScriptGeneral" , Order = 11 ) ]
467+ public Stroke UpperBand1Stroke { get ; set ; }
468+
469+ [ Display ( Name = "Lower Band 1 Stroke" , GroupName = "NinjaScriptGeneral" , Order = 12 ) ]
470+ public Stroke LowerBand1Stroke { get ; set ; }
471+
472+ [ Display ( Name = "Upper Band 2 Stroke" , GroupName = "NinjaScriptGeneral" , Order = 13 ) ]
473+ public Stroke UpperBand2Stroke { get ; set ; }
474+
475+ [ Display ( Name = "Lower Band 2 Stroke" , GroupName = "NinjaScriptGeneral" , Order = 14 ) ]
476+ public Stroke LowerBand2Stroke { get ; set ; }
321477 #endregion
322478 }
323- public enum InputPriceType { High , Low , Median } ;
324479}
0 commit comments