市場シミュレーション(第17回):ソケット(XI)
はじめに
これまでの2つの記事では、MetaTrader 5と連携するためのExcelの準備方法について説明しました。特に、前回の「市場シミュレーション(第16回):ソケット(X)」では、VBAでコードを書く方法について解説しました。これは、ExcelとPythonで構築されたサーバーとの正しい相互作用のために必要なものです。たとえ直感的には問題が起こらないように思えても、実際には重要な役割を持ちます。
ソケットの仕組みについて説明した短いセクションでは、Excelの使い方を誤ると問題が発生する可能性があることを示しました。これはPythonサーバーを使用する場合に起こり得ます。しかし、前回までに示した通り、適切に設計されたVBAコードを用いることで、ExcelとPython間の通信問題は解決され、両者の連携はスムーズに動作します。ただし、実装を完成させるためには、もう1つ重要なステップが残っています。それがMetaTrader 5側の実装です。この部分ではいくつかの判断を下す必要があります。
実装前の設計
最初に決定すべき主要なポイントは、MetaTrader 5上で動作する部分をどのプログラミング言語で実装するかという点です。Pythonを使用する方法と、MQL5自体を使用する方法のどちらも可能です。どちらを選択しても、実現したい機能の大部分、あるいはほぼすべてを実装することはできます。しかしPythonを選択した場合、スクリプトであるという制約があり、チャートに適用して実行する必要があります。一方でMQL5を使用する場合は、タスクの実行により適した方法やモデルを構築することが可能です。
さらにMQL5を使用する場合、サービスとして実装する設計にすることができます。つまり、Pythonのようにチャート上での実行を必要とするのではなく、MQL5ではサービスとしてExcelと通信することが可能です。このアプローチにはいくつかの利点があります。
最大の利点は、サービスが独立して動作し、チャートと連携できる点です。これにより、MetaTrader 5全体をほぼ完全に制御することができ、場合によってはチャートと直接関係しない形で動作させることも可能になります。サービスを用いることで、チャートの開閉、チャート上への要素追加、実行中プロセスの変更などもできます。しかし最も重要なのは、サービスがチャートの外部で動作するため、MetaTrader 5が市場分析以外の用途でも利用できるという点です。
MetaTrader 5は単なる取引ツール以上の用途にも使用できます。この点については、興味深いと判断した場合に別の機会で説明することもあります。それでは、MQL5を使用する方針とし、MetaTrader 5上で動作するコード部分をサービスとして実装することにします。では実装に進みましょう。
実装
MetaTrader 5上で実行されるコードの実装自体は、それほど難しいものではありません。しかし、いくつか考慮すべき重要な点があります。これはシステムを正しく動作させるために必要です。ここで重要な点を1つ覚えておいてください。実際には1つのプログラムだけが動作するわけではありません。現実には、3つのプログラムを同時に実行する必要があります。それぞれのプログラムが相互に連携し、通信できるように設計して構造化することが重要です。また、それぞれが他のプログラムの処理内容を認識できる必要があります。したがって、システムは段階的に、ステップごとにテストすることが非常に重要です。
まず、サーバーとして機能するPythonコードを作成します。次に、Excel上で動作するVBAコードを作成し、テストをおこないます。ExcelがPythonサーバーと正常に通信し、コマンドを送信できることが確認できて初めて、MetaTrader 5側の実装段階へ進むべきです。
したがって、複数のプログラムを同時に扱う経験があまりないユーザーに対しては、段階的に進めることを強く推奨します。すべてを一度に実装しようとすべきではありません。まず計画を立て、その後に一歩ずつ進める必要があります。それでは、以下にソースコードを示します。
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property version "1.00" 05. //+------------------------------------------------------------------+ 06. input string user01 = "127.0.0.1"; //Address Server 07. input int user02 = 9090; //Port Server 08. //+------------------------------------------------------------------+ 09. #define def_SizeBuff 2048 10. //+------------------------------------------------------------------+ 11. void OnStart() 12. { 13. bool tab; 14. int sock; 15. uint len, id, iMem; 16. char buff[], ArrMem[]; 17. string szMsg, szCmd, szRet; 18. 19. sock = INVALID_HANDLE; 20. ArrayResize(ArrMem, def_SizeBuff); 21. while (!IsStopped()) 22. { 23. Print("Trying to connect..."); 24. while (!IsStopped()) 25. { 26. if (sock != INVALID_HANDLE) 27. SocketClose(sock); 28. if ((sock = SocketCreate()) != INVALID_HANDLE) 29. if (SocketConnect(sock, user01, user02, 1000)) 30. break; 31. else 32. Sleep(500); 33. } 34. if (!IsStopped()) 35. { 36. szMsg = "<MT5 with Excel>:MT5"; 37. len = StringToCharArray(szMsg, buff) - 1; 38. SocketSend(sock, buff, len); 39. Print("Excel is online..."); 40. } 41. ArrayInitialize(ArrMem, 0); 42. iMem = 0; 43. while (!IsStopped() && SocketIsConnected(sock)) 44. { 45. szCmd = szRet = ""; 46. id = 0; 47. do 48. { 49. len = SocketIsReadable(sock); 50. len = ((iMem + len) < def_SizeBuff ? len : def_SizeBuff - iMem); 51. if (SocketRead(sock, buff, len, 500) > 0) 52. ArrayInsert(ArrMem, buff, iMem); 53. for (int c0 = 0; (ArrMem[c0] != 0) && (c0 < def_SizeBuff); c0++) switch (ArrMem[c0]) 54. { 55. case '[': 56. id = 1; 57. case ';': 58. if ((SymbolExist(szMsg, tab)) && (id < 2)) 59. szRet += StringFormat("%f%s", iClose(szMsg, PERIOD_D1, 0), (ArrMem[c0] == ';' ? ";" : "")); 60. szMsg = ""; 61. break; 62. case ']': 63. id = 2; 64. iMem = 0; 65. break; 66. default: 67. switch (id) 68. { 69. case 0: 70. szMsg += StringFormat("%c", ArrMem[c0]); 71. break; 72. case 1: 73. szCmd += StringFormat("%c", ArrMem[c0]); 74. break; 75. case 2: 76. for (iMem = 0; (ArrMem[c0 + iMem] != 0) && ((c0 + iMem) < def_SizeBuff); iMem++); 77. ArrayCopy(ArrMem, ArrMem, 0, c0, iMem); 78. break; 79. } 80. } 81. }while (SocketIsConnected(sock) && (!IsStopped()) && (id != 2)); 82. if (!IsStopped() && SocketIsConnected(sock)) 83. { 84. if (szCmd != "") 85. { 86. Print(szCmd, "<CMD<"); 87. szRet = "N/D"; 88. } 89. len = StringToCharArray(szRet, buff) - (szCmd != "" ? 1 : 0); 90. SocketSend(sock, buff, len); 91. } 92. } 93. } 94. if (sock != INVALID_HANDLE) 95. SocketClose(sock); 96. ArrayFree(ArrMem); 97. Print("Shutting down..."); 98. } 99. //+------------------------------------------------------------------+
MQL5のコード
コードの説明に入る前に、このこの内容は純粋に教育目的であることを明確にしておきます。そのため、ここではこの話題には軽く触れる程度にとどめます。ここで示している内容がすべてだと考えないでください。ただし、何が起きているのかを理解できれば、読者の皆さんは多くの興味深いものを作成できるようになるはずです。上で説明したコードは、最初のステップおよび参考例として利用することができます。
それでは、このコードが何をおこなっているのかを見ていきましょう。ただし、興味を引くために、まず全体が正常に動作している様子を確認します。以下にその様子を示します。まずExcelで何が起きているかを観察してください。アニメーションが作成された時点では、MetaTrader 5側ではすでにサービスが起動しており、その様子は別のアニメーションで確認できます。

