
インディケータ間のデータ交換:簡単です
はじめに
初心者の方に感謝したいのは、彼らがかたくなに検索機能を使おうとしないため『よくある質問』、『新規利用者の方へ』、『このリストから質問する人は地獄行き』などというトピックが存在するからです。彼らが本当にするべきは「どうやって~するの?」、「~することは可能なの?」などと尋ねることですが、往々にして答えは「 まさか」とか「できるわけないよ」というものです。
永遠のフレーズ「ネバーセイ、ネバーアゲイン」は科学者、エンジニア、プログラマーが古いものを簡単に忘れ、何か新しいものを考え産み出すさまを表現しています。
1. 問題定義
MQL4のコミュニティ フォーラム(ロシア語からの翻訳)からの引用があります。
インディケータが2つあります。(それをAとBとしましょう)インディケータAは通常どおり価格チャートから直接データを使用し、BはインディケータAのデータを使用します。ここで質問です。「iCustom("インディケータA", ...)を使う代わりにBデータを動的に計算するにはどうすればよいでしょうか?」すなわち、インディケータAの設定をいくつか変更し、それがインディケータBのパラメータ変更に反映されるようにするということです。
言い換えると
チャートに移動平均を貼り付けると仮定します。そうすると、データバッファに直接アクセスするにはどうすればよいのでしょうか?
そして
... iCustomを使っていくつかのインディケータを呼ぶ場合、コールの前にロードされていたとしても再ロードされます。それを防ぐ方法はあるのでしょうか?
そういった質問は拡大する可能性があります。何度も繰り返し尋ねられます。しかも初心者からだけではありません。一般に、問題はMetaTraderがiCustom(MQL4の場合)またはiCustom+CopyBufferのバインディング(MQL5の場合)なしにカスタムインディケータデータにアクセスする手段がないことです。次の傑作を産み出すためMQLコードに関数をいくつか書くというのは心をそそられることです。それは指定のチャートからのデータを使ってデータを取得する、または何かを計算する関数です。
上述の標準関数による作業は便利とは言えません。例えばMQL4で、インディケータのコピーはiCustomが呼ばれるたびに作成されます。MQL5の問題はハンドルの使用により一部解決されました。そして計算はマスターコピーのために一度だけ行われます。しかしいまだにそれは解決策とはいえません。参照するインディケータが計算のリソースを消費しすぎると、事実上再初期化のたびに数十秒で端末はリソースを欠乏するからです。
コピー元にアクセスする方法がいくつか提供さているはずです。以下もそれに含まれるものです。
- 物理的なディスクやメモリにファイルをマッピングする
- データ交換にDLLを介してメモリーを共有する
- データ交換と保存のためにクライアント端末のグローバル変数を使用する
その他のメソッドについては、上述メソッドのバリエーションです。またソケットやメイルスロットなどのような変わった方法もあります。Expert Advisorsでよく使用されるような基本的な手法もあります。インディケータ計算を直接Expert Advisorコードに変換するものですが、これは本稿では述べません。
著者の視点では、すべてのメソッドにはメリットと同時にデメリットもあります。データはまずどこかにコピーされ、それから他所に配布される、というような点です。第一にCPUのリソースを必要とします。次に送信されたデータの適切性に問題をもたらします。(ここではこの点については扱いません。)
では、問題を明らかにしていきましょう。
チャートに添付されるインディケータ データにアクセスし、次のようなプロパティを有する環境を作成したいと思います。
- データをコピーしない(適切性に関する問題もコピーしない)
- 使用可能なメソッドに対しては、使用する必要がある場合、最小限に手を加える
- MQLコードの使用が好ましい(もちろんDLLを使う必要がありますが、C++コードのストリングをいくつか使うだけです)
DLL作成にあたり、著者はC++ビルダ、MetaTrader 4、MetaTrader 5を使用しました。以下に示されるソースコードはMQL5で書かれています。またMQL4コードについては本稿に添付しています。両者の主な相違点についてお話します。
2. 配列
第一にいくばくかのセオリーが必要です。というのも、これからインディケータ バッファで作業するため、そのデータがメモリ内でどのように配置されるか知る必要があるからです。この情報についてはうまくまとめられたものがありません。
MQLの動的配列は変数サイズを伴うストラクチャであるため、配列サイズが増し、配列直後にメモリの空がなかった場合、MQLがデータ再割り当ての問題にいかに取り組むかというのは今興味を引くところです。その解決策は2つあります。
- 新規データを使用可能なメモリーの追加部分に再割り当てる(参照リストを使うなどしてこの配列ぜんぶのアドレスを保存します。)
- 配列全体をひとかたまりとして、割当てに十分な余裕がある新しいメモリ領域に移動させる
最初の方法は後に問題が発生します。その理由はこの場合、MQLコンパイラにより作成されるストラクチャのデータを調査しなければならないからです。次の考察が二番目のバリアントを立証します。(それはよりゆっくりとしたものです):動的配列が外部関数(DELL)に渡されるとき、あとの配列はデータの第一エレメントにポインターを取得します。他の配列要素は整理され第一配列エレメントの直後に配置されます。
参照により渡されるとき、配列データには変更が加えられ、そのため最初の方法では外部関数にそのデータを送信するための別のメモリ領域に配列全体がコピーされる際問題が生じます。そして、結果はソース配列に追加されます。それは、二番目の方法でも起こりえます。
この結論が理論的に100%正しくなくても、多少の信憑性はあるはずです。(この考えに基づいたプロダクトを正しく処理させることで証明されます。)
そこで、以下の記述が正しいと仮定します。
- どんなときでも動的配列のデータはメモリに一つずつ順番に配置され、配列はコンピュータメモリの別領域に再配置されますが、ぜんぶをまとめてではない
- 第一配列エレメントのアドレスは、パラメータとして動的配列が参照によって外部関数に渡されるとき、DLLライブラリに渡される
またここでその他の仮説を考えます。ちょっとした時間、すなわち対応するインディケータのOnCalculate() (for MQL4 - start ())関数が呼ばれる瞬間です。なお単純化のため、ここでのデータバッファはdouble []と同一のディメンションとタイプであるとします。
3. 必須条件
MQLはポインタ(いわゆる、一般的ポインタではないオブジェクト ポインタをのぞいて)をサポートしません。それはMetaQuotes Softwareの代表者によってくりかえし記述され確認されてきています。それでは、これが正しいか確認します。
ポインタとはそもそも何でしょうか?アスタリスクを伴なう識別子というだけではなく、コンピュータメモリ内のセルアドレスです。では、セルアドレスとは何でしょうか?ある地点から始まる連続した数字です。そうすると最後に数字とは?コンピュータメモリのあるセルに対応する整数です。ならば、なぜポインタは整数番号のように扱うことができないのでしょうか?もちろんポインタを整数番号のように扱うことは可能です。MQLプログラムは整数値と完璧に連携しているのですから!
でもポインタを整数に変換する方法は?動的リンクライブラリを使うのです。C++ typecastingの可能性を利用するのです。 C++ポインタが4バイトデータ タイプなので、この場合、intを4バイトデータ タイプとして使うのは好都合です。
サインビットは重要ではありません。また、それに頼ることもしません。(もしサインビットが1に等しければ、整数がマイナスであることを意味するだけです。)重要なことはすべてのポインタビットに変更を加えないことです。MQL5とMQL4のコードが似通っていればサインなし整数を使用することはもちろん可能ですが、MQL4コードにはサインなし整数は含まれていないからです。
そこで、
extern "C" __declspec(dllexport) int __stdcall GetPtr(double *a) { return((int)a); }
どうでしょう。配列の最初のアドレス値でロングタイプの変数を得ました。これで、i-th配列値の読み方を学習すればよいだけです。
extern "C" __declspec(dllexport) double __stdcall GetValue(int pointer,int i) { return(((double*) pointer)[i]); }
そして、値を書くのです。(ここでの場合必ずしも必要ではありませんが。。。)
extern "C" __declspec(dllexport) void __stdcall SetValue(int pointer,int i,double value) { ((double*) pointer)[i]=value; }
以上です。これでMQLのポインタを使うことができるようになりました。
4. ラッピング
システムのカーネルを作成してきました。これからはMQLプログラムにおいてそれを使いこなす準備です。しかしながら、外見にこだわる方々、どうか心配しないでください。まだお伝えすることがあります。
やり方は星の数ほどあります。が、ここでは以下の方法でやっていきましょう。個別のMQLプログラム間でデータ交換をするため、クライアント端末は特別な機能を備えていることを思い出しましょう。それはグローバル変数です。インディケータバッファにポインタを保管するにはそれが最もやりやすい方法です。インディケータバッファにアクセスします。そういう変数を記述テーブルと考えます。デスクリプタにはそれぞれ以下のようなストリングで表される名前がついています。
string_identifier#buffer_number#symbol#period#buffer_length#indexing_direction#random_number,
そして、その値は、コンピュータメモリのバッファポインタに応じた整数表現と等しくなっています。
デスクリプタ フィールドの詳細の一部を以下に述べます。
- string_identifier – あらゆるストリングです。(たとえば、インディケータ名short_name変数が使用可能です、など)必要なポインタを検索するのに使用されます。インディケータの中には同じ識別子でデスクリプタを登録するものもあり、フィールドを用いてお互い同士を識別します。
- buffer_number – バッファを区別するのに使われます。
- buffer_length – 境界を調整するのに必要です。それがないと、クライアント端末とウィンドウズのBlue Screen of Death はクラッシュする可能性があります。
- symbol, period – チャートウィンドウを指定するシンボルと期間です。
- ordering_direction – 配列エレメントの整列方向を指定します。0 :通常方向、1:逆方向(AS_SERIESフラッグが true)
- random_number – クライアント端末にアタッチされた異なるウィンドウやパラメータセットが使われていてもインディケータのコピーがあるとき使用されます。(一番目と二番目の等しい値をセットすることができます。それは両者間をいくらか区別する必要があるからです。)最もすぐれた解決法ではありませんが、使えないことはありません。
最初に、デスクリプタを登録し削除する関数が必要です。関数の最初のストリングを見てみます。グローバル変数のリストに存在する古いデスクリプタを削除するには UnregisterBuffer()を呼ぶ必要があります。
各新規バーでは、バッファサイズが1ずつ増えていきます。そこで、RegisterBuffer()を呼ぶ必要があります。バッファサイズが変わるようなら、テーブル(サイズ情報はそのままの名前で保持されます)に新規デスクリプタが作成され、古いものはテーブルに残ります。それが使われるのはそういう理由からです。
void RegisterBuffer(double &Buffer[], string name, int mode) export { UnregisterBuffer(Buffer); //first delete the variable just in case int direction=0; if(ArrayGetAsSeries(Buffer)) direction=1; //set the right ordering_direction name=name+"#"+mode+"#"+Symbol()+"#"+Period()+"#"+ArraySize(Buffer)+"#"+direction; int ptr=GetPtr(Buffer); // get the buffer pointer if(ptr==0) return; MathSrand(ptr); //it's convenient to use the pointer value instead //of the current time for the random numbers base while(true) { int rnd=MathRand(); if(!GlobalVariableCheck(name+"#"+rnd)) //check for unique name - we assume that { //nobody will use more RAND_MAX buffers :) name=name+"#"+rnd; GlobalVariableSet(name,ptr); //and write it to the global variable break; } } }
void UnregisterBuffer(double &Buffer[]) export { int ptr=GetPtr(Buffer); //we will register by the real address of the buffer if(ptr==0) return; int gt=GlobalVariablesTotal(); int i; for(i=gt-1;i>=0;i--) //just look through all global variables { //and delete our buffer from all places string name=GlobalVariableName(i); if(GlobalVariableGet(name)==ptr) GlobalVariableDel(name); } }
ここでは詳しいコメントは不必要です。最初の関数がグローバル変数に上述フォーマットの新規デスクリプタを作成し、二番目の関数がすべてのグローバル変数内を検索して変数とポインタの値に等しいデスクリプタを削除するという概要を述べるに留まります。
では、二番目のタスクについて考察しましょう。インディケータからのデータ取得です。データへの直接アクセスを実装する前に、まず対応するデスクリプタを見つける必要があります。以下の関数を用いて行うことができます。そのアルゴリズムは下記のとおりです。すべてのグローバル変数にあたり、デスクリプタで指定されたフィールド値がそこにあるか確認します。
フィールド値を見つけたら、配列にその名前を追加し、最後のパラメータとして渡します。その結果、関数は検索条件に合うメモリアドレスをすべて返します。返される値はみつかったデスクリプタの数です。
int FindBuffers(string name, int mode, string symbol, int period, string &buffers[]) export { int count=0; int i; bool found; string name_i; string descriptor[]; int gt=GlobalVariablesTotal(); StringTrimLeft(name); //trim string from unnecessary spaces StringTrimRight(name); ArrayResize(buffers,count); //reset size to 0 for(i=gt-1;i>=0;i--) { found=true; name_i=GlobalVariableName(i); StringExplode(name_i,"#",descriptor); //split string to fields if(StringFind(descriptor[0],name)<0&&name!=NULL) found=false; //check each field for the match //condition if(descriptor[1]!=mode&&mode>=0) found=false; if(descriptor[2]!=symbol&&symbol!=NULL) found=false; if(descriptor[3]!=period&&period>0) found=false; if(found) { count++; //conditions met, add it to the list ArrayResize(buffers,count); buffers[count-1]=name_i; } } return(count); }
関数コードにあるように、検索条件の中にはオミットされているものもあります。たとえば、名前としてNULLを渡した場合string_identifierのチェックはとばされます。その他のフィールドについても同じです。mode<0、symbol:=NULL または period<=0。それによりデスクリプタのテーブルにある検索オプションを拡げることができます。
たとえば、全チャートウィンドウですべての移動平均を見つけることが可能です。あるいは、M15期間におけるEURUSDチャートのみを検索する、などです。備考:string_identifierの確認は厳密な同等性確認の代わりにStringFind()関数によって行われます。デスクリプタの一部によって検索の可能性が得られます。(インディケータのいくつかが“MA(xxx)”タイプのストリングを設定する場合、サブストリングの“MA”によって検索を行うことが可能です。その結果、登録された全移動平均を見つけることができます。)
またStringExplode関数(string s、string separator、string &result[])を使いました。それは、セパレータを用いて指定されたstring sを下位ストリングに分離し、結果配列に結果を書き込む関数です。
void StringExplode(string s, string separator, string &result[]) { int i,pos; ArrayResize(result,1); pos=StringFind(s,separator); if(pos<0) {result[0]=s;return;} for(i=0;;i++) { pos=StringFind(s,separator); if(pos>=0) { result[i]=StringSubstr(s,0,pos); s=StringSubstr(s,pos+StringLen(separator)); } else break; ArrayResize(result,ArraySize(result)+1); } }
さて、必要なデスクリプタリストを得たら、インディケータからデータを取得することが可能です。
double GetIndicatorValue(string descriptor, int shift) export { int ptr; string fields[]; int size,direction; if(GlobalVariableCheck(descriptor)>0) //check that the descriptor is valid { ptr = GlobalVariableGet(descriptor); //get the pointer value if(ptr!=0) { StringExplode(descriptor,"#",fields); //split its name to fields size = fields[4]; //we need the current array size direction=fields[5]; if(direction==1) shift=size-1-shift; //if the ordering_direction is reverse if(shift>=0&&shift<size) //check for its validity - to prevent crashes return(GetValue(MathAbs(ptr),shift)); //ok, return the value } } return(EMPTY_VALUE); //overwise return empty value }
ご覧のようにそれは、DLLからのGetValue()関数のラッピングです。配列の境界について、デスクリプタの有効期限を確認し、インディケータバッファ順序に配慮することが必要です。失敗したら、関数はEMPTY_VALUEを返します。
インディケータバッファの修正も同様です。
bool SetIndicatorValue(string descriptor, int shift, double value) export { int ptr; string fields[]; int size,direction; if(GlobalVariableCheck(descriptor)>0) //check for its validity { ptr = GlobalVariableGet(descriptor); //get descriptor value if(ptr!=0) { StringExplode(descriptor,"#",fields); //split it to fields size = fields[4]; //we need its size direction=fields[5]; if(direction==1) shift=size-1-shift; //the case of the inverse ordering if(shift>=0&&shift<size) //check index to prevent the crash of the client terminal { SetValue(MathAbs(ptr),shift,value); return(true); } } } return(false); }
すべての値が正しければ、DLLからSetValue()関数を呼びます。返された値は修正結果に対応しています。成功であればtrue、エラーの場合はfalseです。
5. 動作確認
それでは確認しましょう。目標は、標準パッケージからAverage True Range (ATR)を使用し、値を他のインディケータ ウィンドウにコピーするにあたり、修正箇所を示すことです。また、バッファデータの修正テストも行います。
まず行うのは、OnCalculate()関数にコード行をいくつか追加することです。
//+------------------------------------------------------------------+ //| Average True Range | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[]) { if(prev_calculated!=rates_total) RegisterBuffer(ExtATRBuffer,"ATR",0); …
示されるように、新規バーができると、毎回新規バッファ登録が行われます。ここでは時間の経過とともに(通常)起こります。または、追加の履歴データがダウンロードされる場合に起こります。
その他、インディケータの操作が終了すると、テーブルからデスクリプタを削除する必要があります。deinitイベント ハンドラにコードをいくらか追加する必要があるのはこのためです。(覚えておくべきは、それはボイドを返しconst int reason入力パラメータを持っていることです。)
void OnDeinit(const int reason) { UnregisterBuffer(ExtATRBuffer); }
クライアント端末のチャートウィンドウに修正されたインディケータ(図1)と、グローバル変数にそのデスクリプタ(図2)があります。
図1 平均的真の範囲
図2 クライアント端末のグローバル変数リストに作成されたデスクリプタ
次のステップはATRデータへのアクセスです。以下のコードを用いて新規インディケータ(便宜的にテストと名付けます)を作成します。
//+------------------------------------------------------------------+ //| test.mq5 | //| Copyright 2009, alsu | //| alsufx@gmail.com | //+------------------------------------------------------------------+ #property copyright "2009, alsu" #property link "alsufx@gmail.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 #include <exchng.mqh> //---- plot ATRCopy #property indicator_label1 "ATRCopy" #property indicator_type1 DRAW_LINE #property indicator_color1 Red #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- indicator buffers double ATRCopyBuffer[]; string atr_buffer; string buffers[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,ATRCopyBuffer,INDICATOR_DATA); //--- return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int& spread[]) { //--- int found=FindBuffers("ATR",0,NULL,0,buffers); //search for descriptors in global variables if(found>0) atr_buffer=buffers[0]; //if we found it, save it to the atr_buffer else atr_buffer=NULL; int i; for(i=prev_calculated;i<rates_total;i++) { if(atr_buffer==NULL) break; ATRCopyBuffer[i]=GetIndicatorValue(atr_buffer,i); //now it easy to get data SetIndicatorValue(atr_buffer,i,i); //and easy to record them } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
難しいことはありませんね。サブストリングによりデスクリプタを取得し、われわれの関数を使用してインディケータバッファを読みます。最後に、そこでの不要物をいくつか書き出します。(われわれの例では配列エレメント インデックスに対応する値を書きます。)
そしてテストインディケータを実行します。(ここで目標としているATRはまだチャートにアタッチする必要があります。)図3にあるように、ATR値はさらに下位のサブウィンドウ(ここでのテストインディケータでは)にあり、その代りに直線が描写されているのがわかります。実際には配列インデックスに対応している値が書き込まれています。
図3 Test.mq5動作の結果
簡単ですね。以上でお終いです。
6. 下位互換性
著者はMQ4の標準になるべく近い形でMQL5にライブラリを作成しようとしました。MQL4で使うには数点の修正が必要です。
特にMQL5だけに存在するエクスポートキーワードを除去する必要があります。そして、間接のtypecastingを使ったコードを修正する必要があります。(MQL4はあまり柔軟性がないので)実際には、インディケータで関数を使用するのはまったく同じです。異なる点は新規バーと履歴追加コントロールの手法だけです。
おわりに
役に立つことを数点だけお話します。
本稿で述べたメソッドの利用は本来の目的だけに限られたものではないでしょう。高速カスケードインディケータの構築に加え、Expert Advisorsには、履歴テストも含めライブラリもうまく適用できるはずです。その他のアプリケーションについては著者がイメージするには難しいものですが、私が思うに、ユーザーのみなさんはこの方向で楽しんで作業されることでしょう。
以下の技術的節目が必要になるのではないかと考えます。
- デスクリプタ テーブルの改善(特にウィンドウのインディケーション)
- デスクリプタ作成順序に対する追加テーブルの開発。それはトレーディングシステムの安定性にとって重要でしょう。
ライブラリの改善に関するいかなるご提案もいただけるとありがたく思います。
必要なファイルはすべて本稿に添付しています。MQL5およびMQL4のコード、 exchng.dllライブラリコードも添付にあります。ファイルはクライアント端末のフォルダと同じ場所にあります。
免責条項
謝辞
著者はMQL4コミュニティフォーラムhttp://forum.mql4.com にて提示された問題点やユーザーによる案 igor.senych、satop、bank、StSpirit、TheXpert、artmailru、ForexTools、 marketeer、IlyaAを使用しました。リストが不完全な場合はご容赦ください。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/19





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