Category Other  Published on 30/03/2022

Synchronized Scrolling

Description

This indicator allows you to synchronize scrolling between your cTrader charts, to use it you just have to attach the indicator on your charts that you want to synchronize their scrolling and it will scroll your other charts if you scroll one of them.

Features

  • Easy to use, you just have to attach it on a chart and it will work
  • It can work with different time frame and symbol charts
  • Three different modes that allows you to limit the indicator

This indicator is open source, you can contribute on Github: 

 


using cAlgo.API;
using System.Collections.Concurrent;
using System;
using System.Collections.Generic;
using System.Threading;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class SynchronizedScrolling : Indicator
    {
        private static ConcurrentDictionary<string, IndicatorInstanceContainer<SynchronizedScrolling, DateTime?>> _indicatorInstances = new ConcurrentDictionary<string, IndicatorInstanceContainer<SynchronizedScrolling, DateTime?>>();

        private static int _numberOfChartsToScroll;

        private DateTime _lastScrollTime;

        private string _chartKey;

        [Parameter("Mode", DefaultValue = Mode.All)]
        public Mode Mode { get; set; }

        protected override void Initialize()
        {
            _chartKey = GetChartKey(this);

            IndicatorInstanceContainer<SynchronizedScrolling, DateTime?> oldIndicatorContainer;

            GetIndicatorInstanceContainer(_chartKey, out oldIndicatorContainer);

            _indicatorInstances.AddOrUpdate(_chartKey, new IndicatorInstanceContainer<SynchronizedScrolling, DateTime?>(this), (key, value) => new IndicatorInstanceContainer<SynchronizedScrolling, DateTime?>(this));

            if (oldIndicatorContainer != null && oldIndicatorContainer.Data.HasValue)
            {
                ScrollXTo(oldIndicatorContainer.Data.Value);
            }

            Chart.ScrollChanged += Chart_ScrollChanged;
        }

        public override void Calculate(int index)
        {
        }

        public void ScrollXTo(DateTime time)
        {
            IndicatorInstanceContainer<SynchronizedScrolling, DateTime?> indicatorContainer;

            if (GetIndicatorInstanceContainer(_chartKey, out indicatorContainer))
            {
                indicatorContainer.Data = time;
            }

            if (Bars[0].OpenTime > time)
            {
                LoadMoreHistory();
            }
            else
            {
                Chart.ScrollXTo(time);
            }
        }

        private void LoadMoreHistory()
        {
            var numberOfLoadedBars = Bars.LoadMoreHistory();

            if (numberOfLoadedBars == 0)
            {
                Chart.DrawStaticText("ScrollError", "Synchronized Scrolling: Can't load more data to keep in sync with other charts as more historical data is not available for this chart", VerticalAlignment.Bottom, HorizontalAlignment.Left, Color.Red);
            }
        }

        private void Chart_ScrollChanged(ChartScrollEventArgs obj)
        {
            IndicatorInstanceContainer<SynchronizedScrolling, DateTime?> indicatorContainer;

            if (GetIndicatorInstanceContainer(_chartKey, out indicatorContainer))
            {
                indicatorContainer.Data = null;
            }

            if (_numberOfChartsToScroll > 0)
            {
                Interlocked.Decrement(ref _numberOfChartsToScroll);

                return;
            }

            var firstBarTime = obj.Chart.Bars.OpenTimes[obj.Chart.FirstVisibleBarIndex];

            if (_lastScrollTime == firstBarTime)
                return;

            _lastScrollTime = firstBarTime;

            switch (Mode)
            {
                case Mode.Symbol:
                    ScrollCharts(firstBarTime, indicator => indicator.SymbolName.Equals(SymbolName, StringComparison.Ordinal));
                    break;

                case Mode.TimeFrame:
                    ScrollCharts(firstBarTime, indicator => indicator.TimeFrame == TimeFrame);
                    break;
                default:


                    ScrollCharts(firstBarTime);
                    break;
            }
        }

        private void ScrollCharts(DateTime firstBarTime, Func<Indicator, bool> predicate = null)
        {
            var toScroll = new List<SynchronizedScrolling>(_indicatorInstances.Values.Count);

            foreach (var indicatorContianer in _indicatorInstances)
            {
                SynchronizedScrolling indicator;

                if (indicatorContianer.Value.GetIndicator(out indicator) == false || indicator == this || (predicate != null && predicate(indicator) == false))
                    continue;

                toScroll.Add(indicator);
            }

            Interlocked.CompareExchange(ref _numberOfChartsToScroll, toScroll.Count, _numberOfChartsToScroll);

            foreach (var indicator in toScroll)
            {
                try
                {
                    indicator.BeginInvokeOnMainThread(() => indicator.ScrollXTo(firstBarTime));
                } catch (Exception)
                {
                    Interlocked.Decrement(ref _numberOfChartsToScroll);
                }
            }
        }

        private string GetChartKey(SynchronizedScrolling indicator)
        {
            return string.Format("{0}_{1}_{2}", indicator.SymbolName, indicator.TimeFrame, indicator.Chart.ChartType);
        }

        private bool GetIndicatorInstanceContainer(string chartKey, out IndicatorInstanceContainer<SynchronizedScrolling, DateTime?> indicatorContainer)
        {
            if (_indicatorInstances.TryGetValue(chartKey, out indicatorContainer))
            {
                return true;
            }

            indicatorContainer = null;

            return false;
        }
    }

    public enum Mode
    {
        All,
        TimeFrame,
        Symbol
    }

    public class IndicatorInstanceContainer<T, TData> where T : Indicator
    {
        private readonly WeakReference _indicatorWeakReference;

        public IndicatorInstanceContainer(T indicator)
        {
            _indicatorWeakReference = new WeakReference(indicator);
        }

        public TData Data { get; set; }

        public bool GetIndicator(out T indicator)
        {
            if (_indicatorWeakReference.IsAlive)
            {
                indicator = (T)_indicatorWeakReference.Target;

                return true;
            }

            indicator = null;

            return false;
        }
    }
}


