Fibonacci Extensions

 
//------------------------------------------------------------------------------
//
// ГрафичСский ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ FibonacciExtensions. 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.Objects.Common;
using TigerTrade.Chart.Objects.Enums;
using TigerTrade.Dx;
using TigerTrade.Dx.Enums;
 
namespace TigerTrade.Chart.Objects.Custom
{
    [DataContract(Name = "FibonacciExtensionsObject", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Objects.Custom")]
    [ChartObject("X_FibonacciExtensions", "Fibonacci Extensions", 3, Type = typeof(FibonacciExtensionsObject))]
    public sealed class FibonacciExtensionsObject : ObjectBase
    {
        [Browsable(false)]
        private XBrush LineBrush { get; set; }
 
        [Browsable(false)]
        public XPen LinePen { get; private set; }
 
        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 _openStart;
 
        [DataMember(Name = "OpenStart")]
        [Category("Π£Ρ€ΠΎΠ²Π½ΠΈ"), DisplayName("ΠŸΡ€ΠΎΠ΄Π»ΠΈΡ‚ΡŒ Π²Π»Π΅Π²ΠΎ")]
        public bool OpenStart
        {
            get => _openStart;
            set
            {
                if (value == _openStart)
                {
                    return;
                }
 
                _openStart = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _openEnd;
 
        [DataMember(Name = "OpenEnd")]
        [Category("Π£Ρ€ΠΎΠ²Π½ΠΈ"), DisplayName("ΠŸΡ€ΠΎΠ΄Π»ΠΈΡ‚ΡŒ Π²ΠΏΡ€Π°Π²ΠΎ")]
        public bool OpenEnd
        {
            get => _openEnd;
            set
            {
                if (value == _openEnd)
                {
                    return;
                }
 
                _openEnd = value;
 
                OnPropertyChanged();
            }
        }
 
        [Browsable(false)]
        private XBrush LevelsLineBrush { get; set; }
 
        [Browsable(false)]
        private XPen LevelsLinePen { get; set; }
 
        private XColor _levelsLineColor;
 
        [DataMember(Name = "LevelsLineColor")]
        [Category("Π£Ρ€ΠΎΠ²Π½ΠΈ"), DisplayName("Π¦Π²Π΅Ρ‚ Π»ΠΈΠ½ΠΈΠΈ")]
        public XColor LevelsLineColor
        {
            get => _levelsLineColor;
            set
            {
                if (value == _levelsLineColor)
                {
                    return;
                }
 
                _levelsLineColor = value;
 
                LevelsLineBrush = new XBrush(_levelsLineColor);
                LevelsLinePen = new XPen(LevelsLineBrush, LevelsLineWidth, LineStyle);
 
                OnPropertyChanged();
            }
        }
 
        private int _levelsLineWidth;
 
        [DataMember(Name = "LevelsLineWidth")]
        [Category("Π£Ρ€ΠΎΠ²Π½ΠΈ"), DisplayName("Π’ΠΎΠ»Ρ‰ΠΈΠ½Π° Π»ΠΈΠ½ΠΈΠΈ")]
        public int LevelsLineWidth
        {
            get => _levelsLineWidth;
            set
            {
                value = Math.Max(1, Math.Min(10, value));
 
                if (value == _levelsLineWidth)
                {
                    return;
                }
 
                _levelsLineWidth = value;
 
                LevelsLinePen = new XPen(LevelsLineBrush, _levelsLineWidth, LineStyle);
 
                OnPropertyChanged();
            }
        }
 
        private XDashStyle _levelsLineStyle;
 
        [DataMember(Name = "LevelsLineStyle")]
        [Category("Π£Ρ€ΠΎΠ²Π½ΠΈ"), DisplayName("Π‘Ρ‚ΠΈΠ»ΡŒ Π»ΠΈΠ½ΠΈΠΈ")]
        public XDashStyle LevelsLineStyle
        {
            get => _levelsLineStyle;
            set
            {
                if (value == _lineStyle)
                {
                    return;
                }
 
                _levelsLineStyle = value;
 
                LevelsLinePen = new XPen(LevelsLineBrush, LevelsLineWidth, _lineStyle);
 
                OnPropertyChanged();
            }
        }
 
        private int _levelsWidth;
 
        [DataMember(Name = "LevelsWidth")]
        [Category("Π£Ρ€ΠΎΠ²Π½ΠΈ"), DisplayName("Π¨ΠΈΡ€ΠΈΠ½Π° уровня")]
        public int LevelsWidth
        {
            get => _levelsWidth;
            set
            {
                value = Math.Max(20, Math.Min(500, value));
 
                if (value == _levelsWidth)
                {
                    return;
                }
 
                _levelsWidth = value;
 
                OnPropertyChanged();
            }
        }
 
        private ObjectTextAlignment _textAlignment;
 
        [DataMember(Name = "TextAlignment")]
        [Category("Π£Ρ€ΠΎΠ²Π½ΠΈ"), DisplayName("ВСкст")]
        public ObjectTextAlignment TextAlignment
        {
            get => _textAlignment;
            set
            {
                if (value == _textAlignment)
                {
                    return;
                }
 
                _textAlignment = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _customLevels;
 
        [DataMember(Name = "CustomLevels")]
        [Category("Π‘Π²ΠΎΠΈ ΡƒΡ€ΠΎΠ²Π½ΠΈ"), DisplayName("Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ")]
        public bool CustomLevels
        {
            get => _customLevels;
            set
            {
                if (value == _customLevels)
                {
                    return;
                }
 
                _customLevels = value;
 
                OnPropertyChanged();
            }
        }
 
        private List<ObjectLine> _lines;
 
        [DataMember(Name = "Levels")]
        [Category("Π‘Π²ΠΎΠΈ ΡƒΡ€ΠΎΠ²Π½ΠΈ"), DisplayName("Π£Ρ€ΠΎΠ²Π½ΠΈ")]
        public List<ObjectLine> Levels
        {
            get => _lines ?? (_lines = new List<ObjectLine>());
            set
            {
                if (Equals(value, _lines))
                {
                    return;
                }
 
                _lines = value;
 
                OnPropertyChanged();
            }
        }
 
        private Point[] _startPoints;
        private Point[] _endPoints;
 
        private double[] _split;
 
        protected override int PenWidth => LineWidth;
 
        public FibonacciExtensionsObject()
        {
            LineColor = Colors.Black;
            LineWidth = 1;
            LineStyle = XDashStyle.Solid;
 
            OpenStart = false;
            OpenEnd = false;
 
            LevelsLineColor = Colors.Black;
            LevelsLineWidth = 1;
            LevelsLineStyle = XDashStyle.Solid;
 
            LevelsWidth = 200;
 
            TextAlignment = ObjectTextAlignment.LeftBottom;
 
            CustomLevels = false;
 
            Levels = new List<ObjectLine>
            {
                new ObjectLine(0.0, Colors.Black),
                new ObjectLine(23.6, Colors.Black),
                new ObjectLine(38.2, Colors.Black),
                new ObjectLine(50.0, Colors.Black),
                new ObjectLine(61.8, Colors.Black),
                new ObjectLine(78.6, Colors.Black),
                new ObjectLine(100.0, Colors.Black),
                new ObjectLine(161.8, Colors.Black),
                new ObjectLine(261.8, Colors.Black),
                new ObjectLine(361.8, Colors.Black),
                new ObjectLine(423.6, Colors.Black)
            };
        }
 
        protected override void Draw(DxVisualQueue visual, ref List<ObjectLabelInfo> labels)
        {
            CalcPoint();
 
            if (_startPoints == null || _endPoints == null)
            {
                return;
            }
 
            var points = ToPoints(ControlPoints);
 
            visual.DrawLines(LinePen, points);
 
            if (InSetup)
            {
                if (ControlPoints[0].X == ControlPoints[2].X && ControlPoints[0].Y == ControlPoints[2].Y)
                {
                    return;
                }
            }
 
            for (var i = 0; i < _startPoints.Length; i++)
            {
                if (CustomLevels)
                {
                    var level = Levels[i];
 
                    if (!level.ShowLine)
                    {
                        continue;
                    }
 
                    visual.DrawLine(level.LinePen, _startPoints[i], _endPoints[i]);
 
                    if (TextAlignment != ObjectTextAlignment.Hide)
                    {
                        visual.DrawString(GetStr(i), Canvas.ChartFont, level.LineBrush, GetRect(i, level.LineWidth));
                    }
                }
                else
                {
                    visual.DrawLine(LevelsLinePen, _startPoints[i], _endPoints[i]);
 
                    if (TextAlignment != ObjectTextAlignment.Hide)
                    {
                        visual.DrawString(GetStr(i), Canvas.ChartFont, LevelsLineBrush, GetRect(i, LineWidth));
                    }
                }
            }
        }
 
        private string GetStr(int i)
        {
            var p = DataProvider.Symbol.FormatPrice((decimal)GetPrice(i), true);
 
            return $"{_split[i]:P2} ({p})";
        }
 
        private Rect GetRect(int i, int lineWidth)
        {
            var textSize = Canvas.ChartFont.GetSize(GetStr(i));
 
            var x = 0.0;
            var y = 0.0;
 
            var width = textSize.Width;
 
            switch (TextAlignment)
            {
                case ObjectTextAlignment.LeftTop:
                case ObjectTextAlignment.CenterTop:
                case ObjectTextAlignment.RightTop:
 
                    y = _startPoints[i].Y - 4 - Math.Ceiling(lineWidth / 2.0) - textSize.Height;
 
                    break;
 
                case ObjectTextAlignment.LeftMiddle:
                case ObjectTextAlignment.CenterMiddle:
                case ObjectTextAlignment.RightMiddle:
 
                    y = _startPoints[i].Y - 4 - Math.Ceiling(lineWidth / 2.0);
 
                    break;
 
                case ObjectTextAlignment.LeftBottom:
                case ObjectTextAlignment.CenterBottom:
                case ObjectTextAlignment.RightBottom:
 
                    y = _startPoints[i].Y + 4 + Math.Ceiling(lineWidth / 2.0);
 
                    break;
            }
 
            switch (TextAlignment)
            {
                case ObjectTextAlignment.LeftTop:
                case ObjectTextAlignment.LeftMiddle:
                case ObjectTextAlignment.LeftBottom:
 
                    x = Math.Min(_startPoints[i].X, _endPoints[i].X) + 4;
 
                    break;
 
                case ObjectTextAlignment.CenterTop:
                case ObjectTextAlignment.CenterMiddle:
                case ObjectTextAlignment.CenterBottom:
 
                    x = (_startPoints[i].X + _endPoints[i].X - width) / 2.0;
 
                    break;
 
                case ObjectTextAlignment.RightTop:
                case ObjectTextAlignment.RightMiddle:
                case ObjectTextAlignment.RightBottom:
 
                    x = Math.Max(_startPoints[i].X, _endPoints[i].X) - width - 4;
 
                    break;
            }
 
            return new Rect(x, y, width, textSize.Height);
        }
 
        private void CalcPoint()
        {
            if (CustomLevels)
            {
                _split = new double[Levels.Count];
 
                for (var i = 0; i < Levels.Count; i++)
                {
                    _split[i] = Levels[i].Value / 100.0;
                }
            }
            else
            {
                _split = new[] { 0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0, 1.618, 2.618, 3.618, 4.236 };
            }
 
            var p = ToPoint(ControlPoints[2]);
 
            _startPoints = new Point[_split.Length];
            _endPoints = new Point[_split.Length];
 
            var op = new ObjectPoint(ControlPoints[2].X, 0.0);
 
            for (var i = 0; i < _split.Length; i++)
            {
                op.Y = GetPrice(i);
 
                var p3 = ToPoint(op);
 
                _startPoints[i] = new Point(p.X, p3.Y);
                _endPoints[i] = new Point(p.X + LevelsWidth, p3.Y);
            }
 
            for (var i = 0; i < _startPoints.Length; i++)
            {
                if (_startPoints[i].X <= _endPoints[i].X)
                {
                    if (OpenStart)
                    {
                        _startPoints[i].X = 0;
                    }
 
                    if (OpenEnd)
                    {
                        _endPoints[i].X = Canvas.Rect.Right;
                    }
                }
                else
                {
                    if (OpenStart)
                    {
                        _endPoints[i].X = 0;
                    }
 
                    if (OpenEnd)
                    {
                        _startPoints[i].X = Canvas.Rect.Right;
                    }
                }
            }
        }
 
        private double GetPrice(int lineIndex)
        {
            return (ControlPoints[1].Y - ControlPoints[0].Y) * _split[lineIndex] +
                   ControlPoints[2].Y;
        }
 
        protected override bool InObject(int x, int y)
        {
            if (_startPoints == null || _endPoints == null || _startPoints.Length != Levels.Count)
            {
                return false;
            }
 
            for (var i = 0; i < _startPoints.Length; i++)
            {
                if (_startPoints[i] == new Point() || (CustomLevels && !Levels[i].ShowLine))
                {
                    continue;
                }
 
                var result = InLineSegment(x, y, _startPoints[i], _endPoints[i], PenWidth + 2);
 
                if (result)
                {
                    return true;
                }
            }
 
            var points = ToPoints(ControlPoints);
 
            for (var i = 0; i < points.Length - 1; i++)
            {
                if (InLineSegment(x, y, points[i], points[i + 1], LineWidth + 2))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        private static bool InLineSegment(int x, int y, Point p1, Point p2, int width)
        {
            var n1 = Math.Max(p1.X, p2.X);
            var n2 = Math.Min(p1.X, p2.X);
            var n3 = Math.Max(p1.Y, p2.Y);
            var n4 = Math.Min(p1.Y, p2.Y);
 
            return (Math.Abs(Dist(x, y, p1, p2)) <= width) && (x <= n1 + width) && (x >= n2 - width) &&
                   (y <= n3 + width) && (y >= n4 - width);
        }
 
        private static double Dist(int x, int y, Point p1, Point p2)
        {
            var d1 = p1.X - p2.X;
            var d2 = p1.Y - p2.Y;
 
            return ((x - p1.X) * (p2.Y - p1.Y) - (p2.X - p1.X) * (y - p1.Y)) / Math.Sqrt(d1 * d1 + d2 * d2);
        }
 
        public override void ApplyTheme(IChartTheme theme)
        {
            base.ApplyTheme(theme);
 
            LevelsLineColor = theme.ChartObjectLineColor;
            LineColor = theme.ChartObjectLineColor;
 
            foreach (var level in Levels)
            {
                level.LineColor = theme.ChartObjectLineColor;
            }
        }
 
        public override void CopyTemplate(ObjectBase objectBase, bool style)
        {
            if (objectBase is FibonacciExtensionsObject obj)
            {
                LineColor = obj.LineColor;
                LineWidth = obj.LineWidth;
                LineStyle = obj.LineStyle;
 
                OpenStart = obj.OpenStart;
                OpenEnd = obj.OpenEnd;
 
                LevelsLineColor = obj.LevelsLineColor;
                LevelsLineStyle = obj.LevelsLineStyle;
                LevelsLineWidth = obj.LevelsLineWidth;
 
                LevelsWidth = obj.LevelsWidth;
 
                TextAlignment = obj.TextAlignment;
 
                CustomLevels = obj.CustomLevels;
 
                Levels = new List<ObjectLine>();
 
                foreach (var level in obj.Levels)
                {
                    Levels.Add(new ObjectLine(level));
                }
 
                OnPropertyChanged(nameof(Levels));
            }
 
            base.CopyTemplate(objectBase, style);
        }
    }
}

Last updated