Live口座でCTradeクラスのResultPrice()関数の戻り値が0になってしまう現象について

 

長文になりますので、時間のある方がいらっしゃれば、よろしくお願い致します。

現在、OANDA証券のCFDのJP225でEAを利用した取引きを行っていて、MT4からMT5に変更しようとしています。
MT4と同等のロジックで動作するEAをMT5(MQL5)で作成し、デモ口座(Demo口座)では意図通りの動作していましたが、
本番口座(Live口座)に切り替えた所、意図しない動作をしたため、その理由や対処法を確認したいと考えております。
(OANDA証券のデモ口座以外に、外為ファイネスト、フィリップ証券のデモ口座でも問題は発生しませんでした)

また、OANDA証券に直接質問したところ、EAについての質問はサポート対象外ですと回答され、
デモ口座と本番口座の違いを質問したところ、デモ口座と本番口座でサーバーは同じですが、
本番口座の場合には実際のマーケットに注文状況を確認しにいくため、遅延が生じる可能性があるとの回答で、
それに起因した現象ではないかというのが現状での認識です。


具体的には、
OnTick()関数内で、注文後に注文価格を取得する部分です。

今回、取引関数に簡単にアクセスするためのクラスCTradeを利用しています。(添付のプログラムです)
Sell()関数実行後に、ResultPrice()関数を実行し、これを注文価格用の変数に代入し、
これを監視して、前回の注文価格から一定の間隔(例:JP225では200円)レートが変化したら追加注文を行うロジックですが、
ResultPrice()関数の戻り値が0になってしまっているため、OnTick()関数が呼び出される度に注文が無限に行われてしまいます。
(本番口座ではResultPrice()関数の戻り値が0で、デモ口座では注文価格になるため意図通り動作する)

MT4(MQL4)の場合では、
OrderSelect()関数の実施後に実行する、選択中の注文の注文価格を返す
OrderOpenPrice()関数と同様の処理がしたいという事を意味します。

インターネットで情報を検索した所、
下記の参考資料①により、retcodeがTRADE_RETCODE_DONEになるまで、値が確定しないとあるので、
trade.ResultRetcode()がTRADE_RETCODE_DONEとなるまで、待つようにプログラムを変更しても結果は変わらず、
参考資料②では、デモ口座と本番口座で結果が変わるという同様の事例が報告されていて、
OnTradeTransaction()関数を利用せよとアドバイスがありましたが、具体的なコードの書き方は示されていませんでした。

プログラムを色々と変更した結果、
ResultPrice()関数は利用せず、注文後に2000ms(決め打ちの時間)のSleep()関数を実行して強制的に待ち時間を作り、
PositionSelectByTicket()関数を実行後に、PositionGetDouble(POSITION_PRICE_OPEN)関数を実行すると、
一応、注文価格が正しく取得できました。
またSleep()関数による待ち時間を300ms程度に減らすと、動作がおかしくなりました。

OnTradeTransaction()関数も試しましたが、Sell()関数実行時のOnTick()関数内では呼び出されず、
OnTick()関数実行後に呼び出される仕組みのようで、
同じOnTick()関数内で注文価格を取得したく、強制的に呼び出しを行いたいとも思いましたが、方法が分かりませんでした。

暫定の方法で一応動作していますが、
待ち時間が2000msの決め打ちになってしまっている点が若干気持ち悪く、
同じような現象を確認した方や、MQL5について知見のある方で、
問題が発生しないようにより確実に、なるべく簡単なコードの書き方が分かる方がいらっしゃれば、
ご教授よろしくお願い致します。(※理想は、デモ口座、本番口座、FX会社を問わずに動作するEA)


参考資料①PDFファイル OrderSend()について有志がまとめた資料
http://www.green.dti.ne.jp/sdimension/mql/mql5_2013_08gatu.pdf

参考資料②同様の事例の報告 MQL5フォーラムでのQ&A(英語)
CTrade ResultPrice() is 0.0 in live trading
CTrade ResultPrice() is 0.0 in live trading
  • 2018.05.18
  • www.mql5.com
I have the problem, that I do not get the price of the execution, when I use the class CTrade, the position is opened or closed, but the price is 0...
 

デモ口座で問題なく、本番口座で問題ありということならそれは処理による遅延ではないかと思いますが、本番口座での問題は実際に再現して確かめることができないので詳しいことは分かりません。


