
美元和欧元指数图表 — MetaTrader 5 服务示例
目录
概述
美元指数是货币市场最受欢迎的指数。它使我们能够预测汇率的变动。这是美元相对价值的重要指标。美元指数(USDX)于 1973 年 3 月推出。其基准值设定为 100 点。换句话说,今天等于 90 点的指数意味着美元相对于 1973 年的数字下跌了 10%,而等于 110 点的指数则意味着上涨了 10%。
根据牙买加国际会议的结果,指数中包含的货币的浮动汇率生效。从那时起,美元指数一直根据世界 500 大银行提供的货币交易数据进行计算。1999 年,欧元取代了一些欧洲国家的本国货币后,美元指数的计算方法发生了变化。
USDX 是一篮子货币的加权几何平均值,其中包括主要的全球货币。该篮子中的每种货币都属于美国的六个主要贸易伙伴,它们的经济实力并不相等,因此指数中的每一种货币都有特定的影响力份额(权重):
货币 | 比率 |
---|---|
欧元(EUR) | 0.576(57.6%) |
日元(JPY) | 0.136(13.6%) |
英镑(GBP) | 0.119(11.9%) |
加拿大元(CAD) | 0.091(9.1%) |
瑞典克朗(SEK) | 0.042(4.2%) |
瑞士法郎(CHF) | 0.036(3.6%) |
美元指数计算公式:
USDX = 50.14348112 * EURUSD^(-0.576) * USDJPY^(0.136) * GBPUSD^(-0.119) * USDCAD^(0.091) * USDSEK^(0.042) * USDCHF^(0.036)
幂指数幅度与所用篮子中货币的权重相对应。如果将 1973 年 3 月的汇率代入公式中,则比率 50.14348112 会使美元指数达到 100.0。因此,当前的 USDX 反映了与 1973 年报价相比,美元对一篮子货币的价值变化。指数值低于 100 点表示美元贬值,而指数值高于 100 点表示与 1973 年相比美元升值。
欧元货币指数是五种世界货币(美元、英镑、日元、瑞士法郎和瑞典克朗)对欧元汇率的平均变化率。
作为一种交易工具,欧元指数(EURX)于 2006 年 1 月 13 日在纽约交易所(NYBOT)推出,交易品种代码为 ECX、EURX 或 E。
欧元指数已成为国际金融市场参与者衡量单一欧洲货币当前价值的标准,也是进行交易操作的工具。
基于五种货币篮子的欧元指数计算与欧洲央行在基于构成欧元区国家主要外贸营业额的国家的货币计算贸易加权欧元指数时使用的数据相匹配。欧元区国家在国际贸易中所占份额最大的是与美国的贸易(31.55%),其次是英国 - 30.56%,日本 - 18.91%,瑞士 - 11.13%和瑞典 - 7.85%。
计算 EUR 指数当前值的基本原理与计算 USDX 的原理类似。欧元指数采用加权几何平均数计算方法计算:
EURX = 34.38805726 * EURUSD^(0.3155) * EURGBP^(0.3056) * EURJPY^(0.1891) * EURCHF^(0.1113) * EURSEK^(0.0785)
其中幂指数是所用篮子中货币的权重。
制定任务
我们需要创建一个合成工具,其价格根据上面提供的方程计算。我们需要一个完整的工具图表,该图表将随着工具篮子中使用的交易品种的每个新分时报价的到来而更新,在这个图表上,我们可以运行任何指标、脚本和 EA。
一般来说,我们需要创建一个与标准工具图表几乎没有区别的合成工具图表。我们需要一个服务程序,它将按照自己的流程完成所有工作,而不管其他打开的图表和在其上运行的程序。
在启动服务时,我们将检查是否存在所需的合成工具,如有必要则创建它,并将其放置在市场报价窗口中。随后将创建合成工具的分钟和分时报价历史记录,然后创建所创建工具的图表。完成这些操作后,服务将收到每个交易品种的新分时报价,并据此计算工具的价格,并将新报价添加到创建的自定义交易品种的历史记录中。重启终端后,如果服务程序是在终端关闭时启动的,则会自动启动。因此,一旦启动了所需的服务,它总是在终端启动时自动重新启动。
我们将创建两个这样的自定义交易品种:美元指数和欧元指数。将为每个交易品种创建一个单独的服务程序。它们将基于一个包含文件,我们将在其中放置创建所需工具的所有函数。服务程序将仅指示计算工具所需的篮子规模、基准比率和交易品种结构及其权重。调用其中设置的函数时,其他所有操作都将在包含的文件中进行。这将允许我们通过指定交易品种列表和每个交易品种的权重,根据具有不同货币集及其权重的已创建函数来创建我们自己的指数。
实现货币指数计算函数文件
在 \MQL5\Services\ 终端目录中,创建一个新的 Indexes\ 文件夹,其中包含一个新的包含文件 CurrencyIndex.mqh 。它将存储项目运行所需的所有函数。
在文件的最开始处,我们将输入宏替换工作所需的结构和枚举:
//+------------------------------------------------------------------+ //| 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
篮子交易品种的结构包含两个字段:交易品种的名称及其在工具篮子中的权重。当编制一篮子工具来计算指数时,使用这种结构的数组会很方便:初始化后,我们立即将交易品种的名称及其权重写入其中,然后从该数组中循环获取交易品种及其权重,以计算指数价格。在这种情况下,任何交易品种及其权重都可以写入数组中,这为创建用于计算指数的任何工具篮子提供了灵活性。
历史和分时报价数据结构使用相应结构的数组:MqlRates 和 MqlTick — 它们将包含工具篮中每个交易品种的数据。每个结构中还有一个数据索引。该指数需要指示现有柱的编号,从中获取数据来计算指数。例如,要计算某个柱上的指数,参与指数计算的工具篮中的每个交易品种都必须具有该分钟柱形上的数据。它们可能不一定位于每个交易品种上 - 某处有柱形间隙(如果在这一分钟内没有任何交易品种的分时报价)。在这种情况下,需要指出计算数据所取自的柱的索引(如果交易品种上没有数据,则索引会增加),以便从前一个柱中获取数据。而且由于我们事先不知道用于计算指数的工具篮子中的交易品种数量,我们无法在程序中提前声明每个工具所需的指数数量。因此,在这种结构中存储和使用它们是方便的。
价格类型枚举只是指定表示要计算柱形价格所要获取的价格的常量。
启动服务时,首先需要创建自定义交易品种,形成一个月内的历史 М1 柱形并创建分时报价历史记录。这将是服务的初始化。我们实现函数如下:
//+------------------------------------------------------------------+ //| 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); }
在 CustomSymbolInitialize() 函数中检查是否存在并创建自定义交易品种:
//+------------------------------------------------------------------+ //| 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); }
在这里我们检查是否存在具有给定名称的自定义交易品种。如果没有,请创建它。接下来,查找此交易品种的打开的图表,如果在终端中打开的图表中没有找到该图表,则打开该图表。
创建自定义交易品种并打开其图表后,我们需要创建一个月的 M1 周期历史记录。这是在 PrepareRates() 函数的帮助下完成的:
//+------------------------------------------------------------------+ //| 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); }
该函数首先复制工具篮中每个交易品种的数据,并设置复制数据的总开始时间。然后为每个工具设置一个数据指数,其时间与其他篮子交易品种的时间一致,以便所有篮子交易品种的起始日期都相同。如果截至开始日期某个交易品种还没有柱形,那么就会为其设置最近的、有数据的前一个柱形的索引。
接下来,在循环中,合成工具时间序列的每个柱形都会逐个柱计算,调整每个后续柱形的索引,以便其上的数据来自当前柱形(如果其上有数据)或来自前一个柱形(如果当前柱形上没有数据)。计算出的柱线被添加到合成工具的时间序列数组中。计算完工具时间序列的所有柱形后,计算出的时间序列将添加到自定义交易品种的价格历史记录中。
合成工具一根柱的历史数据在 CalculateRate() 函数中计算:
//+------------------------------------------------------------------+ //| 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)); }
对于合成工具的每根柱形价格(开盘价、最高价、最低价和收盘价),均使用合成工具的公式进行计算:
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);
篮子中每个交易品种的价格均在 GetRateValue() 函数中获取:
//+------------------------------------------------------------------+ //| 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); }
CalculateRate() 函数返回检查合成工具计算价格的结果:
//--- return the result of checking prices for validity return(CheckRate(rate));
由于合成工具柱的所有价格都是经过计算的,因此有必要检查其计算的有效性并纠正错误(如果有的话)。
所有这些都是在 CheckRate() 函数中完成的,其结果返回:
//+------------------------------------------------------------------+ //| 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); }
如果任何计算出的价格为正无穷或负无穷,或“不是数字”(NaN),或小于或等于零,则该函数返回 false 。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/15684


