-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathCVD.cs
More file actions
228 lines (191 loc) · 8.14 KB
/
Copy pathCVD.cs
File metadata and controls
228 lines (191 loc) · 8.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// -------------------------------------------------------------------------------
// Cumulative Volume Delta (CVD) displays buy/sell volume difference accumulated during some period.
// Supports SMA and EMA smoothing.
//
// Version 1.00
// Copyright 2025, EarnForex.com
// https://www.earnforex.com/indicators/CVD/
// -------------------------------------------------------------------------------
using System;
using cAlgo.API;
namespace cAlgo
{
[Indicator(IsOverlay = false, AccessRights = AccessRights.None)]
public class CVD : Indicator
{
// Enumeration for smoothing type.
public enum SmoothingMethod
{
None, // No Smoothing
SMA, // Simple Moving Average
EMA // Exponential Moving Average
}
// Input parameters.
[Parameter("Source Timeframe", DefaultValue = "Current")]
public TimeFrame DataTimeframe { get; set; }
[Parameter("Cumulative Period", DefaultValue = 20, MinValue = 1)]
public int CumulativePeriod { get; set; }
[Parameter("Smoothing Method", DefaultValue = SmoothingMethod.None)]
public SmoothingMethod SmoothMethod { get; set; }
[Parameter("Smoothing Period", DefaultValue = 1, MinValue = 1)]
public int SmoothPeriod { get; set; }
// Output buffers.
[Output("CVD Positive", LineColor = "LimeGreen", PlotType = PlotType.Histogram, Thickness = 2)]
public IndicatorDataSeries CVDPositive { get; set; }
[Output("CVD Negative", LineColor = "Red", PlotType = PlotType.Histogram, Thickness = 2)]
public IndicatorDataSeries CVDNegative { get; set; }
[Output("CVD Smoothed", LineColor = "DarkGray", PlotType = PlotType.Line, LineStyle = LineStyle.Dots)]
public IndicatorDataSeries CVDSmooth { get; set; }
// Internal data series.
private IndicatorDataSeries CVDRaw;
private IndicatorDataSeries DeltaVolume;
// Multi-timeframe bars.
private Bars lowerTFBars;
// EMA multiplier.
private double alpha;
protected override void Initialize()
{
// Initialize internal data series.
CVDRaw = CreateDataSeries();
DeltaVolume = CreateDataSeries();
// Get lower timeframe bars.
if (DataTimeframe.Name == "Current")
{
lowerTFBars = Bars;
}
else
{
lowerTFBars = MarketData.GetBars(DataTimeframe);
}
// Check if selected timeframe is valid.
if (lowerTFBars.TimeFrame > Bars.TimeFrame)
{
Print("Warning: Data timeframe should be equal to or lower than the current chart timeframe. Using current timeframe.");
lowerTFBars = Bars;
}
// Calculate EMA multiplier.
alpha = 2.0 / (SmoothPeriod + 1.0);
}
public override void Calculate(int index)
{
// Check for sufficient data.
if (index < CumulativePeriod)
{
CVDPositive[index] = 0;
CVDNegative[index] = 0;
CVDSmooth[index] = 0;
return;
}
// Calculate non-cumulative delta volume for current bar.
DeltaVolume[index] = CalculateVolumeDelta(index);
// Calculate rolling cumulative delta over fixed period.
double rollingSumDelta = 0;
int periodsToSum = Math.Min(CumulativePeriod, index + 1);
// Sum delta volume for the last N bars (including current bar).
for (int j = 0; j < periodsToSum; j++)
{
rollingSumDelta += DeltaVolume[index - j];
}
CVDRaw[index] = rollingSumDelta;
// Apply smoothing.
ApplySmoothing(index);
// Split values into positive and negative buffers for histogram display.
if (CVDSmooth[index] >= 0)
{
CVDPositive[index] = CVDSmooth[index];
CVDNegative[index] = 0;
}
else
{
CVDPositive[index] = 0;
CVDNegative[index] = CVDSmooth[index];
}
}
private double CalculateVolumeDelta(int barIndex)
{
// Get current bar time.
DateTime barTime = Bars.OpenTimes[barIndex];
DateTime nextBarTime = Bars.OpenTimes[barIndex + 1]; // A newer bar.
if (IsLastBar) nextBarTime = DateTime.Now; // No newer bar.
// Find corresponding bars in lower timeframe.
int lowerTFStartIndex = lowerTFBars.OpenTimes.GetIndexByTime(barTime);
if (lowerTFStartIndex < 0 || lowerTFBars.OpenTimes[lowerTFStartIndex] < barTime)
return 0;
double totalDelta = 0;
// Accumulate delta from all lower timeframe bars within current bar.
for (int i = lowerTFStartIndex; i < lowerTFBars.Count; i++)
{
// Check if still within current bar timeframe.
if (lowerTFBars.OpenTimes[i] >= nextBarTime)
break;
// Get OHLC and volume for lower timeframe bar.
double ltfHigh = lowerTFBars.HighPrices[i];
double ltfLow = lowerTFBars.LowPrices[i];
double ltfClose = lowerTFBars.ClosePrices[i];
double ltfVolume = lowerTFBars.TickVolumes[i];
// Calculate delta using price position within range.
double range = ltfHigh - ltfLow;
double buyVolume = 0;
double sellVolume = 0;
if (range > 0)
{
// Estimate buy/sell volume based on close position in range.
double closePosition = (ltfClose - ltfLow) / range;
buyVolume = ltfVolume * closePosition;
sellVolume = ltfVolume * (1 - closePosition);
totalDelta += (buyVolume - sellVolume);
}
}
return totalDelta;
}
private void ApplySmoothing(int index)
{
if (SmoothPeriod <= 1 || SmoothMethod == SmoothingMethod.None)
{
CVDSmooth[index] = CVDRaw[index];
}
else if (SmoothMethod == SmoothingMethod.SMA)
{
CVDSmooth[index] = CalculateSMA(index, SmoothPeriod, CVDRaw);
}
else if (SmoothMethod == SmoothingMethod.EMA)
{
CVDSmooth[index] = CalculateEMA(index, SmoothPeriod, CVDRaw);
}
}
private double CalculateSMA(int index, int period, IndicatorDataSeries source)
{
if (period <= 0 || index < period - 1)
{
return source[index];
}
double sum = 0;
int count = 0;
for (int i = 0; i < period && index - i >= 0; i++)
{
sum += source[index - i];
count++;
}
return count > 0 ? sum / count : source[index];
}
private double CalculateEMA(int index, int period, IndicatorDataSeries source)
{
if (period <= 0)
{
return source[index];
}
// Initialize with SMA for the first value.
if (index < period)
{
return CalculateSMA(index, index + 1, source);
}
// For the first complete period, use SMA.
if (index == period - 1 || double.IsNaN(CVDSmooth[index - 1]))
{
return CalculateSMA(index, period, source);
}
// EMA formula: (Current - Previous EMA) * multiplier + Previous EMA.
return (source[index] - CVDSmooth[index - 1]) * alpha + CVDSmooth[index - 1];
}
}
}