Volume Profile

//------------------------------------------------------------------------------
//
// Графический объект VolumeProfile. 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.Data;
using TigerTrade.Chart.Objects.Common;
using TigerTrade.Core.UI.Converters;
using TigerTrade.Dx;
using TigerTrade.Dx.Enums;
 
namespace TigerTrade.Chart.Objects.Custom
{
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    [DataContract(Name = "VolumeProfileType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Objects.Custom")]
    public enum VolumeProfileType
    {
        [EnumMember(Value = "Volume"), Description("Volume")]
        Volume,
        [EnumMember(Value = "Trades"), Description("Trades")]
        Trades,
        [EnumMember(Value = "Delta"), Description("Delta")]
        Delta,
        [EnumMember(Value = "BidAsk"), Description("Bid x Ask")]
        BidAsk
    }
 
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    [DataContract(Name = "VolumeProfileMaximumType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Objects.Custom")]
    public enum VolumeProfileMaximumType
    {
        [EnumMember(Value = "Volume"), Description("Volume")]
        Volume,
        [EnumMember(Value = "Trades"), Description("Trades")]
        Trades,
        [EnumMember(Value = "Delta"), Description("Delta")]
        Delta,
        [EnumMember(Value = "DeltaPlus"), Description("Delta+")]
        DeltaPlus,
        [EnumMember(Value = "DeltaMinus"), Description("Delta-")]
        DeltaMinus,
        [EnumMember(Value = "Bid"), Description("Bid")]
        Bid,
        [EnumMember(Value = "Ask"), Description("Ask")]
        Ask
    }
 
