
USD and EUR index charts — example of a MetaTrader 5 service
Contents
- Introduction
- Formulating the task
- Implementing the currency indices calculation functions file
- Testing service programs
- Conclusion
Introduction
U.S. Dollar Index is the most popular index of the currency market. It allows us to predict the movement of exchange rates. This is an important indicator of the relative USD value. The US Dollar Index (USDX) was introduced in March 1973. Its base value was set at 100 points. In other words, the index equal to 90 points today would mean a 10% fall of USD relative to the 1973 figure, while the index equal to 110 points would mean a 10% rise.
Following the results of the Jamaica International Conference, floating rates for the currencies included in the index came into effect. Since then, the USD index has been calculated continuously based on currency trading data provided by the world's 500 largest banks. The method for calculating the US dollar index changed in 1999 after the introduction of EUR, which replaced the national currencies of a number of European countries.
USDX is calculated as a weighted geometric mean of a basket of currencies that includes major global currencies. Each currency in this basket belongs to a group of six major trading partners of the United States, which are not equal in their economic strength, so each currency in the index is given a specific share of influence (weight):
Currency | Ratio |
---|---|
EUR | 0.576 (57.6%) |
Japanese Yen (JPY) | 0.136 (13.6%) |
British Pound (GBP) | 0.119 (11.9%) |
Canadian Dollar (CAD) | 0.091 (9.1%) |
Swedish Krona (SEK) | 0.042 (4.2%) |
Swiss Franc (CHF) | 0.036 (3.6%) |
Equation for calculating the US dollar index:
USDX = 50.14348112 * EURUSD^(-0.576) * USDJPY^(0.136) * GBPUSD^(-0.119) * USDCAD^(0.091) * USDSEK^(0.042) * USDCHF^(0.036)
The powers the rates are raised to correspond to the weight of the currencies in the basket used. The ratio 50.14348112 brings the dollar index to a value of 100.0 if the exchange rates for March 1973 are substituted into the equation. Thus, the current USDX reflects the change in the value of the US dollar against a basket of currencies compared to the 1973 quotes. An index value of less than 100 points indicates a depreciation of USD, while a value of more than 100 points indicates an increase in the USD value compared to 1973.
Euro Currency Index is the average rate of change in the exchange rates of five world currencies (the US dollar, the British pound, the Japanese yen, the Swiss franc and the Swedish krona) against the euro.
As a trading instrument, the euro index (EURX) was introduced on January 13, 2006 on the New York Board of Trade (NYBOT), ticker symbols ECX, EURX or E.
The EUR index has become a standard for the current value of the single European currency for participants in international financial markets, as well as a tool for conducting trading operations.
The calculation of the EUR index based on a basket of five currencies matches with the data used by the European Central Bank when calculating the trade-weighted euro index based on the currencies of those countries that form the main foreign trade turnover of the Eurozone countries. The largest share of international trade of the countries in the Eurozone is with the United States (31.55%), followed by the United Kingdom - 30.56%, Japan - 18.91%, Switzerland - 11.13% and Sweden - 7.85%.
The basic principles for calculating the current value of the UER Index are similar to those used to calculate USDX. The EUR Index is calculated using the weighted geometric mean calculation method:
EURX = 34.38805726 * EURUSD^(0.3155) * EURGBP^(0.3056) * EURJPY^(0.1891) * EURCHF^(0.1113) * EURSEK^(0.0785)
where the power is the weight of currencies in the basket used.
Formulating the task
We need to create a synthetic instrument, the price of which is calculated according to the equations provided above. We need a full-fledged chart of the instrument, which will be updated with the arrival of each new tick on the symbols used in the basket of instruments, and on this chart we can run any indicators, scripts and EAs.
Generally, we need to create a chart of a synthetic instrument that is practically no different from the charts of standard instruments. We need a service program, which will do everything in its own flow, regardless of other open charts and programs running on them.
When launching the service, we will check for the presence of the required synthetic instrument, create it if necessary, and place it in the Market Watch window. The minute and tick history of the synthetic instrument is to be created afterwards followed by the chart of the created instrument. After these manipulations, the service will receive new ticks for each of the symbols, on the basis of which the price of the instrument is calculated, and new ticks will be added to the history of the created custom symbol. After restarting the terminal, the service program is automatically launched if it had been launched when the terminal was closed. Thus, once the required service is launched, it always restarts automatically when the terminal is started.
We will create two such custom symbols: the US dollar index and the EUR index. A separate service program will be created for each of these symbols. They will be based on a single include file, in which we will place all the functions for creating the required tool. The service program will only indicate the basket size, base ratio, and symbol structure with their weights required for calculating the instrument. Everything else will happen inside the included file when calling the functions set in it. This will allow us to create our own indices based on the created functions with a different set of currencies and their weights just by specifying a list of symbols and the weight of each of them.
Implementing the currency indices calculation functions file
In the \MQL5\Services\ terminal directory, create a new Indexes\ folder containing a new include file CurrencyIndex.mqh. It will store all the functions necessary for the project operation.
At the very beginning of the file, we will enter the structures and enumerations necessary for the macro substitution to work:
//+------------------------------------------------------------------+ //| CurrencyIndex.mqh | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #define SECONDS_IN_DAY (24*60*60) // number of seconds in a day #define SECONDS_IN_MINUTE 60 // number of seconds in a minute #define MSECS_IN_MINIUTE (60*1000) // number of milliseconds in a minute //--- basket symbol structure struct SymbolWeight { pair symbol; // symbol double weight; // weight }; //--- historical data structure struct str_rates { int index; // data index MqlRates rates[]; // array of historical data }; //--- tick data structure struct str_ticks { int index; // data index MqlTick ticks[]; // array of ticks }; //--- enumeration of price types enum ENUM_RATES_VALUES { VALUE_OPEN, // Open price VALUE_HIGH, // High price VALUE_LOW, // Low price VALUE_CLOSE // Close price }; int ExtDigits=5; // symbol price measurement accuracy
The structure of the basket symbol contains two fields: the name of the symbol and its weight in the instrument basket. When compiling a basket of instruments for calculating the index, it will be convenient to use an array of such structures: immediately, upon initialization, we will write the names of the symbols and their weights into it, and then from this array we will obtain the symbol and its weight in a loop for calculating the index prices. In this case, any symbols with their weights can be written in the array, which provides flexibility in creating any baskets of instruments for calculating indices.
The historical and tick data structures use arrays of the corresponding structures: MqlRates and MqlTick — they will contain data on each of the symbols in the instrument basket. There is also a data index in each of these structures. The index is required to indicate the number of the existing bar, from which data is taken to calculate the index. For example, to calculate an index on a bar, it is necessary that each of the symbols of the instrument basket participating in the index calculation have data on that minute bar. They may not necessarily be located on each of the symbols - somewhere there are gaps in bars (if there were no ticks on any of the symbols during this minute). In this case, it is necessary to indicate the index of the bar the data for the calculation is taken from (where there is no data on the symbol, the index increases) in order to take the data from the previous bar. And since we do not know in advance the number of symbols in the basket of instruments for calculating the index, we cannot declare in advance the required number of indices for each instrument in the program. Therefore, it is convenient to store and use them in such a structure.
The price type enumeration simply specifies constants that indicate the price to be obtained to calculate the bar prices.
When starting the service, first of all it is necessary to create a custom symbol, form historical М1 bars in a month and create a tick history. This will be the initialization of the service. Let's implement the following function:
//+------------------------------------------------------------------+ //| Initializing the service | //+------------------------------------------------------------------+ bool InitService(const string custom_symbol,const string custom_group) { MqlRates rates[100]; MqlTick ticks[100]; //--- initialize the custom symbol if(!CustomSymbolInitialize(custom_symbol,custom_group)) return(false); ExtDigits=(int)SymbolInfoInteger(custom_symbol,SYMBOL_DIGITS); //--- we make active all symbols of the instrument basket that participate in the index calculation for(int i=0; i<BASKET_SIZE; i++) { //--- select a symbol in the Market Watch window if(!SymbolSelect(ExtWeights[i].symbol,true)) { PrintFormat("cannot select symbol %s",ExtWeights[i].symbol); return(false); } //--- request historical data of bars and ticks for the selected symbol CopyRates(ExtWeights[i].symbol,PERIOD_M1,0,100,rates); CopyTicks(ExtWeights[i].symbol,ticks,COPY_TICKS_ALL,0,100); } //--- build M1 bars for 1 month if(!PrepareRates(custom_symbol)) return(false); //--- get the last ticks after building M1 bars PrepareLastTicks(custom_symbol); //--- service initialized Print(custom_symbol," datafeed started"); return(true); }
Checking for existence and creating a custom symbol occurs in the CustomSymbolInitialize() function:
//+------------------------------------------------------------------+ //| Initialize a custom symbol | //+------------------------------------------------------------------+ bool CustomSymbolInitialize(string symbol,string group) { bool is_custom=false; //--- if a symbol is selected in the Market Watch window, we get a flag that this is a custom symbol bool res=SymbolSelect(symbol,true); if(res) is_custom=(bool)SymbolInfoInteger(symbol,SYMBOL_CUSTOM); //--- if the selected symbol is not custom, create it if(!res) { if(!CustomSymbolCreate(symbol,group,"EURUSD")) { Print("cannot create custom symbol ",symbol); return(false); } //--- the symbol was successfully created - set the custom symbol flag is_custom=true; //--- place the created symbol in the Market Watch window if(!SymbolSelect(symbol,true)) { Print("cannot select custom symbol ",symbol); return(false); } } //--- open the chart of the created custom symbol if(is_custom) { //--- get the ID of the first window of open charts long chart_id=ChartFirst(); bool found=false; //--- in the loop through the list of open charts, find the chart of the created custom symbol while(chart_id>=0) { //--- if the chart is open, report this to the journal, set the flag of the chart found and exit the search loop if(ChartSymbol(chart_id)==symbol) { found=true; Print(symbol," chart found"); break; } //--- based on the currently selected chart, get the ID of the next one for the next iteration of the search in the loop chart_id=ChartNext(chart_id); } //--- if the symbol chart is not found among the open charts if(!found) { //--- report about opening of M1 chart of a custom symbol, //--- get the chart ID and move on to it Print("open chart ",symbol,",M1"); chart_id=ChartOpen(symbol,PERIOD_M1); ChartSetInteger(chart_id,CHART_BRING_TO_TOP,true); } } //--- user symbol initialized return(is_custom); }
Here we check if there is a custom symbol with a given name. If not, create it. Next, look for an open chart of this symbol and, if the chart is not found among those open in the terminal, open the chart.
After creating a custom symbol and opening its chart, we need to create a history of M1 period for a month. This is done with the help of the PrepareRates() function:
//+------------------------------------------------------------------+ //| Preparing historical data | //+------------------------------------------------------------------+ bool PrepareRates(const string custom_symbol) { str_rates symbols_rates[BASKET_SIZE]; int i,reserve=0; MqlRates usdx_rates[]; // array timeseries of a synthetic instrument MqlRates rate; // synthetic instrument single bar data datetime stop=(TimeCurrent()/SECONDS_IN_MINUTE)*SECONDS_IN_MINUTE; // M1 bar time of the end date datetime start=stop-31*SECONDS_IN_DAY; // initial date M1 bar time datetime start_date=0; //--- copy M1 historical data for a month for all symbols of the instrument basket start/=SECONDS_IN_DAY; start*=SECONDS_IN_DAY; // initial date D1 bar time for(i=0; i<BASKET_SIZE; i++) { if(CopyRates(ExtWeights[i].symbol,PERIOD_M1,start,stop,symbols_rates[i].rates)<=0) { PrintFormat("cannot copy rates for %s,M1 from %s to %s [%d]",ExtWeights[i].symbol,TimeToString(start),TimeToString(stop),GetLastError()); return(false); } PrintFormat("%u %s,M1 rates from %s",ArraySize(symbols_rates[i].rates),ExtWeights[i].symbol,TimeToString(symbols_rates[i].rates[0].time)); symbols_rates[i].index=0; //--- find and set the minimum non-zero start date from the symbol basket if(start_date<symbols_rates[i].rates[0].time) start_date=symbols_rates[i].rates[0].time; } Print("start date set to ",start_date); //--- reserve of historical data array to avoid memory reallocation when changing array size reserve=int(stop-start)/60; //--- set the start of all historical data of the symbol basket to a single date (start_date) for(i=0; i<BASKET_SIZE; i++) { int j=0; //--- as long as j is less than the amount of data in the 'rates' array and //--- time at j index in the array is less than start_date time - increase the index while(j<ArraySize(symbols_rates[i].rates) && symbols_rates[i].rates[j].time<start_date) j++; //--- if the index was increased and it is within the 'rates' array, decrease it by 1 to compensate for the last increment if(j>0 && j<ArraySize(symbols_rates[i].rates)) j--; //--- write the received index into the structure symbols_rates[i].index=j; } //--- USD index timeseries int array_size=0; //--- first bar of M1 time series rate.time=start_date; rate.real_volume=0; rate.spread=0; //--- as long as the bar time is less than the end date time of the M1 timeseries while(!IsStopped() && rate.time<stop) { //--- if the historical data of the instrument bar is calculated if(CalculateRate(rate,symbols_rates)) { //--- increase the timeseries array by 1 and add the calculated data to it ArrayResize(usdx_rates,array_size+1,reserve); usdx_rates[array_size]=rate; array_size++; //--- reset the size of the array size backup value since it is only applied during the first resize reserve=0; } //--- next bar of the M1 timeseries rate.time+=PeriodSeconds(PERIOD_M1); start_date=rate.time; //--- in the loop through the list of basket instruments for(i=0; i<BASKET_SIZE; i++) { //--- get the current data index int j=symbols_rates[i].index; //--- while j is within the timeseries data and if the time of the bar at index j is less than the time set for this bar in rate.time, increase j while(j<ArraySize(symbols_rates[i].rates) && symbols_rates[i].rates[j].time<rate.time) j++; //--- if j is within the timeseries data and the time in start_date is less than the time of the timeseries data by j index //--- and the time in the timeseries at index j is less than or equal to the time in rate.time - write the time from the timeseries at index j to start_date if(j<ArraySize(symbols_rates[i].rates) && start_date<symbols_rates[i].rates[j].time && symbols_rates[i].rates[j].time<=rate.time) start_date=symbols_rates[i].rates[j].time; } //--- in the loop through the list of basket instruments for(i=0; i<BASKET_SIZE; i++) { //--- get the current data index int j=symbols_rates[i].index; //--- while j is within the timeseries data and if the time of the bar at index j is less than the time set for this bar in start_date, increase j while(j<ArraySize(symbols_rates[i].rates) && symbols_rates[i].rates[j].time<=start_date) symbols_rates[i].index=j++; } //--- in rate.time, set the time from start_date for the next bar rate.time=start_date; } //--- add the created timeseries to the database if(array_size>0) { if(!IsStopped()) { int cnt=CustomRatesReplace(custom_symbol,usdx_rates[0].time,usdx_rates[ArraySize(usdx_rates)-1].time+1,usdx_rates); Print(cnt," ",custom_symbol,",M1 rates from ",usdx_rates[0].time," to ",usdx_rates[ArraySize(usdx_rates)-1].time," added"); } } //--- successful return(true); }
The function first copies data for each of the symbols in the instrument basket and sets the total start time for copying data. A data index whose time coincides with the time of the other basket symbols, so that the starting date is the same for all basket symbols, is then set for each of the instruments. If there is no bar for a symbol by the starting date, then the index of the closest previous bar, on which the data exists, is set for it.
Next, in the cycle, each bar of the timeseries of the synthetic instrument is calculated bar by bar, adjusting the index of each subsequent bar so that the data on it is either from the current bar, if there is data on it, or from the previous one, if there is no data on the current bar. The calculated bars are added to the timeseries array of the synthetic instrument. After calculating all bars of the instrument's timeseries, the calculated timeseries is added to the price history of the custom symbol.
Historical data of one bar of a synthetic instrument is calculated in the CalculateRate() function:
//+------------------------------------------------------------------+ //| Calculation of prices and volumes of synthetic instruments | //+------------------------------------------------------------------+ bool CalculateRate(MqlRates& rate,str_rates& symbols_rates[]) { double values[BASKET_SIZE]={0}; long tick_volume=0; int i; //--- get Open prices of all symbols of the instrument basket into the values[] array for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_OPEN); //--- if the tick volume is zero, then there is no data for this minute - return 'false' if(tick_volume==0) return(false); //--- write down the total volume of all timeseries rate.tick_volume=tick_volume; //--- calculate the Open price based on the prices and weights of all instruments in the basket rate.open=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.open*=MathPow(values[i],ExtWeights[i].weight); //--- calculate the High price based on the prices and weights of all instruments in the basket for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_HIGH); rate.high=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.high*=MathPow(values[i],ExtWeights[i].weight); //--- calculate the Low price based on the prices and weights of all instruments in the basket for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_LOW); rate.low=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.low*=MathPow(values[i],ExtWeights[i].weight); //--- calculate the Close price based on the prices and weights of all instruments in the basket for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_CLOSE); rate.close=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.close*=MathPow(values[i],ExtWeights[i].weight); //--- return the result of checking prices for validity return(CheckRate(rate)); }
For each of the bar prices (Open, High, Low and Close) of the synthetic instrument, the prices are calculated using the equation of the synthetic instrument:
Open USDX = 50.14348112 * Open EURUSD^(-0.576) * Open USDJPY^(0.136) * Open GBPUSD^(-0.119) * Open USDCAD^(0.091) * Open USDSEK^(0.042) * Open USDCHF^(0.036); High USDX = 50.14348112 * High EURUSD^(-0.576) * High USDJPY^(0.136) * High GBPUSD^(-0.119) * High USDCAD^(0.091) * High USDSEK^(0.042) * High USDCHF^(0.036); Low USDX = 50.14348112 * Low EURUSD^(-0.576) * Low USDJPY^(0.136) * Low GBPUSD^(-0.119) * Low USDCAD^(0.091) * Low USDSEK^(0.042) * Low USDCHF^(0.036); CloseUSDX = 50.14348112 * CloseEURUSD^(-0.576) * CloseUSDJPY^(0.136) * CloseGBPUSD^(-0.119) * CloseUSDCAD^(0.091) * CloseUSDSEK^(0.042) * CloseUSDCHF^(0.036);
The prices of each symbol of the basket are obtained in the GetRateValue() function:
//+------------------------------------------------------------------+ //| Return the specified bar price | //+------------------------------------------------------------------+ double GetRateValue(long &tick_volume,str_rates &symbol_rates,datetime time,ENUM_RATES_VALUES num_value) { double value=0; // obtained value int index=symbol_rates.index; // data index //--- if the index is within the timeseries if(index<ArraySize(symbol_rates.rates)) { //--- depending on the type of requested data, set the corresponding value to the 'value' variable switch(num_value) { //--- Open price case VALUE_OPEN: if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) { value=symbol_rates.rates[index].open; //--- when requesting the Open price, add the tick volume to the tick_volume variable passed by reference, //--- to get the total volume of all symbols in the instrument basket tick_volume+=symbol_rates.rates[index].tick_volume; } } break; //--- High price case VALUE_HIGH: if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) value=symbol_rates.rates[index].high; } break; //--- Low price case VALUE_LOW: if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) value=symbol_rates.rates[index].low; } break; //--- Close price case VALUE_CLOSE: if(symbol_rates.rates[index].time<=time) value=symbol_rates.rates[index].close; break; } } //--- return the received value return(value); }
The CalculateRate() function returns the result of checking the calculated prices of the synthetic instrument:
//--- return the result of checking prices for validity return(CheckRate(rate));
Since all prices of the synthetic instrument bar are calculated, it is necessary to check the validity of their calculation and correct errors (if any).
All this is done in the CheckRate() function whose result is returned:
//+------------------------------------------------------------------+ //| Check prices for validity and return the check result | //+------------------------------------------------------------------+ bool CheckRate(MqlRates &rate) { //--- if prices are not valid real numbers, or are less than or equal to zero - return 'false' if(!MathIsValidNumber(rate.open) || !MathIsValidNumber(rate.high) || !MathIsValidNumber(rate.low) || !MathIsValidNumber(rate.close)) return(false); if(rate.open<=0.0 || rate.high<=0.0 || rate.low<=0.0 || rate.close<=0.0) return(false); //--- normalize prices to the required number of digits rate.open=NormalizeDouble(rate.open,ExtDigits); rate.high=NormalizeDouble(rate.high,ExtDigits); rate.low=NormalizeDouble(rate.low,ExtDigits); rate.close=NormalizeDouble(rate.close,ExtDigits); //--- adjust prices if necessary if(rate.high<rate.open) rate.high=rate.open; if(rate.low>rate.open) rate.low=rate.open; if(rate.high<rate.close) rate.high=rate.close; if(rate.low>rate.close) rate.low=rate.close; //--- all is fine return(true); }
If any of the calculated prices is plus or minus infinity, or "not a number" (NaN), or less than or equal to zero, the function returns false.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/15684





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Hmmm... When a person guesses, he writes code to check, instead of waiting for days for an answer
I wanted to lead a person step by step to this simple conclusion. They took it and ruined everything :)
Sometimes you should make people think instead of copying ready-made things.
Ah well, it got me thinking. 😅
Artem could you look - earlier services used to draw ok - now they stopped....
example in attachment
restart - restarted.
used to draw - you can see from above where the indicator is red-blue, now it has turned into a strip...)
and I can't do anything with it yet...
I've run it and deleted it and recompiled it and reset it.
It draws sticks on the triple and that's all...))
services used to draw ok - now they stopped....
Does the service attached to the article draw normally? Yes, it does.
So, you need to look for errors in yourself (whether all data, for example, are received for calculation), I guess...
Does the service attached to the article draw normally? I do.
So, you need to look for errors in yourself (whether all data, for example, are received for calculation), I guess...