External Chart Indicator Code
//-------------------------------------------------------------------------------- // // ExternalChart. Copyright (c) 2019 Ilya Smirnov. 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.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; namespace TigerTrade.Chart.Indicators.Custom { [TypeConverter(typeof(EnumDescriptionTypeConverter))] [DataContract(Name = "ExternalChartPeriodType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")] public enum ExternalChartPeriodType { [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 } [DataContract(Name = "ExternalChartBorderType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")] [TypeConverter(typeof(EnumDescriptionTypeConverter))] public enum ExternalChartBorderType { [EnumMember(Value = "None"), Description("Скрыть")] None, [EnumMember(Value = "Box"), Description("Коробка")] Box, [EnumMember(Value = "ColoredBox"), Description("Коробка с раскраской")] ColoredBox, [EnumMember(Value = "Candle"), Description("Свеча")] Candle, [EnumMember(Value = "ColoredCandle"), Description("Свеча с раскраской")] ColoredCandle } [DataContract(Name = "ExternalChartIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")] [Indicator("Z_ExternalChart", "*ExternalChart", true, Type = typeof(ExternalChartIndicator))] internal sealed class ExternalChartIndicator : IndicatorBase { private ExternalChartPeriodType _periodType; [DataMember(Name = "PeriodType")] [Category("Период"), DisplayName("Интервал")] public ExternalChartPeriodType PeriodType { get => _periodType; set { if (value == _periodType) { return; } _periodType = value; _periodValue = _periodType == ExternalChartPeriodType.Minute ? 15 : 1; Clear(); OnPropertyChanged(); OnPropertyChanged(nameof(PeriodValue)); } } 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 bool _showBack; [DataMember(Name = "ShowBack")] [Category("Фон"), DisplayName("Отображать фон")] public bool ShowBack { get => _showBack; set { if (value == _showBack) { return; } _showBack = 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(); } } private bool _showGrid; [DataMember(Name = "ShowGrid")] [Category("Сетка"), DisplayName("Отображать сетку")] public bool ShowGrid { get => _showGrid; set { if (value == _showGrid) { return; } _showGrid = value; OnPropertyChanged(); } } private XBrush _gridBrush; private XPen _gridPen; private XColor _gridColor; [DataMember(Name = "GridColor")] [Category("Сетка"), DisplayName("Цвет Сетки")] public XColor GridColor { get => _gridColor; set { if (value == _gridColor) { return; } _gridColor = value; _gridBrush = new XBrush(_gridColor); _gridPen = new XPen(_gridBrush, 1); OnPropertyChanged(); } } private ExternalChartBorderType _borderType; [DataMember(Name = "BorderType")] [Category("Граница"), DisplayName("Тип границы")] public ExternalChartBorderType BorderType { get => _borderType; set { if (value == _borderType) { return; } _borderType = value; OnPropertyChanged(); } } private int _borderWidth; [DataMember(Name = "BorderWidth")] [Category("Граница"), DisplayName("Ширина границы")] public int BorderWidth { get => _borderWidth; set { value = Math.Max(1, value); if (value == _borderWidth) { return; } _borderWidth = value; OnPropertyChanged(); } } private XBrush _borderBrush; private XColor _borderColor; [DataMember(Name = "BorderColor")] [Category("Граница"), DisplayName("Цвет границы")] public XColor BorderColor { get => _borderColor; set { if (value == _borderColor) { return; } _borderColor = value; _borderBrush = new XBrush(_borderColor); OnPropertyChanged(); } } [Browsable(false)] public override bool ShowIndicatorValues => false; [Browsable(false)] public override bool ShowIndicatorLabels => false; [Browsable(false)] public override IndicatorCalculation Calculation => IndicatorCalculation.OnPriceChange; private List<ExternalBar> _bars; private List<ExternalBar> Bars => _bars ?? (_bars = new List<ExternalBar>()); public ExternalChartIndicator() { ShowIndicatorTitle = false; PeriodType = ExternalChartPeriodType.Hour; PeriodValue = 1; ShowBack = true; BackColor = Color.FromArgb(30, 70, 130, 180); ShowGrid = false; GridColor = Color.FromArgb(255, 70, 130, 180); BorderType = ExternalChartBorderType.ColoredBox; BorderWidth = 1; BorderColor = Color.FromArgb(255, 120, 120, 120); } private int _lastFullID; private void Clear() { _lastFullID = 0; Bars.Clear(); } private int GetSequence(DateTime date, double offset) { var periodType = ChartPeriodType.Hour; switch (PeriodType) { case ExternalChartPeriodType.Minute: periodType = ChartPeriodType.Minute; break; case ExternalChartPeriodType.Hour: periodType = ChartPeriodType.Hour; break; case ExternalChartPeriodType.Day: periodType = ChartPeriodType.Day; break; case ExternalChartPeriodType.Week: periodType = ChartPeriodType.Week; break; case ExternalChartPeriodType.Month: periodType = ChartPeriodType.Month; break; } return DataProvider.Period.GetSequence(periodType, PeriodValue, date, offset); } protected override void Execute() { if (ClearData) { Clear(); } if (Bars.Count > 0 && !Bars[Bars.Count - 1].Completed) { Bars.RemoveAt(Bars.Count - 1); } var timeOffset = TimeHelper.GetSessionOffset(DataProvider.Symbol.Exchange); var lastSequence = -1; for (var i = _lastFullID; i < DataProvider.Count; i++) { var cluster = DataProvider.GetCluster(i); var currSequence = GetSequence(cluster.Time, timeOffset); if (Bars.Count == 0 || currSequence != lastSequence) { lastSequence = currSequence; if (Bars.Count > 0 && i > _lastFullID) { _lastFullID = i; Bars[Bars.Count - 1].Completed = true; } Bars.Add(new ExternalBar(i)); } Bars[Bars.Count - 1].Update(cluster, i); } } public override void Render(DxVisualQueue visual) { if (Bars.Count == 0) { return; } var startIndex = Canvas.Stop; var endIndex = Canvas.Stop + Canvas.Count; var step = (decimal)DataProvider.Step; var columnWidth2 = Canvas.ColumnWidth / 2.0; var prevRight = int.MinValue; foreach (var bar in Bars) { if (bar.EndBar < startIndex || bar.StartBar > endIndex) { continue; } var x1 = Canvas.GetX(bar.StartBar); var x2 = Canvas.GetX(bar.EndBar); var left = (int)(x1 - columnWidth2); var right = (int)(x2 + columnWidth2 - 1); if (prevRight != int.MinValue) { left = prevRight; } prevRight = right; var centerX = (int)((x1 + x2) / 2.0); var isUp = bar.Open < bar.Close; var highY = (int)GetY(bar.High + step / 2m); var lowY = (int)GetY(bar.Low - step / 2m); var bodyHighY = isUp ? (int)GetY(bar.Close + step / 2m) : (int)GetY(bar.Open + step / 2m); var bodyLowY = !isUp ? (int)GetY(bar.Close - step / 2m) : (int)GetY(bar.Open - step / 2m); if (ShowBack) { visual.FillRectangle(_backBrush, new Rect(new Point(left, highY - 1), new Point(right, lowY))); } if (ShowGrid) { for (var j = bar.High + step; j >= bar.Low; j -= step) { var currY = (int)GetY(j - step / 2m) - 1; visual.DrawLine(_gridPen, new Point(left, currY), new Point(right, currY)); } for (var j = bar.StartBar; j <= bar.EndBar; j++) { var barLeft = j == bar.StartBar ? left : Canvas.GetX(j) - columnWidth2 - 1; visual.DrawLine(_gridPen, new Point(barLeft, highY - 1), new Point(barLeft, lowY)); if (j == bar.EndBar) { visual.DrawLine(_gridPen, new Point(right, highY - 1), new Point(right, lowY)); } } } if (BorderType != ExternalChartBorderType.None) { var borderBrush = _borderBrush; if (BorderType == ExternalChartBorderType.ColoredCandle || BorderType == ExternalChartBorderType.ColoredBox) { borderBrush = new XBrush(isUp ? Canvas.Theme.ClusterUpBarColor : Canvas.Theme.ClusterDownBarColor); } var borderPen = new XPen(borderBrush, BorderWidth); if (BorderType == ExternalChartBorderType.Candle || BorderType == ExternalChartBorderType.ColoredCandle) { var correct = (int)Math.Ceiling(_borderWidth / 2.0); visual.DrawRectangle(borderPen, new Rect(new Point(left + correct, bodyHighY), new Point(right - correct, bodyLowY))); visual.DrawLine(borderPen, new Point(centerX, highY), new Point(centerX, bodyHighY)); visual.DrawLine(borderPen, new Point(centerX, bodyLowY), new Point(centerX, lowY)); } else if (BorderType == ExternalChartBorderType.Box || BorderType == ExternalChartBorderType.ColoredBox) { var correct = (int)Math.Ceiling(_borderWidth / 2.0); visual.DrawRectangle(borderPen, new Rect(new Point(left + correct, highY), new Point(right - correct, lowY))); } } } } public override void CopyTemplate(IndicatorBase indicator, bool style) { var i = (ExternalChartIndicator)indicator; PeriodType = i.PeriodType; PeriodValue = i.PeriodValue; ShowBack = i.ShowBack; BackColor = i.BackColor; ShowGrid = i.ShowGrid; GridColor = i.GridColor; BorderType = i.BorderType; BorderWidth = i.BorderWidth; BackColor = i.BorderColor; base.CopyTemplate(indicator, style); } private class ExternalBar { public readonly int StartBar; public int EndBar; public bool Completed; public decimal Open; public decimal High; public decimal Low; public decimal Close; private bool _new; public ExternalBar(int startBar) { StartBar = startBar; _new = true; } public void Update(IChartCluster cluster, int bar) { if (_new) { Open = cluster.Open; High = cluster.High; Low = cluster.Low; _new = false; } else { High = Math.Max(High, cluster.High); Low = Math.Min(Low, cluster.Low); } Close = cluster.Close; EndBar = bar; } } } }