Description
Sync Objects Tool for cTrader 4.0+.
Previous version (3.8): https://ctrader.com/algos/indicators/show/2556
This tool synchronizes objects between several charts with the same symbol.
How to use: just add it as a regular indicator.
Parameters
Feel free to make your suggestions to improve this indicator!
What's new
[06.05.2021]
- Allow to temporarily disable indicator.
- Allow customizing synchronization group.
- Show status icon and allow customizing it.
[01.02.2021]
- Support multiple objects operations for 4.0.
Demo:
using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;
namespace devman
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class SyncObjectsInstance : Indicator
{
[Parameter("Enabled", DefaultValue = true)]
public bool Enabled { get; set; }
[Parameter("Custom Group", DefaultValue = "")]
public string CustomGroup { get; set; }
[Parameter("Visible", Group = "Icon", DefaultValue = true)]
public bool IconVisible { get; set; }
[Parameter("Color", Group = "Icon", DefaultValue = " Auto")]
public string IconColor { get; set; }
[Parameter("Vertical Alignment", Group = "Icon", DefaultValue = VerticalAlignment.Top)]
public VerticalAlignment IconVerticalAlignment { get; set; }
[Parameter("Horizontal Alignment", Group = "Icon", DefaultValue = HorizontalAlignment.Right)]
public HorizontalAlignment IconHorizontalAlignment { get; set; }
public string GroupName { get; private set; }
public event Action<SyncObjectsInstance, ChartObjectsAddedEventArgs> ObjectsAdded;
public event Action<SyncObjectsInstance, ChartObjectsRemovedEventArgs> ObjectsRemoved;
public event Action<SyncObjectsInstance, ChartObjectsUpdatedEventArgs> ObjectsUpdated;
protected override void Initialize()
{
GroupName = CalculateGroupName();
if (IconVisible)
DrawStatusIcon();
if (Enabled)
{
Chart.ObjectsAdded += OnObjectsAdded;
Chart.ObjectsRemoved += OnObjectsRemoved;
Chart.ObjectsUpdated += OnObjectsUpdated;
Synchronizer.Instance.Register(this);
if (Enabled)
Timer.Start(Synchronizer.HeartbeatRate);
}
}
private void OnObjectsAdded(ChartObjectsAddedEventArgs args)
{
if (ObjectsAdded != null)
ObjectsAdded.Invoke(this, args);
}
private void OnObjectsRemoved(ChartObjectsRemovedEventArgs args)
{
if (ObjectsRemoved != null)
ObjectsRemoved.Invoke(this, args);
}
private void OnObjectsUpdated(ChartObjectsUpdatedEventArgs args)
{
if (ObjectsUpdated != null)
ObjectsUpdated.Invoke(this, args);
}
protected override void OnTimer()
{
Synchronizer.Instance.Heartbeat(this);
}
public override void Calculate(int index)
{
// do nothing
}
private string CalculateGroupName()
{
if (string.IsNullOrWhiteSpace(CustomGroup))
return SymbolName;
return CustomGroup;
}
private Color CalculateColor()
{
Color color = IconColor;
if (color != Color.Empty)
return color;
int hashCode = 0;
unchecked
{
for (var index = 0; index < GroupName.Length; index++)
{
hashCode ^= (GroupName[index] + index) * 46811987;
}
}
var hex = hashCode.ToString("X6");
if (hex.Length > 6)
hex = hex.Substring(hex.Length - 6);
return Color.FromHex(hex);
}
private void DrawStatusIcon()
{
var color = CalculateColor();
Chart
.DrawStaticText(
"_sync_group", Enabled ? Icons.EnabledIcon : Icons.DisabledIcon,
IconVerticalAlignment, IconHorizontalAlignment,
color)
.IsInteractive = false;
}
}
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.ObjectsAdded -= OnObjectsAdded;
expiredInstance.ObjectsRemoved -= OnObjectsRemoved;
expiredInstance.ObjectsUpdated -= OnObjectsUpdated;
_instanceHeartbeats.Remove(expiredInstance);
_instances[expiredInstance.GroupName].Remove(expiredInstance);
instance.Print(string.Format("Expired {0}", expiredInstance.GroupName));
}
}
}
private void OnObjectsAdded(SyncObjectsInstance sender, ChartObjectsAddedEventArgs args)
{
lock (Sync)
{
HashSet<SyncObjectsInstance> symbolInstances;
if (!_instances.TryGetValue(sender.GroupName, out symbolInstances))
return;
foreach (var instance in symbolInstances)
{
if (instance.Chart == args.Chart)
continue;
instance.BeginInvokeOnMainThread(() => RestoreObjects(instance, args.ChartObjects));
}
}
}
private void OnObjectsUpdated(SyncObjectsInstance sender, ChartObjectsUpdatedEventArgs args)
{
lock (Sync)
{
HashSet<SyncObjectsInstance> symbolInstances;
if (!_instances.TryGetValue(sender.GroupName, out symbolInstances))
return;
foreach (var instance in symbolInstances)
{
if (instance.Chart == args.Chart)
continue;
instance.BeginInvokeOnMainThread(() => RestoreObjects(instance, args.ChartObjects));
}
}
}
private void OnObjectsRemoved(SyncObjectsInstance sender, ChartObjectsRemovedEventArgs args)
{
lock (Sync)
{
HashSet<SyncObjectsInstance> symbolInstances;
if (!_instances.TryGetValue(sender.GroupName, out symbolInstances))
return;
var objectNames = args.ChartObjects.Select(c => c.Name).ToArray();
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.GroupName, out symbolInstances))
{
symbolInstances = new HashSet<SyncObjectsInstance>();
_instances.Add(sender.GroupName, symbolInstances);
}
var senderObjects = sender.Chart.Objects.ToArray();
foreach (var instance in symbolInstances)
instance.BeginInvokeOnMainThread(() => RestoreObjects(instance, senderObjects));
sender.ObjectsAdded += OnObjectsAdded;
sender.ObjectsRemoved += OnObjectsRemoved;
sender.ObjectsUpdated += OnObjectsUpdated;
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
Joined on 22.10.2019
- Distribution: Free
- Language: C#
- Trading platform: cTrader Automate
- File name: Sync Objects.algo
- Rating: 5
- Installs: 5735
- Modified: 13/10/2021 09:55
Comments
Hey, just to inform for me, it's working on every pair I tried. EU, XAU and XAG. My version is 5.0.28.
This is a fantastic indicator since I'm trading renko and I like to have 2 charts at the same time on screen.
It works well on cTrader 5.0.28 on Gold and doesn't work on EURUSD
It does not work on CTrader version 4.8.30!!!!
useless
not waork ctrader 4.8
It just works exactly as it says on the box.
Thank you Devman, very helpful in setting up my charts.
Hay Devman
Great job with the Sync Objects. love it
You asked for feedback and if there were any improvements we would find useful. Adding the rectangle and other shapes to the Sync Objects would be a great help. if you have the time that is
Thanks again. Marcus
Unfortunately the indicator stopped working on .Net6. It throws the following error:
nable to invoke target method in current thread. Use `BeginInvokeOnMainThread` method to prevent this error.
Any solution?
fcarabat says
"beware"
I wouldn't say that as this developer has selflessly coded this app for the community free of charge
Yes Its got a serious crashing problem and may have been since ctrader 4.1 the problem is more apparent but I'm sure he will look into it when he realises its become unstable.
Though do think this sync of charts functionality along with global crosshairs should be built directly into the core app as not having these stable features makes trading all the more harder on ctrader.
There have been few people to talk about these features on thins forum but its not an active community so the voting system for features to be considered doesn't work
Its a real shame because there are so many great ctrader features that are leaps ahead of old metatrader4/5 but its real competition is tradingview today
well, now is the time to take back my comment from the 23rd.. this indicator is unstable, and making my computer crash everytime i add the indicator. before that, i was getting an error in ctrader saying there is something wrong with this indicator and causing instability, it then removed the indicator from all charts automatically.
beware.
Hi Devman
I upgraded from the previous version sync objects 3.8 some months ago in the hope indicator would stop crashing but the same indicator still crashes with ctrader removing it from all charts on newer 4x version.
error dialogue box:
"Sync Objects" indicator causes serious performance issues and can lead to application freeze.
The indicator was removed from all charts
I am running a high end machine with clean Windows10 & ctrader 4.1 but suspect sync objects 4x can't handle running on multiple instruments looking on multiple timeframes?
The machine isn't short on resources so If there is a limitation of how many instances can be run, could that be increased or application enhanced to stop crashing.
OR could this essential feature be part of the ctrader core given its essential when running multiple charts.
Thanks
much appreciated
thank you for this indicator! finally, one that works!
Hello, can it persist beyond workspace?
Absolute GOLD my friend!
Hi. First, a great indicator. Thank you for sharing it.
How can I solve the following problem?
Example: I'm using H1, M30, M15, M5, M1.
If I draw a line in H1, it will be transferred to all subordinate charts and TFs. That is correct.
But, when I draw a line in M15, it is also drawn in M5 and in M1, but it shouldn't be drawn in M30 and in H1.
So too many small lines collect in M30 and H1 and you no longer have a good view in the higher-level chart TFs.
I tried to create a group with several synch indicators like in the following example. But if I place more like one synch indicator on a chart, then the cTrader freezes and crashes.
H1 = a
M30 = a, b
M15 = a, b, c
M5 = a, b, c, d
M1 = a, b, c, d, e
Looking forward to your answers.
Dear Devman,
This is excellent! Thank you for such a fantastic tool!
For people who failed to get this to work, it may be that they are unfamiliar with cTrader's requirement that the indicator be put on each chart that the user wants the objects to appear upon.
In other applications, such as JForex, there are solutions for this that run at the system level and apply across all open charts. Also, in TradingView, there is a selection that applies across charts. With cTrader, the template needs to be applied to each individual chart.
Question:
Is there a way to make the objects visible on only certain timeframes?
Thanks!
Mike T
North Carolina
This is almost perfect, but theres a HUDGE problem with this unfortunatly.
So I like having one monitor dedicated to all the currency pairs I trade, so i get a general sense of what everythings doing. My second monitor is where I do all my technical analysis. The drawings on that monitor (where I do my drawings) only appear on my second monitor (where I have my diffrent currency pairs) when I select that specific currency pair. So in other words the drawings only appear on my second monitor when i select that specific symbol from my watchlist. Basically i have 6 charts with 6 diffrent symbols and id like to see all my drawings at once rather than only seing the drawings for the selected symbol.
That being said Id still like to thank you for putting in the work for this. Really its much appreciated <3
Happy Trading :)
Hello,
it´s a good idea.
I tried to use this. But it´s not possible.
I use cTrader 4.0 with Roboforex.
I start plugin...I open two charts (XAUUSD)... I create a trendline... cTrader freezes...why?
I tried th previous (3.8) also...no chance
Could you help, please?!
Seems like their is an update from you, I'll have to try and see if it works for me, thanks!
I'm surprised this isn't built into cTRADER by default, many other trading packages have it... hhmmmm
Try previous version. Link in description.
Cant get this to work. Added to the charts but cannot sync drawings. Any ideas ? Thanks.
THAT"S AMAZINGGGGG
THNAKS!!
That's great!!!
Thanks!!
Update to my previous comment. It still has the capability of being an excellent indicator and definitely fills a much needed function neglected by cTrader. However, there is a problem with it. When I have 2 charts open of the same pair it will not draw the indicator on both charts unless I first change the symbol on both charts to something different and change back again. Only after doing this will it sync objects between charts of the same pair. Not sure why it is doing this but it is very irritating and wastes time. Until this glitch is fixed there is not much use to it as an indicator since you may as well just draw the object you want on both charts separately to start with. Hopefully this will be fixed soon.
Excellent indicator. Thanks for adding this capability for something the people at cTrader to lazy to
incorporate amongst.
Thanks a lot for filling this void in cTrader.
This doesnt work!
Enormemente agradecido por este indicador. simplifica mucho la tarea de graficar objetos.
When i draw something on Audusd Its also Duplicate OBject in Another symbol how to fix this ?
I have been experiencing a problem on the Ctrader platform lately and have found that the Sync Objects indicator may be the cause.
Occasionally, when I draw any object (trend line, for example) and try to duplicate it, the duplicate object keeps flashing and disappearing. From there I cannot select the new object. The problem only resolves after restarting the platform. Has anyone else been through this?
Yes, it is. I will improve it in the near future. Stay tuned for updates in the community group: https://t.me/cTraderCommunity
Is it possible to alter this indicator to work on all workspaces?
It's great indicator.Thank you so much.
I have a suggestion! It's possible to add parameters to disable certain objects to be copied? In my case, I use a cbot to calculate lot size. So when I started your indicator, copy the horizontal lines from the cbot. I would love to disable some objects to be copied in this case horizontal lines.