Category Other  Published on 03/02/2021

Favorites

Description

Keep you favorite symbols and periods at your fingertips

Customizable buttons to switch charts symbol and period fast.

For symbols list you need to specify the name of a watchlist from where symbols will be taken.
Periods specified as a comma separated values.

Customization:

  • Buttons size
  • Font size
  • Button label on one or two lines
  • Position top or bottom

 

WORKS ONLY ON NEW CTRADER 4.0

 

 

Hide buttons to keep chart clean

 


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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class Favorites : Indicator
    {
        [Parameter("Watchlist name", DefaultValue = "My Watchlist", Group = "Symbols")]
        public string WatchlistName { get; set; }

        [Parameter("Periods", DefaultValue = "m1,m5,m15,m30,h1,h4,D1,W1", Group = "Periods")]
        public string Timeframes { get; set; }

        [Parameter("Position", DefaultValue = "0", Group = "Position")]
        public Position Position { get; set; }

        [Parameter("Buttons width", DefaultValue = "21", Group = "Size")]
        public int ButtonsWidth { get; set; }

        [Parameter("Buttons height", DefaultValue = "21", Group = "Size")]
        public int ButtonsHeight { get; set; }

        [Parameter("Font size for symbols", DefaultValue = "7", Group = "Size")]
        public int SymbolsFontSize { get; set; }

        [Parameter("Font size 1 for periods", DefaultValue = "10", Group = "Size")]
        public int TimeframeFontSize1 { get; set; }

        [Parameter("Font size 2 for periods", DefaultValue = "7", Group = "Size")]
        public int TimeframeFontSize2 { get; set; }

        [Parameter("Split symbols", DefaultValue = true, Group = "Split")]
        public bool SplitSymbolNames { get; set; }

        [Parameter("Split periods", DefaultValue = true, Group = "Split")]
        public bool SplitTimeframes { get; set; }

        private Dictionary<Button, string> ButtonsSymbolsDictionary;
        private Dictionary<Button, TimeFrame> ButtonsTimeframesDictionary;
        private Panel SymbolsPanel;

        protected override void Initialize()
        {
            if (Application.Version < new Version(4, 0))
            {
                var currentVersion = new Version(Application.Version.Major, Application.Version.Minor);
                var errorMessage = string.Format("Favorites indicators works on cTrader 4.0 and above.\nCurrent cTrader version is {0}.", currentVersion);
                Chart.AddControl(new ErrorMessage(errorMessage));
                return;
            }

            var rootPanel = new Grid(2, 1) 
            {
                HorizontalAlignment = HorizontalAlignment.Left
            };


            Chart.AddControl(rootPanel);

            SymbolsPanel = new WrapPanel 
            {
                VerticalAlignment = Position == Position.Top ? VerticalAlignment.Top : VerticalAlignment.Bottom,
                HorizontalAlignment = HorizontalAlignment.Left,
                Orientation = Orientation.Vertical
            };
            var timeframesPanel = new WrapPanel 
            {
                VerticalAlignment = VerticalAlignment.Top,
                HorizontalAlignment = HorizontalAlignment.Left,
                Orientation = Orientation.Horizontal,
                Margin = Position == Position.Top ? "0 3 0 0" : "0 0 0 3"
            };

            var symbolRowIndex = Position == Position.Top ? 1 : 0;
            var timeframesRowIndex = Position == Position.Top ? 0 : 1;

            rootPanel.Rows[timeframesRowIndex].SetHeightToAuto();

            rootPanel.AddChild(SymbolsPanel, symbolRowIndex, 0);
            rootPanel.AddChild(timeframesPanel, timeframesRowIndex, 0);

            var toggleButton = new ToggleButton 
            {
                Width = ButtonsWidth,
                Height = ButtonsHeight,
                Margin = 2,
                Padding = 1,
                Text = "☰"
            };
            timeframesPanel.AddChild(toggleButton);
            toggleButton.Click += ToggleButton_Click;

            AddSymbolsButtons(SymbolsPanel);
            AddTimeframesButtons(timeframesPanel);
        }

        private void ToggleButton_Click(ToggleButtonEventArgs obj)
        {
            var show = !obj.ToggleButton.IsChecked;
            foreach (var button in ButtonsSymbolsDictionary.Keys)
            {
                button.IsVisible = show;
                button.IsHitTestVisible = show;
            }
            foreach (var button in ButtonsTimeframesDictionary.Keys)
            {
                button.IsVisible = show;
                button.IsHitTestVisible = show;
            }
        }

        private void AddSymbolsButtons(Panel panel)
        {
            var watchlist = Watchlists.FirstOrDefault(w => w.Name == WatchlistName);
            var symbolNames = watchlist == null ? new string[0] 
            {
                            } : watchlist.SymbolNames.ToArray();

            ButtonsSymbolsDictionary = new Dictionary<Button, string>();
            foreach (var symbolName in symbolNames)
            {
                AddSymbolButton(panel, symbolName);
            }

            Watchlists.WatchlistSymbolAdded += Watchlists_WatchlistSymbolAdded;
            Watchlists.WatchlistSymbolRemoved += Watchlists_WatchlistSymbolRemoved;
        }

        private void AddSymbolButton(Panel panel, string symbolName)
        {
            string buttonText;
            if (SplitSymbolNames)
            {
                var symbolNameSplitted = SplitSymbolForTwoLines(symbolName);
                var firstLine = symbolNameSplitted.Item1;
                var secondLine = symbolNameSplitted.Item2;
                buttonText = firstLine + "\n" + secondLine;
            }
            else
            {
                buttonText = symbolName;
            }

            var button = new Button 
            {
                Width = ButtonsWidth,
                Height = ButtonsHeight,
                Margin = 2,
                Text = buttonText,
                FontSize = SymbolsFontSize,
                Padding = 1,
                IsEnabled = symbolName != SymbolName
            };
            ButtonsSymbolsDictionary[button] = symbolName;
            button.Click += SymbolButtonClicked;
            panel.AddChild(button);
        }

        private void Watchlists_WatchlistSymbolAdded(WatchlistSymbolAddedEventArgs obj)
        {
            if (obj.Watchlist.Name != WatchlistName)
                return;

            AddSymbolButton(SymbolsPanel, obj.SymbolName);
        }

        private void Watchlists_WatchlistSymbolRemoved(WatchlistSymbolRemovedEventArgs obj)
        {
            var button = ButtonsSymbolsDictionary.FirstOrDefault(p => p.Value == obj.SymbolName).Key;
            if (button != null)
            {
                button.Click -= SymbolButtonClicked;
                SymbolsPanel.RemoveChild(button);
                ButtonsSymbolsDictionary.Remove(button);
            }
        }

        private void AddTimeframesButtons(Panel panel)
        {
            ButtonsTimeframesDictionary = new Dictionary<Button, TimeFrame>();
            var timeframes = ParseTimeframe();

            foreach (var timeframe in timeframes)
            {
                var button = new Button 
                {
                    Width = ButtonsWidth,
                    Height = ButtonsHeight,
                    Margin = 2,
                    Padding = 1,
                    IsEnabled = TimeFrame != timeframe
                };

                if (SplitTimeframes)
                {
                    var timeframeNameSplitted = SplitTimeframeForTwoLines(timeframe);
                    var typeText = timeframeNameSplitted.Item1;
                    var periodText = timeframeNameSplitted.Item2;

                    var typeLabel = new TextBlock 
                    {
                        Text = typeText,
                        FontSize = TimeframeFontSize1,
                        Margin = "3 0 0 0"
                    };
                    var periodLabel = new TextBlock 
                    {
                        Text = periodText,
                        FontSize = TimeframeFontSize2,
                        Margin = "0 0 3 0",
                        HorizontalAlignment = HorizontalAlignment.Right
                    };

                    var grid = new Grid(2, 1) 
                    {
                        Width = 19
                    };
                    grid.AddChild(typeLabel, 0, 0);
                    grid.AddChild(periodLabel, 1, 0);

                    button.Content = grid;
                    button.VerticalContentAlignment = VerticalAlignment.Stretch;
                    button.HorizontalContentAlignment = HorizontalAlignment.Stretch;
                }
                else
                {
                    button.Text = timeframe.ShortName;
                    button.FontSize = TimeframeFontSize1;
                }

                ButtonsTimeframesDictionary[button] = timeframe;
                button.Click += TimeframeButton_Clicked;
                panel.AddChild(button);
            }
        }

        private void TimeframeButton_Clicked(ButtonClickEventArgs obj)
        {
            var timeframe = ButtonsTimeframesDictionary[obj.Button];
            Chart.TryChangeTimeFrame(timeframe);
        }

        private void SymbolButtonClicked(ButtonClickEventArgs obj)
        {
            var symbolName = ButtonsSymbolsDictionary[obj.Button];
            Chart.TryChangeTimeFrameAndSymbol(TimeFrame, symbolName);
        }

        private IEnumerable<TimeFrame> ParseTimeframe()
        {
            foreach (var t in Timeframes.Split(',').Select(t => t.Trim()))
            {
                TimeFrame typed;

                if (TimeFrame.TryParse(t, out typed))
                {
                    yield return typed;
                }
            }
        }

        private Tuple<string, string> SplitSymbolForTwoLines(string symbolName)
        {
            string firstLine;
            string secondLine;

            if (symbolName.Length <= 3)
            {
                firstLine = symbolName;
                secondLine = "";
            }
            else if (symbolName.Length == 7 && symbolName[3] == '/')
            {
                firstLine = symbolName.Substring(0, 3);
                secondLine = symbolName.Substring(4);
            }
            else
            {
                var len = symbolName.Length / 2;
                firstLine = symbolName.Substring(0, len);
                secondLine = symbolName.Substring(len);
            }
            return new Tuple<string, string>(firstLine, secondLine);
        }

        private Tuple<string, string> SplitTimeframeForTwoLines(TimeFrame timeframe)
        {
            var shortName = timeframe.ShortName;

            var typeText = Regex.Match(shortName, "[a-zA-Z]+").Value;
            var periodText = Regex.Match(shortName, "\\d+").Value;
            periodText = Regex.Replace(periodText, "0{3}$", "k");
            if (Regex.Match(periodText, "\\d500$").Success)
                periodText = Regex.Replace(periodText, "500$", ".5k");
            return new Tuple<string, string>(typeText, periodText);
        }

        public override void Calculate(int index)
        {
        }
    }

    public class ErrorMessage : CustomControl
    {

        public ErrorMessage(string text)
        {
            VerticalAlignment = VerticalAlignment.Center;
            HorizontalAlignment = HorizontalAlignment.Center;
            IsHitTestVisible = false;

            var border = new Border 
            {
                CornerRadius = 8,
                Padding = "40 20",
                BorderColor = Color.DimGray,
                BackgroundColor = "#aa3b3b3b",
                BorderThickness = 1
            };

            var textBlock = new TextBlock 
            {
                VerticalAlignment = VerticalAlignment.Center,
                HorizontalAlignment = HorizontalAlignment.Center,
                Text = text,
                ForegroundColor = Color.White
            };

            border.Child = textBlock;
            AddChild(border);
        }
    }

    public enum Position
    {
        Top = 0,
        Bottom = 1
    }
}


bart1's avatar
bart1

Joined on 03.08.2017

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: Favorites.algo
  • Rating: 5
  • Installs: 1764
  • Modified: 13/10/2021 09:54
Comments
Log in to add a comment.
Tspring's avatar
Tspring · 2 years ago

I meant top right/ bottom right

Tspring's avatar
Tspring · 2 years ago

Cool indicator, thank you.

It only miss the top right/ top left positions option like the old one.