Code of the Dynamic Levels indicator
//-------------------------------------------------------------------------------- // // DynamicLevels. Copyright (c) 2019 Ilya Smirnov. 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(); } } } }