ところで、if(test == true && ...... の部分は待機のためにループ処理したのですか?

DLしたプログラムでは、trueにならなければ実行しないまま通過してしまいます。ループ処理すればSleep(2000)は必要ないように思います。


TradeTransactionについては、

例えば、買いの成行注文を送信すると、注文が処理され、買い注文が口座に記録されます。その後注文が実行されオープン注文の表から削除されて注文履歴に追加されます。 約定が履歴に追加され新しいポジションが作成されます。これらのアクションは全て取引トランザクションです。このようなトランザクションの端末への到着が TradeTransaction イベントです。https://www.mql5.com/ja/docs/runtime/event_fire#tradetransaction

と、説明されており、ターミナルに送られてくるトレード履歴に関する処理のようです。ですから TradeTransaction イベントが発生すれば処理内容がターミナルに到着しているということになります。

結果はOnTradeTransaction関数のMqlTradeResult構造体に格納されています。

https://www.mql5.com/ja/docs/event_handlers/ontradetransaction

https://www.mql5.com/ja/docs/constants/structures/mqltraderesult


その他に、OrderSend関数を使って注文を実行した場合もMqlTradeResultにより結果が返されますので、こちらも使えるかもしれません。

https://www.mql5.com/ja/docs/trading/ordersend


ところでOSは何ですか?Win11 それとも Win10 64bit?

MQL5のドキュメンテーション: イベント処理 / OnTradeTransaction
MQL5のドキュメンテーション: イベント処理 / OnTradeTransaction
  • www.mql5.com
OnTradeTransaction - イベント処理 - MQL5 リファレンス - MetaTrader 5 のためのアルゴリズムの/自動化されたトレーディング言語のリファレンス
 
コード読んでみました。

m_async_modeがfalseでしたら
CTrade::PositionOpen では、OrderSend()で注文しているようですので

trade.ResultRetcode() が TRADE_RETCODE_PLACED は戻って来ないと思われます。(OrderSendAsync用なので)


OANDAのCFDデモ環境を確認してみましたところ
執行モード:Market Execution SYMBOL_TRADE_EXECUTION_MARKET
成り行き注文の場合、JP225のFillingモードは「ORDER_FILLING_FOK」か「ORDER_FILLING_IOC」のどちらかだと思いますが

「ORDER_FILLING_IOC」の場合、一部約定が考えられますので、
trade.ResultRetcode() が「TRADE_RETCODE_DONE_PARTIAL」かどうかの判断が必要になると思われます。

TRADE_RETCODE_DONE_PARTIAL

デモとリアルで動作が異なる点を確認するならば、このあたりかなって思います。

的外れの場合は、ごめんなさい。


ではでは。
MQL5のドキュメンテーション: 標準的な定数、 列挙と構造体 / エラーコードと警告コード / 取引サーバのリターンコード
MQL5のドキュメンテーション: 標準的な定数、 列挙と構造体 / エラーコードと警告コード / 取引サーバのリターンコード
  • www.mql5.com
取引サーバのリターンコード - エラーコードと警告コード - 標準的な定数、 列挙と構造体 - MQL5 リファレンス - MetaTrader 5 のためのアルゴリズムの/自動化されたトレーディング言語のリファレンス
 
Nagisa Unadaさん、ご返答ありがとうございます。

デモ口座では発生しない現象で、本番口座のみ発生する現象で、検証するにも実際のお金が掛かり、
前回注文した約定価格を取得することがロジックの肝になっているので、正直厄介な問題です。

WEB上で公開されている様々なサンプルコードも、本番口座でも動作確認したかは分からず、
MT5の利用者・実運用者もまだ少ないため、検索しても見当たらない事例なのかもしれません。

さて、if(test == true && ...... の部分は待機のためにループ処理しましたかという点につきましては、
trade.ResultRetcode()がTRADE_RETCODE_DONEとなるまで、待つようにプログラムを変更した際に、
以下のようにプログラムを変更しました。(可能であれば、sleep()関数は使いたくないと考えています)
結果としては、本番口座でもデモ口座でも、else if()の条件を満たし、for文のループに入ることは全くありませんでした。


         //注文処理
         bool test = trade.Sell(onelot, tuuka, bid, 0.0, 0.0, IntegerToString(ordersuu,3));
         
         if(test == true)
         {
            uint nowRetcode = trade.ResultRetcode();
            
            //注文完了時
            if(nowRetcode == TRADE_RETCODE_DONE)
            {
                //初回購入時はmax値,min値を共に再設定する
                ordersuu++;
                max = trade.ResultPrice();
                min = max;
                Print("新規購入(売り)、ポジション数:",ordersuu);
            }
            //注文中の場合は、注文完了まで(2000ms)待機する
            else if(nowRetcode == TRADE_RETCODE_PLACED)
            {
               Print("現在注文中、注文完了まで待機します");
               for(int i = 0;i<200;i++)
               {
                  nowRetcode = trade.ResultRetcode();
                  
                  //注文完了時
                  if(nowRetcode == TRADE_RETCODE_DONE)
                  {
                     //初回購入時はmax値,min値を共に再設定する
                     ordersuu++;
                     max = trade.ResultPrice();
                     min = max;
                     Print("新規購入(売り)、ポジション数:",ordersuu);
                     break;
                  }
                  //注文中のままの場合は、10msスリープする(これを200回繰り返す)
                  else if(nowRetcode == TRADE_RETCODE_PLACED)
                  {
                     Sleep(10);
                  }
                  //注文中、注文完了以外になった場合
                  else
                  {
                     Print("注文中にエラー発生!", "、エラーコード:",GetLastError());
                     break;
                  }
                  if(i == 199)
                  {
                     Print("注文に時間が掛かりすぎます!");
                  }
               }
            }
            else
            {
               Print("購入失敗:リターンコードエラー", "、エラーコード:",GetLastError());
            }
         }
         else
         {
            Print("購入失敗:注文処理フラグがfalse", "、エラーコード:",GetLastError());
         }


このことから、本番口座では、retcodeがTRADE_RETCODE_DONEになっても、約定価格が取得できるとは限らないと判断し、
注文処理の直後にsleep()関数を入れてみることを考え、2000msや300ms、5000msの待機時間を試してみましたが、
約定価格が取得できず、本番口座では、待ち時間を入れてもResultPrice()関数は意図通り取得できないと考えました。

さらに、プログラム起動直後のOnInit()関数内で行っていた約定価格取得方法を試したところ、
sleep()関数と組み合わせると、約定価格が取得できたので、こちらを利用しているのが現状です。

また、従来のOrderSend(request,result)関数につきましては、MT4からMT5へ移行をしていた早めの段階で、
構造体を宣言、初期化したり、設定するパラメータが多く、CTradeクラスを利用した方がOnTick()関数の呼び出しから
注文処理までの実行時間が短く、MT4ライクで、コードも短くなるため、デモ口座を試している段階で利用しなくなりました。
という事で、resultの構造体を利用した場合は未検証で、試してみる価値があるかもしれません。


TradeTransactionにつきましては、OnTradeTransaction()関数の動作確認をした際に、
取引処理構造体 MqlTradeTransactionの取引トランザクションの種類 ENUM_TRADE_TRANSACTION_TYPEの状態遷移を確認しました。
(※TRADE_TRANSACTION_は省略)

OANDA証券(本番口座)
・注文時:REQUEST→ORDER_UPDATE→REQUEST→DEAL_ADD→ORDER_DELETE→HISTORY_ADD
・決済時:ORDER_ADD→REQUEST→ORDER_UPDATE→REQUEST→DEAL_ADD→ORDER_DELETE→HISTORY_ADD
OANDA証券(デモ口座)
・注文時:ORDER_DELETE→DEAL_ADD→HISTORY_ADD→REQUEST(DEAL_ADD→ORDER_DELETE→HISTORY_ADD→REQUESTの場合もあり)
・決済時:ORDER_ADD→DEAL_ADD→ORDER_DELETE→HISTORY_ADD→REQUEST
外為ファイネスト(デモ口座)
・注文時:DEAL_ADD→ORDER_DELETE→HISTORY_ADD→REQUEST
・決済時:ORDER_ADD→DEAL_ADD→ORDER_DELETE→HISTORY_ADD→REQUEST
フィリップ証券(デモ口座)
・注文時:ORDER_DELETE→HISTORY_ADD→DEAL_ADD→REQUEST
・決済時:ORDER_ADD→ORDER_DELETE→HISTORY_ADD→DEAL_ADD→REQUEST

数回試しただけですので、必ずしもこの遷移になるとは限りませんが、
本番口座の最後はHISTORY_ADD、デモ口座の最後はREQUESTになるのが大きな違いで、
HISTORY_ADDでポジションが反映されるならば、最後になるので、遅延が生じ待機が必要なのかと思いました。
(ORDER_ADDがTRADE_RETCODE_PLACED、DEAL_ADDがTRADE_RETCODE_DONEに対応?)

OSは、Windows10 64bit 21H2です。
 
Yutaka Okamotoさん、ご返答ありがとうございます。

本番口座では、初めに非同期モードフラグがデフォルトでtrueになっている可能性も疑い、
OnInit()関数の始めに行うCTradeクラスに共通する注文パラメータの設定で、
trade.SetAsyncMode(false);の一文を追加してみましたが、挙動は変わりませんでした。

また、注文の執行タイプの設定も、MT4からMT5へ変更する際に初めて確認したエラーで戸惑いました。
CTradeクラスのSetTypeFillingBySymbol(symbol)関数を利用することで、
FX会社の違いを考慮しなくても、設定可能になるので、CTradeクラスを好んで使うようになりました。

trade.ResultRetcode()がTRADE_RETCODE_DONE_PARTIALになるかどうかも、
厳密には判定しないといけませんが、今回注文したロット数が最小注文単位で、
これ以上分割されることは無いと考え省略しました。
また、仮にtrade.ResultRetcode()がTRADE_RETCODE_DONE_PARTIALになった場合、
Print文で購入失敗と表示されますが、そのようなことはありませんでした。
 
長谷川健治 #:
Yutaka Okamotoさん、ご返答ありがとうございます。

本番口座では、初めに非同期モードフラグがデフォルトでtrueになっている可能性も疑い、
OnInit()関数の始めに行うCTradeクラスに共通する注文パラメータの設定で、
trade.SetAsyncMode(false);の一文を追加してみましたが、挙動は変わりませんでした。

また、注文の執行タイプの設定も、MT4からMT5へ変更する際に初めて確認したエラーで戸惑いました。
CTradeクラスのSetTypeFillingBySymbol(symbol)関数を利用することで、
FX会社の違いを考慮しなくても、設定可能になるので、CTradeクラスを好んで使うようになりました。

trade.ResultRetcode()がTRADE_RETCODE_DONE_PARTIALになるかどうかも、
厳密には判定しないといけませんが、今回注文したロット数が最小注文単位で、
これ以上分割されることは無いと考え省略しました。
また、仮にtrade.ResultRetcode()がTRADE_RETCODE_DONE_PARTIALになった場合、
Print文で購入失敗と表示されますが、そのようなことはありませんでした。


そうですか、ほとんど確認された後の投稿だったとお見受けいたしました。失礼しました。

プログラムを色々と変更した結果、

ResultPrice()関数は利用せず、注文後に2000ms(決め打ちの時間)のSleep()関数を実行して強制的に待ち時間を作り、

PositionSelectByTicket()関数を実行後に、PositionGetDouble(POSITION_PRICE_OPEN)関数を実行すると


わたしは、この実装に近く

[注文]

①OrderCheck()で注文のチェック

②OrderSend()で注文

③②の戻り値がTrueの場合に、.retcodeを判断して、注文の成功確認

④③で失敗しているならば、エラー内容によりリトライ


[ポジション取得]

①PositionSelectByTicket()

②PositionGetDouble(POSITION_PRICE_OPEN)で約定価格を取得

...

(実際は、未決済ポジションを全て取得して、突合を行っています( PositionsTotal()分 PositionGetTicket()で選択して))


[注文]と[ポジション取得]は同じスレッド(同じOnTickイベント内)では行っておらず

注文が通ったら、次のタイミングでポジションを取得するようにしています。


設計思想が異なるため、参考にならないかもしれません。

(挿入しているチャートの通貨ペア以外もコントロールしたい、ms単位での精度を求めず、数秒単位で処理を行いたい

ので、OnTickイベントではなく、OnTimerイベントで管理しています。)


サーバー側の実装はブローカー毎に微妙に異なっていると思われるので悩ましいところですね。

失礼しました。

ではでは。


 
Yutaka Okamotoさん、またまたご返答ありがとうございます。

同じOnTick()関数内で注文価格を取得したいというのは、
MT4(MQL4)で出来ていたからMT5(MQL5)でも出来るに違いないという根拠の無い考えで、
なるべく同じように記述したいというただの願望です。

今回のプログラムのロジック的には、注文処理をした次のOnTick()関数の呼び出しまでに
約定価格の取得が出来ていれば問題はありませんので、ここは柔軟にMT5(MQL5)の作法を取り入れ、
注文処理を行ったOnTick()関数内では、retcodeがTRADE_RETCODE_DONEかどうかを判定するまでにとどめ、
先人の方々が利用せよと言っていたOnTradeTransaction()関数の呼び出しを自然に待ち、
この関数内で、(今回の場合)取引トランザクションの種類 ENUM_TRADE_TRANSACTION_TYPEが
TRADE_TRANSACTION_HISTORY_ADDまで遷移したら、約定価格を取得する
という方法を試してみたいと思います。

注文処理をした次のOnTick()関数の呼び出しと、OnTradeTransaction()関数の呼び出しの
どちらが先に実行されるかが、今回の場合、肝になりますが、
先人の方が利用せよと言っているのもそれなりに根拠があるからだと思いますので、
取り合えず、試してみたいと思います。

これが上手くいけば、Sleep()関数は省けることになります。
 
上記のOnTradeTransaction()関数を利用する方法を試しましたので、結果を報告します。

無事に約定価格を取得することができ、3,4日のみ稼働させた状態ですが、今のところ問題はありません。
Sleep()関数も省略可能になりました。

約定価格の取得方法としては、
PositionSelectByTicket()関数を実行後に、PositionGetDouble(POSITION_PRICE_OPEN)関数を実行する方法を取り、
CTradeクラスのResultPrice()関数は試していません。(待機時間を入れても取得できなかった事から、使えないと予想しています)
また、OnTradeTransaction()関数では、引数の構造体のフィールド(メンバー変数)にpriceがあるため、
こちらを利用しても取得できると思いますが、試してはいません。

また、注文処理をした次のOnTick()関数の呼び出しと、OnTradeTransaction()関数の呼び出しのタイミングにつきましては、
MT5画面下側にあるツールボックスの時間(ms)を見た限り、注文処理後の判定部分とOnTradeTransaction()関数の呼び出しは、
同じ時間か、遅れても1msの差で実行されているため、msオーダーではほぼ同時と言って間違いは無く、
OnTradeTransaction()関数の呼び出しの方が先に実行されるため、この方法で、ロジック的に問題は無いと思います。


さらに、OANDA証券のCFDの本番口座以外に、外為ファイネストの本番口座もこの方法で試しましたが、問題はありませんでした。

外為ファイネストの取引処理構造体 MqlTradeTransactionの取引トランザクションの種類 ENUM_TRADE_TRANSACTION_TYPEの状態遷移を
確認したところ、デモ口座の順番と全く同じでしたので、こちらであれば、CTradeクラスのResultPrice()関数は利用可能かもしれません。
(CTradeクラスのResultPrice()関数が利用できないのは、OANDA証券のCFD特有の現象?)


→私以外に本番口座にて、約定価格の取得方法として、CTradeクラスのResultPrice()関数や、
OnTradeTransaction()関数の引数の構造体のフィールド(メンバー変数)のprice、
または、従来のOrderSend(request,result)関数の引数のresult.priceを利用した場合を
試した方がいらっしゃれば、結果の方をこちらのトピック内にご一報頂けると、後発の方の開発の参考にもなり、とても助かります。
 
度々の投稿すみません。

これにて一件落着かと思いましたが、 OnTradeTransaction()関数を利用する上記の方法でEAを動作させていた所、
ほぼ同じ約定価格・時間で3ポジション保有してしまう意図しない動作が発生したため、OnTradeTransaction()関数をせず
元のSleep()関数の利用する方法に戻しました。

OANDA証券(CFD)のみ元に戻し、外為ファイネストはOnTradeTransaction()関数を利用する方法のままで、現状では異常ありません。

異常が発生した時間は、OANDA証券(CFD)MT5の表示上、3月30日8時45分頃で、レートがそこそこ変動していた時間帯です。
Tickの発生回数が多かったかもしれませんが、その時間帯はパソコンを見ておらず、その後パソコンを見た際には
手動でポジションを解消し、元のEAで動作させることに必死になっていたため、その時の細かい状況は把握できていません。
(一番初めのように無限に注文されてしまうことは無く、3ポジション目で約定価格が取得でき、それ以上は注文されていませんでした)

懸念をしていた、注文処理をした次のOnTick()関数の呼び出しがOnTradeTransaction()関数の呼び出しよりも早く実行されてしまったか、
OnTradeTransaction()関数内での約定価格取得方法、タイミングが悪かったことが考えられますので、
OnTick()関数内でガード処理をする等もう一工夫必要な状況ですが、コードが長く、複雑になりそうで、
元の方法が単純で簡単な方法なので、もうこれでいいかなというのが素直な感想です。