さて前回は、MT4の環境について検討した様子を書き、売買回数を増やしたデモ口座環境を構築した結果、エラー検出した旨の警告メールの嵐にあったところまで話しました。今回は、そのエラー原因を探り、バグ対応している様子を書いてみたいと思います。
まずは、たった1日で起きた事象
【発生事象】
-----------------------
1.ERR_TRADE_DISABLEDが1分強大量発生してリトライアウト
2.ERR_INVALLID_STOPSでクリティカルエラーとなり、新規発注禁止状態に
3.ペンディングオーダをキャンセルした時に、ログ出力でエラー
4.ログ出力内容が不正(決済日時の内容が1970/1/1 0:00)
5.ログ出力でエラー(内部管理のチケット番号変数がいつの間にかゼロ)
-----------------------
なぁんだ、単純ミスばっかりじゃないの?
だいたい、ブログタイトルについてるOrderSelect()はどこいった??
と思いきや、根が深いものが。。
とにかく、発生事象毎に一体何が発生しどう対処したかを。軽い順に。
【「1.ERR_TRADE_DISABLEDが1分強大量発生」】
----------------------------
発生事象としては、OrderModifyでERR_TRADE_DISABLEDが発生し、延々とリトライして、
1回リトライアウトが発生。その後、次のティックデータ受信して、都合1分間強後やっと成功。
1回リトライアウトが発生。その後、次のティックデータ受信して、都合1分間強後やっと成功。
1つ前のバーではOrderModifyは成功している。
まずは、愛用している有志で作成されたヘルプを見ると以下の記載内容。
「Trade is disabled. All attempts to trade must be stopped.
トレードが無効になっています。取引しようとするすべての試みは停止
する必要があります。」
する必要があります。」
意味がわからないんですけど。。。
#英語からして。
「トレードが無効」って何??
マーケットが閉じていたら、ERR_MARKET_CLOSED
サーバが過負荷検出したのであれば、ERR_SERVER_BUSY
さらにサーバ過負荷になれば、ERR_TRADE_TIMEOUT
サーバダウンしてたら、ERR_NO_CONNECTION
トレードが頻繁すぎるのであれば、ERR_TOO_FREQUENT_REQUESTS
オーダの状態が変わろうとしているのであれば、ERR_ORDER_LOCKED
じゃないの??
じゃあ、「ERR_TRADE_DISABLED」の時って何が発生してるの??
なんで、トレードを停止させる必要があるの? リトライしたらダメなのか?? でも、1分強後にはリトライで復活してるで??
メンテナンス中なのか?
なんで、マーケットが閉じている時にメンテナンスしないの?
それとも、計画的犯行か?
それとも、オレの事が嫌いなのか??
ともかく意味不明だけど、最終的にリトライで成功しているので、リトライ回数を増やす事に。
今回、1分強で復旧したので、余裕を見て2分程度リトライすることにした。
そして、よく使うOrderModifyについて、ログから処理時間をサンプリングで
見てみると、処理時間は大体400msec強。
なので1回リトライする毎に、多めに見て500msecにすることに。
なので、OrderSend/OrderModify/OrderClose共、以下の設定に変更。
リトライ回数 :240回(2分×60秒×1000÷500msec)
スリープ時間 :500msec
----------------------------
【「2.ERR_INVALLID_STOPSでクリティカルエラー」】
----------------------------
もともと、以前のブログ記事の通り、オーダ直前にAsk/BidとMODE_STOPLEVELから、
価格補正をしてOrderSendするので、ERR_INVALID_STOPSは発生しないはずと思って
いたので、クリティカルなエラー扱いにし、結果、今回の仕様上「新規発注禁止状態」になる。
で、ログの内容を見てみると、ちゃんとAsk/Bidと、発注逆指値はぎりぎりだけど、
MODE_STOPLEVELの外側で問題ない発注価格。
想定されるのは、OrderModifyがサーバに到達した頃には、価格が変動して、
MODE_STOPLEVEL内に入ってしまったという状況。
よく考えれば当たり前の状況とも言うが。。。
なので、「ERR_INVALLID_STOPS」が発生した場合は、一旦抜けて次のティックデータを
待つように修正。そして、ログは出力するけど、メール通知はしない。
----------------------------
【「3.ペンディングオーダをキャンセル時に、ログ出力でエラー」】
----------------------------
これが、実は原因不明で、仮説を元に、別の手段で逃げた。
何かと言うと、OrderClose/OrderDeleteしたオーダのチケット番号を指定して、
OrderSelect(チケット番号 , SELECT_BY_TICKET)した後に、ログ出力する情報の、
OrderCloseTime()とかを取得する処理。ところが、OrderSelectでFALSEが
返却され、エラーに。
発生条件は、ペンディングオーダをOrderDeleteした後で、前述のOrderSelectした時にFALSE
が返却される。しかもヘルプを見ると、OrderSelectはエラーコードを設定しない関数だから、原因も不明。
が返却される。しかもヘルプを見ると、OrderSelectはエラーコードを設定しない関数だから、原因も不明。
最初はタイミングの問題かとも思ったけど、ペンディングオーダだけでしか発生しなかったし、逆にペンディングオーダで成功したケースも無かったので、タイミングの問題とは考えにくい。
#当然テスターでは正常動作していたけど。。
なのであくまでも仮説だけど、OrderSelectでは、OrderDelete済みのオーダ情報取得はできない仕様としか考えられない。#FX業者依存かも。。。
Twitterで知り合った方に教えて頂いたブログ記事では、上手く動いているみたいだけど、少なくともウチの環境では動かんもんは動かん。 #だから、FX業者依存の可能性が。。
で、どうしたかというと、元々ペンディングオーダをキャンセルした場合はトレードログを出力しない仕様なのでので、別の内部変数でペンディングオーダだったのかどうかを判断して、発注約定済みオーダの決済時のみ、OrderSelectする様に修正。
ちなみに、元々の判断方法は、OrderSelect(SELECT_BY_TICKET)した後、OrderType()がOP_BUY/OP_SELLのどちらでも無い場合は、ペンディングオーダだったという判断ロジック。
----------------------------
【「4.ログ出力内容が不正」】
【「5.ログ出力でエラー」】
----------------------------
実はこの2つは同じ原因で、一番根が深かくて、結論を出すのに時間がかかった。
あくまで仮説ですけど。
さて、まずは発生した事象をもう少し詳しく。
まずは、具体的にログの内容とロジックから個々の事象詳細を見てみると。
●事象「4.ログ出力でエラー」
これは、内部管理しているチケット番号を格納している変数に、決済されたチケット番号が入っ
ているはずが、ゼロになっていた。ログ出力情報を取得するために、このチケット番号を元に、
OrderSelect(SELECT_BY_TICKET)をしたけど、指定したチケット番号がゼロなので、当然FALSE
が返却されてエラー。
が返却されてエラー。
じゃあいつゼロになったかと言うと、ログ上では、決済処理の共通関数を抜けた直後には既に
ゼロになっていた。この時点でなんか嫌な予感。
#C言語とかだと不正なメモリアクセスでよくあるバグだけど、MQL4の場合は、このあたりのややこしい事はできなくしているので考えにくい。
●事象「5.ログ出力内容が不正」
これは、決済されたオーダのログ出力結果で、決済日が「1970/1/1 0:00」になっていた。
つまり、OrderCloseTime()がゼロなので、決済されていない事を意味している。
しかも、ログ出力前に同じ通貨ペアの決済後のログ出力で、OrderSelectでFALSEが返却
されている。しかも、ペンディングオーダじゃない。
最初思ったのは、決済された直後なのでOrderCloseTime()に最新情報が反映されていない
と思った。結果、全然違う原因。
次に、ログを時系列で見てみると、とっても恐ろしい事が発生していた。。
●動作内容概略
1.GBPJPYのFWModifyOrder(OrderModifyする共通関数)と、AUDUSDの
FWClosePositions(通貨ペアとMAGICが同じポジションを全て決済する共通関数)
が同時に処理を開始
2.AUDUSDのEAが、GBPJPY(#5409644)を、OrderCloseして成功 ★恐ろしい事1
→ ここで、内部管理しているチケット番号を格納した変数の内容をゼロに設定。
3.AUDUSDのEAが、FWModifyOrder()開始(キーはOrderTicket())
初回で、ERR_TRADE_TIMEOUTが発生し、リトライ開始
最初のリトライ後、ERR_NO_RESULTが返却され、成功扱いに。
4.AUDUSDのEAが、FWClosePositions()開始
AUDUSDのオーダ(#5412969)をOrderCloseして成功
5.ログ出力の際、内部管理しているチケット番号を格納した変数が0なので、
エラー検出。★発生事象「4.」の箇所。
6.GBPJPYのEAが、AUDUSD(#5412969)のOrderModify(キーはOrderTicket())
に成功 ★恐ろしい事2
全く関係ない別の通貨ペアのポジションを勝手にいじってしまっている。
恐ろしい。。。
つまり、これこそが悪の根源じゃないかと。
チャートで動作しているEAが、その通貨ペアとMAGICが同じものを全て決済(OrderClose/OrderDelete)するときは、OrderSelect(SELECT_BY_POS)で、OrdersTotal()-1から、デクリメントしながら、サーチしているが、何回プログラムを見ても、違う通貨ペアが選ばれる様なロジックは無い。
最初、OrderSelectした結果が最新化されていないんじゃないかと思ったけど、それだと辻褄が合わない。
そして嫌な予感が。
※2011/6/14追記:以下の仮説は、このブログ記事で誤りである事が解りました。
仮説としては、OrderSelectした後に情報取得する関数(OrderTicket()とか)が返却する内容は、1つのMT4あたり、1つだけ。例え複数のチャートでEAが同時に動作していたとしても。
つまり下図の例で言うと、AUDUSDのEAがOrderSelectした後に、GBPJPYのEAがOrderSelectした場合、その後のAUDUSDのEAで取得したOrderTicket()の内容は、GBPJPYでOrderSelectした内容。
チャート毎にOrderSelectしたデータを保持しているのかと思っていた。。。
「直接的な事象」から、この仮説で辻褄があうかを見てみる。
●「4.ログ出力内容が不正(決済日時が1970/1/1 0:00)」
今回のポイントは、GBPJPYのFWModifyOrder()共通関数と、AUDUSDのFWClosePositions()
共通関数が同時に走り始めた点。
共通関数が同時に走り始めた点。
FWModifyOrder()もFWClosePositions()も、OrderSelect()ループ後のOrderTicket()を元に
OrderModify()/OrderClose()をしている。
そこで、AUDUSDでOrderSelectによるループが完了した後、GBPJPYでのOrderSelectの
ループが走ったという状況だったとして、仮説が正しければ、以下の状況に。
ループが走ったという状況だったとして、仮説が正しければ、以下の状況に。
前述の「動作内容概略」の「2.」でログ出力した際、AUDUSDを決済したはずが、実際に決済
成功したのは、GBPJPY。そして、「内部管理のチケット番号変数」でOrderSelectを実施し、
ログ出力。当然実際にはAUDUSDのポジションは決済されていないので、
OrderCloseTime()はゼロ。
OrderCloseTime()はゼロ。
辻褄があう。
ちなみに、ポジション決済した後にログ出力完了すると、「内部管理のチケット番号変数」の
値をゼロに変更している。#この点は、次の内容に関係してくる
●「5.ログ出力でエラー(内部管理のチケット番号変数がいつの間にかゼロ)」
・前述の通り、一回間違えてAUDUSDのEAが、GBPJPYをクローズしたために、
AUDUSDの「内部管理のチケット番号変数」がゼロに設定されている。
・次のティックデータ受信し、AUDUSDのstart()が開始。
・ポジション保有中かどうかを確認する関数は、OrderSelectでループして保有ポジション有無
を確認しているので、AUDUSDはポジション保有中で決済要と判断される。
・ポジション保有中で決済要なので、FWClosePosition()が動き始める。その処理では、
OrderSelectでループしていて、無事にAUDUSDが見つかり、OrderClose()成功。
・OrderClose()に成功したので、ログ出力処理が開始。
・ログ出力処理内容を取得するときは、OrderSelectを「内部管理のチケット番号変数」で
しているが、前述の通り、既にゼロに設定されているため、OrderSelectがFALSEを返却し、
エラー発生。
この仮説であれば、発生事象の辻褄があってしまった。
----------------------------
上記事象はつまるところ、
1つのMT4で複数EAを同時動作させた場合、
OrderSelect関係が上手く動作しないという事。
やだ。
「やだ」と、駄々をこねてもしょうがないので、いろいろ考えてみた結果、1つのMT4で同時に複数のstart()関数が動かない様にする事に。
じゃあ次は、それをどうやって実現するかと言う話。
続きは次回。
ネタ切れ気味だから。
そして、ネタ切れ気味を理由に記事を中断して、「FXシステムトレード初心者奮闘記」の「MT4用EA開発時代」は、この「OrderSelect問題」の解決に向かうのでした。
#MT4はやっぱり手ごわい。。。
こんばんは。次回はグローバル変数を mutex として使う記事でしょうか (笑)。
返信削除> 【「1.ERR_TRADE_DISABLEDが1分強大量発生」】
> 「トレードが無効」って何??
日本時間の朝6時ごろなら、ニューヨーク引け時刻 (17:00 ET) 前後の、いわゆる締め処理のため、(一時的に)取引できませんということだと思います。気配値ウインドのシンボルが灰色に変わるはずです。DISABLED にせずに、その時刻帯の注文をはねるブローカの方が多いのですが。
> 仮説としては、OrderSelectした後に情報取得する関数(OrderTicket()とか)が返却する内容は、1つのMT4あたり、1つだけ。
> 例え複数のチャートでEAが同時に動作していたとしても。
これが正しければ世界中で悲劇が起こっていると思いますが (^^;)、たまに見掛ける下記の重篤なミスではないですよね?
for (i = OrdersTotal() - 1; i >= 0; i--) {
OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
if ((OrderSymbol() == Symbol()) && (OrderMagicNumber() == MagicNumber)) {
…
}
}
どこがミスかというと、OrderSelect() の戻り値を見ずに OrderSymbol() 等を使っているところですが。
戻り値が false の場合 (つまり、建玉内容の変更などの例外処理が起きた場合)、OrderSymbol() 等の内容に一貫性がなくなることもあり得るでしょう (シンボルは注文 #1 だけど、チケットは注文 #5 とか)。
【旧ブログから転記】
※ このコメントは、旧ブログで頂いたコメントを、ブログ筆者が転記したものです。
kartz様
返信削除いつもどうもです!
> こんばんは。次回はグローバル変数を mutex として使う記事でしょうか (笑)。
さすがに、グローバル変数はmutexとして使えないので、「GlobalVariableSetOnCondition」
を使うという記事です。ネタばれですが(笑)
> > 【「1.ERR_TRADE_DISABLEDが1分強大量発生」】
> > 「トレードが無効」って何??
>
> 日本時間の朝6時ごろなら、ニューヨーク引け時刻 (17:00 ET) 前後の、いわゆる締め処理のため、(一時的に)取引できませんということだと思います。気配値ウインドのシンボルが灰色に変わるはずです。DISABLED にせずに、その時刻帯の注文をはねるブローカの方が多いのですが。
なるほど~
そういう瞬間的な停止もあるんですね!
これが不思議なことに、2日間連続で発生したのですが、1日目は6時、2日目は3時という状況で、
定期的な事象ではなさそうなんですよ。
それもあって、ちょっと混乱。
#デモ口座ですが。
800文字制限ゆえ、cont...
【旧ブログから転記】
※ このコメントは、旧ブログのコメントを、ブログ筆者が転記したものです。
kartz様
返信削除続きです。
> > 仮説としては、OrderSelectした後に情報取得する関数(OrderTicket()とか)が返却する内容は、1つのMT4あたり、1つだけ。
> > 例え複数のチャートでEAが同時に動作していたとしても。
>
> これが正しければ世界中で悲劇が起こっていると思いますが (^^;)、たまに見掛ける下記の重篤なミスではないですよね?
>
> どこがミスかというと、OrderSelect() の戻り値を見ずに OrderSymbol() 等を使っているところですが。
>
> 戻り値が false の場合 (つまり、建玉内容の変更などの例外処理が起きた場合)、OrderSymbol() 等の内容に一貫性がなくなることもあり得るでしょう (シンボルは注文 #1 だけど、チケットは注文 #5 とか)。
残念ながら、OrderSelect()の戻り値チェックはしていて、TRUEの時だけ、MAGICとSymbolチェックしてます。
本当ならソース見てほしいぐらいなのですが、2KSぐらいあるんです。。(コメント、空白行含みますが)
本当に、私のミスであって欲しいところです。。
ちなみに、状況としては、1MT4で1種類のEAで6通貨ペアを5分足でトレードしてて、
ポジション保有中はほぼ毎バーでOrderModifyしてます。
で、実際これが発生したのは、1回だけなんですよ。
【旧ブログから転記】
※ このコメントは、旧ブログのコメントを、ブログ筆者が転記したものです。
おはようございます。
返信削除> さすがに、グローバル変数はmutexとして使えないので、「GlobalVariableSetOnCondition」
> を使うという記事です。
??… ですから "GlobalVariable" を mutex (0/1-semaphore) として使うんでしょう。。。
# GlobalVariableSetOnCondition() はそれが可能な唯一の MQL API であって。
trade context busy の回避策としてグローバル変数を (mutex として) 使うのは周知のテクニックなので (例えば、下記 URL 参照)、それと同様に start() の排他制御をするのだろうと。
http://articles.mql4.com/141
> 残念ながら、OrderSelect()の戻り値チェックはしていて、TRUEの時だけ、
> MAGICとSymbolチェックしてます。
そうですか。。。じゃあ、世界中が注目していますね。。。
でも、start() 全体を排他制御したくはないですよね。
CPU 負荷は下がって嬉しいかもしれませんが。
ソースを拝見するのは構わないのですが、肝の FWModifyOrder() と FWClosePositions() だけ公開してみてはいかがでしょうか?
【旧ブログから転記】
※ このコメントは、旧ブログで頂いたコメントを、ブログ筆者が転記したものです。
kartz様
返信削除> ??… ですから "GlobalVariable" を mutex (0/1-semaphore) として使うんでしょう。。。
> # GlobalVariableSetOnCondition() はそれが可能な唯一の MQL API であって。
>
あ、そうですそうです!
#「グローバル変数」を思いっきり、C言語でいうスコープがグローバルなメモリ変数と読み違えてました。。
#命名がややこしい。。
ちなみに、もうこの部分の記事投稿しちゃいました。
> > 残念ながら、OrderSelect()の戻り値チェックはしていて、TRUEの時だけ、
> > MAGICとSymbolチェックしてます。
>
> そうですか。。。じゃあ、世界中が注目していますね。。。
「世界中が注目」とは嬉しい様な、悲しい様な(笑)
いかんせん、MT4を始めたばかりで、何がMT4の常識なのかすらよくわからないので。。
> でも、start() 全体を排他制御したくはないですよね。
> CPU 負荷は下がって嬉しいかもしれませんが。
これがstart()全体でしちゃったんですよね(笑)
> ソースを拝見するのは構わないのですが、肝の FWModifyOrder() と FWClosePositions() だけ公開してみてはいかがでしょうか?
あ!見てもらえるの構わないです?
ちょっと、説明文とか作るので、こDropBox経由でダウンロードできる様に
して準備できれば、またコメントかブログ記事にします。
#どっちにしろ、公開して使ってもらえる様にしてもらうつもりの共通関数なので。
【旧ブログから転記】
※ このコメントは、旧ブログのコメントを、ブログ筆者が転記したものです。
こんばんは。
返信削除続編の記事を拝見しました。
個人的には、OrderSelect() の仮説に対処するためだけにここまでするなら、EA の数だけ MT4 を起動した方がスマートかなと思いました (笑)。
うちの運用では仮説を支持する事例が見つかっていないので、そう思うのでしょうが。
【旧ブログから転記】
※ このコメントは、旧ブログで頂いたコメントを、ブログ筆者が転記したものです。
kartz様
返信削除どうもです!
> 続編の記事を拝見しました。
長文読んで頂いて、ありがとうございます!
>
> 個人的には、OrderSelect() の仮説に対処するためだけにここまでするなら、EA の数だけ MT4 を起動した方がスマートかなと思いました (笑)。
> うちの運用では仮説を支持する事例が見つかっていないので、そう思うのでしょうが。
仮説を「是」とした場合の対処方法は、好みの世界ですね~
私の場合は、以前のMT4構成のブログ記事の通り、ただでさえMT4のインストール数が多いので、
「複数種類のEA×複数通貨ペア」という最終形(夢?)になると、構成管理が面倒になって、
作業ミスの温床になりそうなので、こうすることにしました。
でもロックしないプロパティ値を設けてもいいかもなぁ。
【旧ブログから転記】
※ このコメントは、旧ブログのコメントを、ブログ筆者が転記したものです。