
USDとEURの指数チャート—MetaTrader 5サービスの例
内容
はじめに
米ドル指数(USDX)は、為替市場でもっとも広く利用されている指標の一つであり、為替レートの動向を予測するうえで有効な手がかりとなります。これは、相対的な米ドルの価値を示す重要な指標です。USDXは1973年3月に導入され、基準値は100ポイントに設定されました。つまり、現在の指数が90ポイントであれば、1973年と比べて米ドルが10%下落したことを意味し、110ポイントであれば10%上昇したことを意味します。
ジャマイカ国際通貨会議の決定により、指数に含まれる通貨は変動相場制へと移行しました。以降、USD指数は、世界の大手500行が提供する通貨取引データに基づいて、継続的に算出されています。1999年にユーロ(EUR)が導入され、いくつかの欧州通貨がこれに置き換えられたことで、米ドル指数の計算方法も変更されました。
USDXは、世界主要通貨で構成された通貨バスケットの加重幾何平均として計算されます。このバスケットには、米国の主要な貿易相手国6カ国の通貨が含まれており、各国の経済規模が異なるため、指数内の各通貨には個別の影響力(重み)が設定されています。
通貨 | 重み |
---|---|
ユーロ(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の計算式
USDX = 50.14348112 * EURUSD^(-0.576) * USDJPY^(0.136) * GBPUSD^(-0.119) * USDCAD^(0.091) * USDSEK^(0.042) * USDCHF^(0.036)
使用されるバスケット内の通貨に割り当てられた重みに応じて、為替レートはべき乗されます。係数50.14348112により、1973年3月時点の為替レートをこの式に代入すると、ドル指数の値は100.0になります。したがって、現在のUSDXは、1973年の相場と比較した通貨バスケットに対する米ドルの価値の変化を示しています。指数の値が100ポイント未満の場合、米ドルの価値が下落していることを示し、100ポイントを超える場合は、1973年と比べて米ドルの価値が上昇していることを意味します。
ユーロ通貨指数(EURX)は、世界の主要5通貨(米ドル、英ポンド、日本円、スイスフラン、スウェーデンクローナ)に対するユーロの為替レートの平均変動率を表します。
EURXは、2006年1月13日 にニューヨーク商品取引所(NYBOT)にて取引手段として導入され、ティッカーシンボルはECX、EURX、またはEです。
このユーロ指数は、国際金融市場において単一通貨ユーロの現在価値を示す標準的な指標として広く利用されており、取引活動をおこなう上でも重要なツールとなっています。
EURXは、ユーロ圏諸国の主要な対外貿易相手国の通貨を基にした5通貨バスケットを使用して計算されており、そのデータは、欧州中央銀行(ECB)が貿易加重ユーロ指数を算出する際にも使用されています。ユーロ圏諸国の国際貿易の内訳は、米国(31.55%)が最大で、次いで英国(30.56%)、日本(18.91%)、スイス(11.13%)、スウェーデン(7.85%)となっています。
ユーロ指数の現在値の計算は、USDXの計算と同様の基本原則に基づいており、加重幾何平均法によって求められます。
EURX = 34.38805726 * EURUSD^(0.3155) * EURGBP^(0.3056) * EURJPY^(0.1891) * EURCHF^(0.1113) * EURSEK^(0.0785)
ここでのべき乗(指数)は、それぞれの通貨がバスケット内で占める重みを表しています。
タスクの形成
上記の数式に基づいて価格が計算される合成銘柄を作成する必要があります。この銘柄には、完全なチャートが必要であり、バスケットに含まれる各通貨ペアに新しいティックが到着するたびにチャートが更新され、さらにそのチャート上では任意のインジケーター、スクリプト、エキスパートアドバイザー(EA)を使用できる必要があります。
要するに、標準銘柄のチャートとほぼ同等の機能を備えた合成銘柄のチャートを作成する必要があります。そして、それを管理するサービスプログラムは、他のチャートやその上で動作しているプログラムに干渉することなく、独立したスレッドで処理を実行する必要があります。
サービス起動時には、対象となる合成銘柄がすでに存在するかを確認し、存在しない場合は新たに作成して気配値表示ウィンドウに追加します。続いて、合成銘柄の1分足およびティック履歴を作成し、最後にその銘柄のチャートを表示します。これらの処理が完了すると、サービスはバスケットに含まれる各シンボルの新しいティックを受信し、それらに基づいて合成銘柄の価格をリアルタイムで計算し、履歴にティックデータを追加していきます。また、端末を再起動した際、終了前にこのサービスが起動していた場合には、端末起動時に自動的に再起動されます。つまり、一度サービスを起動しておけば、以降端末起動時に常に自動実行されるようになります。
このようなカスタム銘柄を2つ(米ドル指数とユーロ指数)作成します。各銘柄に対して個別のサービスプログラムを用意し、それらは共通のインクルードファイルに基づいて構築されます。このインクルードファイルには、必要なツールを作成するためのすべての関数をまとめて実装します。サービスプログラム側では、バスケットの構成(銘柄リストと重み)、ベース比率、バスケットサイズなど、指数計算に必要な最小限の情報のみを定義します。その他の処理はすべてインクルードファイル内の関数呼び出しによって自動的に実行されるようにします。これにより、異なる通貨構成や重みを指定するだけで、共通の関数を用いて柔軟にカスタム指数を作成することが可能になります。
通貨指数計算関数ファイルの実装
\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
バスケットに含まれる各銘柄の構造体には、銘柄名とバスケット内での重みの2つのフィールドが含まれます。指数の計算用に銘柄バスケットを作成する際は、この構造体の配列を使用すると非常に便利です。初期化時に、銘柄の名前と重みをこの配列に記述し、その後、指数価格を計算するループ内でこの配列から銘柄とその重みを取得します。この方法を用いることで、任意の銘柄とその重みを配列に記述することが可能になり、指数の計算に使うあらゆる構成の銘柄バスケットを柔軟に作成できます。
履歴およびティックデータの構造体では、MqlRatesとMqlTickのそれぞれに対応した構造体配列を使用します。これらの配列は、指数に使われる銘柄バスケット内の各銘柄に関するデータを格納します。また、それぞれの構造体にはデータ指数も含まれます。この指数は、指数計算に使用するデータを取得する対象バーの番号を示すために必要です。たとえば、あるバーにおいて指数を計算するには、指数計算に参加するすべての銘柄がそのバーに対応する価格データを持っている必要があります。ただし、各銘柄においてバーが常に存在するとは限りません。ある銘柄では、その分足の間にティックがなかった場合、バーが形成されずに空白となることがあります。こういった場合には、計算対象のデータを取得するために、どのバーからデータを取るかを示す指数を指定する必要があります。該当するバーにデータが存在しない場合、その指数を1つ進めて、1つ前のバーからデータを取得するようにします。また、指数計算に用いる銘柄バスケットに含まれる銘柄の数はあらかじめ不明なため、プログラム内であらかじめ必要な指数数を宣言することができません。そのため、このような構造体の配列として保持して使用する方が便利です。
価格タイプの列挙型は、バーの価格を計算するために使用する価格の種類(終値や始値など)を示す定数を定義するためのものです。
サービスを開始するときは、まずカスタム銘柄を作成し、過去1か月分のM1(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); }
ここでは、指定された名前のカスタム銘柄が存在するかどうかを確認します。存在しない場合は、それを新規作成します。次に、その銘柄のチャートが端末内で開かれているかを確認し、見つからなければチャートを開きます。
カスタム銘柄の作成とチャートの表示が完了した後、1か月分の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); }
この関数では、まず指数計算に使用されるバスケット内の各銘柄について、データをコピーし、そのコピー処理の開始時刻を統一します。すべての銘柄で開始日時が一致するように、他のバスケット銘柄と同じ時刻に対応するデータ指数を各銘柄に対して設定します。開始日時に該当するバーが存在しない銘柄があった場合は、データが存在する最も近い過去のバーの指数が設定されます。
その後、ループ内で合成銘柄の時系列バーを1本ずつ計算していきます。このとき、各バーについて、データが存在する場合はそのバーから、存在しない場合は直前のバーからデータを取得するように指数が調整されます。計算された各バーは合成銘柄の時系列配列に追加されます。すべてのバーの計算が完了すると、その時系列データはカスタム銘柄の価格履歴に書き込まれます。
合成銘柄の1本のバーに対応する履歴データは、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





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索