Skip to content

Commit 7b5f200

Browse files
authored
Merge pull request #13 from Linus404/main
Improved VWAP, VP, DrawVWAP
2 parents 0c3f58a + cf367b4 commit 7b5f200

File tree

5 files changed

+435
-114
lines changed

5 files changed

+435
-114
lines changed

DrawingTools/FreeOrderFlow/FofAnchoredVwap.cs

Lines changed: 187 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
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
226
using System;
327
using 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
}

DrawingTools/FreeOrderFlow/FofRangeVolumeProfile.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
/*
2+
* FofRangeVolumeProfile.cs
3+
* Copyright (c) 2025 Roldão Rego Jr.
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in all
13+
* copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
* SOFTWARE.
22+
*/
123
#region Using declarations
224
using System;
325
using System.ComponentModel;

Indicators/FreeOrderFlow/FofCumulativeDelta.cs renamed to Indicators/FreeOrderFlow/FofAggressionDelta.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
//This namespace holds Indicators in this folder and is required. Do not change it.
2525
namespace NinjaTrader.NinjaScript.Indicators.FreeOrderFlow
2626
{
27-
public class FofCumulativeDelta : Indicator
27+
public class FofAggressionDelta : Indicator
2828
{
2929
private double buys;
3030
private double sells;

0 commit comments

Comments
 (0)