Category Other  Published on 10/07/2022

Synchronized ScrZooing

Description

This indicator allows you to synchronize scrolling and zooming in one indicator between your cTrader charts, to use it you just have to attach the indicator on your charts that you want to synchronize. It was merged based on 2 indicators made by Spotware: Synchronized Scrolling  and  Synchronized Zooming

Since I need to sync my charts almost scrolling and zooming at the same time always, so, it is quite useful to me. I hope it can help others as well.

If you are interested in the indicators 'Currency Strength Meter' on the below charts, you can follow the link to download and play.


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 SynchronizedScrZoo : Indicator
    {
        private static ConcurrentDictionary<string, IndicatorInstanceContainer<SynchronizedScrZoo, DateTime?>> _indicatorInstances_S = new ConcurrentDictionary<string, IndicatorInstanceContainer<SynchronizedScrZoo, DateTime?>>();
        private static ConcurrentDictionary<string, IndicatorInstanceContainer<SynchronizedScrZoo, int?>>      _indicatorInstances_Z = new ConcurrentDictionary<string, IndicatorInstanceContainer<SynchronizedScrZoo, int?>>();
        private static int _numberOfChartsToScroll;
        private DateTime _lastScrollTime;
        private int _lastZoomLevel;
        private string _chartKey;

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

        protected override void Initialize()
        {
            _chartKey = GetChartKey(this);
            IndicatorInstanceContainer<SynchronizedScrZoo, DateTime?> oldIndicatorContainer_S;
            IndicatorInstanceContainer<SynchronizedScrZoo, int?> oldIndicatorContainer_Z;

            GetIndicatorInstanceContainer_S(_chartKey, out oldIndicatorContainer_S);
            GetIndicatorInstanceContainer_Z(_chartKey, out oldIndicatorContainer_Z);

            _indicatorInstances_S.AddOrUpdate(_chartKey, new IndicatorInstanceContainer<SynchronizedScrZoo, DateTime?>(this), (key, value) => new IndicatorInstanceContainer<SynchronizedScrZoo, DateTime?>(this));
            _indicatorInstances_Z.AddOrUpdate(_chartKey, new IndicatorInstanceContainer<SynchronizedScrZoo, int?>(this),      (key, value) => new IndicatorInstanceContainer<SynchronizedScrZoo, int?>(this));
            if (oldIndicatorContainer_S != null && oldIndicatorContainer_S.Data.HasValue) { ScrollXTo(oldIndicatorContainer_S.Data.Value); }
            if (oldIndicatorContainer_Z != null && oldIndicatorContainer_Z.Data.HasValue) { ZoomChart(oldIndicatorContainer_Z.Data.Value); }
            
            Chart.ScrollChanged += Chart_ScrollChanged; Chart.ZoomChanged += Chart_ZoomChanged;
        }

        public override void Calculate(int index) {}
        
        public void ScrollXTo(DateTime time)
        {
            IndicatorInstanceContainer<SynchronizedScrZoo, DateTime?> indicatorContainer;
            if (GetIndicatorInstanceContainer_S(_chartKey, out indicatorContainer)) { indicatorContainer.Data = time; }
            if (Bars[0].OpenTime > time) { LoadMoreHistory(); }
            else { Chart.ScrollXTo(time); }
        }
        public void ZoomChart(int zoomLevel)
        {
            IndicatorInstanceContainer<SynchronizedScrZoo, int?> indicatorContainer;
            if (GetIndicatorInstanceContainer_Z(_chartKey, out indicatorContainer)) { indicatorContainer.Data = zoomLevel; }
            Chart.ZoomLevel = zoomLevel;
        }
        
        private void Chart_ScrollChanged(ChartScrollEventArgs obj)
        {
            IndicatorInstanceContainer<SynchronizedScrZoo, DateTime?> indicatorContainer;
 
            if (GetIndicatorInstanceContainer_S(_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 Chart_ZoomChanged(ChartZoomEventArgs obj)
        {
            IndicatorInstanceContainer<SynchronizedScrZoo, int?> indicatorContainer;

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

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

            var zoomLevel = obj.Chart.ZoomLevel;

            if (_lastZoomLevel == zoomLevel) return;

            _lastZoomLevel = zoomLevel;

            switch (Mode)
            {
                case Mode.Symbol:       ZoomCharts(zoomLevel, indicator => indicator.SymbolName.Equals(SymbolName, StringComparison.Ordinal)); break;
                case Mode.TimeFrame:    ZoomCharts(zoomLevel, indicator => indicator.TimeFrame == TimeFrame); break;
                default:                ZoomCharts(zoomLevel); break;
            }
        }
        
        private void ScrollCharts(DateTime firstBarTime, Func<Indicator, bool> predicate = null)
        {
            var toScroll = new List<SynchronizedScrZoo>(_indicatorInstances_S.Values.Count);
 
            foreach (var indicatorContianer in _indicatorInstances_S)
            {
                SynchronizedScrZoo 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 void ZoomCharts(int zoomLevel, Func<Indicator, bool> predicate = null)
        {
            var toScroll = new List<SynchronizedScrZoo>(_indicatorInstances_Z.Values.Count);

            foreach (var indicatorContianer in _indicatorInstances_Z)
            {
                SynchronizedScrZoo 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.ZoomChart(zoomLevel)); }
                catch (Exception) { Interlocked.Decrement(ref _numberOfChartsToScroll); }
            }
        }

        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 string GetChartKey(SynchronizedScrZoo indicator) { return string.Format("{0}_{1}_{2}", indicator.SymbolName, indicator.TimeFrame, indicator.Chart.ChartType); }
        //private string GetChartKey(SynchronizedScrZoo indicator) { return string.Format("{0}_{1}_{2}", indicator.SymbolName, indicator.TimeFrame, indicator.Chart.ChartType); }
        
        private bool GetIndicatorInstanceContainer_S(string chartKey, out IndicatorInstanceContainer<SynchronizedScrZoo, DateTime?> indicatorContainer)
        {
            if (_indicatorInstances_S.TryGetValue(chartKey, out indicatorContainer)) { return true; }
            indicatorContainer = null;
            return false;
        }
        private bool GetIndicatorInstanceContainer_Z(string chartKey, out IndicatorInstanceContainer<SynchronizedScrZoo, int?> indicatorContainer)
        {
            if (_indicatorInstances_Z.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;
        }
    }
}


Capt.Z-Fort.Builder's avatar
Capt.Z-Fort.Builder

Joined on 03.06.2020

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: Synchronized ScrZooing.algo
  • Rating: 5
  • Installs: 855
Comments
Log in to add a comment.
Capt.Z-Fort.Builder's avatar
Capt.Z-Fort.Builder · 1 year ago

@racshen-seo

It's not a perfect solution, I use it to sync both minute1 charts.  There could be problems there, you may try to change the Mode parameter to refresh the indicator, however, I use it in my charts, and it's better than none. ;)

RA
racshen-seo · 1 year ago

Hello! Works unstable. If the cursor is moved to m5, then the crosshair moves to h1. But if the cursor is moved to h1, then the crosshair does not move to m5!(

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

davidiesis

It's working with no problem, I've just tested it in the latest cTrader 4.3.9 version. I can't give you more information here, join Telegram Group cTrader FOREX Club https://t.me/cTraderFXClub  to get more help.

Thanks.

 

DA
davidiesis · 1 year ago

Thanks, I already tried to install the basic version but it doesn't work. When I try to load it on the chart nothing appears and the candlesticks are being canceled

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

davidiesis 

Oh, ok.  Don't worry about the file access authority. Check line32: 'AccessRights = AccessRights.FileSystem'; the indicator only requires file access. Because the indicator has a function to 'Import and Display Economic Calendar's Key Events;' which requires the user to download a*.txt file first, then import.

If you are still concerned about safety problems, you can refer to Currency Strength Meter (Basic) version, which requires none access rights to use it.

DA
davidiesis · 1 year ago

Sorry, I was referring to the Currency Strength Meter (Pro) Ver2.04 indicator ....

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

davidiesis

This indicator requires no access to your local files. Check code line 9: 'AccessRights = AccessRights.None'

If you still have a problem, please post your question to cTrader forum: https://ctrader.com/forum/indicator-support

DA
davidiesis · 1 year ago

Hi, I would like to install the indicator but it requires access to all files in the file system. Since I don't know you, how can I be sure that the code is clean and does no harm to my computer?