Category Technical analysis  Published on 05/08/2024

Currency Strength Board

Description

This is a plugin built based on our previous 'Strong and Weak' indicator formula.

It shows a bar chart of currencies strength, alongside best symbols to long or short according to currencies strength.

You can change the time frame and periods which is the number of x previous bars that will be used to calculate strength of each currency.

Initial load of plugin takes some time as it loads a lot of symbols and their data according to your selected time frame.

It also persists your selected period and time frame in local storage so when you launch cTrader again you will see same time frame and periods data.


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

namespace cAlgo.Plugins
{
    [Plugin(AccessRights = AccessRights.None)]
    public class CurrencyStrengthBoard : Plugin
    {
        #region Fields

        private const double ScaleMin = 1;
        private const double ScaleMax = 500;

        private readonly List<string> _symbolNames = new();
        private readonly List<Bars> _symbolBars = new();
        private readonly Dictionary<string, double> _currencyStrength = new();
        private readonly List<HorizontalBar> _orderedStrengthBars = new();
        private readonly List<string> _bestLongSymbols = new();
        private readonly List<string> _bestShortSymbols = new();
        
        private readonly HashSet<string> _currencies = new()
        {
            "EUR",
            "USD",
            "GBP",
            "AUD",
            "CAD",
            "NZD",
            "JPY",
            "CHF",
            "TRY",
            "SGD",
            "BTC",
            "MXN"
        };

        private StackPanel _strengthBoardPanel;
        private ComboBox _timeFramesComboBox;
        private TextBlock _loadingTextBlock;
        private CustomFrame _frame;
        private TextBox _periodsTextBox;
        private StackPanel _bestLongSymbolsPanel;
        private StackPanel _bestShortSymbolsPanel;
        private Grid _mainPanel;
        
        #endregion Fields

        #region Parameters
        
        public int Periods        
        {
            get => int.Parse(LocalStorage.GetString(nameof(Periods)) ?? 20.ToString());
            set => LocalStorage.SetString(nameof(Periods), value.ToString());
        }
        
        public TimeFrame BarsTimeFrame
        {
            get => TimeFrame.Parse(LocalStorage.GetString(nameof(BarsTimeFrame)) ?? TimeFrame.Daily.ToString());
            set => LocalStorage.SetString(nameof(BarsTimeFrame), value.ToString());
        }

        #endregion Parameters

        #region Overridden methods

        protected override void OnStart()
        {
            ChartManager.FramesRemoved += OnChartManagerFramesRemoved;

            Start();
        }

        private void OnChartManagerFramesRemoved(FramesRemovedEventArgs obj)
        {
            if (_frame is null)
                return;
            
            if (!obj.RemovedFrames.Contains(_frame))
                return;

            Start();
        }

        private void Start()
        {
            CreateFrame();
            
            Loading();
            
            BeginInvokeOnMainThread(() =>
            {
                AddSymbolsNames();

                AddCurrenciesStrength();
                
                RefreshBoard();
            });
        }
        
        private void CreateBestShortSymbolsPanel()
        {
            _bestShortSymbolsPanel = new StackPanel();

            _bestShortSymbolsPanel.AddChild(new TextBlock
            {
                Text = "Best Symbols To Short",
                FontWeight = FontWeight.Bold,
                HorizontalAlignment = HorizontalAlignment.Center,
                Margin = new Thickness(5, 10, 5, 5),
                FontSize = 14
            });
            
            var count = _bestShortSymbols.Count > 10 ? 10 : _bestShortSymbols.Count;
            
            for (var i = 0; i < count; i++)
            {
                _bestShortSymbolsPanel.AddChild(new TextBlock
                {
                    Margin = 5,
                    Text = $"#{i + 1}: {_bestShortSymbols[i]}",
                    FontWeight = FontWeight.Bold,
                });
            }
        }

        private void CreateBestLongSymbolsPanel()
        {
            _bestLongSymbolsPanel = new StackPanel();

            _bestLongSymbolsPanel.AddChild(new TextBlock
            {
                Text = "Best Symbols To Long",
                FontWeight = FontWeight.Bold,
                HorizontalAlignment = HorizontalAlignment.Center,
                Margin = new Thickness(5, 5, 5, 10),
                FontSize = 14
            });
            
            var count = _bestLongSymbols.Count > 10 ? 10 : _bestLongSymbols.Count;
            
            for (var i = 0; i < count; i++)
            {
                _bestLongSymbolsPanel.AddChild(new TextBlock
                {
                    Margin = 5,
                    Text = $"#{i + 1}: {_bestLongSymbols[i]}",
                    FontWeight = FontWeight.Bold,
                });
            }
        }

        private void Loading()
        {
            CreateLoadingTextBlock();
            
            _frame.Child = new ScrollViewer {Content = _loadingTextBlock};
        }

