2011/05/13

MT4用EA開発時代 - オーダ関連関数のエラー発生時処理とリトライ制御



さて前回は、MT4用EAで、共通部品化を目指した内部構造設計した様子を書きました。今回は各論として、共通部品で作っているオーダ関連関数のエラーの種類毎の動作とリトライ制御について検討している様子を書いて見たいと思います。

まず、オーダ関連関数(OrderSend/OrderModify/OrderClose等)でエラーが発生したとき、無条件にリトライすればいいわけでもなく、エラー原因によって、リトライするのか諦めるのかをまずは決めなくちゃいけないと思う。

そして、後からエラー原因も判る様にできて、大量のログからすぐに問題箇所を見つけたいので、ログ出力時には、エラーの程度によって、直ぐに検索できる様な固定文字列を付与することに。

【エラーレベルの定義とログのヘッダ】
---------------------
エラーレベル毎の基本的な処理。異常検出時の基本的な考え方でもある。
"[[CRITICAL]]":致命的なエラー。メール通知と、新規発注停止。
"[[WARN]]"  :致命的ではないものの、処理失敗。メール通知。
"[[INFO]]"   :リカバリ可能と想定されるエラー検出とか、運用操作を行った時等。
---------------------

エラー発生後の動作毎に、以前の記事で検討した「エラー分類」と、実際の「エラーコード」の対応を検討してみた。(参考:エラーコード一覧) 

【エラー発生後処理毎のエラー分類とエラーコード】
-------------------
1.処理を中断し、新規発注停止とするもの
  ●処理内容詳細
   ・"[[CRITICAL]]"レベルでログ出力。
   ・ログ出力した内容をメールで通知。
   ・新規発注停止状態にする
    → 不正な使い方でリトライすると、被害が拡大するかもしれないから。
  ●エラー分類
   「1.プログラムミス起因によるもの」
   「2.原因不明なエラー」
  ●エラーコード
   ERR_COMMON_ERROR , ERR_INVALID_TRADE_PARAMETERS , ERR_OLD_VERSION , 
   ERR_ACCOUNT_DISABLED , ERR_INVALID_ACCOUNT , ERR_INVALID_STOPS , 
   ERR_LONG_POSITIONS_ONLY_ALLOWED , ERR_TRADE_EXPIRATION_DENIED , 
   ERR_TRADE_TOO_MANY_ORDERS , ERR_TRADE_HEDGE_PROHIBITED 
   その他どのエラーコードにも当てはまらないもの

   「ERR_ACCOUNT_DISABLED」はプログラムミスではなく、アカウントが
   無効になっている事が原因だけど、ミスを広義にとらえて、この分類に。

2.リトライを実施するもの
  ●処理内容詳細
   後述。
  ●エラー分類
   「3.サーバ/ネットワーク障害起因によるもの」
   「4.サーバ処理遅延に起因するもの/コンテキスト・ビジー」
  ●エラーコード
   ERR_SERVER_BUSY , ERR_TOO_FREQUENT_REQUESTS , 
   ERR_TRADE_TIMEOUT , ERR_TRADE_DISABLED , 
   ERR_ORDER_LOCKED , ERR_TOO_MANY_REQUESTS , 
   142 , 143 , 144 , ERR_TRADE_CONTEXT_BUSY ,  
   ERR_TRADE_PROHIBITED_BY_FIFO , ERR_TRADE_MODIFY_DENIED , 
   ERR_NO_CONNECTION
  ●特殊ケース
   「ERR_NO_CONNECTION」については、まずIsConnected()を繰り返す事に。
   これは、IsConnected()のヘルプを見ると、「接続に成功したらTRUE
   を返す」という様に読み取れて、解釈のしようによっては、IsConnected()を
   呼び出す事で、クライアント側から接続を試みる事ができるのかもしれない。
   なので、OrderSend/OrderModify/OrderCloseをリトライするのではなく、
   IsConnected()をリトライで成功したら、本来やりたかった、オーダ関連
   関数のリトライをすることに。
  ●迷い中
   「ERR_TOO_FREQUENT_REQUESTS」に関しては、正直どうしていいか
   迷い中だけど、とりあえずリトライすることに。
   エラー理由である「頻繁すぎる」というレベルが判らないし、サーバ側の誤判断
   の可能性もあるので、リトライしてみることにした。
   ただ、バグ起因による頻繁すぎる関数呼出しという危険性も。

3.一旦中断し、次のティックデータ受信を待つもの
  ●処理内容詳細
   [[INFO]]レベルのログ出力後、エラーとして関数を抜ける。
  ●エラー分類
   「5.サーバ側とクライアント側のデータ不整合によるもの」
  ●エラーコード
   ERR_INVALID_PRICE , ERR_PRICE_CHANGED , 
   ERR_OFF_QUOTES , ERR_REQUOTE

   本当は、Sleep() → RefreshRates()で、価格計算をやり直せばええんやけど、
   プログラムの構造上、次のティックデータを待つ事に。
   あと、「ERR_INVALID_PRICE」に関しては、何が起因してるのかわからないので、
   迷った。でも、クライアントとサーバの価格ズレが起因している可能性もあるので
   この処理分類にすることに。

