# Dynamic Levels

```
//--------------------------------------------------------------------------------
//
// Индикатор 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();
            }
        }
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://support.tiger.com/razrabotka-dlya-tiger.trade-windows/primery-indikatorov/dynamic-levels.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
