ScrollXTo does not scroll to date
ScrollXTo does not scroll to date
09 Mar 2022, 20:11
Hi
I'm developing an indicator that scroll a chart by the date clicked on a different chart (Code attached).
The problem is that ScrollXTo does not scroll to the correct date.
Instructions: Place indicator on 2 charts of the same symbol and click at one of them at a specific date.
What suppose to happen is that the other chart will scroll to the clicked date of the first, however is it scrolled to a different date, although the clicked date is recognized ok.
I even tried to use ScrollXBy instead of ScrollXTo but there the problem is that the Chart.FirstVisibleBarIndex and Chart.LastVisibleBarIndex values are not updated after the call to ScrollXBy. (the code which is commented on ScrollChart).
Thanks
using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;
using cAlgo.Indicators;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.IsraelStandardTime, AccessRights = AccessRights.FileSystem)]
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 = null;
public static Synchronizer Instance
{
get
{
if (_instance != null)
return _instance;
lock (_sync)
{
if (_instance != null)
return _instance;
_instance = new Synchronizer();
}
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)
{
// instance.Print("Register");
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.MouseUp -= OnMouseUp;
_instanceHeartbeats.Remove(expiredInstance);
_instances[expiredInstance.SymbolName].Remove(expiredInstance);
}
}
}
private void OnMouseUp(ChartMouseEventArgs 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(() => ScrollChart(instance, args.TimeValue));
}
}
}
private void ScrollChart(SyncObjectsInstance targetInstance, DateTime targetTime)
{
p("ScrollChart " + targetTime);
targetInstance.Chart.ScrollXTo(targetTime);
//int middleBar = targetInstance.Chart.FirstVisibleBarIndex + ((targetInstance.Chart.LastVisibleBarIndex - targetInstance.Chart.FirstVisibleBarIndex) / 2);
//DateTime midDate = targetInstance.Bars.OpenTimes[middleBar];
//bool crossDate = false;
//int crossDir = midDate < targetTime ? 1 : -1;
//while (midDate != targetTime && !crossDate)
//{
// p("midDate= " + midDate + " targetTime= " + targetTime + " middleBar= " + middleBar + " midDate= " + midDate);
// p("FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " LastVisibleBarIndex= " + targetInstance.Chart.LastVisibleBarIndex);
// targetInstance.Chart.ScrollXBy(crossDir);
// middleBar = targetInstance.Chart.FirstVisibleBarIndex + ((targetInstance.Chart.LastVisibleBarIndex - targetInstance.Chart.FirstVisibleBarIndex) / 2);
// midDate = targetInstance.Bars.OpenTimes[middleBar];
// crossDate = crossDir == 1 ? midDate > targetTime : midDate < targetTime;
//}
}
private void p(string text)
{
File.AppendAllText("d:\\temp\\a.log", text + Environment.NewLine);
}
//p("a targetTime= " + targetTime +
// " targetInstance.Chart.TimeFrame.ToString()= " + targetInstance.Chart.TimeFrame.ToString() +
// " FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex +
// );
//targetInstance.Chart.ScrollXBy(100);
//targetInstance.Chart.ScrollXTo(targetTime);
//Thread.Sleep(300);
//targetInstance.Chart.ScrollXBy((targetInstance.Chart.FirstVisibleBarIndex - targetInstance.Chart.LastVisibleBarIndex) / 2);
//p("diff= " + ((targetInstance.Chart.FirstVisibleBarIndex - targetInstance.Chart.LastVisibleBarIndex) / 2));
//p("f" + " TimeFrame= " + targetInstance.Chart.TimeFrame + " args.targetTime= " + targetTime + " LastVisibleBarIndex= " +
// targetInstance.Chart.LastVisibleBarIndex + " lastTime= " + targetInstance.Bars.OpenTimes[targetInstance.Chart.LastVisibleBarIndex] +
// " FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " firstTime= " +
// targetInstance.Bars.OpenTimes[targetInstance.Chart.FirstVisibleBarIndex] +
// " middleIdx= " + middleBar +
// " middleDate= " + targetInstance.Bars.OpenTimes[middleBar] +
// "-2Bars= " + ((targetInstance.Chart.LastVisibleBarIndex - targetInstance.Chart.FirstVisibleBarIndex) / -2).ToString()
// );
//p("crossDir= " + crossDir);
//p("a FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " LastVisibleBarIndex= " + targetInstance.Chart.LastVisibleBarIndex);
//for (int i = 0; i < 100; i++)
//{
// //p("FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " LastVisibleBarIndex= " + targetInstance.Chart.LastVisibleBarIndex);
// targetInstance.Chart.ScrollXBy(-1);
// targetInstance.Chart.
// //Thread.Sleep(300);
//}
//p("b FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " LastVisibleBarIndex= " + targetInstance.Chart.LastVisibleBarIndex);
private void Restore(SyncObjectsInstance sender)
{
HashSet<SyncObjectsInstance> symbolInstances;
if (!_instances.TryGetValue(sender.SymbolName, out symbolInstances))
{
symbolInstances = new HashSet<SyncObjectsInstance>();
_instances.Add(sender.SymbolName, symbolInstances);
}
sender.Chart.MouseUp += OnMouseUp;
symbolInstances.Add(sender);
_instanceHeartbeats[sender] = DateTime.Now;
}
}
}
Replies
eynt
10 Mar 2022, 16:10
RE:
Hello and thank you for your reply.
I was able to solve the issue by making a few small changes in the ScrollChart method. I tried to apply it to your version of the indicator however there were sync issues when 2 charts are changed at the same time so I let it go. I am uploading my fixed version of the indicator, feel free to use/improve/share it with the community. If there are any updates or improvements on the subject please let me know.
using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;
using cAlgo.Indicators;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.IsraelStandardTime, AccessRights = AccessRights.FileSystem)]
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 = null;
public static Synchronizer Instance
{
get
{
if (_instance != null)
return _instance;
lock (_sync)
{
if (_instance != null)
return _instance;
_instance = new Synchronizer();
}
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)
{
// instance.Print("Register");
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.MouseUp -= OnMouseUp;
_instanceHeartbeats.Remove(expiredInstance);
_instances[expiredInstance.SymbolName].Remove(expiredInstance);
}
}
}
private void OnMouseUp(ChartMouseEventArgs 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(() => ScrollChart(instance, args.TimeValue));
}
}
}
private void ScrollChart(SyncObjectsInstance targetInstance, DateTime targetTime)
{
//p("ScrollChart " + targetTime);
//targetInstance.Chart.ScrollXTo(targetTime);
int leftBar = targetInstance.Chart.FirstVisibleBarIndex;
int rightBar = targetInstance.Chart.LastVisibleBarIndex;
int middleBar = leftBar + ((rightBar - leftBar) / 2);
DateTime midDate = targetInstance.Bars.OpenTimes[middleBar];
bool crossDate = false;
int crossDir = midDate < targetTime ? 1 : -1;
int scrolledBars = 0;
while (midDate != targetTime && !crossDate)
{
//p("midDate= " + midDate + " targetTime= " + targetTime + " middleBar= " + middleBar + " midDate= " + midDate);
//p("FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " LastVisibleBarIndex= " + targetInstance.Chart.LastVisibleBarIndex);
//targetInstance.Chart.ScrollXBy(crossDir);
leftBar += crossDir;
rightBar += crossDir;
scrolledBars += crossDir;
//middleBar = targetInstance.Chart.FirstVisibleBarIndex + ((targetInstance.Chart.LastVisibleBarIndex - targetInstance.Chart.FirstVisibleBarIndex) / 2);
middleBar = leftBar + ((rightBar - leftBar) / 2);
midDate = targetInstance.Bars.OpenTimes[middleBar];
crossDate = crossDir == 1 ? midDate > targetTime : midDate < targetTime;
}
targetInstance.Chart.ScrollXBy(scrolledBars);
}
private void p(string text)
{
File.AppendAllText("d:\\temp\\a.log", text + Environment.NewLine);
}
//p("a targetTime= " + targetTime +
// " targetInstance.Chart.TimeFrame.ToString()= " + targetInstance.Chart.TimeFrame.ToString() +
// " FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex +
// );
//targetInstance.Chart.ScrollXBy(100);
//targetInstance.Chart.ScrollXTo(targetTime);
//Thread.Sleep(300);
//targetInstance.Chart.ScrollXBy((targetInstance.Chart.FirstVisibleBarIndex - targetInstance.Chart.LastVisibleBarIndex) / 2);
//p("diff= " + ((targetInstance.Chart.FirstVisibleBarIndex - targetInstance.Chart.LastVisibleBarIndex) / 2));
//p("f" + " TimeFrame= " + targetInstance.Chart.TimeFrame + " args.targetTime= " + targetTime + " LastVisibleBarIndex= " +
// targetInstance.Chart.LastVisibleBarIndex + " lastTime= " + targetInstance.Bars.OpenTimes[targetInstance.Chart.LastVisibleBarIndex] +
// " FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " firstTime= " +
// targetInstance.Bars.OpenTimes[targetInstance.Chart.FirstVisibleBarIndex] +
// " middleIdx= " + middleBar +
// " middleDate= " + targetInstance.Bars.OpenTimes[middleBar] +
// "-2Bars= " + ((targetInstance.Chart.LastVisibleBarIndex - targetInstance.Chart.FirstVisibleBarIndex) / -2).ToString()
// );
//p("crossDir= " + crossDir);
//p("a FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " LastVisibleBarIndex= " + targetInstance.Chart.LastVisibleBarIndex);
//for (int i = 0; i < 100; i++)
//{
// //p("FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " LastVisibleBarIndex= " + targetInstance.Chart.LastVisibleBarIndex);
// targetInstance.Chart.ScrollXBy(-1);
// targetInstance.Chart.
// //Thread.Sleep(300);
//}
//p("b FirstVisibleBarIndex= " + targetInstance.Chart.FirstVisibleBarIndex + " LastVisibleBarIndex= " + targetInstance.Chart.LastVisibleBarIndex);
private void Restore(SyncObjectsInstance sender)
{
HashSet<SyncObjectsInstance> symbolInstances;
if (!_instances.TryGetValue(sender.SymbolName, out symbolInstances))
{
symbolInstances = new HashSet<SyncObjectsInstance>();
_instances.Add(sender.SymbolName, symbolInstances);
}
sender.Chart.MouseUp += OnMouseUp;
symbolInstances.Add(sender);
_instanceHeartbeats[sender] = DateTime.Now;
}
}
}
@eynt
amusleh
10 Mar 2022, 10:00 ( Updated at: 10 Mar 2022, 10:05 )
Hi,
First of all this is great idea! we might develop an indicator for this.
The issue with your code is you take the mouse up event time value and you use it as target for another chart.
The time value of mouse up event is not related to scrolling at all, it's the time point where the mouse cursor was.
To solve the issue you can use the chart scroll changed event instead, ex:
You can improve it by using Bars delta of scroll changed event args instead of first visible bar on the chart.
Also there is the issue of simultaneous scrolling which can be solved if you work little bit on it.
@amusleh