Summary
In this post I provide a tutorial on how to develop an ROC based Currency Strength Indicator developed on the cTrader platform.
The full source code of the developed indicator is available to download for free
1. What is a currency strength indicator ?
The Currency Strength Indicator or also called Momentum Meter is a tool used in the context of Forex trading by many professional traders and investment banks. It is a tool that allows to see the strength of currencies against each other
It allows to show in a glimpse which currencies are trading strongly against the ones that are trending weakly. This allows making sense of conflicting market trends
The Currency Strength Indicator can be used on a specific Time-frame or it can aggregate different Time-frames
The indicator can calculate the strength of a currency based on its price ROC (rate of change) or based on other price-based indicators such as moving-averages...
In this tutorial I will show how you can code a basic currency strengh indicator based on the ROC (rate of change) of Forex currency-pairs.
2. Basic implementation based on ROC
For this implementation I will be using the following setup:
- cTrader platform - Desktop 3.8 Release
- C# as the programming language
To know more about the cTrader trading API please refer to the following link: cTrader Trading API Help
The first thing to do on cTrader's automate section is to add a new Indicator. Then we should give the indicator a name. In our example we will name it CurrencyStrenghMeter
We will also include the necessary 'Using' directives. For now the indicator code should look like this
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.Reflection;
namespace cAlgo
{
[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class CurrencyStrengthMeter : Indicator
{
protected override void Initialize()
{
}
public override void Calculate(int index)
{
}
}
I will briefly explain those two methods and how they are called.
The first method Initialize() is called one time when the indicator is initialized. Indicator initialization can occur at two occasions:
- When an instance of the indicator is first added
- Whenever a prameter of the indicator is changed by hand, for example if you change the timeframe or the symbol of the indicator instance
The second method Calculate(int index) is called according the following logic
- Once at each historical price bar until the last active price bar. The index argument will contain the index of the bar
- On the active bar, this method will be called at each tick with the same value for the index argument. What changes at each new tick is the close price of the current active price bar
- When the active bar is closed. The index is incremented and this method will be called at each tick fo the next bar
Adding Parameters and Output DataSeries
This indicator will calculate the strength of the following major forex currencies : USD, EUR, GBP, JPY, AUD, NZD, CAD, and NZD.
We should also be able to specify the Periods on which the ROC (Rate of change) will be calculated
For that we add the following code
[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class CurrencyStrengthMeter : Indicator
{
[Parameter("Periods", DefaultValue = 2, MinValue = 1)]
public int ParamPeriods { get; set; }
[Output("EUR", LineColor = "Red")]
public IndicatorDataSeries ResultEUR { get; set; }
[Output("GBP", LineColor = "Green")]
public IndicatorDataSeries ResultGBP { get; set; }
[Output("USD", LineColor = "Purple")]
public IndicatorDataSeries ResultUSD { get; set; }
[Output("JPY", LineColor = "Orange")]
public IndicatorDataSeries ResultJPY { get; set; }
[Output("CHF", LineColor = "Cyan")]
public IndicatorDataSeries ResultCHF { get; set; }
[Output("CAD", LineColor = "Blue")]
public IndicatorDataSeries ResultCAD { get; set; }
[Output("AUD", LineColor = "RosyBrown")]
public IndicatorDataSeries ResultAUD { get; set; }
[Output("NZD", LineColor = "White")]
public IndicatorDataSeries ResultNZD { get; set; }
string[] currencies = new string[]
{
"EUR",
"GBP",
"USD",
"JPY",
"CHF",
"CAD",
"AUD",
"NZD"
};
protected override void Initialize()
{
}
public override void Calculate(int index)
{
}
}
Calculating currency strength and drawing the indicator chart lines
The logic behind calculating the strength of each currency based on the ROC of its currency pairs is the following:
- We will hold a sum based value for each currency in which we will cumulate ROC values
- For each currency-pair composed of the concatenation of two currency names from within our list of 8 currencies, do the following:
- Calculate the ROC of the currency-pair on the number of periods set by the Parameter ParamPeriods
- Add the ROC value (whether positive or negative) to the first currency of the symbol pair
- Subsctract the ROC value (whether positive or negative) from the second currency of the symbol pair
Here is the code with thte calculation methods
[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class CurrencyStrengthMeter : Indicator
{
[Parameter("Periods", DefaultValue = 2, MinValue = 1)]
public int ParamPeriods { get; set; }
[Output("EUR", LineColor = "Red")]
public IndicatorDataSeries ResultEUR { get; set; }
[Output("GBP", LineColor = "Green")]
public IndicatorDataSeries ResultGBP { get; set; }
[Output("USD", LineColor = "Purple")]
public IndicatorDataSeries ResultUSD { get; set; }
[Output("JPY", LineColor = "Orange")]
public IndicatorDataSeries ResultJPY { get; set; }
[Output("CHF", LineColor = "Cyan")]
public IndicatorDataSeries ResultCHF { get; set; }
[Output("CAD", LineColor = "Blue")]
public IndicatorDataSeries ResultCAD { get; set; }
[Output("AUD", LineColor = "RosyBrown")]
public IndicatorDataSeries ResultAUD { get; set; }
[Output("NZD", LineColor = "White")]
public IndicatorDataSeries ResultNZD { get; set; }
StackPanel legenedPanel;
string[] currencies = new string[]
{
"EUR",
"GBP",
"USD",
"JPY",
"CHF",
"CAD",
"AUD",
"NZD"
};
protected override void Initialize()
{
}
public override void Calculate(int index)
{
if (ParamPeriods <= 0)
return;
if (IsLastBar)
{
ResultEUR[index] = 0;
ResultGBP[index] = 0;
ResultCHF[index] = 0;
ResultAUD[index] = 0;
ResultJPY[index] = 0;
ResultNZD[index] = 0;
ResultCAD[index] = 0;
ResultUSD[index] = 0;
}
foreach (var currency1 in currencies)
{
foreach (var currency2 in currencies)
{
if (currency1 == currency2)
continue;
string currentSymbolName = currency1 + currency2;
if (Symbols.Exists(currentSymbolName))
{
var bars = MarketData.GetBars(TimeFrame, currentSymbolName);
int indexInBars = bars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
double valAtIndex = (bars.ClosePrices[indexInBars] - bars.ClosePrices[indexInBars - ParamPeriods]) / bars.ClosePrices[indexInBars - ParamPeriods];
valAtIndex *= 100;
Calculate(currentSymbolName, "EUR", ResultEUR, index, valAtIndex);
Calculate(currentSymbolName, "GBP", ResultGBP, index, valAtIndex);
Calculate(currentSymbolName, "CHF", ResultCHF, index, valAtIndex);
Calculate(currentSymbolName, "AUD", ResultAUD, index, valAtIndex);
Calculate(currentSymbolName, "JPY", ResultJPY, index, valAtIndex);
Calculate(currentSymbolName, "NZD", ResultNZD, index, valAtIndex);
Calculate(currentSymbolName, "CAD", ResultCAD, index, valAtIndex);
Calculate(currentSymbolName, "USD", ResultUSD, index, valAtIndex);
}
}
}
}
/**
* This method will cumulate calculation of ROC on a currency
* @symbolName : the current symbol on which ROC was calculated
* @currency : the current currency to cumulate ROC
* @dataSeries : the outputDataSeries corresponding to the currency
* @index : the indicator current index.
* @val : the ROC of currency pair.
**/
protected void Calculate(string symbolName, string currency, IndicatorDataSeries dataSeries, int index, double val)
{
if (!symbolName.Contains(currency))
return;
//First time calculation
if (double.IsNaN(dataSeries[index]))
{
if (symbolName.StartsWith(currency))
{
dataSeries[index] = val;
return;
}
if (symbolName.EndsWith(currency))
{
dataSeries[index] = -1 * val;
return;
}
}
if (symbolName.StartsWith(currency))
{
dataSeries[index] = dataSeries[index] + val;
return;
}
if (symbolName.EndsWith(currency))
{
dataSeries[index] = dataSeries[index] - val;
return;
}
}
}
For now the indicator should be abe to display chart lines representing the evolution each currency strength
If you change the symbol on which the indicator instance is running, it will not impact the indicator. This is because the indicator is not based on the selected currency-pair, as you can see in the code it used the cTrader API to get price data of a fixed set of currency pairs regardless of the current pair selected to run the indicator
If you change the timeframe of the indicator instance, this will have an impact on the indicator calculation, and so the selected timeframe will be the one used in our calculations
For example if you which to display the strength of currencies on the last 25 Days, you should set the following parameters:
- Timeframe: Daily
- Periods : 25
Displaying the legend on the Indicator chart area
To make it easier to read the indicator we will add a legend to the chart. The legend will contain the strength of each currency colored with its corresponding chart color
To achieve this we will need to add a couple of methods to the indicator code. But before this we will need to modify the AccessRights attribute value to AccessRights.FullAccess in order to be able to use C# Reflection inside the indicator, otherwise calling any C# method that uses reflection will throw an exception.
In the purpose of drawing the legend we will need to sort currencies by their calculated strength. Then we will need to get the color of each currency, for that we will be using Reflection. Last, we will display and refresh the legend at each new tick.
Here is the code that we add to draw the legend at the top right corner of the indicator chart area
StackPanel legenedPanel;
public void DisplayLegend()
{
if (IndicatorArea == null)
return;
var legenedData = GetLegendData();
// remove the last tick control
if (legenedPanel != null)
IndicatorArea.RemoveControl(legenedPanel);
legenedPanel = new StackPanel();
legenedPanel.Orientation = Orientation.Vertical;
foreach (var pair in legenedData)
{
var currencyName = pair.Key;
var strength = pair.Value;
string color = GetColor(currencyName);
TextBlock textBlock = new TextBlock();
textBlock.Text = string.Format("{0} {1:N2}%", currencyName, strength);
textBlock.ForegroundColor = color;
legenedPanel.AddChild(textBlock);
}
legenedPanel.VerticalAlignment = VerticalAlignment.Top;
legenedPanel.HorizontalAlignment = HorizontalAlignment.Right;
IndicatorArea.AddControl(legenedPanel);
}
/**
* This will return an ordered map of currencies->strength from strongest to weakest
*/
public Dictionary GetLegendData()
{
Dictionary result = new Dictionary();
result.Add("AUD", ResultAUD.LastValue);
result.Add("EUR", ResultEUR.LastValue);
result.Add("GBP", ResultGBP.LastValue);
result.Add("USD", ResultUSD.LastValue);
result.Add("JPY", ResultJPY.LastValue);
result.Add("CHF", ResultCHF.LastValue);
result.Add("CAD", ResultCAD.LastValue);
result.Add("NZD", ResultNZD.LastValue);
return result.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
}
/**
* get the color parameter associated with a currency
**/
public string GetColor(string currency)
{
MemberInfo[] myMembers = this.GetType().GetMembers();
for (int i = 0; i < myMembers.Length; i++)
{
Object[] myAttributes = myMembers[i].GetCustomAttributes(typeof(OutputAttribute), true);
if (myAttributes.Length > 0)
{
var att = myAttributes[0] as OutputAttribute;
if (att != null && att.Name == currency)
{
return att.LineColor;
}
}
}
return null;
}
The indicator output should look like the figure below with the legend displayed on the top-right corner
Another display method using Histograms
Another way to display currency strength is using Histograms. No better way to explain this then looking directly into the wanted result. See below:
For each bar, the indicator prints two histogram bars corresponding to the strongest currency (above) and to the weakest currency (below)
By doing this we get a cleaner and less cluttered way to see which currency is the strongest and which one is the weakest
To achieve this we are going to modify the code of the indicator. The idea is to change the display type from "Line Chart" mode to "Histogram Mode".
For this purpose we we will create a new Indicator and we will name it CurrencyStrengthMeterHistogram. See the code below
// -------------------------------------------------------------------------------
// Currency Strength Indicator
// cTrader Desktop 3.8 Release
// This indicator was developed by Hichem MHAMED on https://www.automated-trading.ch
// Please contact for feedback or feature requests.
// This piece of software is licensed under the MIT License
// Copyright (c) 2020 Hichem MHAMED
// -------------------------------------------------------------------------------
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.Reflection;
namespace cAlgo
{
[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
public class CurrencyStrengthMeterHistogram : Indicator
{
[Parameter("Periods", DefaultValue = 2, MinValue = 1)]
public int ParamPeriods { get; set; }
[Output("EUR", IsHistogram = true, LineColor = "Red")]
public IndicatorDataSeries ResultEUR { get; set; }
[Output("GBP", IsHistogram = true, LineColor = "Green")]
public IndicatorDataSeries ResultGBP { get; set; }
[Output("USD", IsHistogram = true, LineColor = "Purple")]
public IndicatorDataSeries ResultUSD { get; set; }
[Output("JPY", IsHistogram = true, LineColor = "Orange")]
public IndicatorDataSeries ResultJPY { get; set; }
[Output("CHF", IsHistogram = true, LineColor = "Cyan")]
public IndicatorDataSeries ResultCHF { get; set; }
[Output("CAD", IsHistogram = true, LineColor = "Blue")]
public IndicatorDataSeries ResultCAD { get; set; }
[Output("AUD", IsHistogram = true, LineColor = "RosyBrown")]
public IndicatorDataSeries ResultAUD { get; set; }
[Output("NZD", IsHistogram = true, LineColor = "White")]
public IndicatorDataSeries ResultNZD { get; set; }
StackPanel legenedPanel;
string[] currencies = new string[]
{
"EUR",
"GBP",
"USD",
"JPY",
"CHF",
"CAD",
"AUD",
"NZD"
};
protected override void Initialize()
{
}
/**
* get the color parameter associated with a currency
**/
public string GetColor(string currency)
{
MemberInfo[] myMembers = this.GetType().GetMembers();
for (int i = 0; i < myMembers.Length; i++)
{
Object[] myAttributes = myMembers[i].GetCustomAttributes(typeof(OutputAttribute), true);
if (myAttributes.Length > 0)
{
var att = myAttributes[0] as OutputAttribute;
if (att != null && att.Name == currency)
{
return att.LineColor;
}
}
}
return null;
}
/**
* This will return an ordered map of currencies->strength from strongest to weakest
*/
public Dictionary GetLegendData()
{
Dictionary result = new Dictionary();
result.Add("AUD", ResultAUD.LastValue);
result.Add("EUR", ResultEUR.LastValue);
result.Add("GBP", ResultGBP.LastValue);
result.Add("USD", ResultUSD.LastValue);
result.Add("JPY", ResultJPY.LastValue);
result.Add("CHF", ResultCHF.LastValue);
result.Add("CAD", ResultCAD.LastValue);
result.Add("NZD", ResultNZD.LastValue);
return result.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
}
public void DisplayLegend()
{
if (IndicatorArea == null)
return;
var legenedData = GetLegendData();
// remove the last tick control
if (legenedPanel != null)
IndicatorArea.RemoveControl(legenedPanel);
legenedPanel = new StackPanel();
legenedPanel.Orientation = Orientation.Vertical;
foreach (var pair in legenedData)
{
var currencyName = pair.Key;
var strength = pair.Value;
string color = GetColor(currencyName);
TextBlock textBlock = new TextBlock();
textBlock.Text = string.Format("{0} {1:N2}%", currencyName, strength);
textBlock.ForegroundColor = color;
legenedPanel.AddChild(textBlock);
}
legenedPanel.VerticalAlignment = VerticalAlignment.Top;
legenedPanel.HorizontalAlignment = HorizontalAlignment.Right;
IndicatorArea.AddControl(legenedPanel);
}
public override void Calculate(int index)
{
if (ParamPeriods <= 0)
return;
if (IsLastBar)
{
ResultEUR[index] = 0;
ResultGBP[index] = 0;
ResultCHF[index] = 0;
ResultAUD[index] = 0;
ResultJPY[index] = 0;
ResultNZD[index] = 0;
ResultCAD[index] = 0;
ResultUSD[index] = 0;
}
foreach (var currency1 in currencies)
{
foreach (var currency2 in currencies)
{
if (currency1 == currency2)
continue;
string currentSymbolName = currency1 + currency2;
if (Symbols.Exists(currentSymbolName))
{
var bars = MarketData.GetBars(TimeFrame, currentSymbolName);
int indexInBars = bars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
double valAtIndex = (bars.ClosePrices[indexInBars] - bars.ClosePrices[indexInBars - ParamPeriods]) / bars.ClosePrices[indexInBars - ParamPeriods];
valAtIndex *= 100;
Calculate(currentSymbolName, "EUR", ResultEUR, index, valAtIndex);
Calculate(currentSymbolName, "GBP", ResultGBP, index, valAtIndex);
Calculate(currentSymbolName, "CHF", ResultCHF, index, valAtIndex);
Calculate(currentSymbolName, "AUD", ResultAUD, index, valAtIndex);
Calculate(currentSymbolName, "JPY", ResultJPY, index, valAtIndex);
Calculate(currentSymbolName, "NZD", ResultNZD, index, valAtIndex);
Calculate(currentSymbolName, "CAD", ResultCAD, index, valAtIndex);
Calculate(currentSymbolName, "USD", ResultUSD, index, valAtIndex);
}
}
}
if (IsLastBar)
DisplayLegend();
FilterMinMaxDataSeries(index);
}
protected void FilterMinMaxDataSeries(int index)
{
IndicatorDataSeries maxDataSeries = null;
IndicatorDataSeries minDataSeries = null;
double maxValue = 0;
double minValue = 0;
List dataSeriesList = new List
{
ResultEUR,
ResultGBP,
ResultCHF,
ResultAUD,
ResultJPY,
ResultNZD,
ResultCAD,
ResultUSD
};
// first loop to calculate min max
foreach (var dataSeries in dataSeriesList)
{
if (maxDataSeries == null && minDataSeries == null)
{
maxDataSeries = dataSeries;
maxValue = dataSeries[index];
minDataSeries = dataSeries;
minValue = dataSeries[index];
continue;
}
if (maxValue < dataSeries[index])
{
maxValue = dataSeries[index];
maxDataSeries = dataSeries;
}
if (minValue > dataSeries[index])
{
minValue = dataSeries[index];
minDataSeries = dataSeries;
}
}
// second loop to filter out other dataSeries from histogram
foreach (var dataSeries in dataSeriesList)
{
if (dataSeries != minDataSeries && dataSeries != maxDataSeries)
{
dataSeries[index] = 0;
}
}
}
/**
* This method will cumulate calculation of ROC on a currency
* @symbolName : the current symbol on which ROC was calculated
* @currency : the current currency to cumulate ROC
* @dataSeries : the outputDataSeries corresponding to the currency
* @index : the indicator current index.
* @val : the ROC of currency pair.
**/
protected void Calculate(string symbolName, string currency, IndicatorDataSeries dataSeries, int index, double val)
{
if (!symbolName.Contains(currency))
return;
//First time calculation
if (double.IsNaN(dataSeries[index]))
{
if (symbolName.StartsWith(currency))
{
dataSeries[index] = val;
return;
}
if (symbolName.EndsWith(currency))
{
dataSeries[index] = -1 * val;
return;
}
}
if (symbolName.StartsWith(currency))
{
dataSeries[index] = dataSeries[index] + val;
return;
}
if (symbolName.EndsWith(currency))
{
dataSeries[index] = dataSeries[index] - val;
return;
}
}
}
}
As you can see we have made few modifications to the first version of the indicator
- We have added the IsHistogram = true proprety to the IndicatorDataSeries outputs
- We have added the FilterMinMaxDataSeries(index); that will only keep values of the strongest/weakest currencies and filter out the others. So that only two histogram bars get drawn for each price bar
- We call the FilterMinMaxDataSeries(index); method after calling the DisplayLegend(); method
Using this indicator in a trading Robot?
Using this indicator as it is inside a robot as an entry signal provider is definitely possible using the IndicatorDataSeries output values. Yet, a more robust way to include the curency strength indicator inside a robot is preferred.
This other way of doing things is to reimplement the currency strength calculation directly inside the robot. By doing this we avoid using IndicatorDataSeries and instead we can use C# native containers.
IndicatorDataSeries are very good for displaying things on the chart, but in the context of developing a trading robot we would prefer to use native C# collections so that we can achieve full control of the calculated currency strength data.