Volume Profile Indicator Code
using System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.Serialization; using System.Windows; using System.Windows.Media; using TigerTrade.Chart.Base.Enums; using TigerTrade.Chart.Data; using TigerTrade.Chart.Indicators.Common; using TigerTrade.Chart.Indicators.Enums; using TigerTrade.Core.UI.Converters; using TigerTrade.Core.Utils.Time; using TigerTrade.Dx; using TigerTrade.Dx.Enums; namespace TigerTrade.Chart.Indicators.Custom { [TypeConverter(typeof(EnumDescriptionTypeConverter))] [DataContract(Name = "VolumeProfilesPeriodType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")] public enum VolumeProfilesPeriodType { [EnumMember(Value = "Minute"), Description("Минута")] Minute, [EnumMember(Value = "Hour"), Description("Час")] Hour, [EnumMember(Value = "Day"), Description("День")] Day, [EnumMember(Value = "Week"), Description("Неделя")] Week, [EnumMember(Value = "Month"), Description("Месяц")] Month } [TypeConverter(typeof(EnumDescriptionTypeConverter))] [DataContract(Name = "VolumeProfilesType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")] public enum VolumeProfilesType { [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 = "VolumeProfilesMaximumType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")] public enum VolumeProfilesMaximumType { [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 = "VolumeProfilesIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")] [Indicator("X_VolumeProfiles", "*VolumeProfiles", true, Type = typeof(VolumeProfilesIndicator))] internal sealed class VolumeProfilesIndicator : IndicatorBase { private List<VolumeProfile> _profiles; private List<VolumeProfile> Profiles => _profiles ?? (_profiles = new List<VolumeProfile>()); private VolumeProfilesPeriodType _periodType; [DataMember(Name = "PeriodType")] [Category("Период"), DisplayName("Интервал")] public VolumeProfilesPeriodType PeriodType { get => _periodType; set { if (value == _periodType) { return; } _periodType = value; _periodValue = _periodType == VolumeProfilesPeriodType.Minute ? 15 : 1; Clear(); OnPropertyChanged(); } } private int _periodValue; [DataMember(Name = "PeriodValue")] [Category("Период"), DisplayName("Значение")] public int PeriodValue { get => _periodValue; set { value = Math.Max(1, value); if (value == _periodValue) { return; } _periodValue = value; Clear(); OnPropertyChanged(); } } private VolumeProfilesType _profileType; [DataMember(Name = "ProfileType")] [Category("Профиль"), DisplayName("Тип")] public VolumeProfilesType ProfileType { get => _profileType; set { if (value == _profileType) { return; } _profileType = value; OnPropertyChanged(); } } private int _profileProportion; [DataMember(Name = "ProfileProportion")] [Category("Профиль"), DisplayName("Объём шкалы")] public int ProfileProportion { get => _profileProportion; set { value = Math.Max(0, value); if (value == _profileProportion) { return; } _profileProportion = 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 _backBrush; private XColor _profileBackColor; [DataMember(Name = "ProfileBackColor")] [Category("Профиль"), DisplayName("Цвет фона")] public XColor ProfileBackColor { get => _profileBackColor; set { if (value == _profileBackColor) { return; } _profileBackColor = value; _backBrush = new XBrush(_profileBackColor); 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 IndicatorIntParam _roundValueParam; [DataMember(Name = "RoundValueParam")] public IndicatorIntParam RoundValuesParam { get => _roundValueParam ?? (_roundValueParam = new IndicatorIntParam(0)); set => _roundValueParam = value; } [DefaultValue(0)] [Category("Значения"), DisplayName("Округлять")] public int RoundValues { get => RoundValuesParam.Get(SettingsLongKey); set { if (!RoundValuesParam.Set(SettingsLongKey, value, -4, 4)) { return; } 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 VolumeProfilesMaximumType _maximumType; [DataMember(Name = "MaximumType")] [Category("Максимум"), DisplayName("Тип")] public VolumeProfilesMaximumType 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 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 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; Clear(); 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 = "FilterValue")] [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(); } } [Browsable(false)] public override bool ShowIndicatorValues => false; [Browsable(false)] public override bool ShowIndicatorLabels => false; [Browsable(false)] public override IndicatorCalculation Calculation => IndicatorCalculation.OnEachTick; public VolumeProfilesIndicator() { ShowIndicatorTitle = false; PeriodType = VolumeProfilesPeriodType.Hour; PeriodValue = 1; ProfileType = VolumeProfilesType.Volume; ProfileProportion = 0; ProfileColor = Color.FromArgb(127, 70, 130, 180); ProfileBackColor = Color.FromArgb(30, 70, 130, 180); ShowValues = false; MinimizeValues = false; ValuesColor = Color.FromArgb(255, 255, 255, 255); MaximumType = VolumeProfilesMaximumType.Volume; ShowMaximum = true; MaximumColor = Color.FromArgb(127, 178, 34, 34); ShowValueArea = true; ValueAreaPercent = 70; ValueAreaColor = Color.FromArgb(127, 128, 128, 128); EnableFilter = false; FilterMin = 0; FilterMax = 0; FilterColor = Color.FromArgb(127, 46, 139, 87); } private int _lastFullID; private void Clear() { _lastFullID = 0; Profiles.Clear(); } private int GetSequence(DateTime date, double offset) { var cycleBase = ChartPeriodType.Hour; switch (PeriodType) { case VolumeProfilesPeriodType.Minute: cycleBase = ChartPeriodType.Minute; break; case VolumeProfilesPeriodType.Hour: cycleBase = ChartPeriodType.Hour; break; case VolumeProfilesPeriodType.Day: cycleBase = ChartPeriodType.Day; break; case VolumeProfilesPeriodType.Week: cycleBase = ChartPeriodType.Week; break; case VolumeProfilesPeriodType.Month: cycleBase = ChartPeriodType.Month; break; } return DataProvider.Period.GetSequence(cycleBase, PeriodValue, date, offset); } protected override void Execute() { if (ClearData) { Clear(); } if (Profiles.Count > 0 && !Profiles[Profiles.Count - 1].Completed) { Profiles.RemoveAt(Profiles.Count - 1); } var timeOffset = TimeHelper.GetSessionOffset(DataProvider.Symbol.Exchange); var lastSequence = -1; for (var i = _lastFullID; i < DataProvider.Count; i++) { var cluster = DataProvider.GetRawCluster(i); var currSequence = GetSequence(cluster.Time, timeOffset); if (Profiles.Count == 0 || currSequence != lastSequence) { lastSequence = currSequence; if (Profiles.Count > 0 && i > _lastFullID) { _lastFullID = i; Profiles[Profiles.Count - 1].Completed = true; Profiles[Profiles.Count - 1].Cluster.UpdateData(); } Profiles.Add(new VolumeProfile(new RawCluster(cluster.Time), i)); } var lastCluster = Profiles[Profiles.Count - 1]; lastCluster.Cluster.AddCluster(cluster); lastCluster.EndBar = i; } if (Profiles.Count > 0 && !Profiles[Profiles.Count - 1].Completed) { Profiles[Profiles.Count - 1].Cluster.UpdateData(); } } public override void Render(DxVisualQueue visual) { if (Profiles.Count == 0) { return; } var minPrice = (long)(Canvas.MinY / Helper.DataProvider.Step) - 1; var maxPrice = (long)(Canvas.MaxY / Helper.DataProvider.Step) + 1; if (maxPrice - minPrice > 20000) { return; } switch (ProfileType) { case VolumeProfilesType.Volume: RenderVolume(visual); break; case VolumeProfilesType.Trades: RenderTrades(visual); break; case VolumeProfilesType.Delta: RenderDelta(visual); break; case VolumeProfilesType.BidAsk: RenderBidAsk(visual); break; } } private void RenderVolume(DxVisualQueue visual) { var startIndex = Canvas.Stop; var endIndex = Canvas.Stop + Canvas.Count; var step = DataProvider.Step; var symbol = DataProvider.Symbol; var minFilter = symbol.CorrectSizeFilter(FilterMin); var maxFilter = symbol.CorrectSizeFilter(FilterMax); var proportion = symbol.CorrectSizeFilter(ProfileProportion); var height = GetY(0.0) - GetY(step); 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 columnWidth2 = Canvas.ColumnWidth / 2.0; var roundValues = RoundValues; var prevRight = int.MinValue; foreach (var volumeProfile in Profiles) { if (volumeProfile.EndBar < startIndex || volumeProfile.StartBar > endIndex) { continue; } var x1 = Canvas.GetX(volumeProfile.StartBar); var x2 = Canvas.GetX(volumeProfile.EndBar); var profile = volumeProfile.Cluster; var maxValues = profile.MaxValues; var valueArea = ShowValueArea ? profile.GetValueArea(ValueAreaPercent) : null; var left = x1 - columnWidth2; var right = x2 + columnWidth2 - 1; if (prevRight != int.MinValue) { left = prevRight; } prevRight = (int)right; var dist = Math.Max(right - left, 1); var max = ProfileProportion > 0 ? proportion : maxValues.MaxVolume; var volStep = dist / Math.Max(max, 1); var colorRects = new List<Tuple<Rect, XBrush>>(); var colorRects2 = new List<Tuple<Rect, XBrush>>(); var valueRects = new List<Tuple<Rect, string>>(); 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 = Math.Min(volStep * item.Volume, dist); var currX = (int)(left + width); var currY = (int)GetY((j - .5) * step); var currHeight = Math.Max(currY - prevY, 1); 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>(new Rect(left, topY, dist, currHeight), _maximumBrush)); } else if (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val)) { colorRects2.Add(new Tuple<Rect, XBrush>(new Rect(left, topY, dist, currHeight), _valueAreaBrush)); } else if (EnableFilter && item.Volume >= minFilter && (maxFilter == 0 || item.Volume <= maxFilter)) { 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, topY, dist, height), symbol.FormatRawSize(item.Volume, roundValues, MinimizeValues))); } } points.Add(new Point(left, prevY)); visual.FillRectangle(_backBrush, new Rect(new Point(left, points[0].Y), new Point(right, 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); } } } private void RenderTrades(DxVisualQueue visual) { var startIndex = Canvas.Stop; var endIndex = Canvas.Stop + Canvas.Count; var step = DataProvider.Step; var symbol = DataProvider.Symbol; var height = GetY(0.0) - GetY(step); 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 columnWidth2 = Canvas.ColumnWidth / 2.0; var roundValues = RoundValues; var prevRight = int.MinValue; foreach (var volumeProfile in Profiles) { if (volumeProfile.EndBar < startIndex || volumeProfile.StartBar > endIndex) { continue; } var x1 = Canvas.GetX(volumeProfile.StartBar); var x2 = Canvas.GetX(volumeProfile.EndBar); var profile = volumeProfile.Cluster; var maxValues = profile.MaxValues; var valueArea = ShowValueArea ? profile.GetValueArea(ValueAreaPercent) : null; var left = x1 - columnWidth2; var right = x2 + columnWidth2 - 1; if (prevRight != int.MinValue) { left = prevRight; } prevRight = (int)right; var dist = Math.Max(right - left, 1); var max = ProfileProportion > 0 ? ProfileProportion : maxValues.MaxTrades; var volStep = dist / Math.Max(max, 1); var colorRects = new List<Tuple<Rect, XBrush>>(); var colorRects2 = new List<Tuple<Rect, XBrush>>(); var valueRects = new List<Tuple<Rect, string>>(); 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 = Math.Min(volStep * item.Trades, dist); var currX = (int)(left + width); var currY = (int)GetY((j - .5) * step); var currHeight = Math.Max(currY - prevY, 1); 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>(new Rect(left, topY, dist, currHeight), _maximumBrush)); } else if (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val)) { colorRects2.Add(new Tuple<Rect, XBrush>(new Rect(left, topY, dist, currHeight), _valueAreaBrush)); } 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, topY, dist, height), symbol.FormatTrades(item.Trades, roundValues, MinimizeValues))); } } points.Add(new Point(left, prevY)); visual.FillRectangle(_backBrush, new Rect(new Point(left, points[0].Y), new Point(right, 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); } } } private void RenderDelta(DxVisualQueue visual) { var startIndex = Canvas.Stop; var endIndex = Canvas.Stop + Canvas.Count; var step = DataProvider.Step; var symbol = DataProvider.Symbol; var minFilter = symbol.CorrectSizeFilter(FilterMin); var maxFilter = symbol.CorrectSizeFilter(FilterMax); var proportion = symbol.CorrectSizeFilter(ProfileProportion); var height = GetY(0.0) - GetY(step); 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 columnWidth2 = Canvas.ColumnWidth / 2.0; var roundValues = RoundValues; var prevRight = int.MinValue; foreach (var volumeProfile in Profiles) { if (volumeProfile.EndBar < startIndex || volumeProfile.StartBar > endIndex) { continue; } var x1 = Canvas.GetX(volumeProfile.StartBar); var x2 = Canvas.GetX(volumeProfile.EndBar); var profile = volumeProfile.Cluster; var maxValues = profile.MaxValues; var valueArea = ShowValueArea ? profile.GetValueArea(ValueAreaPercent) : null; var left = x1 - columnWidth2; var right = x2 + columnWidth2 - 1; if (prevRight != int.MinValue) { left = prevRight; } prevRight = (int)right; var dist = Math.Max(right - left, 1); var max = ProfileProportion > 0 ? proportion : Math.Max(Math.Abs(maxValues.MinDelta), Math.Abs(maxValues.MaxDelta)); var volStep = dist / Math.Max(max, 1); 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 center = left + dist / 2.0; // right part 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(volStep * 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, 1); 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>(new Rect(left, topY, dist, currHeight), _maximumBrush)); } else if (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val)) { colorRects.Add(new Tuple<Rect, XBrush>(new Rect(left, topY, dist, currHeight), _valueAreaBrush)); } else if (EnableFilter) { if (item.Delta > 0 && item.Delta >= minFilter && (maxFilter == 0 || item.Delta <= maxFilter)) { 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)); // left part 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(volStep * 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, 1); 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 (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val)) { } else if (EnableFilter) { if (item.Delta < 0 && -item.Delta >= minFilter && (maxFilter == 0 || -item.Delta <= maxFilter)) { 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.FillRectangle(_backBrush, new Rect(new Point(left, pointsLeft[0].Y), new Point(right, prevY))); visual.FillPolygon(_profileBrush, 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); } } } private void RenderBidAsk(DxVisualQueue visual) { var startIndex = Canvas.Stop; var endIndex = Canvas.Stop + Canvas.Count; var step = DataProvider.Step; var symbol = DataProvider.Symbol; var minFilter = symbol.CorrectSizeFilter(FilterMin); var maxFilter = symbol.CorrectSizeFilter(FilterMax); var proportion = symbol.CorrectSizeFilter(ProfileProportion); var height = GetY(0.0) - GetY(step); 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 columnWidth2 = Canvas.ColumnWidth / 2.0; var roundValues = RoundValues; var prevRight = int.MinValue; foreach (var volumeProfile in Profiles) { if (volumeProfile.EndBar < startIndex || volumeProfile.StartBar > endIndex) { continue; } var x1 = Canvas.GetX(volumeProfile.StartBar); var x2 = Canvas.GetX(volumeProfile.EndBar); var profile = volumeProfile.Cluster; var maxValues = profile.MaxValues; var valueArea = ShowValueArea ? profile.GetValueArea(ValueAreaPercent) : null; var left = x1 - columnWidth2; var right = x2 + columnWidth2 - 1; if (prevRight != int.MinValue) { left = prevRight; } prevRight = (int)right; var dist = Math.Max(right - left, 1); var max = ProfileProportion > 0 ? proportion : Math.Max(maxValues.MaxBid, maxValues.MaxAsk); var volStep = dist / Math.Max(max, 1); 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 center = left + dist / 2.0; // right part - ask 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 = item.Ask > 0 ? (int)(Math.Min(volStep * item.Ask, dist) / 2.0) : 0; var currX = (int)(center + askWidth); var currY = (int)GetY((j - .5) * step); var currHeight = Math.Max(currY - prevY, 1); 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>(new Rect(left, topY, dist, currHeight), _maximumBrush)); } else if (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val)) { colorRects.Add(new Tuple<Rect, XBrush>(new Rect(left, topY, dist, currHeight), _valueAreaBrush)); } else if (EnableFilter) { if (item.Ask >= minFilter && (maxFilter == 0 || item.Ask <= maxFilter)) { 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)); // left part - bid 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(volStep * item.Bid, dist) / 2.0); var currX = (int)(center - bidWidth); var currY = (int)GetY((j - .5) * step); var currHeight = Math.Max(currY - prevY, 1); 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 (valueArea != null && (item.Price == valueArea.Vah || item.Price == valueArea.Val)) { } else if (EnableFilter) { if (item.Bid >= minFilter && (maxFilter == 0 || item.Bid <= maxFilter)) { 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.FillRectangle(_backBrush, new Rect(new Point(left, pointsLeft[0].Y), new Point(right, prevY))); visual.FillPolygon(_profileBrush, 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); } } } private bool CheckMaximum(IRawClusterItem item, IRawClusterMaxValues maxValues) { switch (MaximumType) { case VolumeProfilesMaximumType.Volume: return item.Volume == maxValues.MaxVolume; case VolumeProfilesMaximumType.Trades: return item.Trades == maxValues.MaxTrades; case VolumeProfilesMaximumType.Delta: return Math.Abs(item.Delta) == Math.Max(Math.Abs(maxValues.MaxDelta), Math.Abs(maxValues.MinDelta)); case VolumeProfilesMaximumType.DeltaPlus: return item.Delta > 0 && item.Delta == maxValues.MaxDelta; case VolumeProfilesMaximumType.DeltaMinus: return item.Delta < 0 && item.Delta == maxValues.MinDelta; case VolumeProfilesMaximumType.Bid: return item.Bid == maxValues.MaxBid; case VolumeProfilesMaximumType.Ask: return item.Ask == maxValues.MaxAsk; } return false; } public override void CopyTemplate(IndicatorBase indicator, bool style) { var i = (VolumeProfilesIndicator)indicator; PeriodType = i.PeriodType; PeriodValue = i.PeriodValue; ProfileType = i.ProfileType; ProfileProportion = i.ProfileProportion; ProfileColor = i.ProfileColor; ProfileBackColor = i.ProfileBackColor; ShowValues = i.ShowValues; MinimizeValues = i.MinimizeValues; ValuesColor = i.ValuesColor; RoundValuesParam.Copy(i.RoundValuesParam); MaximumType = i.MaximumType; ShowMaximum = i.ShowMaximum; MaximumColor = i.MaximumColor; ShowValueArea = i.ShowValueArea; ValueAreaPercent = i.ValueAreaPercent; ValueAreaColor = i.ValueAreaColor; EnableFilter = i.EnableFilter; FilterMin = i.FilterMin; FilterMax = i.FilterMax; FilterColor = i.FilterColor; base.CopyTemplate(indicator, style); } private class VolumeProfile { public readonly int StartBar; public int EndBar; public readonly RawCluster Cluster; public bool Completed; public VolumeProfile(RawCluster cluster, int startBar) { Cluster = cluster; StartBar = startBar; } } } }