Spotware's avatar
Spotware

Joined on 23.09.2013

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: Synchronized Scrolling.algo
  • Rating: 5
  • Installs: 1866
  • Modified: 30/03/2022 11:41
Comments
Log in to add a comment.
VI
viatrufka · 3 months ago

I've changed the code so that the indicators scroll to the chart's right side. Helpful for market replay.

using cAlgo.API;
using System.Collections.Concurrent;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Text.RegularExpressions;

namespace cAlgo
{
	[Indicator(IsOverlay = true, TimeZone = TimeZones.RussianStandardTime, AccessRights = AccessRights.None)]
	public class SynchronizedScrolling : Indicator
	{
		private static ConcurrentDictionary<string, IndicatorInstanceContainer<SynchronizedScrolling, DateTime?>> _indicatorInstances = new ConcurrentDictionary<string, IndicatorInstanceContainer<SynchronizedScrolling, DateTime?>>();

		private static int _numberOfChartsToScroll;

		private DateTime _lastScrollTime;

		private string _chartKey;

		[Parameter("Mode", DefaultValue = Mode.All)]
		public Mode Mode { get; set; }

		protected override void Initialize()
		{
			_chartKey = GetChartKey(this);

			IndicatorInstanceContainer<SynchronizedScrolling, DateTime?> oldIndicatorContainer;

			GetIndicatorInstanceContainer(_chartKey, out oldIndicatorContainer);

			_indicatorInstances.AddOrUpdate(_chartKey, new IndicatorInstanceContainer<SynchronizedScrolling, DateTime?>(this),
				(key, value) => new IndicatorInstanceContainer<SynchronizedScrolling, DateTime?>(this));

			if (oldIndicatorContainer != null && oldIndicatorContainer.Data.HasValue)
			{
				ScrollXTo(oldIndicatorContainer.Data.Value);
			}

			Chart.ScrollChanged += Chart_ScrollChanged;
		}

		public override void Calculate(int index)
		{
		}

		public void ScrollXTo(DateTime time)
		{
			IndicatorInstanceContainer<SynchronizedScrolling, DateTime?> indicatorContainer;

			if (GetIndicatorInstanceContainer(_chartKey, out indicatorContainer))
			{
				indicatorContainer.Data = time;
			}

			if (Bars[0].OpenTime > time)
			{
				LoadMoreHistory();
			}
			else
			{
				Chart.ScrollXTo(time - Chart.MaxVisibleBars * GetCurrentTimeSpan());
			}
		}
		private TimeSpan GetCurrentTimeSpan()
		{
			var TimeFrameName = Chart.TimeFrame.ToString();

			if (!int.TryParse(Regex.Replace(TimeFrameName, "[^0-9]", ""), out int prefix)) prefix = 1;

			if (TimeFrameName.Contains("Minute"))
				return TimeSpan.FromMinutes(prefix);
			else if (TimeFrameName.Contains("Hour"))
				return TimeSpan.FromHours(prefix);
			else if (TimeFrameName.Contains("Day") || TimeFrameName.Contains("Daily"))
				return TimeSpan.FromDays(prefix);
			else if (TimeFrameName == "Weekly")
				return TimeSpan.FromDays(7);
			else if (TimeFrameName == "Monthly")
				return TimeSpan.FromDays(31);
			else
				return TimeSpan.Zero;
		}
		private void LoadMoreHistory()
		{
			var numberOfLoadedBars = Bars.LoadMoreHistory();

			if (numberOfLoadedBars == 0)
			{
				Chart.DrawStaticText("ScrollError", "Synchronized Scrolling: Can't load more data to keep in sync with other charts as more historical data is not available for this chart", VerticalAlignment.Bottom, HorizontalAlignment.Left, Color.Red);
			}
		}

