Dynamic Levels
//--------------------------------------------------------------------------------
//
// Indicator DynamicLevels. 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.Media;
using TigerTrade.Chart.Base;
using TigerTrade.Chart.Base.Enums;
using TigerTrade.Chart.Data;
using TigerTrade.Chart.Indicators.Common;
using TigerTrade.Chart.Indicators.Drawings;
using TigerTrade.Chart.Indicators.Drawings.Enums;
using TigerTrade.Chart.Indicators.Enums;
using TigerTrade.Core.UI.Converters;
using TigerTrade.Core.Utils.Time;
using TigerTrade.Dx;
namespace TigerTrade.Chart.Indicators.Custom
{
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
[DataContract(Name = "DynamicLevelsPeriodType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
public enum DynamicLevelsPeriodType
{
[EnumMember(Value = "Hour"), Description("Час")]
Hour,
[EnumMember(Value = "Day"), Description("День")]
Day,
[EnumMember(Value = "Week"), Description("Неделя")]
Week,
[EnumMember(Value = "Month"), Description("Месяц")]
Month,
[EnumMember(Value = "AllBars"), Description("Все бары")]
AllBars,
}
[DataContract(Name = "DynamicLevelsIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
[Indicator("X_DynamicLevels", "*DynamicLevels", true, Type = typeof(DynamicLevelsIndicator))]
internal sealed class DynamicLevelsIndicator : IndicatorBase
{
private DynamicLevelsPeriodType _period;
[DataMember(Name = "Period")]
[Category("Параметры"), DisplayName("Период")]
public DynamicLevelsPeriodType Period
{
get => _period;
set
{
if (value == _period)
{
return;
}
_period = value;
_lastDataWrapper?.Clear();
OnPropertyChanged();
}
}
private bool _showValueArea;
[DataMember(Name = "ShowValueArea")]
[Category("ChartIndicatorsValueArea"), DisplayName("Отображать Value Area")]
public bool ShowValueArea
{
get => _showValueArea;
set
{
if (value == _showValueArea)
{
return;
}
_showValueArea = value;
_lastDataWrapper?.Clear();
OnPropertyChanged();
}
}
private int _valueAreaPercent;
[DataMember(Name = "ValueAreaPercent")]
[Category("ChartIndicatorsValueArea"), DisplayName("ValueArea %")]
public int ValueAreaPercent
{
get => _valueAreaPercent;
set
{
value = Math.Max(0, Math.Min(100, value));
if (value == 0)
{
value = 70;
}
if (value == _valueAreaPercent)
{
return;
}
_valueAreaPercent = value;
_lastDataWrapper?.Clear();
OnPropertyChanged();
}
}
private XColor _valueAreaBackColor;
[DataMember(Name = "ValueAreaBackColor")]
[Category("ChartIndicatorsValueArea"), DisplayName("Цвет фона")]
public XColor ValueAreaBackColor
{
get => _valueAreaBackColor;
set
{
if (value == _valueAreaBackColor)
{
return;
}
_valueAreaBackColor = value;
OnPropertyChanged();
}
}
private XColor _valueAreaBorderColor;
[DataMember(Name = "ValueAreaBorderColor")]
[Category("ChartIndicatorsValueArea"), DisplayName("Цвет границы")]
public XColor ValueAreaBorderColor
{
get => _valueAreaBorderColor;
set
{
if (value == _valueAreaBorderColor)
{
return;
}
_valueAreaBorderColor = value;
OnPropertyChanged();
}
}
private int _valueAreaBorderWidth;
[DataMember(Name = "ValueAreaBorderWidth")]
[Category("ChartIndicatorsValueArea"), DisplayName("Ширина границы")]
public int ValueAreaBorderWidth
{
get => _valueAreaBorderWidth;
set
{
value = Math.Max(0, Math.Min(10, value));
if (value == _valueAreaBorderWidth)
{
return;
}
_valueAreaBorderWidth = value;
OnPropertyChanged();
}
}
private bool _showPoc;
[DataMember(Name = "ShowPoc")]
[Category("ChartIndicatorsPoc"), DisplayName("Отображать POC")]
public bool ShowPoc
{
get => _showPoc;
set
{
if (value == _showPoc)
{
return;
}
_showPoc = value;
OnPropertyChanged();
}
}
private XColor _pocLineColor;
[DataMember(Name = "PocLineColor")]
[Category("ChartIndicatorsPoc"), DisplayName("Цвет линии")]
public XColor PocLineColor
{
get => _pocLineColor;
set
{
if (value == _pocLineColor)
{
return;
}
_pocLineColor = value;
OnPropertyChanged();
}
}
private int _pocLineWidth;
[DataMember(Name = "PocLineWidth")]
[Category("ChartIndicatorsPoc"), DisplayName("Толщина линии")]
public int PocLineWidth
{
get => _pocLineWidth;
set
{
value = Math.Max(1, Math.Min(10, value));
if (value == _pocLineWidth)
{
return;
}
_pocLineWidth = value;
OnPropertyChanged();
}
}
[Browsable(false)]
public override IndicatorCalculation Calculation => IndicatorCalculation.OnBarClose;
public DynamicLevelsIndicator()
{
Period = DynamicLevelsPeriodType.Day;
ShowValueArea = true;
ValueAreaPercent = 70;
ValueAreaBackColor = Color.FromArgb(50, 255, 255, 255);
ValueAreaBorderColor = Colors.Black;
ValueAreaBorderWidth = 1;
ShowPoc = true;
PocLineColor = Colors.Black;
PocLineWidth = 1;
}
private class LastDataWrapper
{
public double[] Vah;
public double[] Val;
public double[] Poc;
public bool[] Splits;
public int LastSequence;
public int LastIndex;
public int LastCount;
public readonly List<DynamicCluster> Clusters = new List<DynamicCluster>();
public LastDataWrapper()
{
Clear();
}
public void Clear()
{
LastSequence = -1;
LastIndex = 0;
LastCount = 0;
Vah = new double[0];
Val = new double[0];
Poc = new double[0];
Splits = new bool[0];
Clusters.Clear();
}
public void Prepare(int count, double step)
{
LastIndex = Math.Max(0, count - 1);
LastCount = count;
Vah = new double[count];
Val = new double[count];
Poc = new double[count];
Splits = new bool[count];
for (var i = 0; i < count - 1; i++)
{
var cluster = Clusters[i];
Vah[i] = cluster.Vah * step;
Val[i] = cluster.Val * step;
Poc[i] = cluster.Poc * step;
Splits[i] = cluster.Split;
}
if (count > 1)
{
Vah[count - 1] = Vah[count - 2];
Val[count - 1] = Val[count - 2];
Poc[count - 1] = Poc[count - 2];
}
}
}
private LastDataWrapper _lastDataWrapper;
protected override void Execute()
{
if (ClearData)
{
_lastDataWrapper = null;
}
if (_lastDataWrapper == null)
{
_lastDataWrapper = new LastDataWrapper();
}
var date = Helper.Date;
var timeOffset = TimeHelper.GetSessionOffset(DataProvider.Symbol.Exchange);
if (date.Length > _lastDataWrapper.LastCount)
{
for (var i = _lastDataWrapper.LastIndex; i < date.Length - 1; i++)
{
var sequence = -1;
switch (Period)
{
case DynamicLevelsPeriodType.Hour:
sequence = DataProvider.Period.GetSequence(ChartPeriodType.Hour, 1, date[i], timeOffset);
break;
case DynamicLevelsPeriodType.Day:
sequence = DataProvider.Period.GetSequence(ChartPeriodType.Day, 1, date[i], timeOffset);
break;
case DynamicLevelsPeriodType.Week:
sequence = DataProvider.Period.GetSequence(ChartPeriodType.Week, 1, date[i], timeOffset);
break;
case DynamicLevelsPeriodType.Month:
sequence = DataProvider.Period.GetSequence(ChartPeriodType.Month, 1, date[i], timeOffset);
break;
}
var currCluster = DataProvider.GetRawCluster(i);
var prevCluster = i > 0 ? _lastDataWrapper.Clusters[i - 1] : null;
var split = false;
if (sequence != _lastDataWrapper.LastSequence)
{
prevCluster = null;
_lastDataWrapper.LastSequence = sequence;
split = true;
}
var newCluster = new DynamicCluster(prevCluster)
{
Split = split
};
newCluster.AddCluster(currCluster);
newCluster.UpdateData();
if (ShowValueArea)
{
newCluster.UpdateValueArea(ValueAreaPercent / 100.0);
}
if (_lastDataWrapper.Clusters.Count == date.Length)
{
_lastDataWrapper.Clusters[i] = newCluster;
}
else
{
_lastDataWrapper.Clusters.Add(newCluster);
if (_lastDataWrapper.Clusters.Count > 1)
{
_lastDataWrapper.Clusters[_lastDataWrapper.Clusters.Count - 2].ClearItems();
}
}
}
_lastDataWrapper.Prepare(date.Length, DataProvider.Step);
}
if (ShowValueArea)
{
var vaData = new IndicatorSeriesData(_lastDataWrapper.Vah, new ChartRegion(ValueAreaBackColor))
{
Style = { DisableMinMax = true },
["L"] = _lastDataWrapper.Val
};
Series.Add(vaData);
if (ValueAreaBorderWidth > 0)
{
var vahData = new IndicatorSeriesData(_lastDataWrapper.Vah,
new ChartSeries(ChartSeriesType.Line, ValueAreaBorderColor)
{
Width = ValueAreaBorderWidth
})
{
Style =
{
DisableMinMax = true
}
};
var valData = new IndicatorSeriesData(_lastDataWrapper.Val,
new ChartSeries(ChartSeriesType.Line, ValueAreaBorderColor)
{
Width = ValueAreaBorderWidth
})
{
Style =
{
DisableMinMax = true
}
};
Series.Add(vahData, valData);
}
}
if (ShowPoc)
{
var pocData = new IndicatorSeriesData(_lastDataWrapper.Poc,
new ChartSeries(ChartSeriesType.Line, PocLineColor))
{
Style =
{
DisableMinMax = true,
StraightLine = true,
LineWidth = PocLineWidth
}
};
Series.Add(pocData);
}
foreach (var series in Series)
{
series.UserData["S"] = _lastDataWrapper.Splits;
series.UserData["SE"] = true;
}
}
public override void ApplyColors(IChartTheme theme)
{
ValueAreaBorderColor = theme.GetNextColor();
ValueAreaBackColor = new XColor(50, ValueAreaBorderColor);
PocLineColor = theme.GetNextColor();
base.ApplyColors(theme);
}
public override void CopyTemplate(IndicatorBase indicator, bool style)
{
var i = (DynamicLevelsIndicator)indicator;
Period = i.Period;
ShowValueArea = i.ShowValueArea;
ValueAreaPercent = i.ValueAreaPercent;
ValueAreaBackColor = i.ValueAreaBackColor;
ValueAreaBorderColor = i.ValueAreaBorderColor;
ValueAreaBorderWidth = i.ValueAreaBorderWidth;
ShowPoc = i.ShowPoc;
PocLineColor = i.PocLineColor;
PocLineWidth = i.PocLineWidth;
base.CopyTemplate(indicator, style);
}
internal sealed class DynamicCluster
{
public DateTime Time;
public long High;
public long Low;
public long Volume;
public long MaxVolume;
public long Poc;
public long Vah;
public long Val;
public bool Split;
private readonly Dictionary<long, long> _items = new Dictionary<long, long>();
public DynamicCluster(DynamicCluster cluster)
{
if (cluster == null)
{
return;
}
Time = cluster.Time;
_items = new Dictionary<long, long>(cluster._items.Count);
foreach (var item in cluster._items)
{
_items.Add(item.Key, item.Value);
}
High = cluster.High;
Low = cluster.Low;
Volume = cluster.Volume;
}
public void AddCluster(IRawCluster cluster)
{
Time = cluster.Time;
foreach (var item in cluster.Items)
{
if (!_items.ContainsKey(item.Price))
{
_items.Add(item.Price, 0);
}
_items[item.Price] += item.Volume;
}
High = High == 0 ? cluster.High : Math.Max(High, cluster.High);
Low = Low == 0 ? cluster.Low : Math.Min(Low, cluster.Low);
Volume += cluster.Volume;
}
public void UpdateData()
{
MaxVolume = 0;
Poc = 0;
foreach (var item in _items)
{
if (item.Value <= MaxVolume)
{
continue;
}
MaxVolume = item.Value;
Poc = item.Key;
}
}
public void UpdateValueArea(double valueArea)
{
if (_items.Count == 0)
{
return;
}
if (High - Low > 100000)
{
return;
}
if (valueArea < 0.01)
{
Vah = Poc;
Val = Poc;
return;
}
if (valueArea > 0.99)
{
Vah = High;
Val = Low;
return;
}
var vah = 0L;
var val = 0L;
if (High != 0 && Low != 0)
{
vah = Poc;
val = Poc;
var vol = MaxVolume;
var areaVol = Volume * valueArea;
while (vol < areaVol)
{
if (vah >= High && val <= Low)
{
break;
}
var currentVahVol = 0L;
var currentValVol = 0L;
var currentVah = vah;
var currentVal = val;
for (var i = 0; i <= 1; i++)
{
if (High >= currentVah + 1)
{
currentVah++;
if (_items.ContainsKey(currentVah))
{
currentVahVol += _items[currentVah];
}
}
}
for (var j = 0; j <= 1; j++)
{
if (Low <= currentVal - 1)
{
currentVal--;
if (_items.ContainsKey(currentVal))
{
currentValVol += _items[currentVal];
}
}
}
if (currentVahVol == currentValVol && currentVahVol == 0)
{
if (High == currentVah)
{
val = currentVal;
}
if (Low == currentVal)
{
vah = currentVah;
}
}
if (currentVahVol >= currentValVol)
{
vah = currentVah;
vol += currentVahVol;
}
else
{
val = currentVal;
vol += currentValVol;
}
if (vol >= areaVol)
{
break;
}
}
}
Vah = vah;
Val = val;
}
public void ClearItems()
{
_items.Clear();
}
}
}
}
Last updated