Русский

Профиль объёма

//------------------------------------------------------------------------------
//
// Графический объект 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)
                {