Category Other  Published on 25/04/2022

Synchronized Crosshair

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?

  1. Attach the indicator on those charts that you want to use synchronized crosshair
  2. Press Ctrl and the synchronized crosshair will appear
  3. While you press Ctrl button move your mouse cursor
  4. 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's avatar
Spotware

Joined on 23.09.2013

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: Synchronized Crosshair.algo
  • Rating: 0
  • Installs: 1710
Comments
Log in to add a comment.
RY
ryosukeh2100 · 1 year ago

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. 

 

Capt.Z-Fort.Builder's avatar
Capt.Z-Fort.Builder · 2 years ago

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.

Capt.Z-Fort.Builder's avatar
Capt.Z-Fort.Builder · 2 years ago

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.