Description
With this indicator you can show a synchronized crosshair on multiple cTrader charts on same points.
Features
- Fast and easy to use like a built-in feature of platform
- No setup or configuration is needed
- It can work on different symbols not just same symbol charts
How to use it?
- Attach the indicator on those charts that you want to use synchronized crosshair
- Press Ctrl and the synchronized crosshair will appear
- While you press Ctrl button move your mouse cursor
- Leave Ctrl button and move the mouse cursor again, it will display the data box on a corner of your charts
This indicator is open source, feel free to improve it: spotware/Synchronized-Crosshair: A cTrader desktop indicator that allows you to synchronize the chart crosshair (github.com)
using cAlgo.API;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class SynchronizedCrosshair : Indicator
{
private static ConcurrentDictionary<string, IndicatorInstanceContainer> _indicatorInstances = new ConcurrentDictionary<string, IndicatorInstanceContainer>();
private string _chartKey;
private string _horizontalLineObjectName;
private string _verticalLineObjectName;
private string _lineObjectName;
private ChartHorizontalLine _horizontalLine;
private ChartVerticalLine _verticalLine;
private ChartTrendLine _line;
private DateTime _lastMouseLocationTime;
private double _lastMouseLocationPrice;
private bool _isActive;
private DateTime _lastMoveTime;
private DataBoxControl _dataBoxControl;
[Parameter("Mode", DefaultValue = Mode.All, Group = "General")]
public Mode Mode { get; set; }
[Parameter("Horizontal Alignment", DefaultValue = HorizontalAlignment.Right, Group = "Data Box")]
public HorizontalAlignment DataBoxHorizontalAlignment { get; set; }
[Parameter("Vertical Alignment", DefaultValue = VerticalAlignment.Bottom, Group = "Data Box")]
public VerticalAlignment DataBoxVerticalAlignment { get; set; }
[Parameter("Opacity", DefaultValue = 0.8, MinValue = 0, MaxValue = 1, Group = "Data Box")]
public double DataBoxOpacity { get; set; }
[Parameter("Margin", DefaultValue = 1, MinValue = 0, Group = "Data Box")]
public double DataBoxMargin { get; set; }
protected override void Initialize()
{
var chartObjectNamePrefix = string.Format("SynchronizedCrosshair_{0}_{1}_{2}", SymbolName, TimeFrame, Chart.ChartType);
_chartKey = string.Format("{0}_{1}", chartObjectNamePrefix, Server.Time.Ticks);
foreach (var chartObject in Chart.Objects)
{
if (chartObject.Name.StartsWith(chartObjectNamePrefix, StringComparison.Ordinal))
{
Chart.RemoveObject(chartObject.Name);
}
}
_horizontalLineObjectName = string.Format("{0}_Horizontal", _chartKey);
_verticalLineObjectName = string.Format("{0}_Vertical", _chartKey);
_lineObjectName = string.Format("{0}_Line", _chartKey);
_indicatorInstances.AddOrUpdate(_chartKey, new IndicatorInstanceContainer(this), (key, value) => new IndicatorInstanceContainer(this));
_dataBoxControl = new DataBoxControl
{
HorizontalAlignment = DataBoxHorizontalAlignment,
VerticalAlignment = DataBoxVerticalAlignment,
Opacity = DataBoxOpacity,
IsVisible = false,
Margin = DataBoxMargin
};
Chart.AddControl(_dataBoxControl);
Chart.MouseMove += Chart_MouseMove;
Chart.MouseDown += Chart_MouseDown;
}
public override void Calculate(int index)
{
}
public void OnMouseDown()
{
_isActive = false;
if (_horizontalLine != null)
{
Chart.RemoveObject(_horizontalLineObjectName);
_horizontalLine = null;
}
if (_verticalLine != null)
{
Chart.RemoveObject(_verticalLineObjectName);
_verticalLine = null;
}
if (_line != null)
{
Chart.RemoveObject(_lineObjectName);
_line = null;
}
_dataBoxControl.IsVisible = false;
}
public void ShowCrosshair(DateTime timeValue, double yValue, bool ctrlKey)
{
if (_isActive && ctrlKey == false)
{
if (_line == null)
{
_line = Chart.DrawTrendLine(_lineObjectName, _lastMouseLocationTime, _lastMouseLocationPrice, timeValue, yValue, Chart.ColorSettings.ForegroundColor);
}
else
{
_line.Time2 = timeValue;
_line.Y2 = yValue;
}
var timeValueOffset = new DateTimeOffset(timeValue, TimeSpan.FromSeconds(0));
_dataBoxControl.Time = timeValueOffset.ToOffset(Application.UserTimeOffset).ToString("dd/MM/yyyy HH:mm");
_dataBoxControl.Pips = Math.Round(GetInPips(Math.Abs(_line.Y2 - _line.Y1)), 2).ToString();
_dataBoxControl.Periods = Math.Abs(Bars.OpenTimes.GetIndexByTime(_line.Time1) - Bars.OpenTimes.GetIndexByTime(_line.Time2)).ToString();
_dataBoxControl.Price = Math.Round(yValue, Symbol.Digits).ToString();
_dataBoxControl.IsVisible = true;
}
else if (ctrlKey)
{
_isActive = true;
if (_horizontalLine == null)
{
_horizontalLine = Chart.DrawHorizontalLine(_horizontalLineObjectName, yValue, Chart.ColorSettings.ForegroundColor);
}
else
{
_horizontalLine.Y = yValue;
}
if (_verticalLine == null)
{
_verticalLine = Chart.DrawVerticalLine(_verticalLineObjectName, timeValue, Chart.ColorSettings.ForegroundColor);
}
else
{
_verticalLine.Time = timeValue;
}
_horizontalLine.IsInteractive = true;
_verticalLine.IsInteractive = true;
_horizontalLine.IsLocked = true;
_verticalLine.IsLocked = true;
}
_lastMouseLocationTime = timeValue;
_lastMouseLocationPrice = yValue;
}
private double GetInPips(double price)
{
return price * (Symbol.TickSize / Symbol.PipSize * Math.Pow(10, Symbol.Digits));
}
private void Chart_MouseDown(ChartMouseEventArgs obj)
{
if (_isActive == false)
return;
OnMouseDown();
TriggerOnMouseDownOnCharts();
}
private void Chart_MouseMove(ChartMouseEventArgs obj)
{
if (Server.TimeInUtc - _lastMoveTime < TimeSpan.FromMilliseconds(1))
return;
_lastMoveTime = Server.TimeInUtc;
ShowCrosshair(obj.TimeValue, obj.YValue, obj.CtrlKey);
ShowCrosshairOnCharts(obj);
}
private List<KeyValuePair<string, SynchronizedCrosshair>> GetIndicators()
{
Func<SynchronizedCrosshair, bool> predicate;
switch (Mode)
{
case Mode.Symbol:
predicate = indicator => indicator.SymbolName.Equals(SymbolName, StringComparison.Ordinal);
break;
case Mode.TimeFrame:
predicate = indicator => indicator.TimeFrame == TimeFrame;
break;
default:
predicate = null;
break;
}
var result = new List<KeyValuePair<string, SynchronizedCrosshair>>(_indicatorInstances.Values.Count);
foreach (var indicatorContianer in _indicatorInstances)
{
SynchronizedCrosshair indicator;
if (indicatorContianer.Value.GetIndicator(out indicator) == false || indicator == this || (predicate != null && predicate(indicator) == false))
continue;
result.Add(new KeyValuePair<string, SynchronizedCrosshair>(indicatorContianer.Key, indicator));
}
return result;
}
private void ShowCrosshairOnCharts(ChartMouseEventArgs mouseEventArgs)
{
var indicators = GetIndicators();
var topToBottomDiff = Chart.TopY - Chart.BottomY;
var diff = mouseEventArgs.YValue - Chart.BottomY;
var percent = diff / topToBottomDiff;
foreach (var indicator in indicators)
{
try
{
double yValue;
if (indicator.Value.SymbolName.Equals(SymbolName, StringComparison.Ordinal))
{
yValue = mouseEventArgs.YValue;
}
else
{
var indicatorChartTopToBottomDiff = indicator.Value.Chart.TopY - indicator.Value.Chart.BottomY;
yValue = indicator.Value.Chart.BottomY + (indicatorChartTopToBottomDiff * percent);
}
indicator.Value.BeginInvokeOnMainThread(() => indicator.Value.ShowCrosshair(mouseEventArgs.TimeValue, yValue, mouseEventArgs.CtrlKey));
}
catch (Exception)
{
IndicatorInstanceContainer instanceContainer;
_indicatorInstances.TryRemove(indicator.Key, out instanceContainer);
}
}
}
private void TriggerOnMouseDownOnCharts()
{
var indicators = GetIndicators();
foreach (var indicator in indicators)
{
try
{
indicator.Value.BeginInvokeOnMainThread(() => indicator.Value.OnMouseDown());
}
catch (Exception)
{
IndicatorInstanceContainer instanceContainer;
_indicatorInstances.TryRemove(indicator.Key, out instanceContainer);
}
}
}
}
public enum Mode
{
All,
TimeFrame,
Symbol
}
public class IndicatorInstanceContainer
{
private readonly WeakReference _indicatorWeakReference;
public IndicatorInstanceContainer(SynchronizedCrosshair indicator)
{
_indicatorWeakReference = new WeakReference(indicator);
}
public DateTime? TimeToScroll { get; set; }
public bool GetIndicator(out SynchronizedCrosshair indicator)
{
if (_indicatorWeakReference.IsAlive)
{
indicator = (SynchronizedCrosshair)_indicatorWeakReference.Target;
return true;
}
indicator = null;
return false;
}
}
public class DataBoxControl : CustomControl
{
private readonly Grid _panel = new Grid(4, 2);
private readonly TextBox _timeTextBox = new TextBox();
private readonly TextBox _pipsTextBox = new TextBox();
private readonly TextBox _periodsTextBox = new TextBox();
private readonly TextBox _priceTextBox = new TextBox();
public DataBoxControl()
{
_panel.AddChild(new TextBox
{
Text = "Time"
}, 0, 0);
_panel.AddChild(_timeTextBox, 0, 1);
_panel.AddChild(new TextBox
{
Text = "Pips"
}, 1, 0);
_panel.AddChild(_pipsTextBox, 1, 1);
_panel.AddChild(new TextBox
{
Text = "Periods"
}, 2, 0);
_panel.AddChild(_periodsTextBox, 2, 1);
_panel.AddChild(new TextBox
{
Text = "Price"
}, 3, 0);
_panel.AddChild(_priceTextBox, 3, 1);
AddChild(_panel);
}
public string Time
{
get { return _timeTextBox.Text; }
set { _timeTextBox.Text = value; }
}
public string Pips
{
get { return _pipsTextBox.Text; }
set { _pipsTextBox.Text = value; }
}
public string Periods
{
get { return _periodsTextBox.Text; }
set { _periodsTextBox.Text = value; }
}
public string Price
{
get { return _priceTextBox.Text; }
set { _priceTextBox.Text = value; }
}
}
}
Spotware
Joined on 23.09.2013
- Distribution: Free
- Language: C#
- Trading platform: cTrader Automate
- File name: Synchronized Crosshair.algo
- Rating: 0
- Installs: 1943
- Modified: 25/04/2022 10:03
Comments
Thanks, amusleh it works.
Though the highlight on vertical and horizontal lines is a bit annoying.
I believe there is no way to get rid of the highlights and keep the time value displaying, simultaneously for the moment. Anyway, the solution is good.
Hello,
This is a great tool, I've made some changes to it for my own use. But, I don't know how to make the vertical line always show the time tag, like pressing down the mouse middle wheel, the crosshair will show the time tag at X-axis always?
Please advise,
Thanks.
Hi,
could you please add a possibility to change the CTRL key? CTRL is also used for copying objects so if I want to duplicate for example horizontal line and press CTRL is messes together with the crosshair function.
Thank you.