4.システム異常じゃないけど、リトライせずに新規発注停止にするもの
  ●処理内容詳細
   ・[[CRITICAL]]レベルでログ出力
   ・ログの内容をメール送信
   ・新規発注停止状態にする
  ●エラー分類
   「6.証拠金に起因するもの」
  ●エラーコード
   ERR_NOT_ENOUGH_MONEY

5.システム異常じゃないけど、リトライしないもの
  ●処理内容詳細
   ・[[WARN]]レベルでログ出力
   ・ログの内容をメール送信   
  ●エラー分類
   「7.処理失敗だが異常ではないもの」
  ●エラーコード
   ERR_MARKET_CLOSED

6.正常終了扱いとするもの
  ●処理内容詳細
   正常として扱う
  ●エラー分類
   「8.実は問題ないもの」
  ●該当エラーコード
   ERR_NO_RESULT
----------------------

デモ口座で動かしているけど、トレード回数が少ないシステムだし、本物の口座とは微妙に動きが違うはずだから、実際のトレードを開始したら、見直しが発生するかもしれない。
少なくとも、デモ口座では運用上の問題が発生しない様にしたいところ。

そして、「後述」としていた、リトライ処理について。

【リトライ処理について】
----------------------
FXメタトレーダー実践プログラミング」とは異なるアプローチ。
上記書籍では、リトライ回数は不定で、トータルの経過時間が一定時間を超過した時にリトライアウトにする様になっている。
でも、1回のOrder関連関数の応答時間が異常に長くなった場合、リトライを一回もしないでリトライアウトになってしまう。なので、スリープは同様にするものの、リトライ回数は固定(プロパティ化はする)にする事に。

以下の処理を、指定した回数分繰り返す。

●ループ内の処理
 成功するまで、下記「1.」~「5.」を繰り返す
 1.IsStopped()チェック
   EA停止指示があれば、2.5秒以内に処理を終了させなければならないので、
   冒頭にチェック。IsStopped()がTRUEであれば、[[INFO]]レベルでログ出力後、
   大域変数を保存して、異常終了。 
 2.IsTradeAllowed()がTRUE
   競合しているときに処理をしてもしょうがないので、隙間があるものの、
   一応チェックすることに。
 3.オーダ関連関数呼出し
   エラー発生時は、エラーコード毎に前述の処理を行う。
 4.Sleep()処理
   一定時間スリープする。
 5.RefreshRates()処理
   スリープしている間に、次のティックデータが来ている可能性が高いので、
   RefreshRates()で、各種データ更新をする

このロジックでのもう一つのポイントは、IsStopped()を入れてる事と、オーダ関連関数呼出しの前にRefreshRates()をせずに、Sleep()の後にRefreshRates()している部分。

1点目のIsStopped()のチェックは、2.5秒ルールによるもの。
2点目は共通部品の構造上、発注可否判定まで終ったタイミングで、RefreshRates()を実施していて、それから、発注要否判定と価格計算をしているので、ループの最初ではRefreshRates()は不要という理由。
#発注要否判定や価格計算にはそんなに処理時間かからないから。
----------------------

で、ロジックが決まれば後は、リトライ回数とスリープ時間を仮決め。
今はデモ口座でしか試せないので、あくまで暫定値。問題あれば見直していく。

【リトライ処理でのリトライ回数とスリープ時間】
-------------------------------
●具体的なリトライ回数とスリープ時間について
 本当は、リトライ回数や、リトライ時のスリープ時間も検討したいところだけど、感覚で決
 めて、少なければ回数を増やすし、スリープ時間も見直す。デモ口座で動かしているEAの
 設定は以下。
 OrderSend →  リトライ回数: 3回、スリープ時間700msec
 OrderModify → リトライ回数: 4回、スリープ時間500msec
 OrderClose →  リトライ回数:11回、スリープ時間200msec

●IsConnected()リトライ時のスリープ時間について
 IsConnected()に関するスリープ時間は、意味あって2.1秒にすることに。
 まずは、IsStopped()の2.5秒ルールがある以上、2.5秒未満でないといけない。
 でも、通信でパケットロストが発生すると、パケット再送に2秒ぐらいかかるので、
 2秒以上にしたい。そして、通信復旧後の処理時間も残しておかなければならない。
 PC側は邪魔がはいりやすそうやけど、400msecあればさすがに十分やろ
 という決め方。そんな感じなので、2.25秒でもいいかも。
 ちなみに、リトライ回数は3回。これは感覚で決めた。
-------------------------------

今回、リトライ制御を検討したけど、リトライアウトしたとしても、次のティック・データ受信時にやりたかった処理ができる様になっていれば、あんまり神経質になる必要は無いかもしれない。

むしろ、「エラーが発生した」という事実を検出できる事の方がメリットとしてあるかもしれない。でもオオカミ少年にはなってはいけないから、リトライするという事。


ちなみに、なんでいきなりこんな局所的で細かい各論の記事になったかと言うと。
今作っている共通部品の構成に拠らず、MT4用EAプログラミングで汎用的な部分だと思うから。









つまり、共通部品固有の話ばっかりじゃ

誰も読んでくれないから。









そして、脈絡の無いブログ進行に、後ろめたさを感じながら、「FXシステムトレード初心者奮闘記」の「MT4用EA開発時代」は部品の説明等が続くのでした。
#運用系機能は、トレード回数が少ないとテストが難しいよ~

0 件のコメント:

コメントを投稿