    [DataContract(Name = "VolumeProfileObject", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Objects.Custom")]
    [ChartObject("X_VolumeProfile", "Профиль объёма", 2, Type = typeof(VolumeProfileObject))]
    public sealed class VolumeProfileObject : ObjectBase
    {
        private VolumeProfileType _profileType;
 
        [DataMember(Name = "ProfileType")]
        [Category("Профиль"), DisplayName("Тип")]
        public VolumeProfileType ProfileType
        {
            get => _profileType;
            set
            {
                if (value == _profileType)
                {
                    return;
                }
 
                _profileType = value;
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _profileBrush;
 
        private XColor _profileColor;
 
        [DataMember(Name = "ProfileColor")]
        [Category("Профиль"), DisplayName("Цвет")]
        public XColor ProfileColor
        {
            get => _profileColor;
            set
            {
                if (value == _profileColor)
                {
                    return;
                }
 
                _profileColor = value;
 
                _profileBrush = new XBrush(_profileColor);
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _profile2Brush;
 
        private XColor _profile2Color;
 
        [DataMember(Name = "Profile2Color")]
        [Category("Профиль"), DisplayName("Цвет 2")]
        public XColor Profile2Color
        {
            get => _profile2Color;
            set
            {
                if (value == _profile2Color)
                {
                    return;
                }
 
                _profile2Color = value;
 
                _profile2Brush = new XBrush(_profile2Color);
 
                OnPropertyChanged();
            }
        }
 
        private bool _extendProfile;
 
        [DataMember(Name = "ExtendProfile")]
        [Category("Профиль"), DisplayName("Расширить")]
        public bool ExtendProfile
        {
            get => _extendProfile;
            set
            {
                if (value == _extendProfile)
                {
                    return;
                }
 
                _extendProfile = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _showCumValue;
 
        [DataMember(Name = "ShowCumValue")]
        [Category("Профиль"), DisplayName("Отобр. общ. знач.")]
        public bool ShowCumValue
        {
            get => _showCumValue;
            set
            {
                if (value == _showCumValue)
                {
                    return;
                }
 
                _showCumValue = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _showValues;
 
        [DataMember(Name = "ShowValues")]
        [Category("Значения"), DisplayName("Отображать")]
        public bool ShowValues
        {
            get => _showValues;
            set
            {
                if (value == _showValues)
                {
                    return;
                }
 
                _showValues = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _minimizeValues;
 
        [DataMember(Name = "MinimizeValues")]
        [Category("Значения"), DisplayName("Минимизировать")]
        public bool MinimizeValues
        {
            get => _minimizeValues;
            set
            {
                if (value == _minimizeValues)
                {
                    return;
                }
 
                _minimizeValues = value;
 
                OnPropertyChanged();
            }
        }
 
        private int _roundValues;
 
        [DefaultValue(0)]
        [DataMember(Name = "RoundValues")]
        [Category("Значения"), DisplayName("Округлять")]
        public int RoundValues
        {
            get => _roundValues;
            set
            {
                value = Math.Max(-4, Math.Min(4, value));
 
                if (value == _roundValues)
                {
                    return;
                }
 
                _roundValues = value;
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _valuesBrush;
 
        private XColor _valuesColor;
 
        [DataMember(Name = "ValuesColor")]
        [Category("Значения"), DisplayName("Цвет")]
        public XColor ValuesColor
        {
            get => _valuesColor;
            set
            {
                if (value == _valuesColor)
                {
                    return;
                }
 
                _valuesColor = value;
 
                _valuesBrush = new XBrush(_valuesColor);
 
                OnPropertyChanged();
            }
        }
 
        private VolumeProfileMaximumType _maximumType;
 
        [DataMember(Name = "MaximumType")]
        [Category("Максимум"), DisplayName("Тип")]
        public VolumeProfileMaximumType MaximumType
        {
            get => _maximumType;
            set
            {
                if (value == _maximumType)
                {
                    return;
                }
 
                _maximumType = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _showMaximum;
 
        [DataMember(Name = "ShowMaximum")]
        [Category("Максимум"), DisplayName("Отображать")]
        public bool ShowMaximum
        {
            get => _showMaximum;
            set
            {
                if (value == _showMaximum)
                {
                    return;
                }
 
                _showMaximum = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _showMaximumValue;
 
        [DataMember(Name = "ShowMaximumValue")]
        [Category("Максимум"), DisplayName("Значение")]
        public bool ShowMaximumValue
        {
            get => _showMaximumValue;
            set
            {
                if (value == _showMaximumValue)
                {
                    return;
                }
 
                _showMaximumValue = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _extendMaximum;
 
        [DataMember(Name = "ExtendMaximum")]
        [Category("Максимум"), DisplayName("Продлить")]
        public bool ExtendMaximum
        {
            get => _extendMaximum;
            set
            {
                if (value == _extendMaximum)
                {
                    return;
                }
 
                _extendMaximum = value;
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _maximumBrush;
 
        private XColor _maximumColor;
 
        [DataMember(Name = "MaximumColor")]
        [Category("Максимум"), DisplayName("Цвет")]
        public XColor MaximumColor
        {
            get => _maximumColor;
            set
            {
                if (value == _maximumColor)
                {
                    return;
                }
 
                _maximumColor = value;
 
                _maximumBrush = new XBrush(_maximumColor);
 
                OnPropertyChanged();
            }
        }
 
        private bool _showValueArea;
 
        [DataMember(Name = "ShowValueArea")]
        [Category("Value Area"), DisplayName("Отображать")]
        public bool ShowValueArea
        {
            get => _showValueArea;
            set
            {
                if (value == _showValueArea)
                {
                    return;
                }
 
                _showValueArea = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _extendValueArea;
 
        [DataMember(Name = "ExtendValueArea")]
        [Category("Value Area"), DisplayName("Продлить")]
        public bool ExtendValueArea
        {
            get => _extendValueArea;
            set
            {
                if (value == _extendValueArea)
                {
                    return;
                }
 
                _extendValueArea = value;
 
                OnPropertyChanged();
            }
        }
 
        private int _valueAreaPercent;
 
        [DataMember(Name = "ValueAreaPercent")]
        [Category("Value Area"), 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;
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _valueAreaBrush;
 
        private XColor _valueAreaColor;
 
        [DataMember(Name = "ValueAreaColor")]
        [Category("Value Area"), DisplayName("Цвет")]
        public XColor ValueAreaColor
        {
            get => _valueAreaColor;
            set
            {
                if (value == _valueAreaColor)
                {
                    return;
                }
 
                _valueAreaColor = value;
 
                _valueAreaBrush = new XBrush(_valueAreaColor);
 
                OnPropertyChanged();
            }
        }
 
        private bool _enableFilter;
 
        [DataMember(Name = "EnableFilter")]
        [Category("Фильтр"), DisplayName("Включить")]
        public bool EnableFilter
        {
            get => _enableFilter;
            set
            {
                if (value == _enableFilter)
                {
                    return;
                }
 
                _enableFilter = value;
 
                OnPropertyChanged();
            }
        }
 
        private int _filterMin;
 
        [DataMember(Name = "FilterMin")]
        [Category("Фильтр"), DisplayName("Минимум")]
        public int FilterMin
        {
            get => _filterMin;
            set
            {
                value = Math.Max(0, value);
 
                if (value == _filterMin)
                {
                    return;
                }
 
                _filterMin = value;
 
                OnPropertyChanged();
            }
        }
 
        private int _filterMax;
 
        [DataMember(Name = "FilterMax")]
        [Category("Фильтр"), DisplayName("Максимум")]
        public int FilterMax
        {
            get => _filterMax;
            set
            {
                value = Math.Max(0, value);
 
                if (value == _filterMax)
                {
                    return;
                }
 
                _filterMax = value;
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _filterBrush;
 
        private XColor _filterColor;
 
        [DataMember(Name = "FilterColor")]
        [Category("Фильтр"), DisplayName("Цвет")]
        public XColor FilterColor
        {
            get => _filterColor;
            set
            {
                if (value == _filterColor)
                {
                    return;
                }
 
                _filterColor = value;
 
                _filterBrush = new XBrush(_filterColor);
 
                OnPropertyChanged();
            }
        }
 
        private bool _drawBorder;
 
        [DataMember(Name = "DrawBorder")]
        [Category("Граница"), DisplayName("Граница")]
        public bool DrawBorder
        {
            get => _drawBorder;
            set
            {
                if (value == _drawBorder)
                {
                    return;
                }
 
                _drawBorder = value;
 
                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, LineStyle);
 
                OnPropertyChanged();
            }
        }
 
        private int _lineWidth;
 
        [DataMember(Name = "LineWidth")]
        [Category("Граница"), DisplayName("Толщина линии")]
        public int LineWidth
        {
            get => _lineWidth;
            set
            {
                value = Math.Max(1, Math.Min(10, value));
 
                if (value == _lineWidth)
                {
                    return;
                }
 
                _lineWidth = value;
 
                _linePen = new XPen(_lineBrush, _lineWidth, LineStyle);
 
                OnPropertyChanged();
            }
        }
 
        private XDashStyle _lineStyle;
 
        [DataMember(Name = "LineStyle")]
        [Category("Граница"), DisplayName("Стиль линии")]
        public XDashStyle LineStyle
        {
            get => _lineStyle;
            set
            {
                if (value == _lineStyle)
                {
                    return;
                }
 
                _lineStyle = value;
 
                _linePen = new XPen(_lineBrush, LineWidth, _lineStyle);
 
                OnPropertyChanged();
            }
        }
 
        private bool _drawBack;
 
        [DataMember(Name = "DrawBack")]
        [Category("Фон"), DisplayName("Фон")]
        public bool DrawBack
        {
            get => _drawBack;
            set
            {
                if (value == _drawBack)
                {
                    return;
                }
 
                _drawBack = value;
 
                OnPropertyChanged();
            }
        }
 
        private XBrush _backBrush;
 
        private XColor _backColor;
 
        [DataMember(Name = "BackColor")]
        [Category("Фон"), DisplayName("Цвет фона")]
        public XColor BackColor
        {
            get => _backColor;
            set
            {
                if (value == _backColor)
                {
                    return;
                }
 
                _backColor = value;
 
                _backBrush = new XBrush(_backColor);
 
                OnPropertyChanged();
            }
        }
 
        protected override int PenWidth => LineWidth;
 
        private bool _isObjectInArea;
 
        public class RectangleInfo
        {
            public Point ControlPoint1;
            public Point ControlPoint2;
 
            public Point ExtraPoint1;
            public Point ExtraPoint2;
 
            public Rect Rectangle;
        }
 
        private RectangleInfo _rectInfo;
 
        public VolumeProfileObject()
        {
            ProfileType = VolumeProfileType.Volume;
            ProfileColor = Color.FromArgb(127, 70, 130, 180);
            Profile2Color = Color.FromArgb(127, 178, 34, 34);
            ExtendProfile = false;
            ShowCumValue = false;
 
            ShowValues = false;
            MinimizeValues = false;
            ValuesColor = Color.FromArgb(255, 255, 255, 255);
 
            MaximumType = VolumeProfileMaximumType.Volume;
            ShowMaximum = true;
            ShowMaximumValue = true;
            ExtendMaximum = false;
            MaximumColor = Color.FromArgb(127, 178, 34, 34);
 
            ShowValueArea = true;
            ExtendValueArea = false;
            ValueAreaPercent = 70;
            ValueAreaColor = Color.FromArgb(127, 128, 128, 128);
 
            EnableFilter = false;
            FilterMin = 0;
            FilterMax = 0;
            FilterColor = Color.FromArgb(127, 46, 139, 87);
 
            DrawBorder = true;
            LineColor = Colors.Black;
            LineWidth = 1;
            LineStyle = XDashStyle.Solid;
 
            DrawBack = true;
            BackColor = Color.FromArgb(30, 0, 0, 0);
        }
 
        protected override void Prepare()
        {
            var point1 = ToPoint(ControlPoints[0]);
            var point2 = ToPoint(ControlPoints[1]);
 
            var ep1 = ToPoint(ExtraPoints[0]);
            var ep2 = ToPoint(ExtraPoints[1]);
 
            var w = Canvas.ColumnWidth / 2.0;
            var h = Canvas.StepHeight / 2.0;
 
            if (point1.X > point2.X)
            {
                point1.X += w;
                point2.X -= w;
            }
            else
            {
                point1.X -= w;
                point2.X += w;
            }
 
            if (point1.Y > point2.Y)
            {
                point1.Y += h;
                point2.Y -= h;
            }
            else
            {
                point1.Y -= h;
                point2.Y += h;
            }
 
            if (ep1.X > ep2.X)
            {
                ep1.X += w;
                ep2.X -= w;
            }
            else
            {
                ep1.X -= w;
                ep2.X += w;
            }
 
            if (ep1.Y > ep2.Y)
            {
                ep1.Y += h;
                ep2.Y -= h;
            }
            else
            {
                ep1.Y -= h;
                ep2.Y += h;
            }
 
            _rectInfo = new RectangleInfo
            {
                ControlPoint1 = point1,
                ControlPoint2 = point2,
                ExtraPoint1 = ep1,
                ExtraPoint2 = ep2,
                Rectangle = new Rect(point1, point2)
            };
 
            _isObjectInArea = Canvas.Rect.IntersectsWith(_rectInfo.Rectangle);
        }
 
        protected override void Draw(DxVisualQueue visual, ref List<ObjectLabelInfo> labels)
        {
            if (!ExtendProfile)
            {
                if (DrawBack)
                {
                    visual.FillRectangle(_backBrush, _rectInfo.Rectangle);
                }
 
                if (DrawBorder)
                {
                    visual.DrawRectangle(_linePen, _rectInfo.Rectangle);
                }
            }
 
            if (!Canvas.IsStock)
            {
                return;
            }
 
            var index1 = 0;
            var index2 = 1;
 
            if (ControlPoints[0].X > ControlPoints[1].X)
            {
                index1 = 1;
                index2 = 0;
            }
 
            var bar1 = Canvas.DateToIndex(ControlPoints[index1].X, 0);
            var bar2 = Canvas.DateToIndex(ControlPoints[index2].X, 0);
 
            var step = DataProvider.Step;
 
            var maxPrice = (long)(Math.Max(ControlPoints[0].Y, ControlPoints[1].Y) / step);
            var minPrice = (long)(Math.Min(ControlPoints[0].Y, ControlPoints[1].Y) / step);
 
            var profile = new RawCluster(DateTime.MinValue);
 
            if (ExtendProfile)
            {
                for (var i = bar1; i <= bar2; i++)
                {
                    var cluster = DataProvider.GetRawCluster(i);
 
                    if (cluster == null)
                    {
                        continue;
                    }
 
                    foreach (var item in cluster.Items)
                    {
                        profile.AddItem(item);
                    }
 
                    maxPrice = Math.Max(maxPrice, cluster.High);
                    minPrice = Math.Min(minPrice, cluster.Low);
                }
            }
            else
            {
                for (var i = bar1; i <= bar2; i++)
                {
                    var cluster = DataProvider.GetRawCluster(i);
 
                    if (cluster == null)
                    {
                        continue;
                    }
 
                    foreach (var item in cluster.Items)
                    {
                        if (item.Price >= minPrice && item.Price <= maxPrice)
                        {
                            profile.AddItem(item);
                        }
                    }
                }
            }
 
            profile.UpdateData();
 
            if (profile.Volume <= 0)
            {
                return;
            }
 
            var valueArea = ShowValueArea ? profile.GetValueArea(ValueAreaPercent) : null;
 
            var p1 = ToPoint(ControlPoints[index1]);
            var p2 = ToPoint(ControlPoints[index2]);
 
            switch (ProfileType)
            {
                case VolumeProfileType.Volume:
 
                    DrawVolume(visual, profile, valueArea, p1, p2, ref labels);
 
                    break;
 
                case VolumeProfileType.Trades:
 
                    DrawTrades(visual, profile, valueArea, p1, p2, ref labels);
 
                    break;
 
                case VolumeProfileType.Delta:
 
                    DrawDelta(visual, profile, valueArea, p1, p2, ref labels);
 
                    break;
 
                case VolumeProfileType.BidAsk:
 
                    DrawBidAsk(visual, profile, valueArea, p1, p2, ref labels);
 
                    break;
            }
 
            if (ShowCumValue)
            {
                DrawValues(visual, profile);
            }
        }
 
        private void DrawValues(DxVisualQueue visual, IRawCluster profile)
        {
            var symbol = DataProvider.Symbol;
 
            var mainRect = _rectInfo.Rectangle;
 
            var height = Canvas.ChartFont.GetHeight();
 
            var rect = new Rect(new Point(mainRect.Left + 2, mainRect.Bottom + 4),
                new Point(mainRect.Right - 2, mainRect.Bottom + height + 4));
 
            switch (ProfileType)
            {
                case VolumeProfileType.Volume:
 
                    var volumeText = symbol.FormatRawSize(profile.Volume, RoundValues, MinimizeValues);
 
                    visual.DrawString($"V: {volumeText}", Canvas.ChartFont, _valuesBrush, rect, XTextAlignment.Right);
 
                    break;
 
                case VolumeProfileType.Trades:
 
                    var tradesText = symbol.FormatTrades(profile.Trades, RoundValues, MinimizeValues);
 
                    visual.DrawString($"T: {tradesText}", Canvas.ChartFont, _valuesBrush, rect, XTextAlignment.Right);
 
                    break;
 
                case VolumeProfileType.Delta:
 
                    var deltaText = symbol.FormatRawSize(profile.Delta, RoundValues, MinimizeValues);
 
                    visual.DrawString($"D: {deltaText}", Canvas.ChartFont, _valuesBrush, rect, XTextAlignment.Right);
 
                    break;
 
                case VolumeProfileType.BidAsk:
 
                    var bidText = symbol.FormatRawSize(profile.Bid, RoundValues, MinimizeValues);
                    var askText = symbol.FormatRawSize(profile.Ask, RoundValues, MinimizeValues);
 
                    visual.DrawString($"B: {bidText} A: {askText}", Canvas.ChartFont, _valuesBrush, rect,
                        XTextAlignment.Right);
 
                    break;
            }
        }
 
        private bool CheckMaximum(IRawClusterItem item, IRawClusterMaxValues maxValues)
        {
            switch (MaximumType)
            {
                case VolumeProfileMaximumType.Volume:
 
                    return item.Volume == maxValues.MaxVolume;
 
                case VolumeProfileMaximumType.Trades:
 
                    return item.Trades == maxValues.MaxTrades;
 
                case VolumeProfileMaximumType.Delta:
 
                    return Math.Abs(item.Delta) ==
                           Math.Max(Math.Abs(maxValues.MaxDelta), Math.Abs(maxValues.MinDelta));
 
                case VolumeProfileMaximumType.DeltaPlus:
 
                    return item.Delta > 0 && item.Delta == maxValues.MaxDelta;
 
                case VolumeProfileMaximumType.DeltaMinus:
 
                    return item.Delta < 0 && item.Delta == maxValues.MinDelta;
 
                case VolumeProfileMaximumType.Bid:
 
                    return item.Bid == maxValues.MaxBid;
 
                case VolumeProfileMaximumType.Ask:
 
                    return item.Ask == maxValues.MaxAsk;
            }
 
            return false;
        }
 
        private string FormatMaximum(IRawClusterItem item)
        {
            switch (MaximumType)
            {
                case VolumeProfileMaximumType.Volume:
 
                    return DataProvider.Symbol.FormatRawSize(item.Volume, RoundValues, MinimizeValues);
 
                case VolumeProfileMaximumType.Trades:
 
                    return DataProvider.Symbol.FormatTrades(item.Trades, RoundValues, MinimizeValues);
 
                case VolumeProfileMaximumType.Delta:
                case VolumeProfileMaximumType.DeltaPlus:
                case VolumeProfileMaximumType.DeltaMinus:
 
                    return DataProvider.Symbol.FormatRawSize(item.Delta, RoundValues, MinimizeValues);
 
                case VolumeProfileMaximumType.Bid:
 
                    return DataProvider.Symbol.FormatRawSize(item.Bid, RoundValues, MinimizeValues);
 
                case VolumeProfileMaximumType.Ask:
 
                    return DataProvider.Symbol.FormatRawSize(item.Ask, RoundValues, MinimizeValues);
            }
 
            return "";
        }
 
        private void DrawVolume(DxVisualQueue visual, IRawCluster profile, IRawClusterValueArea valueArea,
            Point p1, Point p2, ref List<ObjectLabelInfo> labels)
        {
            var colorRects = new List<Tuple<Rect, XBrush>>();
            var colorRects2 = new List<Tuple<Rect, XBrush>>();
            var valueRects = new List<Tuple<Rect, string>>();
            var valueRects2 = new List<Tuple<Rect, string>>();
 
            var step = DataProvider.Step;
            var symbol = DataProvider.Symbol;
 
            var height = Math.Max(Canvas.StepHeight, 1);
 
            var fontSize = Math.Min(height - 2, 18) * 96 / 72;
 
            fontSize = Math.Min(fontSize, Canvas.ChartFont.Size);
 
            var normalFont = new XFont(Canvas.ChartFont.Name, fontSize);
 
            var dist = Math.Max(p2.X - p1.X + Canvas.ColumnWidth - LineWidth, 0);
 
            var left = p1.X - Canvas.ColumnWidth / 2.0 + Math.Ceiling(LineWidth / 2.0);
 
            if (ExtendProfile)
            {
                if (DrawBack)
                {
                    visual.FillRectangle(_backBrush,
                        new Rect(new Point(left, Canvas.Rect.Top), new Point(left + dist, Canvas.Rect.Bottom)));
                }
 
                if (DrawBorder)
                {
                    visual.DrawLine(_linePen, new Point(left, Canvas.Rect.Top),
                        new Point(left, Canvas.Rect.Bottom));
                    visual.DrawLine(_linePen, new Point(left + dist, Canvas.Rect.Top),
                        new Point(left + dist, Canvas.Rect.Bottom));
                }
            }
 
            if (profile.High - profile.Low > 100000)
            {
                return;
            }
 
            var maxValues = profile.MaxValues;
 
            var roundValues = RoundValues;
 
            var prevX = (int)left;
            var prevY = (int)GetY((profile.High + .5) * step);
 
            var points = new List<Point>
            {
                new Point(prevX, prevY)
            };
 
            for (var j = profile.High; j >= profile.Low; j--)
            {
                var item = profile.GetItem(j) ?? new RawClusterItem(j);
 
                var width = item.Volume > 0 ? Math.Min(dist / maxValues.MaxVolume * item.Volume, dist) : 0;
 
                var currX = (int)(left + width);
                var currY = (int)GetY((j - .5) * step);
 
                var currHeight = Math.Max(currY - prevY, height);
 
                if (currY <= prevY && points.Count > 2)
                {
                    if (currX > prevX)
                    {
                        points[points.Count - 2] = new Point(currX, points[points.Count - 2].Y);
                        points[points.Count - 1] = new Point(currX, points[points.Count - 1].Y);
 
                        prevX = currX;
                    }
                }
                else
                {
                    points.Add(new Point(currX, prevY));
                    points.Add(new Point(currX, currY));
 
                    prevX = currX;
                }
 
                prevY = currY;
 
                var topY = points[points.Count - 2].Y;
 
                if (ShowMaximum && CheckMaximum(item, maxValues))
                {
                    colorRects2.Add(
                        new Tuple<Rect, XBrush>(
                            ExtendMaximum
                                ? new Rect(new Point(left, topY),
                                    new Point(Canvas.Rect.Right + 1, topY + currHeight))
                                : new Rect(left, topY, dist, currHeight), _maximumBrush));
 
                    if (ExtendMaximum)
                    {
                        labels.Add(new ObjectLabelInfo(j * step, MaximumColor));
                    }
 
                    if (ShowMaximumValue)
                    {
                        var h = Canvas.ChartFont.GetHeight();
 
                        valueRects2.Add(new Tuple<Rect, string>(
                            new Rect(left + 2, topY - h - 2, Math.Max(dist - 4, 1), h),
                            FormatMaximum(item)));
                    }
                }
                else if (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val))
                {
                    colorRects2.Add(
                        new Tuple<Rect, XBrush>(
                            ExtendValueArea
                                ? new Rect(new Point(left, topY), new Point(Canvas.Rect.Right + 1, topY + currHeight))
                                : new Rect(left, topY, dist, currHeight), _valueAreaBrush));
 
                    if (ExtendValueArea)
                    {
                        labels.Add(new ObjectLabelInfo(j * step, ValueAreaColor));
                    }
                }
                else if (EnableFilter && item.Volume >= symbol.CorrectSizeFilter(FilterMin) &&
                         (FilterMax == 0 || item.Volume <= symbol.CorrectSizeFilter(FilterMax)))
                {
                    if (colorRects.Count > 0)
                    {
                        var lastRect = colorRects[colorRects.Count - 1].Item1;
 
                        if ((int)lastRect.Y == (int)topY)
                        {
                            if (width > lastRect.Width)
                            {
                                colorRects[colorRects.Count - 1] =
                                    new Tuple<Rect, XBrush>(new Rect(left, topY, width, lastRect.Height), _filterBrush);
                            }
                        }
                        else
                        {
                            colorRects.Add(new Tuple<Rect, XBrush>(new Rect(left, topY, width, currHeight),
                                _filterBrush));
                        }
                    }
                    else
                    {
                        colorRects.Add(new Tuple<Rect, XBrush>(new Rect(left, topY, width, currHeight), _filterBrush));
                    }
                }
 
                if (ShowValues && height > 7 && item.Volume > 0)
                {
                    valueRects.Add(new Tuple<Rect, string>(new Rect(left + 2, topY, dist, height),
                        symbol.FormatRawSize(item.Volume, roundValues, MinimizeValues)));
                }
            }
 
            points.Add(new Point(left, prevY));
 
            visual.FillPolygon(_profileBrush, points.ToArray());
 
            foreach (var colorRect in colorRects)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var colorRect in colorRects2)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var valueRect in valueRects)
            {
                visual.DrawString(valueRect.Item2, normalFont, _valuesBrush, valueRect.Item1);
            }
 
            foreach (var valueRect in valueRects2)
            {
                visual.DrawString(valueRect.Item2, Canvas.ChartFont, _valuesBrush, valueRect.Item1, XTextAlignment.Right);
            }
        }
 
        private void DrawTrades(DxVisualQueue visual, IRawCluster profile, IRawClusterValueArea valueArea,
            Point p1, Point p2, ref List<ObjectLabelInfo> labels)
        {
            var colorRects = new List<Tuple<Rect, XBrush>>();
            var colorRects2 = new List<Tuple<Rect, XBrush>>();
            var valueRects = new List<Tuple<Rect, string>>();
            var valueRects2 = new List<Tuple<Rect, string>>();
 
            var step = DataProvider.Step;
            var symbol = DataProvider.Symbol;
 
            var height = Math.Max(Canvas.StepHeight, 1);
 
            var fontSize = Math.Min(height - 2, 18) * 96 / 72;
 
            fontSize = Math.Min(fontSize, Canvas.ChartFont.Size);
 
            var normalFont = new XFont(Canvas.ChartFont.Name, fontSize);
 
            var dist = p2.X - p1.X + Canvas.ColumnWidth - LineWidth;
 
            var left = p1.X - Canvas.ColumnWidth / 2.0 + Math.Ceiling(LineWidth / 2.0);
 
            if (ExtendProfile)
            {
                if (DrawBack)
                {
                    visual.FillRectangle(_backBrush,
                        new Rect(new Point(left, Canvas.Rect.Top), new Point(left + dist, Canvas.Rect.Bottom)));
                }
 
                if (DrawBorder)
                {
                    visual.DrawLine(_linePen, new Point(left, Canvas.Rect.Top),
                        new Point(left, Canvas.Rect.Bottom));
                    visual.DrawLine(_linePen, new Point(left + dist, Canvas.Rect.Top),
                        new Point(left + dist, Canvas.Rect.Bottom));
                }
            }
 
            if (profile.High - profile.Low > 10000)
            {
                return;
            }
 
            var maxValues = profile.MaxValues;
 
            var roundValues = RoundValues;
 
            var prevX = (int)left;
            var prevY = (int)GetY((profile.High + .5) * step);
 
            var points = new List<Point>
            {
                new Point(prevX, prevY)
            };
 
            for (var j = profile.High; j >= profile.Low; j--)
            {
                var item = profile.GetItem(j) ?? new RawClusterItem(j);
 
                var width = item.Trades > 0 ? Math.Min(dist / maxValues.MaxTrades * item.Trades, dist) : 0;
 
                var currX = (int)(left + width);
                var currY = (int)GetY((j - .5) * step);
 
                var currHeight = Math.Max(currY - prevY, height);
 
                if (currY <= prevY && points.Count > 2)
                {
                    if (currX > prevX)
                    {
                        points[points.Count - 2] = new Point(currX, points[points.Count - 2].Y);
                        points[points.Count - 1] = new Point(currX, points[points.Count - 1].Y);
 
                        prevX = currX;
                    }
                }
                else
                {
                    points.Add(new Point(currX, prevY));
                    points.Add(new Point(currX, currY));
 
                    prevX = currX;
                }
 
                prevY = currY;
 
                var topY = points[points.Count - 2].Y;
 
                if (ShowMaximum && CheckMaximum(item, maxValues))
                {
                    colorRects2.Add(
                        new Tuple<Rect, XBrush>(
                            ExtendMaximum
                                ? new Rect(new Point(left, topY),
                                    new Point(Canvas.Rect.Right + 1, topY + currHeight))
                                : new Rect(left, topY, dist, currHeight), _maximumBrush));
 
                    if (ExtendMaximum)
                    {
                        labels.Add(new ObjectLabelInfo(j * step, MaximumColor));
                    }
 
                    if (ShowMaximumValue)
                    {
                        var h = Canvas.ChartFont.GetHeight();
 
                        valueRects2.Add(new Tuple<Rect, string>(
                            new Rect(left + 2, topY - h - 2, Math.Max(dist - 4, 1), h),
                            FormatMaximum(item)));
                    }
                }
                else if (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val))
                {
                    colorRects2.Add(
                        new Tuple<Rect, XBrush>(
                            ExtendValueArea
                                ? new Rect(new Point(left, topY), new Point(Canvas.Rect.Right + 1, topY + currHeight))
                                : new Rect(left, topY, dist, currHeight), _valueAreaBrush));
 
                    if (ExtendValueArea)
                    {
                        labels.Add(new ObjectLabelInfo(j * step, ValueAreaColor));
                    }
                }
                else if (EnableFilter && item.Trades >= FilterMin &&
                         (FilterMax == 0 || item.Trades <= FilterMax))
                {
                    if (colorRects.Count > 0)
                    {
                        var lastRect = colorRects[colorRects.Count - 1].Item1;
 
                        if ((int)lastRect.Y == (int)topY)
                        {
                            if (width > lastRect.Width)
                            {
                                colorRects[colorRects.Count - 1] =
                                    new Tuple<Rect, XBrush>(new Rect(left, topY, width, lastRect.Height), _filterBrush);
                            }
                        }
                        else
                        {
                            colorRects.Add(new Tuple<Rect, XBrush>(new Rect(left, topY, width, currHeight),
                                _filterBrush));
                        }
                    }
                    else
                    {
                        colorRects.Add(new Tuple<Rect, XBrush>(new Rect(left, topY, width, currHeight), _filterBrush));
                    }
                }
 
                if (ShowValues && height > 7 && item.Trades > 0)
                {
                    valueRects.Add(new Tuple<Rect, string>(new Rect(left + 2, topY, dist, height),
                        symbol.FormatTrades(item.Trades, roundValues, MinimizeValues)));
                }
            }
 
            points.Add(new Point(left, prevY));
 
            visual.FillPolygon(_profileBrush, points.ToArray());
 
            foreach (var colorRect in colorRects)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var colorRect in colorRects2)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var valueRect in valueRects)
            {
                visual.DrawString(valueRect.Item2, normalFont, _valuesBrush, valueRect.Item1);
            }
 
            foreach (var valueRect in valueRects2)
            {
                visual.DrawString(valueRect.Item2, Canvas.ChartFont, _valuesBrush, valueRect.Item1, XTextAlignment.Right);
            }
        }
 
        private void DrawDelta(DxVisualQueue visual, IRawCluster profile, IRawClusterValueArea valueArea,
            Point p1, Point p2, ref List<ObjectLabelInfo> labels)
        {
            var colorRects = new List<Tuple<Rect, XBrush>>();
            var colorRectsLeft = new List<Tuple<Rect, XBrush>>();
            var colorRectsRight = new List<Tuple<Rect, XBrush>>();
            var valueLeftRects = new List<Tuple<Rect, string>>();
            var valueRightRects = new List<Tuple<Rect, string>>();
            var valueRects2 = new List<Tuple<Rect, string>>();
 
            var step = DataProvider.Step;
            var symbol = DataProvider.Symbol;
 
            var height = Math.Max(Canvas.StepHeight, 1);
 
            var fontSize = Math.Min(height - 2, 18) * 96 / 72;
 
            fontSize = Math.Min(fontSize, Canvas.ChartFont.Size);
 
            var normalFont = new XFont(Canvas.ChartFont.Name, fontSize);
 
            var dist = p2.X - p1.X + Canvas.ColumnWidth - LineWidth;
 
            var left = p1.X - Canvas.ColumnWidth / 2.0 + Math.Ceiling(LineWidth / 2.0);
 
            if (ExtendProfile)
            {
                if (DrawBack)
                {
                    visual.FillRectangle(_backBrush,
                        new Rect(new Point(left, Canvas.Rect.Top), new Point(left + dist, Canvas.Rect.Bottom)));
                }
 
                if (DrawBorder)
                {
                    visual.DrawLine(_linePen, new Point(left, Canvas.Rect.Top),
                        new Point(left, Canvas.Rect.Bottom));
                    visual.DrawLine(_linePen, new Point(left + dist, Canvas.Rect.Top),
                        new Point(left + dist, Canvas.Rect.Bottom));
                }
            }
 
            if (profile.High - profile.Low > 10000)
            {
                return;
            }
 
            var maxValues = profile.MaxValues;
 
            var roundValues = RoundValues;
 
            var center = left + dist / 2.0;
 
            var prevX = (int)center;
            var prevY = (int)GetY((profile.High + .5) * step);
 
            var pointsRight = new List<Point>
            {
                new Point(prevX, prevY)
            };
 
            for (var j = profile.High; j >= profile.Low; j--)
            {
                var item = profile.GetItem(j) ?? new RawClusterItem(j);
 
                var width = item.Delta > 0
                    ? Math.Min(dist / Math.Max(Math.Abs(maxValues.MinDelta), Math.Abs(maxValues.MaxDelta)) *
                               Math.Abs(item.Delta), dist) / 2.0
                    : 0;
 
                var currX = (int)(center + width);
                var currY = (int)GetY((j - .5) * step);
 
                var currHeight = Math.Max(currY - prevY, height);
 
                if (currY <= prevY && pointsRight.Count > 2)
                {
                    if (currX > prevX)
                    {
                        pointsRight[pointsRight.Count - 2] = new Point(currX, pointsRight[pointsRight.Count - 2].Y);
                        pointsRight[pointsRight.Count - 1] = new Point(currX, pointsRight[pointsRight.Count - 1].Y);
 
                        prevX = currX;
                    }
                }
                else
                {
                    pointsRight.Add(new Point(currX, prevY));
                    pointsRight.Add(new Point(currX, currY));
 
                    prevX = currX;
                }
 
                prevY = currY;
 
                var topY = pointsRight[pointsRight.Count - 2].Y;
 
                if (ShowMaximum && CheckMaximum(item, maxValues))
                {
                    colorRects.Add(
                        new Tuple<Rect, XBrush>(
                            ExtendMaximum
                                ? new Rect(new Point(left, topY),
                                    new Point(Canvas.Rect.Right + 1, topY + currHeight))
                                : new Rect(left, topY, dist, currHeight), _maximumBrush));
 
                    if (ExtendMaximum)
                    {
                        labels.Add(new ObjectLabelInfo(j * step, MaximumColor));
                    }
 
                    if (ShowMaximumValue)
                    {
                        var h = Canvas.ChartFont.GetHeight();
 
                        valueRects2.Add(new Tuple<Rect, string>(
                            new Rect(left + 2, topY - h - 2, Math.Max(dist - 4, 1), h),
                            FormatMaximum(item)));
                    }
                }
                else if (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val))
                {
                    colorRects.Add(
                        new Tuple<Rect, XBrush>(
                            ExtendValueArea
                                ? new Rect(new Point(left, topY), new Point(Canvas.Rect.Right + 1, topY + currHeight))
                                : new Rect(left, topY, dist, currHeight), _valueAreaBrush));
 
                    if (ExtendValueArea)
                    {
                        labels.Add(new ObjectLabelInfo(j * step, ValueAreaColor));
                    }
                }
                else if (EnableFilter)
                {
                    if (item.Delta > 0 && item.Delta >= symbol.CorrectSizeFilter(FilterMin) &&
                        (FilterMax == 0 || item.Delta <= symbol.CorrectSizeFilter(FilterMax)))
                    {
                        if (colorRectsRight.Count > 0)
                        {
                            var lastRect = colorRectsRight[colorRectsRight.Count - 1].Item1;
 
                            if ((int)lastRect.Y == (int)topY)
                            {
                                if (width > lastRect.Width)
                                {
                                    colorRectsRight[colorRectsRight.Count - 1] =
                                        new Tuple<Rect, XBrush>(new Rect(center, topY, width, lastRect.Height), _filterBrush);
                                }
                            }
                            else
                            {
                                colorRectsRight.Add(new Tuple<Rect, XBrush>(new Rect(center, topY, width, currHeight),
                                    _filterBrush));
                            }
                        }
                        else
                        {
                            colorRectsRight.Add(new Tuple<Rect, XBrush>(new Rect(center, topY, width, currHeight), _filterBrush));
                        }
                    }
                }
 
                if (ShowValues && height > 7 && item.Delta > 0)
                {
                    valueRightRects.Add(new Tuple<Rect, string>(new Rect(center + 2, topY, dist / 2.0, height),
                        symbol.FormatRawSize(item.Delta, roundValues, MinimizeValues)));
                }
            }
 
            pointsRight.Add(new Point(center, prevY));
 
            prevX = (int)center;
            prevY = (int)GetY((profile.High + .5) * step);
 
            var pointsLeft = new List<Point>
            {
                new Point(prevX, prevY)
            };
 
            for (var j = profile.High; j >= profile.Low; j--)
            {
                var item = profile.GetItem(j) ?? new RawClusterItem(j);
 
                var width = item.Delta < 0
                    ? Math.Min(dist / Math.Max(Math.Abs(maxValues.MinDelta), Math.Abs(maxValues.MaxDelta)) *
                               Math.Abs(item.Delta), dist) / 2.0
                    : 0;
 
                var currX = (int)(center - width);
                var currY = (int)GetY((j - .5) * step);
 
                var currHeight = Math.Max(currY - prevY, height);
 
                if (currY <= prevY && pointsLeft.Count > 2)
                {
                    if (currX < prevX)
                    {
                        pointsLeft[pointsLeft.Count - 2] = new Point(currX, pointsLeft[pointsLeft.Count - 2].Y);
                        pointsLeft[pointsLeft.Count - 1] = new Point(currX, pointsLeft[pointsLeft.Count - 1].Y);
 
                        prevX = currX;
                    }
                }
                else
                {
                    pointsLeft.Add(new Point(currX, prevY));
                    pointsLeft.Add(new Point(currX, currY));
 
                    prevX = currX;
                }
 
                prevY = currY;
 
                var topY = pointsLeft[pointsLeft.Count - 2].Y;
 
                if (ShowMaximum && CheckMaximum(item, maxValues))
                {
                }
                else if (EnableFilter)
                {
                    if (item.Delta < 0 && -item.Delta >= symbol.CorrectSizeFilter(FilterMin) &&
                        (FilterMax == 0 || -item.Delta <= symbol.CorrectSizeFilter(FilterMax)))
                    {
                        if (colorRectsLeft.Count > 0)
                        {
                            var lastRect = colorRectsLeft[colorRectsLeft.Count - 1].Item1;
 
                            if ((int)lastRect.Y == (int)topY)
                            {
                                if (width > lastRect.Width)
                                {
                                    colorRectsLeft[colorRectsLeft.Count - 1] =
                                        new Tuple<Rect, XBrush>(new Rect(center - width, topY, width, lastRect.Height), _filterBrush);
                                }
                            }
                            else
                            {
                                colorRectsLeft.Add(new Tuple<Rect, XBrush>(new Rect(center - width, topY, width, currHeight),
                                    _filterBrush));
                            }
                        }
                        else
                        {
                            colorRectsLeft.Add(new Tuple<Rect, XBrush>(new Rect(center - width, topY, width, currHeight), _filterBrush));
                        }
                    }
                }
 
                if (ShowValues && height > 7 && item.Delta < 0)
                {
                    valueLeftRects.Add(new Tuple<Rect, string>(new Rect(left, topY, dist / 2.0 - 2, height),
                        symbol.FormatRawSize(item.Delta, roundValues, MinimizeValues)));
                }
            }
 
            pointsLeft.Add(new Point(center, prevY));
 
            visual.FillPolygon(_profile2Brush, pointsLeft.ToArray());
            visual.FillPolygon(_profileBrush, pointsRight.ToArray());
 
            foreach (var colorRect in colorRectsLeft)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var colorRect in colorRectsRight)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var colorRect in colorRects)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var valueRect in valueLeftRects)
            {
                visual.DrawString(valueRect.Item2, normalFont, _valuesBrush, valueRect.Item1, XTextAlignment.Right);
            }
 
            foreach (var valueRect in valueRightRects)
            {
                visual.DrawString(valueRect.Item2, normalFont, _valuesBrush, valueRect.Item1);
            }
 
            foreach (var valueRect in valueRects2)
            {
                visual.DrawString(valueRect.Item2, Canvas.ChartFont, _valuesBrush, valueRect.Item1, XTextAlignment.Right);
            }
        }
 
        private void DrawBidAsk(DxVisualQueue visual, IRawCluster profile, IRawClusterValueArea valueArea,
            Point p1, Point p2, ref List<ObjectLabelInfo> labels)
        {
            var colorRects = new List<Tuple<Rect, XBrush>>();
            var colorRectsLeft = new List<Tuple<Rect, XBrush>>();
            var colorRectsRight = new List<Tuple<Rect, XBrush>>();
            var valueLeftRects = new List<Tuple<Rect, string>>();
            var valueRightRects = new List<Tuple<Rect, string>>();
            var valueRects2 = new List<Tuple<Rect, string>>();
 
            var step = DataProvider.Step;
            var symbol = DataProvider.Symbol;
 
            var height = Math.Max(Canvas.StepHeight, 1);
 
            var fontSize = Math.Min(height - 2, 18) * 96 / 72;
 
            fontSize = Math.Min(fontSize, Canvas.ChartFont.Size);
 
            var normalFont = new XFont(Canvas.ChartFont.Name, fontSize);
 
            var dist = p2.X - p1.X + Canvas.ColumnWidth - LineWidth;
 
            var left = p1.X - Canvas.ColumnWidth / 2.0 + Math.Ceiling(LineWidth / 2.0);
 
            if (ExtendProfile)
            {
                if (DrawBack)
                {
                    visual.FillRectangle(_backBrush,
                        new Rect(new Point(left, Canvas.Rect.Top), new Point(left + dist, Canvas.Rect.Bottom)));
                }
 
                if (DrawBorder)
                {
                    visual.DrawLine(_linePen, new Point(left, Canvas.Rect.Top),
                        new Point(left, Canvas.Rect.Bottom));
                    visual.DrawLine(_linePen, new Point(left + dist, Canvas.Rect.Top),
                        new Point(left + dist, Canvas.Rect.Bottom));
                }
            }
 
            if (profile.High - profile.Low > 10000)
            {
                return;
            }
 
            var maxValues = profile.MaxValues;
 
            var roundValues = RoundValues;
 
            var center = Math.Floor(left + dist / 2.0);
 
            var prevX = (int)center;
            var prevY = (int)GetY((profile.High + .5) * step);
 
            var pointsRight = new List<Point>
            {
                new Point(prevX, prevY)
            };
 
            for (var j = profile.High; j >= profile.Low; j--)
            {
                var item = profile.GetItem(j) ?? new RawClusterItem(j);
 
                var askWidth =
                    (int)(Math.Min(dist / Math.Max(maxValues.MaxBid, maxValues.MaxAsk) * item.Ask, dist) / 2.0);
 
                var currX = (int)(center + askWidth);
                var currY = (int)GetY((j - .5) * step);
 
                var currHeight = Math.Max(currY - prevY, height);
 
                if (currY <= prevY && pointsRight.Count > 2)
                {
                    if (currX > prevX)
                    {
                        pointsRight[pointsRight.Count - 2] = new Point(currX, pointsRight[pointsRight.Count - 2].Y);
                        pointsRight[pointsRight.Count - 1] = new Point(currX, pointsRight[pointsRight.Count - 1].Y);
 
                        prevX = currX;
                    }
                }
                else
                {
                    pointsRight.Add(new Point(currX, prevY));
                    pointsRight.Add(new Point(currX, currY));
 
                    prevX = currX;
                }
 
                prevY = currY;
 
                var topY = pointsRight[pointsRight.Count - 2].Y;
 
                if (ShowMaximum && CheckMaximum(item, maxValues))
                {
                    colorRects.Add(
                        new Tuple<Rect, XBrush>(
                            ExtendMaximum
                                ? new Rect(new Point(left, topY),
                                    new Point(Canvas.Rect.Right + 1, topY + currHeight))
                                : new Rect(left, topY, dist, currHeight), _maximumBrush));
 
                    if (ExtendMaximum)
                    {
                        labels.Add(new ObjectLabelInfo(j * step, MaximumColor));
                    }
 
                    if (ShowMaximumValue)
                    {
                        var h = Canvas.ChartFont.GetHeight();
 
                        valueRects2.Add(new Tuple<Rect, string>(
                            new Rect(left + 2, topY - h - 2, Math.Max(dist - 4, 1), h),
                            FormatMaximum(item)));
                    }
                }
                else if (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val))
                {
                    colorRects.Add(
                        new Tuple<Rect, XBrush>(
                            ExtendValueArea
                                ? new Rect(new Point(left, topY), new Point(Canvas.Rect.Right + 1, topY + currHeight))
                                : new Rect(left, topY, dist, currHeight), _valueAreaBrush));
 
                    if (ExtendValueArea)
                    {
                        labels.Add(new ObjectLabelInfo(j * step, ValueAreaColor));
                    }
                }
                else if (EnableFilter)
                {
                    if (item.Ask >= symbol.CorrectSizeFilter(FilterMin) &&
                        (FilterMax == 0 || item.Ask <= symbol.CorrectSizeFilter(FilterMax)))
                    {
                        if (colorRectsRight.Count > 0)
                        {
                            var lastRect = colorRectsRight[colorRectsRight.Count - 1].Item1;
 
                            if ((int)lastRect.Y == (int)topY)
                            {
                                if (askWidth > lastRect.Width)
                                {
                                    colorRectsRight[colorRectsRight.Count - 1] =
                                        new Tuple<Rect, XBrush>(new Rect(center, topY, askWidth, lastRect.Height),
                                            _filterBrush);
                                }
                            }
                            else
                            {
                                colorRectsRight.Add(
                                    new Tuple<Rect, XBrush>(new Rect(center, topY, askWidth, currHeight),
                                        _filterBrush));
                            }
                        }
                        else
                        {
                            colorRectsRight.Add(new Tuple<Rect, XBrush>(new Rect(center, topY, askWidth, currHeight),
                                _filterBrush));
                        }
                    }
                }
 
                if (ShowValues && height > 7 && item.Ask > 0)
                {
                    valueRightRects.Add(new Tuple<Rect, string>(new Rect(center + 2, topY, dist / 2.0, height),
                        symbol.FormatRawSize(item.Ask, roundValues, MinimizeValues)));
                }
            }
 
            pointsRight.Add(new Point(center, prevY));
 
            prevX = (int)center;
            prevY = (int)GetY((profile.High + .5) * step);
 
            var pointsLeft = new List<Point>
            {
                new Point(prevX, prevY)
            };
 
            for (var j = profile.High; j >= profile.Low; j--)
            {
                var item = profile.GetItem(j) ?? new RawClusterItem(j);
 
                var bidWidth =
                    (int)(Math.Min(dist / Math.Max(maxValues.MaxBid, maxValues.MaxAsk) * item.Bid, dist) / 2.0);
 
                var currX = (int)(center - bidWidth);
                var currY = (int)GetY((j - .5) * step);
 
                var currHeight = Math.Max(currY - prevY, height);
 
                if (currY <= prevY && pointsLeft.Count > 2)
                {
                    if (currX < prevX)
                    {
                        pointsLeft[pointsLeft.Count - 2] = new Point(currX, pointsLeft[pointsLeft.Count - 2].Y);
                        pointsLeft[pointsLeft.Count - 1] = new Point(currX, pointsLeft[pointsLeft.Count - 1].Y);
 
                        prevX = currX;
                    }
                }
                else
                {
                    pointsLeft.Add(new Point(currX, prevY));
                    pointsLeft.Add(new Point(currX, currY));
 
                    prevX = currX;
                }
 
                prevY = currY;
 
                var topY = pointsLeft[pointsLeft.Count - 2].Y;
 
                if (ShowMaximum && CheckMaximum(item, maxValues))
                {
                }
                else if (EnableFilter)
                {
                    if (item.Bid >= symbol.CorrectSizeFilter(FilterMin) &&
                        (FilterMax == 0 || item.Bid <= symbol.CorrectSizeFilter(FilterMax)))
                    {
                        if (colorRectsLeft.Count > 0)
                        {
                            var lastRect = colorRectsLeft[colorRectsLeft.Count - 1].Item1;
 
                            if ((int)lastRect.Y == (int)topY)
                            {
                                if (bidWidth > lastRect.Width)
                                {
                                    colorRectsLeft[colorRectsLeft.Count - 1] =
                                        new Tuple<Rect, XBrush>(
                                            new Rect(center - bidWidth, topY, bidWidth, lastRect.Height), _filterBrush);
                                }
                            }
                            else
                            {
                                colorRectsLeft.Add(new Tuple<Rect, XBrush>(
                                    new Rect(center - bidWidth, topY, bidWidth, currHeight), _filterBrush));
                            }
                        }
                        else
                        {
                            colorRectsLeft.Add(
                                new Tuple<Rect, XBrush>(new Rect(center - bidWidth, topY, bidWidth, currHeight),
                                    _filterBrush));
                        }
                    }
                }
 
                if (ShowValues && height > 7 && item.Bid > 0)
                {
                    valueLeftRects.Add(new Tuple<Rect, string>(new Rect(left, topY, dist / 2.0 - 2, height),
                        symbol.FormatRawSize(item.Bid, roundValues, MinimizeValues)));
                }
            }
 
            pointsLeft.Add(new Point(center, prevY));
 
            visual.FillPolygon(_profile2Brush, pointsLeft.ToArray());
            visual.FillPolygon(_profileBrush, pointsRight.ToArray());
 
            foreach (var colorRect in colorRectsLeft)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var colorRect in colorRectsRight)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var colorRect in colorRects)
            {
                visual.FillRectangle(colorRect.Item2, colorRect.Item1);
            }
 
            foreach (var valueRect in valueLeftRects)
            {
                visual.DrawString(valueRect.Item2, normalFont, _valuesBrush, valueRect.Item1, XTextAlignment.Right);
            }
 
            foreach (var valueRect in valueRightRects)
            {
                visual.DrawString(valueRect.Item2, normalFont, _valuesBrush, valueRect.Item1);
            }
 
            foreach (var valueRect in valueRects2)
            {
                visual.DrawString(valueRect.Item2, Canvas.ChartFont, _valuesBrush, valueRect.Item1, XTextAlignment.Right);
            }
        }
 
        public override void DrawControlPoints(DxVisualQueue visual)
        {
            if (_rectInfo == null)
            {
                return;
            }
 
            DrawControlPoint(visual, _rectInfo.ControlPoint1);
            DrawControlPoint(visual, _rectInfo.ControlPoint2);
 
            DrawControlPoint(visual, _rectInfo.ExtraPoint1);
            DrawControlPoint(visual, _rectInfo.ExtraPoint2);
        }
 
        public override int GetControlPoint(int x, int y)
        {
            if (Canvas == null || _rectInfo == null)
            {
                return -1;
            }
 
            var points = new[] { _rectInfo.ControlPoint1, _rectInfo.ControlPoint2 };
 
            for (var i = 0; i < points.Length; i++)
            {
                var distX = points[i].X - x;
                var distY = points[i].Y - y;
 
                if (distX * distX + distY * distY < 9.0 + PenWidth / 2.0)
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        public override int GetExtraPoint(int x, int y)
        {
            if (Canvas == null || _rectInfo == null)
            {
                return -1;
            }
 
            var points = new[] { _rectInfo.ExtraPoint1, _rectInfo.ExtraPoint2 };
 
            for (var i = 0; i < points.Length; i++)
            {
                var distX = points[i].X - x;
                var distY = points[i].Y - y;
 
                if (distX * distX + distY * distY < 9.0 + PenWidth / 2.0)
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        protected override bool IsObjectInArea()
        {
            return _isObjectInArea;
        }
 
        protected override bool InObject(int x, int y)
        {
            if (_rectInfo == null)
            {
                return false;
            }
 
            return _rectInfo.Rectangle != Rect.Empty && _rectInfo.Rectangle.Contains(x, y);
        }
 
        protected override int GetMinDist(int x, int y)
        {
            var rect = _rectInfo.Rectangle;
 
            var dx = Math.Min(rect.X + rect.Width - x, x - rect.X);
            var dy = Math.Min(rect.Y + rect.Height - y, y - rect.Y);
 
            var result = Math.Min(dx, dy);
 
            return result > 0 ? (int)result : -1;
        }
 
        public override ObjectPoint[] ExtraPoints
        {
            get
            {
                var cp1 = ControlPoints[0];
                var cp2 = ControlPoints[1];
 
                var ep1 = new ObjectPoint(cp2.X, cp1.Y);
                var ep2 = new ObjectPoint(cp1.X, cp2.Y);
 
                var extraPoints = new[] { ep1, ep2 };
 
                return extraPoints;
            }
        }
 
        public override void ExtraPointChanged(int index, ObjectPoint op)
        {
            switch (index)
            {
                case 0:
 
                    ControlPoints[1].X = op.X;
                    ControlPoints[0].Y = op.Y;
 
                    break;
 
                case 1:
 
                    ControlPoints[0].X = op.X;
                    ControlPoints[1].Y = op.Y;
 
                    break;
            }
        }
 
        public override void ApplyTheme(IChartTheme theme)
        {
            base.ApplyTheme(theme);
 
            LineColor = theme.ChartObjectLineColor;
            BackColor = theme.ChartObjectFillColor;
        }
 
        public override void CopyTemplate(ObjectBase objectBase, bool style)
        {
            if (objectBase is VolumeProfileObject obj)
            {
                ProfileType = obj.ProfileType;
                ProfileColor = obj.ProfileColor;
                Profile2Color = obj.Profile2Color;
                ExtendProfile = obj.ExtendProfile;
                ShowCumValue = obj.ShowCumValue;
 
                ShowValues = obj.ShowValues;
                MinimizeValues = obj.MinimizeValues;
                RoundValues = obj.RoundValues;
                ValuesColor = obj.ValuesColor;
 
                MaximumType = obj.MaximumType;
                ShowMaximum = obj.ShowMaximum;
                ShowMaximumValue = obj.ShowMaximumValue;
                ExtendMaximum = obj.ExtendMaximum;
                MaximumColor = obj.MaximumColor;
 
                ShowValueArea = obj.ShowValueArea;
                ExtendValueArea = obj.ExtendValueArea;
                ValueAreaPercent = obj.ValueAreaPercent;
                ValueAreaColor = obj.ValueAreaColor;
 
                EnableFilter = obj.EnableFilter;
                FilterMin = obj.FilterMin;
                FilterMax = obj.FilterMax;
                FilterColor = obj.FilterColor;
 
                DrawBorder = obj.DrawBorder;
                LineColor = obj.LineColor;
                LineWidth = obj.LineWidth;
                LineStyle = obj.LineStyle;
 
                DrawBack = obj.DrawBack;
                BackColor = obj.BackColor;
            }
 
            base.CopyTemplate(objectBase, style);
        }
    }
}

Last updated