        private void Show()
        {
            _mainPanel = new Grid(1, 2);
            
            _mainPanel.AddChild(_strengthBoardPanel, 0, 0);

            _mainPanel.Columns[0].SetWidthInStars(1);
            _mainPanel.Columns[1].SetWidthToAuto();
            
            var bestSymbolsPanel = new StackPanel() {Orientation = Orientation.Vertical, Margin = 20};
            
            bestSymbolsPanel.AddChild(_bestLongSymbolsPanel);
            bestSymbolsPanel.AddChild(_bestShortSymbolsPanel);
            
            _mainPanel.AddChild(bestSymbolsPanel, 0, 1);

            _frame.Child = new ScrollViewer {Content = _mainPanel};
        }

        private void CreateFrame()
        {
            _frame = ChartManager.AddCustomFrame("Currency Strength Board");
        }

        private void CreateLoadingTextBlock()
        {
            _loadingTextBlock = new TextBlock
            {
                Text = "Loading...",
                FontWeight = FontWeight.ExtraBold,
                FontSize = 16,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center,
                Margin = 10
            };
        }

        private void UpdateAllCurrenciesStrength()
        {
            foreach (var currency in _currencies) UpdateCurrencyStrength(currency);
        }

        private void CreateStrengthBoard()
        {
            _strengthBoardPanel = new StackPanel {Orientation = Orientation.Vertical};

            var titleTextBlock = new TextBlock
            {
                Text = $"Currency Strength Board ({ScaleMin} - {ScaleMax})",
                Margin = 20,
                FontWeight = FontWeight.Bold,
                FontSize = 14,
                HorizontalAlignment = HorizontalAlignment.Center
            };
            
            _strengthBoardPanel.AddChild(titleTextBlock);

            var timeFramesTextBox = new TextBlock
            {
                Margin = 20,
                Text = "Timeframe",
                FontWeight = FontWeight.Bold
            };
            
            _timeFramesComboBox = new ComboBox
            {
                Margin = 20,
                HorizontalAlignment = HorizontalAlignment.Left,
                MinWidth = 80,
                FontWeight = FontWeight.Bold,
            };

            PopulateTimeFrames();

            _timeFramesComboBox.SelectedItemChanged += OnTimeFramesComboBoxSelectedItemChanged;

            var periodsTextBlock = new TextBlock
            {
                Margin = 20,
                Text = "Periods",
                FontWeight = FontWeight.Bold
            };

            _periodsTextBox = new TextBox
            {
                Text = Periods.ToString(),
                Margin = 20,
                FontWeight = FontWeight.Bold,
                MinWidth = 80
            };
            
            _periodsTextBox.TextChanged += OnPeriodsTextBoxTextChanged;
            
            var settingsPanel = new StackPanel {Orientation = Orientation.Horizontal};
            
            settingsPanel.AddChild(timeFramesTextBox);
            settingsPanel.AddChild(_timeFramesComboBox);
            
            settingsPanel.AddChild(periodsTextBlock);
            settingsPanel.AddChild(_periodsTextBox);

            _strengthBoardPanel.AddChild(settingsPanel);
            
            _orderedStrengthBars.Clear();
            
            for (var i = 0; i < _currencies.Count; i++)
            {
                var bar = new HorizontalBar {Margin = 20};

                _strengthBoardPanel.AddChild(bar);
                
                _orderedStrengthBars.Add(bar);
            }
        }

        private void OnPeriodsTextBoxTextChanged(TextChangedEventArgs obj)
        {
            Periods = int.Parse(obj.TextBox.Text);
            
            RefreshBoard();
        }

        private void OnTimeFramesComboBoxSelectedItemChanged(ComboBoxSelectedItemChangedEventArgs args)
        {
            BarsTimeFrame = TimeFrame.Parse(args.SelectedItem);

            RefreshBoard();
        }

        private void RefreshBoard()
        {
            Loading();
            
            BeginInvokeOnMainThread(() =>
            {
                UnsubscribeFromBars();
            
                AddBars();
                
                UpdateAllCurrenciesStrength();

                CreateStrengthBoard();

                UpdateStrengthBoard(); 
                
                UpdateBestLongShortSymbols();
                
                CreateBestLongSymbolsPanel();

                CreateBestShortSymbolsPanel();
                
                Show();
            });
        }

        private void PopulateTimeFrames()
        {
            foreach (var fieldInfo in typeof(TimeFrame).GetFields())
            {
                _timeFramesComboBox.AddItem(fieldInfo.Name);
            }

            _timeFramesComboBox.SelectedItem = BarsTimeFrame.ToString();
        }

        private void AddBars()
        {
            _symbolBars.Clear();
            
            foreach (var symbolName in _symbolNames)
            {
                var bars = MarketData.GetBars(BarsTimeFrame, symbolName);

                _symbolBars.Add(bars);

                bars.BarOpened += OnBarOpened;
            }
        }

        private void UnsubscribeFromBars()
        {
            if (_symbolBars is null)
                return;
            
            foreach (var bars in _symbolBars)
            {
                bars.BarOpened -= OnBarOpened;
            }
        }
        
