さて前回は、エラー警告メールの嵐への対応として幾つかのMT4用EAのバグ対応をしている様子を書きました。今回は、未処置バグだった、1つのMT4で複数EAを同時実行したときに、OrderSelect関連が上手く動作しない事への対処について、検討している様子を書いてみたいと思います。
さて、前回の記事の通り、1つのMT4で複数EA(複数通貨ペアでの同時実行も含む)を動かしていた場合、OrderSelectの結果が格納されている領域が1つだけみたいなので、OrderSelect後のデータ取得が上手く動作しないという「仮説」に対処するためには、1つのMT4で複数のstart()関数が同時実行されなければいいと判断。
この実現方法を少し具体的に言うと。
【start関数を同時実行されない様にする方法】
---------------------------
1.start関数直後に、ロックをする。
2.start関数から抜ける時に、アンロックする。
---------------------------
つまり、あるEAがロックして、まだアンロックしていない間は、他のEAでロックしようとすると、ロックしたEAがアンロックするまでは待たされる様になる。そしてアンロックされると、待たされていた方のロック処理が終るので、start()関数の続きの処理が開始される。
じゃあ、そもそもそんな事をしていいのか?という話。
【start関数複数同時実行回避していいのかどうか】
--------------------------
●前提条件
今回の共通部品を使うと、前のバーが完成したあとの最初のティックデータ受信時に、
発注したり、トレーリングしたり、決済したりする仕様にしているので、複数通貨ペアを
1つのMT4でEAを実行していた場合、そもそも競合が発生しやすい状況。
この前提を踏まえて、そもそもstart()を同時実行禁止することが、いいのかどうなのかを、
ケース毎に考えて見た。
ケース毎に考えて見た。
懸念点として挙がるのが処理遅延がどの程度で、許容可能な範囲なのかどうなのか。
●ケース毎の検討
1.前のバーが完成した直後の最初のティックデータ受信時
一斉に、オーダ関連処理が動き始めるが、そもそも同時にオーダ関連の関数を呼び出せる
のは1つだけ。なので、このケースではstart()が同時処理していたとしても、実質的に処理が
シリアライズされている状況。
#同時にオーダすると「ERR_TRADE_CONTEXT_BUSY」が返却されるので。
そして、オーダ関連関数以外は多分処理時間があまりかからない(RefreshRatesは怪しいけど)。
なので、ロックしてもしなくても、処理遅延にはほとんど影響は無い。
2.上記「1.」以外の状況で、オーダ関連処理が発生しない場合
このケースでは、影響を受けるけど、そもそもする事がほとんどないので、
実質的に問題ない。あるとすれば、SL/TPにひっかかってて、ログ出力処理
が実行されるぐらい。このログ出力が遅延したところでたいした問題ない。
3.基本的に上記「2.」だが、ある通貨ペアのリトライが残っている
他の通貨ペアの処理では、処理時間はおそらく短いので、ほとんど影響を受けない。
これは、ロック処理でリトライするときのスリープ時間検討時に考えればいい話。
トレード頻度にもよるけど、実質問題なさそう。
懸念点があるとしたら、上記「2.」でもRefreshRates()は動作するし、RefreshRates()は時間が
かかるというブログ記事もあった点。その記事での調査によると、0~60msec程度だったとのこと。
#確かに解る様な気がする。
かかるというブログ記事もあった点。その記事での調査によると、0~60msec程度だったとのこと。
#確かに解る様な気がする。
--------------------------
【「オーダ関連関数」って何?】
--------------------------
さて、次はそもそも「オーダ関連関数」って何が含まれるのか?という話。
つまり、「ERR_TRADE_CONTEXT_BUSY」になりうる関数の実行がシリアライズされるので、
それらを精査しておきたい。
それを探るには、「トレード サーバーから返されるエラーコード」を返却する関数を調べればいい。
じゃあ、どの関数が該当するのかをみようとすると、いつもの愛用している有志の日本語ヘルプのこのページを見ればわかる。
#当然英語がわかれば、MT4のヘルプを見れば判るのですが。。
つまり、このヘルプの中で、「トレード サーバーから返されるエラーコード」を返却する関数を精査すればいいという事。("code returned by trade server"と説明に書かれている関数)
精査した結果を列挙してみると。
OrderSend / OrderClose / OrderCloseBy / OrderDelete / OrderModify
の5つだけ。
この判断方法だと、RefreshRates契機では通信が発生する訳ではなさそう。
で、RefreshRates()のヘルプを見ると、
「定義済み変数と直列配列内のデータを更新します。この関数は、エキスパート アドバイザーが
長時間の計算をして、データの更新が必要なときに使用されます。データが更新された場合、TRUE
を返します。それ以外の場合は FALSE を返します。データを更新することができない唯一の理由 は、ターミナル クライアントが現在データである場合です。」
を返します。それ以外の場合は FALSE を返します。データを更新することができない唯一の理由 は、ターミナル クライアントが現在データである場合です。」
つまり、1ティックデータ受信後に何回もRefreshRates()をしても、処理中にティックデータを受信していなければ、最初の一回を除いては、何もせずにFALSEが返却されるだけっぽい。
#そもそもstart()開始直前に同等処理をMT4内でしているハズだから、最初の一回目もFALSEの様な気がするけど。
--------------------------
なので、ここまでの結論を纏めると以下。
【対応可否に関しての、調査/検討の結論】
--------------------------
1.OrderSend / OrderClose / OrderCloseBy / OrderDelete / OrderModifyは
そもそも同時実行不可能
2.上記「1.」のオーダ関連関数以外はきっとあまり時間がかからない。
3.処理時間がかかりそうなのは、RefreshRates()で、長くても60msec程度
#PC性能次第という話でもありますが。。
そして、当然Sleep以外でのはなし。
4.上記「1.」~「3.」を踏まえると、やっぱり問題なさそう。
ただ、ロック時のリトライ処理のスリープ時間は、前述の「ケース毎の検討」の
「2.」/「3.」や、RefreshRates()の処理時間を踏まえて検討が必要。
--------------------------
じゃあ、次はどうやってロック/アンロックをMQL4でプログラミングするのか、という話。
【ロック/アンロックをする方法】
-------------------------------
簡単に言うと、GlobalVariableSetOnCondition()関数を用いて排他制御する。
これは、いつもお世話になっているブログのこの記事に載っていた方法。
確かに、このGlobalVariableSetOnCondition()という関数仕様をヘルプで見ると、
セマフォ(排他制御用の仕組み)としての用途を想定している旨の記述があるし、OSが
排他制御に使用している、TS(Test&Set)命令というCPU命令と考え方としては同じ。
大域変数はファイルで管理されているので、排他制御ができても確かに不思議ではない。
-------------------------------
【今回共通部品としてどうするのか】
---------------------------------
ロック/アンロックの処理方法で、ブログ記事とのロジックの差異は、以下。
●元ブログ記事のとの差異
1.二重ロックした場合は成功扱いにした事と、ロックしてないのにアンロックした場合も
成功扱いにした事。
2.別のEAがロックしてた時にアンロック共通関数を呼び出した場合は、成功扱いにするものの、
ロック状態は保持する様にした。
3.スリープ時間は、最初は100msecで、リトライ回数が増える毎に段階的に長くし、最大2秒 とした。
これは、「ケース毎の検討」の「2.」/「3.」の状況ではスリープ時間を短くした方が遅延が少なく
なると思ったから。
なると思ったから。
100secのゆえんは、前述のRefreshRates()が最大60msecという事を踏まえて、多めに丸めた。
2秒のゆえんは、IsStopped()の2.5秒ルールと、処理時間のおおまかな見積もりが
500msecという事を踏まえて、「2.5秒-0.5秒=2秒」という理屈。
ちなみに、なぜ上記「1.」/「2.」の様にしたかと言うと。
・二重ロックというプログラムミスでも成功できる様に。
・アンロック漏れに対処するため、deinit()でもアンロック処理を入れたかったから。こうして
おけば、もしプログラムミスによるアンロック漏れが発生して、トレードが停止してしまった
場合でも、MT4を再起動させれば、自動でアンロックされる事になるから。
つまり、暫定処置をすばやくできる点。
つまり、暫定処置をすばやくできる点。
・それでもアンロック漏れに対処できないのは、MT4が突如プロセスダウンしてしまった場合。
この場合は、MT4起動後に一旦該当大域変数にゼロを設定する事で対応する。
-------------------------------
じゃあ、次は具体的なMQL4のプログラムは?という話。
#このブログで初のMQL4のプログラムかも。。結構恥ずかしい。。
【ロック/アンロックのMQL4プログラム】
-----------------------------
1.プログラムの冒頭に、以下を記述。
#define KEY_SEMSTART "SEM_START"double SemValue = 0.0;int Errno = 0;
2.init関数内で、SemValueをWindowHandle()とMAGICナンバーからそのEAで一意に
なる様な値に設定している。
3.その他SemValue = [MAGICナンバー]/1000000.0+WindowHandle(Symbol(),Period())*100.0;
・ロジック中のOnError()関数は独自の共通関数で、ログ出力したり、
エラーレベルによってメール送信する関数。
・ロジック中のFWRefreshRates()関数は独自の共通関数で、RefreshRates()が
主な処理内容。
主な処理内容。
4.ロック関数
本当は、長時間ロックできなかったら、警告メールを出したほうがいいと思うけど、
まだそれは書いていない。あと、「ERR_GLOBAL_VARIABLE_NOT_FOUND」を成功扱い
にしているのは、取得した値がゼロだったら、「ERR_GLOBAL_VARIABLE_NOT_FOUND」
が返ってきてたので。
あと、後半のSleppしている箇所の各数字に関しては、「100」と「2000」以外にこれといってbool FWLockStart(){int i=0;// テスターだと発生しないので、正常終了するif( IsTesting() ) return(TRUE);// 成功するまで繰り返すwhile( !IsStopped() ){if( GlobalVariableSetOnCondition(KEY_SEMSTART,SemValue,0) ) return(TRUE);if( GlobalVariableSetOnCondition(KEY_SEMSTART,SemValue,SemValue) ) return(TRUE);Errno = GetLastError();switch(Errno){case ERR_STRING_PARAMETER_EXPECTED:OnError(ERR_CRITICAL , "FWLockStart:GlobalVariableSetOnCondition failed");return(FALSE);default:break;}i = i+1;if( i<20 ) Sleep(100);else if( i<40 ) Sleep(250);else if( i<80 ) Sleep(500);else Sleep(2000);FWRefreshRates();}return(FALSE);}
意味は無い。雰囲気として、徐々にリトライ間隔を伸ばしていきたかったという程度。
5.アンロック関数
ここでのミソは、大域変数自体がファイルI/Oしているので、GlobalVariableSet
の時、ファイル自体の競合が発生して、「ERR_GLOBAL_VARIABLES_PROCESSING」が返却
される可能性があるという事。なので、アンロックでもリトライすることに。
でもできるだけリトライしたいので、IsStopped()がTRUEの時は、最大2秒間まで
ループを続ける事にした。
bool FWUnlockStart(){// アンロックでリトライが発生したら、IsStoppedでも最大2秒まで繰り返すint elap = GetTickCount()+2*1000;// テスターだと発生しないので、正常終了するif( IsTesting() ) return(TRUE);// 成功するまで繰り返すwhile( TRUE ){// 現在のセマフォ値を取得するdouble wkSemVal = GlobalVariableGet(KEY_SEMSTART);Errno = GetLastError();switch( Errno ){case ERR_STRING_PARAMETER_EXPECTED:OnError(ERR_CRITICAL , "FWUnlockStart:GlobalValiableGet error.");return(FALSE);}// 他でロックしているか、ロックがされていなければ、正常リターンif( wkSemVal != SemValue ) return(TRUE);// アンロックするif( GlobalVariableSet(KEY_SEMSTART,0) != 0 ) return(TRUE);// アンロックに失敗した場合Errno = GetLastError();switch(Errno){case ERR_STRING_PARAMETER_EXPECTED:OnError(ERR_CRITICAL , "FWUnlockStart:GlobalVariableSet error.");return(FALSE);case ERR_GLOBAL_VARIABLE_NOT_FOUND:return(TRUE);default:break;}if( IsStopped() && (elap<GetTickCount()) ) break;Sleep(250);}return(TRUE);}
-----------------------------
色々もっともらしく書いたが、
実は、まだ動作実績が少ない。
だれか、考慮漏れ指摘してください。
そして、なんだかカサ増し感のある記事でお茶を濁して、「FXシステムトレード初心者奮闘記」の「MT4用EA開発時代」は、続きをどうしよう。。。
#バグ対応してから、今のところ警告メール受け取っていない!!1日程度だけど。。
0 件のコメント:
コメントを投稿