Category Other  Published on 02/02/2021

Sync Objects [3.8+]

Description

Sync Objects Tool for cTrader 3.8+. 

Latest version: https://ctrader.com/algos/indicators/show/2063

This tool synchronizes objects between several charts with the same symbol.
How to use: just add it as a regular indicator.

Feel free to make your suggestions to improve this indicator!

Demo:

 


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

namespace devman
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class SyncObjectsInstance : Indicator
    {
        protected override void Initialize()
        {
            Synchronizer.Instance.Register(this);
            Timer.Start(Synchronizer.HeartbeatRate);
        }

        protected override void OnTimer()
        {
            Synchronizer.Instance.Heartbeat(this);
        }

        public override void Calculate(int index)
        {
            // do nothing
        }
    }

    public class Synchronizer
    {
        public static readonly TimeSpan HeartbeatTimeout = TimeSpan.FromSeconds(5);
        public static readonly TimeSpan HeartbeatRate = TimeSpan.FromSeconds(4);

        private static readonly object Sync = new object();
        private static Synchronizer _instance;

        public static Synchronizer Instance
        {
            get
            {
                if (_instance != null)
                    return _instance;

                lock (Sync)
                {
                    var instance = new Synchronizer();

                    if (_instance == null)
                        _instance = instance;
                }

                return _instance;
            }
        }

        private readonly Dictionary<string, HashSet<SyncObjectsInstance>> _instances;
        private readonly Dictionary<SyncObjectsInstance, DateTime> _instanceHeartbeats;

        public Synchronizer()
        {
            _instances = new Dictionary<string, HashSet<SyncObjectsInstance>>(StringComparer.OrdinalIgnoreCase);
            _instanceHeartbeats = new Dictionary<SyncObjectsInstance, DateTime>();
        }

        public void Register(SyncObjectsInstance instance)
        {
            lock (Sync)
            {
                Restore(instance);
            }
        }

        public void Heartbeat(SyncObjectsInstance instance)
        {
            instance.Print("Heartbeat");

            lock (Sync)
            {
                var now = DateTime.Now;

                _instanceHeartbeats[instance] = now;

                var expiredInstances = _instanceHeartbeats.Where(hb => now - hb.Value > HeartbeatTimeout).Select(hb => hb.Key).ToArray();

                foreach (var expiredInstance in expiredInstances)
                {
                    expiredInstance.Chart.ObjectAdded -= OnObjectAdded;
                    expiredInstance.Chart.ObjectRemoved -= OnObjectRemoved;
                    expiredInstance.Chart.ObjectUpdated -= OnObjectUpdated;

                    _instanceHeartbeats.Remove(expiredInstance);
                    _instances[expiredInstance.SymbolName].Remove(expiredInstance);

                    instance.Print(string.Format("Expired {0}", expiredInstance.SymbolName));
                }
            }
        }

        private void OnObjectAdded(ChartObjectAddedEventArgs args)
        {
            lock (Sync)
            {
                HashSet<SyncObjectsInstance> symbolInstances;

                if (!_instances.TryGetValue(args.Chart.SymbolName, out symbolInstances))
                    return;

                foreach (var instance in symbolInstances)
                {
                    if (instance.Chart == args.Chart)
                        continue;

                    instance.BeginInvokeOnMainThread(() => RestoreObject(instance, args.ChartObject));
                }
            }
        }

        private void OnObjectUpdated(ChartObjectUpdatedEventArgs args)
        {
            lock (Sync)
            {
                HashSet<SyncObjectsInstance> symbolInstances;

                if (!_instances.TryGetValue(args.Chart.SymbolName, out symbolInstances))
                    return;

                foreach (var instance in symbolInstances)
                {
                    if (instance.Chart == args.Chart)
                        continue;

                    instance.BeginInvokeOnMainThread(() => RestoreObject(instance, args.ChartObject));
                }
            }
        }

        private void OnObjectRemoved(ChartObjectRemovedEventArgs args)
        {
            lock (Sync)
            {
                HashSet<SyncObjectsInstance> symbolInstances;

                if (!_instances.TryGetValue(args.Chart.SymbolName, out symbolInstances))
                    return;

                var objectNames = new[] 
                {
                    args.ChartObject.Name
                };

                foreach (var instance in symbolInstances)
                {
                    if (instance.Chart == args.Chart)
                        continue;

                    instance.BeginInvokeOnMainThread(() => RemoveObjects(instance, objectNames));
                }
            }
        }

        private void Restore(SyncObjectsInstance sender)
        {
            HashSet<SyncObjectsInstance> symbolInstances;

            if (!_instances.TryGetValue(sender.SymbolName, out symbolInstances))
            {
                symbolInstances = new HashSet<SyncObjectsInstance>();
                _instances.Add(sender.SymbolName, symbolInstances);
            }

            var senderObjects = sender.Chart.Objects.ToArray();

            foreach (var instance in symbolInstances)
                instance.BeginInvokeOnMainThread(() => RestoreObjects(instance, senderObjects));

            sender.Chart.ObjectAdded += OnObjectAdded;
            sender.Chart.ObjectRemoved += OnObjectRemoved;
            sender.Chart.ObjectUpdated += OnObjectUpdated;

            symbolInstances.Add(sender);
            _instanceHeartbeats[sender] = DateTime.Now;
        }

        private static void RemoveObjects(SyncObjectsInstance targetInstance, IEnumerable<string> sourceObjects)
        {
            foreach (var sourceObject in sourceObjects)
                targetInstance.Chart.RemoveObject(sourceObject);
        }

        private static void RestoreObjects(SyncObjectsInstance targetInstance, IEnumerable<ChartObject> sourceObjects)
        {
            foreach (var sourceObject in sourceObjects)
                RestoreObject(targetInstance, sourceObject);
        }

        private static void RestoreObject(SyncObjectsInstance targetInstance, ChartObject sourceObject)
        {
            var targetChart = targetInstance.Chart;

            if (sourceObject is ChartAndrewsPitchfork)
            {
                RestoreAndrewsPitchfork(targetChart, (ChartAndrewsPitchfork)sourceObject);
                return;
            }

            if (sourceObject is ChartEllipse)
            {
                RestoreEllipse(targetChart, (ChartEllipse)sourceObject);
                return;
            }

            if (sourceObject is ChartEquidistantChannel)
            {
                RestoreEquidistantChannel(targetChart, (ChartEquidistantChannel)sourceObject);
                return;
            }

            if (sourceObject is ChartFibonacciExpansion)
            {
                RestoreFibonacciExpansion(targetChart, (ChartFibonacciExpansion)sourceObject);
                return;
            }

            if (sourceObject is ChartFibonacciFan)
            {
                RestoreFibonacciFan(targetChart, (ChartFibonacciFan)sourceObject);
                return;
            }

            if (sourceObject is ChartFibonacciRetracement)
            {
                RestoreFibonacciRetracement(targetChart, (ChartFibonacciRetracement)sourceObject);
                return;
            }

            if (sourceObject is ChartHorizontalLine)
            {
                RestoreHorizontalLine(targetChart, (ChartHorizontalLine)sourceObject);
                return;
            }

            if (sourceObject is ChartIcon)
            {
                RestoreIcon(targetChart, (ChartIcon)sourceObject);
                return;
            }

            if (sourceObject is ChartRectangle)
            {
                RestoreRectangle(targetChart, (ChartRectangle)sourceObject);
                return;
            }

            if (sourceObject is ChartText)
            {
                RestoreText(targetChart, (ChartText)sourceObject);
                return;
            }

            if (sourceObject is ChartTrendLine)
            {
                RestoreTrendLine(targetChart, (ChartTrendLine)sourceObject);
                return;
            }

            if (sourceObject is ChartTriangle)
            {
                RestoreTriangle(targetChart, (ChartTriangle)sourceObject);
                return;
            }

            if (sourceObject is ChartVerticalLine)
            {
                RestoreVerticalLine(targetChart, (ChartVerticalLine)sourceObject);
                return;
            }

            targetInstance.Print(string.Format("Type \"{0}\" is not supported.", sourceObject.GetType().Name));
        }

        private static void RestoreTrendLine(Chart targetChart, ChartTrendLine sourceObject)
        {
            var targetObject = targetChart.DrawTrendLine(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
            targetObject.ExtendToInfinity = sourceObject.ExtendToInfinity;
        }

        private static void RestoreVerticalLine(Chart targetChart, ChartVerticalLine sourceObject)
        {
            var targetObject = targetChart.DrawVerticalLine(sourceObject.Name, sourceObject.Time, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
        }

        private static void RestoreTriangle(Chart targetChart, ChartTriangle sourceObject)
        {
            var targetObject = targetChart.DrawTriangle(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.Time3, sourceObject.Y3, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
            targetObject.IsFilled = sourceObject.IsFilled;
        }

        private static void RestoreText(Chart targetChart, ChartText sourceObject)
        {
            var targetObject = targetChart.DrawText(sourceObject.Name, sourceObject.Text, sourceObject.Time, sourceObject.Y, sourceObject.Color);

            targetObject.IsInteractive = true;
            targetObject.HorizontalAlignment = sourceObject.HorizontalAlignment;
            targetObject.VerticalAlignment = sourceObject.VerticalAlignment;
        }

        private static void RestoreRectangle(Chart targetChart, ChartRectangle sourceObject)
        {
            var targetObject = targetChart.DrawRectangle(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
            targetObject.IsFilled = sourceObject.IsFilled;
        }

        private static void RestoreIcon(Chart targetChart, ChartIcon sourceObject)
        {
            var targetObject = targetChart.DrawIcon(sourceObject.Name, sourceObject.IconType, sourceObject.Time, sourceObject.Y, sourceObject.Color);

            targetObject.IsInteractive = true;
        }

        private static void RestoreHorizontalLine(Chart targetChart, ChartHorizontalLine sourceObject)
        {
            var targetObject = targetChart.DrawHorizontalLine(sourceObject.Name, sourceObject.Y, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
        }

        private static void RestoreFibonacciRetracement(Chart targetChart, ChartFibonacciRetracement sourceObject)
        {
            var targetObject = targetChart.DrawFibonacciRetracement(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
            targetObject.DisplayPrices = sourceObject.DisplayPrices;

            for (int i = 0; i < targetObject.FibonacciLevels.Count; i++)
            {
                targetObject.FibonacciLevels[i].IsVisible = sourceObject.FibonacciLevels[i].IsVisible;
                targetObject.FibonacciLevels[i].PercentLevel = sourceObject.FibonacciLevels[i].PercentLevel;
            }
        }

        private static void RestoreFibonacciFan(Chart targetChart, ChartFibonacciFan sourceObject)
        {
            var targetObject = targetChart.DrawFibonacciFan(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
            targetObject.DisplayPrices = sourceObject.DisplayPrices;

            for (int i = 0; i < targetObject.FibonacciLevels.Count; i++)
            {
                targetObject.FibonacciLevels[i].IsVisible = sourceObject.FibonacciLevels[i].IsVisible;
                targetObject.FibonacciLevels[i].PercentLevel = sourceObject.FibonacciLevels[i].PercentLevel;
            }
        }

        private static void RestoreFibonacciExpansion(Chart targetChart, ChartFibonacciExpansion sourceObject)
        {
            var targetObject = targetChart.DrawFibonacciExpansion(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.Time3, sourceObject.Y3, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
            targetObject.DisplayPrices = sourceObject.DisplayPrices;

            for (int i = 0; i < targetObject.FibonacciLevels.Count; i++)
            {
                targetObject.FibonacciLevels[i].IsVisible = sourceObject.FibonacciLevels[i].IsVisible;
                targetObject.FibonacciLevels[i].PercentLevel = sourceObject.FibonacciLevels[i].PercentLevel;
            }
        }

        private static void RestoreEquidistantChannel(Chart targetChart, ChartEquidistantChannel sourceObject)
        {
            var targetObject = targetChart.DrawEquidistantChannel(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.ChannelHeight, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
            targetObject.ExtendToInfinity = sourceObject.ExtendToInfinity;
            targetObject.ShowAngle = sourceObject.ShowAngle;
        }

        private static void RestoreEllipse(Chart targetChart, ChartEllipse sourceObject)
        {
            var targetObject = targetChart.DrawEllipse(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
            targetObject.IsFilled = sourceObject.IsFilled;
        }

        private static void RestoreAndrewsPitchfork(Chart targetChart, ChartAndrewsPitchfork sourceObject)
        {
            var targetObject = targetChart.DrawAndrewsPitchfork(sourceObject.Name, sourceObject.Time1, sourceObject.Y1, sourceObject.Time2, sourceObject.Y2, sourceObject.Time3, sourceObject.Y3, sourceObject.Color, sourceObject.Thickness, sourceObject.LineStyle);

            targetObject.IsInteractive = true;
        }
    }
}


devman's avatar
devman

Joined on 22.10.2019

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: Sync Objects [3.8].algo
  • Rating: 0
  • Installs: 2343
  • Modified: 13/10/2021 09:55
Comments
Log in to add a comment.
OV
ovettkn · 7 months ago

not working ctrader 4.8

vitofx's avatar
vitofx · 2 years ago

Hi - this indicator stopped working in v.4.2+ would you mind updating it pls?

PA
padik11 · 3 years ago

Thank you for your work. The indicator is very useful.