Cluster Search

//------------------------------------------------------------------------------
//
// Индикатор ClusterSearch. 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.Input;
using System.Windows.Media;
using TigerTrade.Chart.Alerts;
using TigerTrade.Chart.Base.Enums;
using TigerTrade.Chart.Data;
using TigerTrade.Chart.Indicators.Common;
using TigerTrade.Chart.Indicators.Enums;
using TigerTrade.Core.UI.Converters;
using TigerTrade.Dx;
 
namespace TigerTrade.Chart.Indicators.Custom
{
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    [DataContract(Name = "ClusterSearchDataType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    public enum ClusterSearchDataType
    {
        [EnumMember(Value = "Volume"), Description("Volume")]
        Volume,
        [EnumMember(Value = "MaxVolume"), Description("Max Volume")]
        MaxVol,
        [EnumMember(Value = "Trades"), Description("Trades")]
        Trades,
        [EnumMember(Value = "Bid"), Description("Bid")]
        Bid,
        [EnumMember(Value = "Ask"), Description("Ask")]
        Ask,
        [EnumMember(Value = "Delta"), Description("Delta")]
        Delta,
        [EnumMember(Value = "DeltaPlus"), Description("Delta+")]
        DeltaPlus,
        [EnumMember(Value = "DeltaMinus"), Description("Delta-")]
        DeltaMinus
    }
 
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    [DataContract(Name = "ClusterSearchObjectType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    public enum ClusterSearchObjectType
    {
        [EnumMember(Value = "Rectangle"), Description("Квадрат")]
        Rectangle,
        [EnumMember(Value = "Triangle"), Description("Треугольник")]
        Triangle,
        [EnumMember(Value = "Diamond"), Description("Ромб")]
        Diamond,
        [EnumMember(Value = "Circle"), Description("Круг")]
        Circle,
        [EnumMember(Value = "SelectionOnly"), Description("Только выделение")]
        SelectionOnly
    }
 
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    [DataContract(Name = "ClusterSearchBarDirection", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    public enum ClusterSearchBarDirection
    {
        [EnumMember(Value = "Any"), Description("Любое")]
        Any,
        [EnumMember(Value = "Up"), Description("Рост")]
        Up,
        [EnumMember(Value = "Down"), Description("Падение")]
        Down
    }
 
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    [DataContract(Name = "ClusterSearchPriceLocation", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    public enum ClusterSearchPriceLocation
    {
        [EnumMember(Value = "Any"), Description("Любое")]
        Any,
        [EnumMember(Value = "High"), Description("High")]
        High,
        [EnumMember(Value = "Low"), Description("Low")]
        Low,
        [EnumMember(Value = "HighLow"), Description("High или Low")]
        HighLow,
        [EnumMember(Value = "Body"), Description("Тело")]
        Body,
        [EnumMember(Value = "Wick"), Description("Тень")]
        Wick,
        [EnumMember(Value = "UpperWick"), Description("Верхняя тень")]
        UpperWick,
        [EnumMember(Value = "LowerWick"), Description("Нижняя тень")]
        LowerWick
    }
 
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    [DataContract(Name = "ClusterSearchPriceRangeDirection", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    public enum ClusterSearchPriceRangeDirection
    {
        [EnumMember(Value = "All"), Description("Оба")]
        All,
        [EnumMember(Value = "Downward"), Description("Сверху вниз")]
        Downward,
        [EnumMember(Value = "Upward"), Description("Снизу вверх")]
        Upward
    }
 
    [DataContract(Name = "ClusterSearchIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    [Indicator("X_ClusterSearch", "*ClusterSearch", true, Type = typeof(ClusterSearchIndicator))]
    internal sealed class ClusterSearchIndicator : IndicatorBase, IContainsSelection
    {
        private class ClusterSearchItem
        {
            public DateTime Time { get; }
            public long PriceHigh { get; }
            public long PriceLow { get; }
            public long Value { get; }
 
            public ClusterSearchItem(DateTime time, long priceHigh, long priceLow, long value)
            {
                Time = time;
                PriceHigh = priceHigh;
                PriceLow = priceLow;
                Value = value;
            }
        }
 
        private class ClusterSearchRect
        {
            private Rect _rect;
 
            private readonly ClusterSearchItem _item;
 
            public ClusterSearchRect(Rect rect, ClusterSearchItem item)
            {
                _rect = rect;
                _item = item;
            }
 
            public bool Contains(Point p)
            {
                return _rect.Contains(p);
            }
 
            public string GetLabel(ClusterSearchDataType type, IChartDataProvider dp, string name)
            {
                var price = dp.Symbol.FormatRawPrice((_item.PriceHigh + _item.PriceLow) / 2, true);
 
                var val = type == ClusterSearchDataType.Trades
                    ? dp.Symbol.FormatTrades(_item.Value)
                    : dp.Symbol.FormatRawSizeShort(_item.Value);
 
                var time = dp.Symbol.FormatTime(_item.Time, "HH:mm:ss");
 
                return $"{name} {time} {price} x {val}";
            }
        }
 
        private class ClusterSearchBar
        {
            public List<ClusterSearchItem> Items { get; }
            public HashSet<long> Selections { get; }
            public HashSet<long> SingleSelection { get; }
 
            private readonly HashSet<long> _signals;
 
            public ClusterSearchBar()
            {
                Items = new List<ClusterSearchItem>();
                Selections = new HashSet<long>();
                SingleSelection = new HashSet<long>();
 
                _signals = new HashSet<long>();
            }
 
            public void Add(ClusterSearchItem item)
            {
                Items.Add(item);
            }
 
            public bool CheckSignal(long price)
            {
                if (_signals.Contains(price))
                {
                    return false;
                }
 
                _signals.Add(price);
 
                return true;
            }
 
            public void Clear()
            {
                Items.Clear();
                Selections.Clear();
                SingleSelection.Clear();
            }
 
            public void Update()
            {
                ClusterSearchItem maxItem = null;
 
                foreach (var item in Items)
                {
                    if (maxItem == null || maxItem.Value < item.Value)
                    {
                        maxItem = item;
                    }
 
                    for (var price = item.PriceLow; price <= item.PriceHigh; price++)
                    {
                        if (!Selections.Contains(price))
                        {
                            Selections.Add(price);
                        }
                    }
                }
 
                if (maxItem == null)
                {
                    return;
                }
 
                for (var price = maxItem.PriceLow; price <= maxItem.PriceHigh; price++)
                {
                    if (!SingleSelection.Contains(price))
                    {
                        SingleSelection.Add(price);
                    }
                }
            }
        }
 
        private ClusterSearchDataType _type;
 
        [DataMember(Name = "Type"), DefaultValue(ClusterSearchDataType.Volume)]
        [Category("Параметры"), DisplayName("Тип")]
        public ClusterSearchDataType Type
        {
            get => _type;
            set
            {
                if (value == _type)
                {
                    return;
                }
 
                _type = value;
 
                Clear();
 
                OnPropertyChanged();
                OnPropertyChanged(nameof(Title));
            }
        }
 
        private IndicatorIntParam _minimumParam;
 
        [DataMember(Name = "MinimumParam")]
        public IndicatorIntParam MinimumParam
        {
            get => _minimumParam ?? (_minimumParam = new IndicatorIntParam(1000));
            set => _minimumParam = value;
        }
 
        [DefaultValue(1000)]
        [Category("Параметры"), DisplayName("Минимум")]
        public int Minimum
        {
            get => MinimumParam.Get(SettingsLongKey);
            set
            {
                if (!MinimumParam.Set(SettingsLongKey, value, 0))
                {
                    return;
                }
 
                Clear();
 
                OnPropertyChanged();
                OnPropertyChanged(nameof(Title));
            }
        }
 
        private IndicatorNullIntParam _maximumParam;
 
        [DataMember(Name = "MaximumParam")]
        public IndicatorNullIntParam MaximumParam
        {
            get => _maximumParam ?? (_maximumParam = new IndicatorNullIntParam(null));
            set => _maximumParam = value;
        }
 
        [DefaultValue(null)]
        [Category("Параметры"), DisplayName("Максимум")]
        public int? Maximum
        {
            get => MaximumParam.Get(SettingsLongKey);
            set
            {
                if (!MaximumParam.Set(SettingsLongKey, value, 0))
                {
                    return;
                }
 
                Clear();
 
                OnPropertyChanged();
                OnPropertyChanged(nameof(Title));
            }
        }
 
        private ChartAlertSettings _alert;
 
        [DataMember(Name = "Alert"), Browsable(true)]
        [Category("Параметры"), DisplayName("Оповещение")]
        public ChartAlertSettings Alert
        {
            get => _alert ?? (_alert = new ChartAlertSettings());
            set
            {
                if (Equals(value, _alert))
                {
                    return;
                }
 
                _alert = value;
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _selectionBrush;
 
        private XColor _selectionColor;
 
        [DataMember(Name = "SelectionColor")]
        [Category("Стиль"), DisplayName("Цвет выделения")]
        public XColor SelectionColor
        {
            get => _selectionColor;
            set
            {
                if (value == _selectionColor)
                {
                    return;
                }
 
                _selectionColor = value;
 
                _selectionBrush = new XBrush(_selectionColor);
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _objectBackBrush;
 
        private XColor _objectBackColor;
 
        [DataMember(Name = "ObjectColor")]
        [Category("Стиль"), DisplayName("Цвет фона объекта")]
        public XColor ObjectBackColor
        {
            get => _objectBackColor;
            set
            {
                if (value == _objectBackColor)
                {
                    return;
                }
 
                _objectBackColor = value;
 
                _objectBackBrush = new XBrush(_objectBackColor);
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _objectBorderBrush;
 
        private XPen _objectBorderPen;
 
        private XColor _objectBorderColor;
 
        [DataMember(Name = "ObjectBorderColor")]
        [Category("Стиль"), DisplayName("Цвет границы объекта")]
        public XColor ObjectBorderColor
        {
            get => _objectBorderColor;
            set
            {
                if (value == _objectBorderColor)
                {
                    return;
                }
 
                _objectBorderColor = value;
 
                _objectBorderBrush = new XBrush(_objectBorderColor);
                _objectBorderPen = new XPen(_objectBorderBrush, 1);
 
                OnPropertyChanged();
            }
        }
 
        private ClusterSearchObjectType _objectType;
 
        [DataMember(Name = "ObjectType")]
        [Category("Стиль"), DisplayName("Тип фигуры")]
        public ClusterSearchObjectType ObjectType
        {
            get => _objectType;
            set
            {
                if (value == _objectType)
                {
                    return;
                }
 
                _objectType = value;
 
                OnPropertyChanged();
            }
        }
 
        private int _objectMinSize;
 
        [DataMember(Name = "ObjectMinSize")]
        [Category("Стиль"), DisplayName("Мин. размер")]
        public int ObjectMinSize
        {
            get => _objectMinSize;
            set
            {
                value = Math.Max(10, Math.Min(value, 200));
 
                if (value == _objectMinSize)
                {
                    return;
                }
 
                _objectMinSize = value;
 
                OnPropertyChanged();
            }
        }
 
        private int _objectMaxSize;
 
        [DataMember(Name = "ObjectMaxSize")]
        [Category("Стиль"), DisplayName("Макс. размер")]
        public int ObjectMaxSize
        {
            get => _objectMaxSize;
            set
            {
                value = Math.Min(200, Math.Max(value, 10));
 
                if (value == _objectMaxSize)
                {
                    return;
                }
 
                _objectMaxSize = value;
 
                OnPropertyChanged();
            }
        }
 
        private int _barsRange;
 
        [DataMember(Name = "BarsRange"), DefaultValue(1)]
        [Category("Фильтры"), DisplayName("Объедин. баров")]
        public int BarsRange
        {
            get => _barsRange;
            set
            {
                value = Math.Max(1, value);
 
                if (value == _barsRange)
                {
                    return;
                }
 
                _barsRange = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private int _priceRange;
 
        [DataMember(Name = "PriceRange"), DefaultValue(1)]
        [Category("Фильтры"), DisplayName("Объедин. цен")]
        public int PriceRange
        {
            get => _priceRange;
            set
            {
                value = Math.Max(1, value);
 
                if (value == _priceRange)
                {
                    return;
                }
 
                _priceRange = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private ClusterSearchPriceRangeDirection _priceRangeDir;
 
        [DataMember(Name = "PriceRangeDir"), DefaultValue(ClusterSearchPriceRangeDirection.All)]
        [Category("Фильтры"), DisplayName("Напр. объедин. цен")]
        public ClusterSearchPriceRangeDirection PriceRangeDir
        {
            get => _priceRangeDir;
            set
            {
                if (value == _priceRangeDir)
                {
                    return;
                }
 
                _priceRangeDir = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private IndicatorNullIntParam _minValueParam;
 
        [DataMember(Name = "MinValueParam")]
        public IndicatorNullIntParam MinValueParam
        {
            get => _minValueParam ?? (_minValueParam = new IndicatorNullIntParam());
            set => _minValueParam = value;
        }
 
        [DefaultValue(null)]
        [Category("Фильтры"), DisplayName("Мин. значение")]
        public int? MinValue
        {
            get => MinValueParam.Get(SettingsLongKey);
            set
            {
                if (!MinValueParam.Set(SettingsLongKey, value, 0))
                {
                    return;
                }
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private int? _minDelta;
 
        [DataMember(Name = "MinDelta"), DefaultValue(null)]
        [Category("Фильтры"), DisplayName("Мин. дельта")]
        public int? MinDelta
        {
            get => _minDelta;
            set
            {
                if (value == _minDelta)
                {
                    return;
                }
 
                _minDelta = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private int? _bidAskImbalance;
 
        [DataMember(Name = "BidAskImbalance"), DefaultValue(null)]
        [Category("Фильтры"), DisplayName("Перевес BidAsk")]
        public int? BidAskImbalance
        {
            get => _bidAskImbalance;
            set
            {
                if (value == _bidAskImbalance)
                {
                    return;
                }
 
                _bidAskImbalance = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private int? _rangeFromHigh;
 
        [DataMember(Name = "RangeFromHigh"), DefaultValue(null)]
        [Category("Фильтры"), DisplayName("Диапазон от High")]
        public int? RangeFromHigh
        {
            get => _rangeFromHigh;
            set
            {
                if (value.HasValue && value.Value < 1 && _rangeFromHigh.HasValue)
                {
                    value = null;
                }
                else if (value.HasValue && value.Value < 1 && !_rangeFromHigh.HasValue)
                {
                    value = 1;
                }
 
                if (value == _rangeFromHigh)
                {
                    return;
                }
 
                _rangeFromHigh = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private int? _rangeFromLow;
 
        [DataMember(Name = "RangeFromLow"), DefaultValue(null)]
        [Category("Фильтры"), DisplayName("Диапазон от Low")]
        public int? RangeFromLow
        {
            get => _rangeFromLow;
            set
            {
                if (value.HasValue && value.Value < 0)
                {
                    value = null;
                }
 
                if (value == _rangeFromLow)
                {
                    return;
                }
 
                _rangeFromLow = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private int? _minAvgTrade;
 
        [DataMember(Name = "MinAvgTrade"), DefaultValue(null)]
        [Category("Фильтры"), DisplayName("Мин. средний трейд")]
        public int? MinAvgTrade
        {
            get => _minAvgTrade;
            set
            {
                if (value.HasValue && value.Value < 0)
                {
                    value = null;
                }
 
                if (value == _minAvgTrade)
                {
                    return;
                }
 
                _minAvgTrade = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private int? _maxAvgTrade;
 
        [DataMember(Name = "MaxAvgTrade"), DefaultValue(null)]
        [Category("Фильтры"), DisplayName("Макс. средний трейд")]
        public int? MaxAvgTrade
        {
            get => _maxAvgTrade;
            set
            {
                if (value.HasValue && value.Value < 0)
                {
                    value = null;
                }
 
                if (value == _maxAvgTrade)
                {
                    return;
                }
 
                _maxAvgTrade = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private ClusterSearchBarDirection _barDirection;
 
        [DataMember(Name = "BarDirection"), DefaultValue(ClusterSearchBarDirection.Any)]
        [Category("Фильтры"), DisplayName("Направление бара")]
        public ClusterSearchBarDirection BarDirection
        {
            get => _barDirection;
            set
            {
                if (value == _barDirection)
                {
                    return;
                }
 
                _barDirection = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private ClusterSearchPriceLocation _priceLocation;
 
        [DataMember(Name = "PriceLocation"), DefaultValue(ClusterSearchPriceLocation.Any)]
        [Category("Фильтры"), DisplayName("Расположение цены")]
        public ClusterSearchPriceLocation PriceLocation
        {
            get => _priceLocation;
            set
            {
                if (value == _priceLocation)
                {
                    return;
                }
 
                _priceLocation = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private bool _singleSelection;
 
        [DataMember(Name = "SingleSelection"), DefaultValue(false)]
        [Category("Фильтры"), DisplayName("Одно выдел. в баре")]
        public bool SingleSelection
        {
            get => _singleSelection;
            set
            {
                if (value == _singleSelection)
                {
                    return;
                }
 
                _singleSelection = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private bool _useTimeFilter;
 
        [DataMember(Name = "UseTimeFilter")]
        [Category("Фильтр по времени"), DisplayName("Включить фильтр")]
        public bool UseTimeFilter
        {
            get => _useTimeFilter;
            set
            {
                if (value == _useTimeFilter)
                {
                    return;
                }
 
                _useTimeFilter = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private TimeSpan _startTime;
 
        [DataMember(Name = "StartTime")]
        [Category("Фильтр по времени"), DisplayName("Начальное время")]
        public TimeSpan StartTime
        {
            get => _startTime;
            set
            {
                if (value == _startTime)
                {
                    return;
                }
 
                _startTime = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        private TimeSpan _endTime;
 
        [DataMember(Name = "EndTime")]
        [Category("Фильтр по времени"), DisplayName("Конечное время")]
        public TimeSpan EndTime
        {
            get => _endTime;
            set
            {
                if (value == _endTime)
                {
                    return;
                }
 
                _endTime = value;
 
                Clear();
 
                OnPropertyChanged();
            }
        }
 
        [Browsable(false)]
        public override bool ShowIndicatorValues => false;
 
        [Browsable(false)]
        public override bool ShowIndicatorLabels => false;
 
        [Browsable(false)]
        public override IndicatorCalculation Calculation => IndicatorCalculation.OnEachTick;
 
        private long _minValue;
        private long _maxValue;
 
        private int _lastFullID;
 
        private Dictionary<int, ClusterSearchBar> _bars;
        private Dictionary<int, ClusterSearchBar> Bars => _bars ?? (_bars = new Dictionary<int, ClusterSearchBar>());
 
        private List<ClusterSearchRect> _rects;
     
        public ClusterSearchIndicator()
        {
            Type = ClusterSearchDataType.Volume;
 
            SelectionColor = Color.FromArgb(255, 178, 34, 34);
            ObjectBackColor = Color.FromArgb(127, 30, 144, 255);
            ObjectBorderColor = Color.FromArgb(255, 30, 144, 255);
            ObjectType = ClusterSearchObjectType.Diamond;
            ObjectMinSize = 20;
            ObjectMaxSize = 80;
 
            BarsRange = 1;
            PriceRange = 1;
            PriceRangeDir = ClusterSearchPriceRangeDirection.All;
            MinDelta = null;
            BidAskImbalance = null;
            RangeFromHigh = null;
            RangeFromLow = null;
            MaxAvgTrade = null;
            MinAvgTrade = null;
            BarDirection = ClusterSearchBarDirection.Any;
            PriceLocation = ClusterSearchPriceLocation.Any;
            SingleSelection = false;
 
            UseTimeFilter = false;
            StartTime = TimeSpan.Zero;
            EndTime = TimeSpan.Zero;
        }
 
        private void Clear()
        {
            _maxValue = 0;
            _minValue = 0;
 
            _lastFullID = 0;
 
            Bars.Clear();
        }
 
        private bool GetValue(IRawCluster cluster, IRawClusterItem item, long? minFilter, ref long value, ref long bid, ref long ask)
        {
            var itemValue = 0L;
 
            switch (Type)
            {
                case ClusterSearchDataType.Volume:
 
                    itemValue = item.Volume;
 
                    break;
 
                case ClusterSearchDataType.Trades:
 
                    itemValue = item.Trades;
 
                    break;
 
                case ClusterSearchDataType.Bid:
 
                    itemValue = item.Bid;
 
                    break;
 
                case ClusterSearchDataType.Ask:
 
                    itemValue = item.Ask;
 
                    break;
 
                case ClusterSearchDataType.Delta:
                case ClusterSearchDataType.DeltaPlus:
                case ClusterSearchDataType.DeltaMinus:
 
                    itemValue = item.Delta;
 
                    break;
 
                case ClusterSearchDataType.MaxVol:
 
                    var maxValues = cluster.MaxValues;
 
                    itemValue = item.Price == maxValues.Poc ? maxValues.MaxVolume : 0;
 
                    break;
            }
 
            if (minFilter.HasValue && minFilter.Value > Math.Abs(itemValue))
            {
                return false;
            }
 
            value += itemValue;
            bid += item.Bid;
            ask += item.Ask;
 
            return true;
        }
 
        private bool CheckMinMax(long value, long min, long? max)
        {
            switch (Type)
            {
                case ClusterSearchDataType.DeltaPlus:
 
                    if (value < 0)
                    {
                        return false;
                    }
 
                    break;
 
                case ClusterSearchDataType.DeltaMinus:
 
                    if (value > 0)
                    {
                        return false;
                    }
 
                    break;
            }
 
            if (Math.Abs(value) < min)
            {
                return false;
            }
 
            if (max.HasValue && Math.Abs(value) > max.Value)
            {
                return false;
            }
 
            return true;
        }
 
        protected override void Execute()
        {
            if (ClearData)
            {
                Clear();
            }
 
            var symbol = DataProvider.Symbol;
 
            var min = Type == ClusterSearchDataType.Trades ? Minimum : symbol.CorrectSizeFilter(Minimum);
            var max = Type == ClusterSearchDataType.Trades ? Maximum : symbol.CorrectSizeFilter(Maximum);
            var minValue = Type == ClusterSearchDataType.Trades ? MinValue : symbol.CorrectSizeFilter(MinValue);
            var minDelta = symbol.CorrectSizeFilter(MinDelta);
            var bidAskImb = symbol.CorrectSizeFilter(BidAskImbalance);
 
            var maxAvgTrade = symbol.CorrectSizeFilter(MaxAvgTrade);
            var minAvgTrade = symbol.CorrectSizeFilter(MinAvgTrade);
 
            for (var i = _lastFullID; i < DataProvider.Count; i++)
            {
                var cluster = DataProvider.GetRawCluster(i);
 
                if (!Bars.ContainsKey(i))
                {
                    Bars.Add(i, new ClusterSearchBar());
                }
 
                var currBar = Bars[i];
 
                currBar.Clear();
 
                if (UseTimeFilter)
                {
                    var openTime = symbol.ConvertTimeToLocal(cluster.OpenTime);
 
                    if (StartTime <= EndTime)
                    {
                        if (openTime < cluster.OpenTime.Date.Add(StartTime) ||
                            openTime > cluster.OpenTime.Date.Add(EndTime))
                        {
                            continue;
                        }
                    }
                    else
                    {
                        if (openTime < cluster.OpenTime.Date.Add(StartTime) &&
                            openTime > cluster.OpenTime.Date.Add(EndTime))
                        {
                            continue;
                        }
                    }
                }
 
                switch (BarDirection)
                {
                    case ClusterSearchBarDirection.Up:
 
                        if (cluster.Open > cluster.Close)
                        {
                            continue;
                        }
 
                        break;
 
                    case ClusterSearchBarDirection.Down:
 
                        if (cluster.Open < cluster.Close)
                        {
                            continue;
                        }
 
                        break;
                }
 
                for (var price = cluster.High; price >= cluster.Low; price--)
                {
                    var item = cluster.GetItem(price);
 
                    if (item == null)
                    {
                        continue;
                    }
 
                    switch (PriceLocation)
                    {
                        case ClusterSearchPriceLocation.High:
 
                            if (price != cluster.High)
                            {
                                continue;
                            }
 
                            break;
 
                        case ClusterSearchPriceLocation.Low:
 
                            if (price != cluster.Low)
                            {
                                continue;
                            }
 
                            break;
 
                        case ClusterSearchPriceLocation.HighLow: