Cumulative Delta
//------------------------------------------------------------------------------
//
// Indicator CumulativeDelta. Copyright (c) 2023 Tiger Trade Capital AG. All rights reserved.
//
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Media;
using TigerTrade.Chart.Base;
using TigerTrade.Chart.Base.Enums;
using TigerTrade.Chart.Indicators.Common;
using TigerTrade.Chart.Indicators.Enums;
using TigerTrade.Core.Utils.Time;
using TigerTrade.Dx;
namespace TigerTrade.Chart.Indicators.Custom
{
[DataContract(Name = "CumulativeDeltaIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
[Indicator("X_CumulativeDelta", "*CumulativeDelta", false, Type = typeof(CumulativeDeltaIndicator))]
internal sealed class CumulativeDeltaIndicator : IndicatorBase
{
private IndicatorPeriodType _period;
[DataMember(Name = "Period")]
[Category("Параметры"), DisplayName("Период")]
public IndicatorPeriodType Period
{
get => _period;
set
{
if (value == _period)
{
return;
}
_period = value;
Clear();
OnPropertyChanged();
}
}
private IndicatorViewType _type;
[DataMember(Name = "Type")]
[Category("Параметры"), DisplayName("Тип")]
public IndicatorViewType Type
{
get => _type;
set
{
if (value == _type)
{
return;
}
_type = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UpColor));
OnPropertyChanged(nameof(DownColor));
OnPropertyChanged(nameof(LineColor));
OnPropertyChanged(nameof(LineWidth));
}
}
private XBrush _upBrush;
private XPen _upPen;
private XColor _upColor;
[DataMember(Name = "UpColor")]
[Category("Стиль"), DisplayName("Цвет при росте")]
public XColor UpColor
{
get => _upColor;
set
{
if (value == _upColor)
{
return;
}
_upColor = value;
_upBrush = new XBrush(_upColor);
_upPen = new XPen(_upBrush, 1);
OnPropertyChanged();
}
}
private XBrush _downBrush;
private XPen _downPen;
private XColor _downColor;
[DataMember(Name = "DownColor")]
[Category("Стиль"), DisplayName("Цвет при падении")]
public XColor DownColor
{
get => _downColor;
set
{
if (value == _downColor)
{
return;
}
_downColor = value;
_downBrush = new XBrush(_downColor);
_downPen = new XPen(_downBrush, 1);
OnPropertyChanged();
}
}
private XBrush _lineBrush;
private XPen _linePen;
private XColor _lineColor;
[DataMember(Name = "LineColor")]
[Category("Стиль"), DisplayName("Цвет линии")]
public XColor LineColor
{
get => _lineColor;
set
{
if (value == _lineColor)
{
return;
}
_lineColor = value;
_lineBrush = new XBrush(_lineColor);
_linePen = new XPen(_lineBrush, LineWidth);
OnPropertyChanged();
}
}
private int _lineWidth;
[DataMember(Name = "LineWidth")]
[Category("Стиль"), DisplayName("Толщина линии")]
public int LineWidth
{
get => _lineWidth;
set
{
value = Math.Max(1, Math.Min(9, value));
if (value == _lineWidth)
{
return;
}
_lineWidth = value;
_linePen = new XPen(_lineBrush, _lineWidth);
OnPropertyChanged();
}
}
[Browsable(false)]
public override IndicatorCalculation Calculation => IndicatorCalculation.OnEachTick;
public override bool IntegerValues => true;
private class CumDeltaItem
{
public long Open;
public long High;
public long Low;
public long Close;
public int Seq = -1;
}
private int _lastFullID;
private List<CumDeltaItem> _items;
private List<CumDeltaItem> Items => _items ?? (_items = new List<CumDeltaItem>());
private void Clear()
{
_lastFullID = 0;
Items.Clear();
}
public CumulativeDeltaIndicator()
{
Period = IndicatorPeriodType.Day;
Type = IndicatorViewType.Columns;
UpColor = Color.FromArgb(255, 30, 144, 255);
DownColor = Color.FromArgb(255, 178, 34, 34);
LineColor = Color.FromArgb(255, 30, 144, 255);
LineWidth = 1;
}
protected override void Execute()
{
if (ClearData)
{
Clear();
}
var timeOffset = TimeHelper.GetSessionOffset(DataProvider.Symbol.Exchange);
for (var i = _lastFullID; i < DataProvider.Count; i++)
{
var cluster = DataProvider.GetRawCluster(i);
if (Items.Count < i + 1)
{
Items.Add(new CumDeltaItem());
}
var item = Items[i];
var prevItem = Items.Count > 1 ? Items[i - 1] : new CumDeltaItem();
var sequence = 1;
switch (Period)
{
case IndicatorPeriodType.Day:
sequence = DataProvider.Period.GetSequence(ChartPeriodType.Day, 1, cluster.Time, timeOffset);
break;
case IndicatorPeriodType.Week:
sequence = DataProvider.Period.GetSequence(ChartPeriodType.Week, 1, cluster.Time, timeOffset);
break;
case IndicatorPeriodType.Month:
sequence = DataProvider.Period.GetSequence(ChartPeriodType.Month, 1, cluster.Time, timeOffset);
break;
}
var cumDeltaValue = 0L;
if (sequence == prevItem.Seq)
{
cumDeltaValue = prevItem.Close;
}
item.Seq = sequence;
item.Open = cumDeltaValue;
cumDeltaValue += cluster.Delta;
item.Close = cumDeltaValue;
item.High = cumDeltaValue + (cluster.DeltaHigh - cluster.Delta);
item.Low = cumDeltaValue + (cluster.DeltaLow - cluster.Delta);
}
_lastFullID = Math.Max(DataProvider.Count - 2, 0);
}
public override bool GetMinMax(out double min, out double max)
{
min = double.MaxValue;
max = double.MinValue;
if (Items.Count == 0)
{
return false;
}
var longMin = long.MaxValue;
var longMax = long.MinValue;
if (Type == IndicatorViewType.Candles)
{
for (var i = 0; i < Canvas.Count; i++)
{
var index = Canvas.GetIndex(i);
if (index < 0 || index >= Items.Count)
{
continue;
}
var item = Items[index];
if (longMin > item.Low)
{
longMin = item.Low;
}
if (longMax < item.High)
{
longMax = item.High;
}
}
}
else
{
for (var i = 0; i < Canvas.Count; i++)
{
var index = Canvas.GetIndex(i);
if (index < 0 || index >= Items.Count)
{
continue;
}
var item = Items[index];
if (longMin > item.Close)
{
longMin = item.Close;
}
if (longMax < item.Close)
{
longMax = item.Close;
}
}
}
if (longMin > longMax)
{
return false;
}
min = (double) DataProvider.Symbol.GetSize(longMin);
max = (double) DataProvider.Symbol.GetSize(longMax);
return true;
}
public override void Render(DxVisualQueue visual)
{
var symbol = DataProvider.Symbol;
switch (Type)
{
case IndicatorViewType.Candles:
{
var width = (int)(Canvas.ColumnWidth * Canvas.ColumnPercent + 0.4);
var halfWith = Math.Max((int)(width / 2.0), 1.0);
for (var i = 0; i < Canvas.Count; i++)
{
var index = Canvas.GetIndex(i);
if (index < 0 || index >= Items.Count)
{
continue;
}
var item = Items[index];
var isUp = item.Close > item.Open;
var centerX = (int)Canvas.GetX(index);
var openY = (int)GetY(symbol.GetSize(item.Open));
var highY = (int)GetY(symbol.GetSize(item.High));
var lowY = (int)GetY(symbol.GetSize(item.Low));
var closeY = (int)GetY(symbol.GetSize(item.Close));
var topY = Math.Min(openY, closeY);
var bottomY = Math.Max(openY, closeY);
var height = bottomY - topY;
var backBrush = isUp ? _upBrush : _downBrush;
var backPen = isUp ? _upPen : _downPen;
if (height == 0 || width <= 1)
{
if (highY == lowY)
{
lowY++;
}
visual.DrawLine(backPen,
new Point(centerX, highY),
new Point(centerX, lowY));
}
else
{
visual.DrawLine(backPen,
new Point(centerX, highY),
new Point(centerX, topY));
visual.DrawLine(backPen,
new Point(centerX, bottomY),
new Point(centerX, lowY));
}
if (width > 1)
{
if (height == 0)
{
visual.DrawLine(backPen,
new Point(centerX - halfWith, topY),
new Point(centerX + halfWith + 1, topY));
}
else
{
visual.FillRectangle(backBrush,
new Rect(centerX - halfWith, topY, halfWith * 2 + 1, height));
}
}
}
}
break;
case IndicatorViewType.Columns:
{
var zeroPoint = GetY(0.0);
var width = Math.Max(Canvas.ColumnWidth - 1, 1);
for (var i = 0; i < Canvas.Count; i++)
{
var index = Canvas.GetIndex(i);
if (index < 0 || index >= Items.Count)
{
continue;
}
var item = Items[index];
var x = Canvas.GetX(index);
var y = GetY(symbol.GetSize(item.Open));
var h = (int)zeroPoint - (int)y;
var isUp = h > 0;
if (h < 0.0)
{
y += h;
h = -h;
}
h = Math.Max(1, h);
if (width > 1.0)
{
var rect = new Rect(x - width / 2.0, y, width, h);
visual.FillRectangle(isUp ? _upBrush : _downBrush, rect);
}
else
{
visual.DrawLine(isUp ? _upPen : _downPen, x, y, x, y + h);
}
}
}
break;
case IndicatorViewType.Line:
{
if (Items.Count < 2)
{
return;
}
var points = new Point[Canvas.Count];
for (var i = 0; i < Canvas.Count; i++)
{
var index = Canvas.GetIndex(i);
if (index < 0 || index >= Items.Count)
{
continue;
}
var item = Items[index];
var x = Canvas.GetX(index);
var y = GetY(symbol.GetSize(item.Open));
points[i] = new Point(x, y);
}
visual.DrawLines(_linePen, points);
}
break;
}
}
public override List<IndicatorValueInfo> GetValues(int cursorPos)
{
var info = new List<IndicatorValueInfo>();
if (cursorPos >= 0 && cursorPos < Items.Count)
{
var s = Canvas.FormatValue((double)DataProvider.Symbol.GetSize(Items[cursorPos].Close));
info.Add(new IndicatorValueInfo(s,
Type == IndicatorViewType.Line ? _lineBrush : Canvas.Theme.ChartFontBrush));
}
return info;
}
public override void GetLabels(ref List<IndicatorLabelInfo> labels)
{
if (Items.Count == 0)
{
return;
}
if (DataProvider.Count <= Canvas.Start)
{
return;
}
var index = DataProvider.Count - 1 - Canvas.Start;
if (index < 0 && index >= Items.Count)
{
return;
}
var item = Items[index];
var lastValue = (double)DataProvider.Symbol.GetSize(item.Close);
if (Type == IndicatorViewType.Line)
{
labels.Add(new IndicatorLabelInfo(lastValue, _lineColor));
return;
}
var isUp = false;
switch (Type)
{
case IndicatorViewType.Line:
case IndicatorViewType.Columns:
isUp = !double.IsNaN(lastValue) && lastValue > 0;
break;
case IndicatorViewType.Candles:
isUp = !double.IsNaN(lastValue) && item.Close > item.Open;
break;
}
labels.Add(new IndicatorLabelInfo(lastValue, isUp ? UpColor : DownColor));
}
public override void ApplyColors(IChartTheme theme)
{
UpColor = theme.PaletteColor6;
DownColor = theme.PaletteColor7;
LineColor = theme.GetNextColor();
base.ApplyColors(theme);
}
public override void CopyTemplate(IndicatorBase indicator, bool style)
{
var i = (CumulativeDeltaIndicator)indicator;
Period = i.Period;
Type = i.Type;
UpColor = i.UpColor;
DownColor = i.DownColor;
LineColor = i.LineColor;
LineWidth = i.LineWidth;
base.CopyTemplate(indicator, style);
}
public override string ToString()
{
return $"{Name} ({Period})";
}
public override bool GetPropertyVisibility(string propertyName)
{
switch (propertyName)
{
case "LineWidth":
case "LineColor":
return Type == IndicatorViewType.Line;
case "UpColor":
case "DownColor":
return Type != IndicatorViewType.Line;
}
return true;
}
}
}
Last updated