Category Other  Published on 29/11/2019

Chart Overview

Description

Chart Overview control.

Feel free to make your suggestions to improve this indicator!

Demo:

Settings:


using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class ChartOverview : Indicator
    {
        private const int YAxisWidth = 45;
        private const int YAxisMargin = 10;

        private Polygon _cloudControl;
        private Rectangle _viewportControl;

        private double _dragOffset;
        private int _lastBarsTotal;

        [Parameter()]
        public DataSeries Input { get; set; }

        [Parameter("Cloud opacity", DefaultValue = 0.2, MaxValue = 1, MinValue = 0.0, Group = "Cloud")]
        public double CloudOpacity { get; set; }

        [Parameter("Cloud color", DefaultValue = "#FFADD8E6", Group = "Cloud")]
        public string CloudColor { get; set; }

        [Parameter("Viewport opacity", DefaultValue = 0.2, MaxValue = 1, MinValue = 0.0, Group = "Viewport")]
        public double ViewportOpacity { get; set; }

        [Parameter("Viewport color", DefaultValue = "#FFFFFFE0", Group = "Viewport")]
        public string ViewportColor { get; set; }

        [Parameter("Density", DefaultValue = 1, MinValue = 0.1, MaxValue = 100)]
        public double Density { get; set; }

        protected override void Initialize()
        {
            InitializeControls();

            UpdateCloud();
            UpdateViewport();

            IndicatorArea.ScrollChanged += OnIndicatorAreaScrollChanged;
            IndicatorArea.SizeChanged += OnIndicatorAreaSizeChanged;
            IndicatorArea.MouseDown += OnIndicatorAreaMouseDown;
            IndicatorArea.DragStart += OnIndicatorAreaDragStart;
            IndicatorArea.DragEnd += OnIndicatorAreaDragEnd;
            IndicatorArea.Drag += OnIndicatorAreaDrag;

            _lastBarsTotal = Chart.BarsTotal;

            Print("Initialized");
        }

        public override void Calculate(int index)
        {
            if (!IsLastBar)
                return;

            if (_lastBarsTotal != Chart.BarsTotal)
            {
                UpdateCloud();
                UpdateViewport();

                _lastBarsTotal = Chart.BarsTotal;
            }
        }

        private void InitializeControls()
        {
            var cloudColor = Color.FromHex(CloudColor);
            var viewportColor = Color.FromHex(ViewportColor);

            _cloudControl = new Polygon
            {
                StrokeThickness = 1,
                StrokeColor = cloudColor,
                FillColor = Color.FromArgb((int) (cloudColor.A * CloudOpacity), cloudColor),
                IsHitTestVisible = false
            };

            _viewportControl = new Rectangle
            {
                StrokeThickness = 1,
                StrokeColor = viewportColor,
                FillColor = Color.FromArgb((int) (viewportColor.A * ViewportOpacity), viewportColor),
                IsHitTestVisible = false
            };

            var canvas = new Canvas
            {
                BackgroundColor = Color.Transparent,
                VerticalAlignment = VerticalAlignment.Stretch,
                HorizontalAlignment = HorizontalAlignment.Stretch,
                Margin = 0,
                IsHitTestVisible = false
            };

            canvas.AddChild(_cloudControl);
            canvas.AddChild(_viewportControl);

            var border = new Border
            {
                BorderThickness = 0,
                BackgroundColor = Chart.ColorSettings.BackgroundColor,
                VerticalAlignment = VerticalAlignment.Stretch,
                HorizontalAlignment = HorizontalAlignment.Stretch,
                Child = canvas,
                IsHitTestVisible = false,
                Margin = string.Format("0 0 -{0} 0", YAxisWidth)
            };

            IndicatorArea.AddControl(border);
        }

        private void OnIndicatorAreaDragStart(ChartDragEventArgs args)
        {
            Chart.IsScrollingEnabled = false;
        }

        private void OnIndicatorAreaDragEnd(ChartDragEventArgs args)
        {
            Chart.IsScrollingEnabled = true;
        }

        private void OnIndicatorAreaDrag(ChartDragEventArgs args)
        {
            var selectedBarIndex = TransformXToBarIndex(0, IndicatorArea.Width + YAxisWidth, args.MouseX);
            ScrollTo(selectedBarIndex);
        }

        private void OnIndicatorAreaMouseDown(ChartMouseEventArgs args)
        {
            var selectedBarIndex = TransformXToBarIndex(0, IndicatorArea.Width + YAxisWidth, args.MouseX);

            if (selectedBarIndex >= Chart.FirstVisibleBarIndex && selectedBarIndex <= Chart.LastVisibleBarIndex)
                _dragOffset = Chart.FirstVisibleBarIndex - selectedBarIndex;
            else
                _dragOffset = -Chart.MaxVisibleBars / 2.0;

            ScrollTo(selectedBarIndex);
        }

        private void OnIndicatorAreaSizeChanged(ChartSizeEventArgs args)
        {
            UpdateCloud();
            UpdateViewport();
        }

        private void OnIndicatorAreaScrollChanged(ChartScrollEventArgs args)
        {
            if (args.BarsDelta != 0)
                UpdateViewport();

            if (Chart.IsScrollingEnabled)
                return;

            var min = double.MaxValue;
            var max = double.MinValue;

            for (var i = Chart.FirstVisibleBarIndex; i <= Chart.LastVisibleBarIndex; i++)
            {
                if (MarketSeries.Low[i] < min)
                    min = MarketSeries.Low[i];

                if (MarketSeries.High[i] > max)
                    max = MarketSeries.High[i];
            }

            Chart.SetYRange(min - Symbol.PipSize * 10, max + Symbol.PipSize * 10);
        }

        private void UpdateViewport()
        {
            _viewportControl.Left = TransformBarIndexToX(0, Chart.BarsTotal, Chart.FirstVisibleBarIndex) - 1;
            _viewportControl.Width = TransformBarIndexToX(0, Chart.BarsTotal, Chart.LastVisibleBarIndex + 1) - TransformBarIndexToX(0, Chart.BarsTotal, Chart.FirstVisibleBarIndex) + 2;

            _viewportControl.Top = -10;
            _viewportControl.Height = IndicatorArea.Height + 20;
        }

        private void UpdateCloud()
        {
            _cloudControl.Points = GeneratePoints().ToArray();
        }

        private IEnumerable<Point> GeneratePoints()
        {
            var density = 1 / Density;

            var series = Input;
            var high = series.Maximum(series.Count) + Symbol.PipSize;
            var low = series.Minimum(series.Count) - Symbol.PipSize;

            yield return new Point(0, IndicatorArea.Height);

            var lastGroupX = 0.0;
            var lastGroupY = 0.0;

            for (var i = 0; i < series.Count; i++)
            {
                var x = TransformBarIndexToX(0, series.Count - 1, i);
                var y = TransformPriceToY(low, high, series[i]);

                if (Math.Abs(lastGroupX - x) >= density)
                {
                    yield return new Point(lastGroupX, lastGroupY);
                    lastGroupX = x;
                }

                lastGroupY = y;
            }

            yield return new Point(IndicatorArea.Width + YAxisWidth, IndicatorArea.Height);
        }

        private void ScrollTo(double barIndex)
        {
            var newBarIndex = (int)Math.Round(barIndex + _dragOffset, MidpointRounding.AwayFromZero);

            if (newBarIndex < 0)
                newBarIndex = 0;

            if (newBarIndex >= Chart.BarsTotal)
                newBarIndex = Chart.BarsTotal - 1;

            Chart.ScrollXTo(newBarIndex);
        }

        private double TransformPriceToY(double min, double max, double val)
        {
            var height = IndicatorArea.Height - YAxisMargin * 2;

            var y = height - (val - min) / (max - min) * height;

            if (double.IsNaN(y))
                y = IndicatorArea.Height;

            if (double.IsInfinity(y))
                y = 0;

            return y + YAxisMargin;
        }

        private double TransformBarIndexToX(double min, double max, double val)
        {
            var x = (val - min) / (max - min) * (IndicatorArea.Width + YAxisWidth);

            if (double.IsNaN(x))
                x = 0;

            if (double.IsInfinity(x))
                x = IndicatorArea.Width + YAxisWidth;

            return x;
        }

        private double TransformXToBarIndex(double min, double max, double val)
        {
            var barIndex = (val - min) / (max - min) * (Chart.BarsTotal - 1);

            if (double.IsNaN(barIndex))
                barIndex = 0;

            if (double.IsInfinity(barIndex))
                barIndex = Chart.BarsTotal - 1;

            return barIndex;
        }
    }
}


devman's avatar
devman

Joined on 22.10.2019

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: Chart Overview.algo
  • Rating: 5
  • Installs: 1424
Comments
Log in to add a comment.
DI
DiamondTrades · 2 years ago

This indicator is amazing, really great quality of life improvement for cTrader!

If I had to come up with a a suggestion (really not sure if it is even possible) then the only thing I could think of is Dates along the X-Axis, not every date of course but maybe spaced out enough that a quick glance could give a guestimate of when that above peak took place.