Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions Technical/Delta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ public enum Location
Down
}

[Serializable]
public enum AverageMode
{
[Display(ResourceType = typeof(Strings), Name = nameof(Strings.SMA))]
Sma = 0,
[Display(ResourceType = typeof(Strings), Name = nameof(Strings.EMA))]
Ema = 1
}

#endregion

#region Fields
Expand Down Expand Up @@ -204,6 +213,32 @@ public enum Location
IgnoredByAlerts = true
};

#region Fields (average delta)

private readonly ValueDataSeries _avgSeries = new("AverageDelta", Strings.Average)
{
VisualType = VisualMode.Hide,
Width = 2,
Color = CrossColor.FromArgb(255, 60, 120, 240),
ShowCurrentValue = false
};

// Per-bar input of the average. Values are written by bar index, so repeated
// OnCalculate calls for the forming bar overwrite the sample instead of adding one.
private readonly ValueDataSeries _avgInput = new("AverageDeltaInput") { IsHidden = true };

// Running SMA sum per bar: _avgSum[bar] = _avgSum[bar - 1] + input[bar] - input[bar - period].
private readonly ValueDataSeries _avgSum = new("AverageDeltaSum") { IsHidden = true };

private bool _showAverage;
private int _averagePeriod = 20;
private AverageMode _avgMode = AverageMode.Sma;
private bool _avgColoredDirection;
private Color _avgBullishColor = Color.Green;
private Color _avgBearishColor = Color.Red;

#endregion

#endregion

#region Properties
Expand Down Expand Up @@ -560,6 +595,109 @@ public decimal NegativeAlertFilter

#endregion

#region Average Delta

[Display(ResourceType = typeof(Strings), Name = nameof(Strings.ShowAverage),
GroupName = nameof(Strings.Average), Order = 400)]
[Tab(TabName = nameof(Strings.Visualization), TabOrder = 1, ResourceType = typeof(Strings))]
public bool ShowAverage
{
get => _showAverage;
set
{
if (_showAverage == value)
return;

_showAverage = value;
_avgSeries.VisualType = value ? VisualMode.Line : VisualMode.Hide;
RecalculateValues();
RedrawChart();
}
}

[Display(ResourceType = typeof(Strings), Name = nameof(Strings.AveragePeriod),
GroupName = nameof(Strings.Average), Order = 410)]
[Range(1, 1000)]
[Tab(TabName = nameof(Strings.Visualization), TabOrder = 1, ResourceType = typeof(Strings))]
public int AveragePeriod
{
get => _averagePeriod;
set
{
if (value < 1) value = 1;
if (_averagePeriod == value)
return;

_averagePeriod = value;
RecalculateValues();
RedrawChart();
}
}

[Display(ResourceType = typeof(Strings), Name = nameof(Strings.CalculationMode),
GroupName = nameof(Strings.Average), Order = 420)]
[Tab(TabName = nameof(Strings.Visualization), TabOrder = 1, ResourceType = typeof(Strings))]
public AverageMode AvgMode
{
get => _avgMode;
set
{
if (_avgMode == value)
return;

_avgMode = value;
RecalculateValues();
RedrawChart();
}
}

[Display(ResourceType = typeof(Strings), Name = nameof(Strings.ColoredDirection),
GroupName = nameof(Strings.Average), Order = 430)]
[Tab(TabName = nameof(Strings.Visualization), TabOrder = 1, ResourceType = typeof(Strings))]
public bool AvgColoredDirection
{
get => _avgColoredDirection;
set
{
if (_avgColoredDirection == value)
return;

_avgColoredDirection = value;

// Per-bar colors cannot be unset individually; a recalculation clears them.
RecalculateValues();
RedrawChart();
}
}

[Display(ResourceType = typeof(Strings), Name = nameof(Strings.BullishColor),
GroupName = nameof(Strings.Average), Order = 431)]
[Tab(TabName = nameof(Strings.Visualization), TabOrder = 1, ResourceType = typeof(Strings))]
public CrossColor AvgBullishColor
{
get => _avgBullishColor.Convert();
set
{
_avgBullishColor = value.Convert();
RecolorAverage();
}
}

[Display(ResourceType = typeof(Strings), Name = nameof(Strings.BearishColor),
GroupName = nameof(Strings.Average), Order = 432)]
[Tab(TabName = nameof(Strings.Visualization), TabOrder = 1, ResourceType = typeof(Strings))]
public CrossColor AvgBearishColor
{
get => _avgBearishColor.Convert();
set
{
_avgBearishColor = value.Convert();
RecolorAverage();
}
}

#endregion

#endregion

#region ctor
Expand All @@ -586,6 +724,7 @@ public Delta()
DataSeries.Add(_divergenceDownCandles);

DataSeries.Add(_absorptionCandles);
DataSeries.Add(_avgSeries);

UpAlert.PropertyChanged += OnUpAlertChanged;
DownAlert.PropertyChanged += OnDownAlertChanged;
Expand Down Expand Up @@ -906,6 +1045,8 @@ protected override void OnCalculate(int bar, decimal value)
}
}

CalculateAverage(bar, deltaValue);

_prevDeltaValue = deltaValue;

if (Absorption.Enabled)
Expand Down Expand Up @@ -986,6 +1127,60 @@ protected override void OnCalculate(int bar, decimal value)

#region Private methods

private void CalculateAverage(int bar, decimal deltaValue)
{
if (!_showAverage)
{
_avgSeries[bar] = 0m;
return;
}

// Everything below depends only on the values of this bar and the stored results of
// previous bars, so recalculating the forming bar any number of times gives the same
// value as a full historical recalculation.
_avgInput[bar] = deltaValue;

decimal average;

if (_avgMode == AverageMode.Sma)
{
var sum = (bar > 0 ? _avgSum[bar - 1] : 0m) + deltaValue;

if (bar >= _averagePeriod)
sum -= _avgInput[bar - _averagePeriod];

_avgSum[bar] = sum;
average = sum / Math.Min(_averagePeriod, bar + 1);
}
else
{
var k = 2m / (_averagePeriod + 1m);
average = bar == 0 ? deltaValue : _avgSeries[bar - 1] + (deltaValue - _avgSeries[bar - 1]) * k;
}

_avgSeries[bar] = average;
SetAverageColor(bar);
}

private void SetAverageColor(int bar)
{
// Without direction coloring no per-bar color is set, so the line keeps the series color.
if (!_avgColoredDirection || bar == 0)
return;

_avgSeries.Colors[bar] = _avgSeries[bar] >= _avgSeries[bar - 1] ? _avgBullishColor : _avgBearishColor;
}

private void RecolorAverage()
{
if (_showAverage && _avgColoredDirection)
{
for (var bar = 0; bar < CurrentBar; bar++)
SetAverageColor(bar);
}

RedrawChart();
}

private int GetMinWidth(RenderContext context, int startBar, int endBar)
{
Expand Down