External Chart

/--------------------------------------------------------------------------------
//
// Indicator ExternalChart. 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.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;
            }
        }
    }
}

Last updated