		private void Chart_ScrollChanged(ChartScrollEventArgs obj)
		{
			IndicatorInstanceContainer<SynchronizedScrolling, DateTime?> indicatorContainer;

			if (GetIndicatorInstanceContainer(_chartKey, out indicatorContainer))
			{
				indicatorContainer.Data = null;
			}

			if (_numberOfChartsToScroll > 0)
			{
				Interlocked.Decrement(ref _numberOfChartsToScroll);

				return;
			}

			var lastBarTime = obj.Chart.Bars.OpenTimes[obj.Chart.LastVisibleBarIndex];

			if (_lastScrollTime == lastBarTime)
				return;

			_lastScrollTime = lastBarTime;

			switch (Mode)
			{
				case Mode.Symbol:
					ScrollCharts(lastBarTime, indicator => indicator.SymbolName.Equals(SymbolName, StringComparison.Ordinal));
					break;

				case Mode.TimeFrame:
					ScrollCharts(lastBarTime, indicator => indicator.TimeFrame == TimeFrame);
					break;
				default:


					ScrollCharts(lastBarTime);
					break;
			}
		}

		private void ScrollCharts(DateTime lastBarTime, Func<Indicator, bool> predicate = null)
		{
			var toScroll = new List<SynchronizedScrolling>(_indicatorInstances.Values.Count);

			foreach (var indicatorContianer in _indicatorInstances)
			{
				SynchronizedScrolling indicator;

				if (indicatorContianer.Value.GetIndicator(out indicator) == false || indicator == this || (predicate != null && predicate(indicator) == false))
					continue;

				toScroll.Add(indicator);
			}

			Interlocked.CompareExchange(ref _numberOfChartsToScroll, toScroll.Count, _numberOfChartsToScroll);

			foreach (var indicator in toScroll)
			{
				try
				{
					indicator.BeginInvokeOnMainThread(() => indicator.ScrollXTo(lastBarTime));
				}
				catch (Exception)
				{
					Interlocked.Decrement(ref _numberOfChartsToScroll);
				}
			}
		}

		private string GetChartKey(SynchronizedScrolling indicator)
		{
			return string.Format("{0}_{1}_{2}", indicator.SymbolName, indicator.TimeFrame, indicator.Chart.ChartType);
		}

		private bool GetIndicatorInstanceContainer(string chartKey, out IndicatorInstanceContainer<SynchronizedScrolling, DateTime?> indicatorContainer)
		{
			if (_indicatorInstances.TryGetValue(chartKey, out indicatorContainer))
			{
				return true;
			}

			indicatorContainer = null;

			return false;
		}
	}

	public enum Mode
	{
		All,
		TimeFrame,
		Symbol
	}

	public class IndicatorInstanceContainer<T, TData> where T : Indicator
	{
		private readonly WeakReference _indicatorWeakReference;

		public IndicatorInstanceContainer(T indicator)
		{
			_indicatorWeakReference = new WeakReference(indicator);
		}

		public TData Data { get; set; }

		public bool GetIndicator(out T indicator)
		{
			if (_indicatorWeakReference.IsAlive)
			{
				indicator = (T)_indicatorWeakReference.Target;

				return true;
			}

			indicator = null;

			return false;
		}
	}
}
CT
ctid5698155 · 11 months ago

it doesnn't work for the renko bar chart

Capt.Z-Fort.Builder's avatar
Capt.Z-Fort.Builder · 1 year ago

This is a great tool, however, I encounter a little problem in usage:

When I set 2 charts with the same Symbol and same TimeFrame, if I scroll in the first chart and the indicator works well in the second chart, then while I swap to scroll the 2nd chart, the first chart won't get synchronized scrolled.

In other words, synchronizing scrolling can only get triggered by one chart, not all charts' scrolling could trigger synchronizing. Please help and advise. Thanks.

FC
fcarabat · 2 years ago

great tool! I wish many of these features were a part of ctrader from installation. 

PH
phill.beaney · 2 years ago

A really great tool that is saving me loads of time.

Many Thanks