        private void OnBarOpened(BarOpenedEventArgs args)
        {
            var symbol = Symbols.GetSymbol(args.Bars.SymbolName);

            if (_currencies.TryGetValue(symbol.BaseAsset.Name.ToUpperInvariant(), out var baseCurrency))
                UpdateCurrencyStrength(baseCurrency);

            if (_currencies.TryGetValue(symbol.QuoteAsset.Name.ToUpperInvariant(), out var quoteCurrency))
                UpdateCurrencyStrength(quoteCurrency);

            UpdateStrengthBoard();

            UpdateBestLongShortSymbols();
        }

        private void UpdateStrengthBoard()
        {
            var valueMax = _currencyStrength.Values.Max();
            var valueMin = _currencyStrength.Values.Min();

            var barIndex = 0;

            foreach (var (currency, strength) in _currencyStrength.OrderByDescending(i => i.Value))
            {
                var bar = _orderedStrengthBars[barIndex];

                bar.Label = currency;
                bar.Value = MinMax(strength, valueMin, valueMax, ScaleMin, ScaleMax);

                barIndex++;
            }
        }

        private static double MinMax(double number, double min, double max, double minAllowedNumber,
            double maxAllowedNumber)
        {
            var b = max - min != 0 ? max - min : 1 / max;
            var uninterpolate = (number - min) / b;
            var result = minAllowedNumber * (1 - uninterpolate) + maxAllowedNumber * uninterpolate;

            return result;
        }

        private void AddCurrenciesStrength()
        {
            _currencyStrength.Clear();
            
            foreach (var currency in _currencies) _currencyStrength.Add(currency, 0);
        }

        #endregion Overridden methods

        #region Other methods

        private void UpdateCurrencyStrength(string currency)
        {
            double symbolPercentage = 0;

            var seriesCounter = 0;

            foreach (var bars in _symbolBars)
            {
                var symbol = Symbols.GetSymbol(bars.SymbolName);

                var impact = 0;

                if (symbol.BaseAsset.Name == currency)
                    impact = 1;
                else if ((symbol.QuoteAsset.Name == currency))
                    impact = -1;

                if (impact == 0)
                    continue;

                var index = bars.Count - 1;

                seriesCounter++;

                double seriesResult = 0;

                for (var i = index - Periods; i <= index; i++) seriesResult += GetPercentageChange(bars, i);

                symbolPercentage += seriesResult * impact;
            }

            _currencyStrength[currency] = symbolPercentage / seriesCounter;
        }

        private void UpdateBestLongShortSymbols()
        {
            _bestLongSymbols.Clear();
            _bestShortSymbols.Clear();
            
            var orderedCurrenciesStrength = _currencyStrength
                .OrderByDescending(kv => kv.Value)
                .ToArray();

            for (var i = 0; i < orderedCurrenciesStrength.Length; i++)
            {
                var strongCurrency = orderedCurrenciesStrength[i].Key;

                for (var j = orderedCurrenciesStrength.Length - 1; j > i; j--)
                {
                    var weakCurrency = orderedCurrenciesStrength[j].Key;

                    foreach (var symbolName in Symbols)
                    {
                        var symbol = Symbols.GetSymbol(symbolName);

                        if (symbol.BaseAsset.Name == strongCurrency && symbol.QuoteAsset.Name == weakCurrency)
                        {
                            _bestLongSymbols.Add(symbolName);
                        }
                        else if (symbol.BaseAsset.Name == weakCurrency && symbol.QuoteAsset.Name == strongCurrency)
                        {
                            _bestShortSymbols.Add(symbolName);
                        }
                    }
                }
            }
        }

        private void AddSymbolsNames()
        {
            _symbolNames.Clear();
            
            foreach (var symbolName in Symbols)
            {
                var symbol = Symbols.GetSymbol(symbolName);

                var baseAssetName = symbol.BaseAsset.Name;
                var quoteAssetName = symbol.QuoteAsset.Name;

                if (!IsInCurrencies(baseAssetName) || !IsInCurrencies(quoteAssetName) ||
                    baseAssetName == quoteAssetName)
                    continue;

                _symbolNames.Add(symbolName);
            }
        }

        private bool IsInCurrencies(string currency) => _currencies.Contains(currency.ToUpperInvariant());

        private static double GetPercentageChange(Bars bars, int index)
        {
            return -((bars.OpenPrices[index] - bars.ClosePrices[index]) / bars.OpenPrices[index]) * 100;
        }

        #endregion Other methods
    }
}

afhacker's avatar
afhacker

Joined on 15.10.2015

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: Currency Strength Board_withSourceCode.algo
  • Rating: 5
  • Installs: 710
  • Modified: 05/08/2024 10:19
Comments
Log in to add a comment.
BO
BobbyLee · 1 week ago

Not a coder but there seems to be an issue:

Error CS0246: The Type or namespace name "HorizontalBar" could not be found (are you missing a using directive or an assembly reference?)