Excel上での実行中のMetaTrader 5からのメッセージを観察すると、以下のような動作が確認できます。

興味深い結果ではないでしょうか。なお、ExcelとMetaTrader 5は異なるコンピュータにインストールされていても動作可能であり、その場合でも同様の結果が得られます。ここまでで興味が高まったところで、次にMQL5側のコードがどのように動作しているのかを見ていきましょう。他の部分については既に過去の記事で説明済みです。
MQL5コード分析
2行目では、このコードがサービスとして実装されていることを示しています。この行が存在しない場合、同じコードはスクリプトとして実行されます。これまでの記事で、サービスとスクリプトの違いについてはすでに説明しました。疑問がある場合、サービスはチャートに直接紐づかない一方で、スクリプトはチャートに紐づいて実行されるという点を思い出してください。ただし、サービスコードとスクリプトコード自体はほぼ同一であり、両者の違いはこの2行目にのみ存在します。
次に理解すべきなのは6行目と7行目です。これらはMetaTrader 5上でサービスを起動する際に、ユーザーが特定のパラメータを渡せるようにするためのものです。ここで指定するのは調整が必要なパラメータ、つまりPythonサーバーが稼働しているアドレスとそのポートです。
ここで注意すべき点があります。Excelの起動場所ではなく、Pythonサーバーが稼働している場所を指定する必要があります。この点は混乱を招きやすい部分です。しかし重要なのは、PythonサーバーはExcel内部に組み込まれているわけではなく、別のコンピュータ上に存在する可能性もあるということです。一方でExcelはそのサーバーの場所を正しく認識していなければなりません。詳細については前回の記事を参照してください。Excel内に存在するコードのうち、VBAで記述されている部分について説明しています。
初期設定が完了すると、11行目からコードの主要部分が始まります。13行目から17行目では、このコードで使用される変数が宣言されています。その後、少し複雑に感じる部分に移りますが、後で必ず明確になります。19行目では、変数を初期化しています。コード全体が1つの大きなループ構造で囲まれているためです。このループは21行目から93行目まで続いており、終了させる唯一の方法は、サービスを手動で停止するかMetaTrader 5を閉じることです。
サーバーへの接続状態を確認するために、MetaTrader 5のメッセージウィンドウへ複数のメッセージを出力します(これは上のアニメーションですでに示されています)。最初のメッセージは23行目で出力されます。このメッセージでは、クライアント(つまりMetaTrader 5)が接続を試みていることを示しているだけです。これは24行目で無限ループに入るために起こります。無限ループとなっているのは、プログラムが終了するまで処理を継続するためです。
「しかし、無限ループの中にさらにループを入れる意味があるのか」と思うかもしれません。安心してください。24行目のループは一見無限ループですが、特定の条件で終了します。ただし、ソケットを保持する変数にはすでに値が入っている可能性があるため、まず既存のソケットを解放する必要があります。この確認は26行目でおこなわれ、実際の解放処理は27行目で実行されます。そのため、19行目での初期化処理が重要になります。接続が存在しないことを示す値をあらかじめ設定しておかなければ、27行目でソケットを解放すべきかどうかを正しく判断できなくなるためです。
したがって、ここでは次のように動作します。接続が存在する場合はそれを閉じ、存在しない場合は何もしません。その後、28行目でソケットの作成を試みます。これは通常成功します。しかし、ソケットの作成は接続の確立を意味するわけではありません。そのため、29行目でサーバーへの接続に失敗した場合、32行目が実行され、短く待機した後、再試行されます。
しかし、24行目の無限ループにおける本質的な問題はここにあります。接続に失敗した場合でもソケットは割り当てられるため、それを必ず解放しなければなりません。そのため再び26行目のチェックに戻り、27行目でソケットが解放されます。この「確保と解放」のプロセスは、接続が成功するか、ユーザーがプログラムを終了するまで繰り返されます。
そしてアニメーションを見ると、ある時点からデータの送受信が始まっていることが分かります。「24行目が無限ループなのに、どうして通信が始まるのか」という疑問が出るかもしれません。その理由は29行目にあります。サーバーへの接続が成功すると、30行目が実行され、24行目から始まるループを抜けるためです。
ここで重要な点があります。34行目は、プログラムがユーザーによって終了されていない場合にのみ実行されます。つまりこの時点ではサーバーへの接続が確立されています。そのため、クライアント情報をサーバーに通知する必要があります。このメッセージは36行目で作成されます。このメッセージはサーバーが必ず認識できる形式でなければなりません。認識されない場合、サーバーはクライアントを切断し、再び第24行のループに戻ります。そのため、今後システムを変更する場合には特に注意が必要です。すべてが正しく動作し、サーバーがクライアントを有効として認識すれば、その後はいくつかの初期処理と安全対策が必要になります。そのため41行目ではメモリバッファをクリアし、直後の42行目でバッファの先頭位置をゼロに設定します。
ソケットプログラミングの世界を初めて学び始めたばかりの方は、41行および42行で説明されている処理の理由が理解できないかもしれません。しかし、ある程度経験がある方は、ソケットへの書き込み時には、読み取り側の想定以上のデータが蓄積されることがあると理解しているはずです。より正確に言えば、これはTCP接続で頻繁に発生します。サーバーを介してExcelから送信されるリクエストやデータを失わないようにするためには、データ損失を防ぐための対策を講じる必要があります。そのため、一連の処理を実行する必要があります。しかし、これをどのように、そしてなぜ行う必要があるのかをよりよく理解するためには、コードをより詳細に確認する必要があります。
その後、新しいループに入ります。これはメインループであり、ExcelとMetaTrader 5間でメッセージを交換するためのループです。このループは43行目から始まります。ここでループの定義を確認してください。プログラムの終了条件とソケットの接続状態を確認していることに注意してください。ここではすべて正常であると仮定し、このループに入り、長時間その中に留まることができます。
したがって、最初にやるべきことは、いくつかの変数を初期化することです。これは45行目と46行目で行われます。その直後に、47行目から81行目まで実行される別のループに入ります。このループ内で何が起こるかに注意してください。これは非常に重要です。なぜなら、この部分は後に変更や改善を行う可能性が最も高い箇所だからです。たとえば、Excelへより多くの情報を送信する必要が生じた場合や、Excelから送信されるコマンドをより正確に処理する必要が生じた場合などが該当します。
たとえば48行目では、ソケット内にどれだけのデータが存在するかを確認しています。その後51行目で、そのデータの読み取りを試みます。ただし、ここで注意すべき小さな点があります。49行目では、メモリバッファに格納しようとしているデータ量が割り当て済みのバッファサイズを超えていないかを確認しています。この場合、読み取るデータ量を調整し、割り当て済みサイズを超えないようにしています。しかし必要であれば、割り当てられた領域のサイズ自体を変更することも可能です。いずれにしても最終結果は大きく変わりません。
50行目がなぜ存在するのかを説明した後、51行目に進みます。データが読み取られた場合、そのデータは52行目で割り当てられたメモリに格納されます。この時点では52行目は意味を持たないように見え、存在しないかのように感じられるかもしれません。しかしコードの動作を続けて確認することで、この行の意味は後に明確になります。ただし重要なのは、ソケットから読み取られたデータは特定の位置から格納されるという点です。この位置はiMem変数によって示されます。初回読み取り時にはiMemは0ですが、その後の読み取りでは異なる値を持つ可能性があります。
次に53行目に到達します。ここでは本コードの中核となるループが開始されます。このループは処理の大部分を担当します。この処理ではいくつかの条件を確認し、条件が満たされた場合には追加の小さな処理が実行されます。これらの処理はMetaTrader 5とExcel間の通信プロトコルを構成しています。そのため、ここで変更をおこなう場合はVBAコード側にも同様の変更を反映する必要があります。さらに、サーバー側で処理をおこなう場合はPythonコードにも同様の変更が必要になります。
この53行目のループは、受信メッセージを1文字ずつ処理し、何が起きているのか、そしてどのように処理すべきかを判断します。このコードは教育目的で作成されていますが、仕組みを理解すれば、各自の用途に応じて応用することが可能です。
また、同じ処理はStringSplit関数などを用いて実装することも可能です。その方法を使っても問題はありませんが、構造はやや複雑になる可能性があります。これは使用する通信プロトコルに依存します。本ケースではプロトコルは比較的単純であり、括弧の前に特定の文字列があり、その内部に実行されるコマンドが記述されています。とても簡単です。ただしこれは市場での売買を直接行う部分ではないため、その機能についてはここでは扱いませんが、気にする必要はありません。
この記事の最後では参考リンクを示す予定です。この機能はすでにリプリケーション/モデリングシステムに実装されています。ただし、手順に従わない場合は実装方法を理解することは難しくなります。実装自体は難しくありませんが、通信プロトコルを設計する必要があります。そのためにはコマンドの実行方法を理解する必要があります。この方法がわからない場合は、参考資料として示す記事を確認してください。
それではコードに戻ります。多くの人、特に初心者を混乱させる可能性がある部分がありますので注意してください。55行目を見てください。この時点で開き括弧が存在しています。これは銘柄名の終了とExcelコマンド部分の開始を意味します。明確にするために説明すると、56行目でid変数の値を0から1へ変更しています。この点は忘れないでください。ループ開始時点では、46行目の処理によりidの値は0になっており、これは43行目でループが最初に実行されたときに発生しています。これらの点に注意してください。
開き括弧は、すでに銘柄名の取得が完了していることを示しているため、この時点ではその銘柄に関するデータを取得する必要があります。このケースではバーの終値のみを対象としています。それだけですが、必要であれば任意の情報を取得することも可能です。そのためには、どの値を取得するかを示すルールを作成すれば十分です。ここで本題に戻ります。55行目のcase文が57行目のcase文へbreakなしでそのままフォールスルーしている点に注意してください。これは可能です。しかし57行目のcaseを見る前に、そこに現れる文字が異なるものであることに注意する必要があります。なぜでしょうか。
ここが非常に重要な部分です。57行目のcaseに登場する文字は、複数銘柄の分割を可能にするものを示しています。つまりExcelが複数の銘柄を対象に情報を取得したい場合、この文字で区切ることでそれを実現できます。しかし前回の記事のVBAコードを参照すると、この機能は使用されていません。単に戻り値を1つのセルに出力しているだけです。実際にはこれは読者への演習課題です。このデータを分割し、適切なセルへ転送してください。スプレッドシートの構造はプログラマーごとに異なるため、分割方法はケースごとに異なります。ただし難しい処理ではなく、スプレッドシートの構造次第です。
いずれにしても、58行目では指定された銘柄が存在するかどうかを確認します。この場合、59行目で返却用の文字列を作成します。ただし注意してください。この時点で作成している文字列は、実際にサーバーやExcelへ返される最終的な文字列ではありません。その理由は後ほど説明します。その後、銘柄処理が完了したため、60行目で文字列をクリアし、次の銘柄を取得するために61行目でbreakを使用し、次のcaseへのフォールスルーを防ぎます。
次のcaseでは62行目に閉じ括弧文字が登場します。これはExcelからのコマンド取得が完了したことを意味し、次の銘柄名の処理へ進みます。ただしソケットから追加データが読み取られても、この時点では使用されません。これは58行目のチェックにより、63行目でデータを使用しないよう制御されているためです。ここで、コマンド部分の解析について見ていきましょう。しかし、それぞれが何を表しているのかを知る必要があります。caseに該当しない場合は66行目のdefaultが実行されます。
ここでの処理内容はid変数の値によって決まります。idが0の場合は銘柄名を取得し、70行目で処理されます。idが1の場合はExcelからのコマンドを取得し、73行目で処理されます。idが2の場合は、データを現在位置からバッファ先頭へ移動します。
この処理により、47行目から開始されたループは81行目で終了します。その理由の1つはコマンドブロックの終了が検出されるためです。その後、最終処理へ進みます。まず82行目で追加チェックを行い、成功した場合、コマンドが送信されているかどうかを確認します。コマンドが送信されている場合、そのコマンドがすべての処理よりも優先されます。そして86行目において、Excelからの売買リクエストを処理するために他の記事で説明されている処理が実行されます。
このタスクを正しく理解し実装するためには、参考資料を先に確認することを推奨します。コマンド実行リクエストに対して、MetaTrader 5からの戻り値は変化し、単なるシンボルデータではなく特定の文字列になります。最後に89行目および90行目でMetaTrader 5からの応答が送信されます。この応答は、コマンド実行か銘柄データ取得かによって異なります。
最終的な考察
本日のコードには、Excelから渡されたコマンドを実際に実行する部分の実装は含まれていませんが、それでもExcelとMetaTrader 5間の相互作用は、比較的スムーズに、大きな問題なくおこなわれていることが分かります。ただし、この実装にはおそらくいくつかの小さな改善や修正が必要になるでしょう。しかし、それが必要になるということは、この段階において私たちの目的、すなわちソケットの使い方を説明するという目標が確かに達成されたことを意味します。なぜなら、多くの人々は、MetaTrader 5のチャートを直接見ることなく、またファンダメンタル分析を用いることなく市場で取引をおこなうことができるという事実を知らなかったからです。
注文送信を担当する部分を実装するためには、このシステムの他の要素も実装する必要がありますが、この場でその方法をすべて示さないことに問題はないと考えています。重要なのは、何をおこなおうとしているのかを読者の皆さんが明確に理解することです。そのため、最終的な完全版はここでは提示しません。ただし、これまでに示した記事をしっかりと学習すれば、この機能はほぼ確実に実装できます。次回の記事では、ソケットと同様に事前の理解が必要となる別のテーマを扱い、その後で再びリプリケーション/モデリングシステムへと戻る予定です。
参照文献
リプレイシステムの開発(第74回):新しいChart Trade(I)
リプレイシステムの開発(第75回):新しいChart Trade(II)
リプレイシステムの開発(第76回):新しいChart Trade(III)
リプレイシステムの開発(第77回):新しいChart Trade (IV)
リプレイシステムの開発(第78回):新しいChart Trade(V)
| ファイル | 説明 |
|---|---|
| Experts\Expert Advisor.mq5 | Chart TradeとEA間の相互作用をデモンストレーションする(相互作用にはMouse Studyが必要) |
| Indicators\Chart Trade.mq5 | 送信する注文を設定するためのウィンドウを作成する(操作にはMouse Studyが必要) |
| Indicators\Market Replay.mq5 | リプレイ/シミュレーションサービスと対話するためのコントロールを作成する(Mouse Studyが必要) |
| Indicators\Mouse Study.mq5 | グラフィカルコントロールとユーザー間のインタラクションを提供する(リプレイシステムと実市場での運用の両方に必要) |
| Services\Market Replay.mq5 | 市場レプリケーション/モデリングサービスを作成および維持する(システム全体のメインファイル) |
| VS C++ Server.cpp | C++で作成されたソケットサーバーを作成および維持する(ミニチャット版) |
| Python Server.py | MetaTrader 5とExcel間の通信用のPythonソケットを作成および維持する |
| ScriptsCheckSocket.mq5 | 外部ソケットへの接続をテストする |
| Indicators\Mini Chat.mq5 | インジケーターを通じてミニチャットを実装する(サーバーの使用が必要) |
| Experts\Mini Chat.mq5 | EAを通じてミニチャットを実装する(サーバーの使用が必要) |
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12829
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
サンゴ礁最適化(CRO)
市場シミュレーション(第16回):ソケット(X)
ペアトレード:Zスコアの差に基づく自動最適化機能を備えたアルゴリズム取引
バトルロイヤル最適化(BRO)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
本や時間やトレードのデータをExcelやPythonに取り込む方法はありますか?
ご清聴ありがとうございました!