MQL5サービスからPythonアプリケーションへのMetaTraderティック情報アクセス(ソケット使用)
はじめに
場合によっては、MQL5言語だけではすべてをプログラムできないことがあります。また、既存の高度なライブラリをMQL5に移植することは可能であっても、非常に時間がかかります。そのため、既存のライブラリを統合し、活用して目的を達成する方が効率的です。たとえば、Pythonには多数の機械学習ライブラリがあります。取引のために機械学習タスクを実行するだけの目的で、同じライブラリをMQL5で一から作成するのは非効率です。代わりに、必要なデータをPythonライブラリに渡して処理をおこない、結果をMQL5プログラムに返す方が賢明です。本記事では、MetaTraderターミナルからPython環境へデータを送る方法について説明します。
Pythonには「MetaTrader 5」パッケージがあり、バー情報、ティック情報、ユーザー情報、取引情報など、MetaTraderのチャートデータにアクセスできます。しかし、このパッケージはWindows専用であるため、利用するにはWindows OSが必要です。本記事では、ソケットを使ってMetaTraderからPythonプログラムにティックデータを送信する方法を紹介します。
1983年に4.2BSD Unixで導入されたBerkeleyソケットにより、マシン間通信が簡単かつ広く普及しました。高度なアプリケーション層ライブラリやプロトコルも、すべてこのトランスポートレベルのソケットプロトコルに依存しています。本記事では、この仕組みを利用します。
デモとして、今回はティックデータ(Bid、Ask、ティック時刻)のみをソケット経由でPythonアプリケーションに送信し、Python側で利用します。この方法を応用すれば、バー情報、ユーザー情報、取引情報など、さまざまなチャートデータも同様に送信可能です。また、この手法はサービスだけでなく、スクリプト、インジケーター、エキスパートアドバイザー(EA)でも利用できます。
プログラムフロー
本記事では、MetaTrader 5のサービスプログラムを利用して、Bid、Ask、時刻などのティック情報をPythonサーバーに送信し、そのPythonサーバーが接続されているすべてのクライアントソケットに情報をブロードキャストする流れに焦点を当てています。以下の図でイメージしやすくなります。

図の通り、MetaTraderサービスプログラムは、ポート9070で待機するPythonサーバーに接続されています。MetaTrader 5ターミナルで開かれているチャートのすべてのティックデータは、ポート9070を通じてPythonサーバーに送信されます。PythonサーバーはMetaTrader 5から受信したデータを解析し、必要な処理や分析をおこないます。その後、ティック情報を接続済みクライアントにブロードキャストします。クライアントは受け取ったデータを使用して必要なタスクを実行したり、アルゴリズムを適用して所望の結果を生成したりすることができます。必要に応じて、結果を再びMetaTraderサービスプログラムに送信して、さらなる処理に利用することも可能です。
サービスとPythonを使う理由
MetaTrader 5のサービスを使ってティック情報を送信することに、特別な理由があるわけではありません。スクリプト、インジケーター、EAでも同様の処理は可能です。これについては、次回の記事で紹介する予定です。今回の目的は、ティックやその他のチャート関連情報を、ソケットプログラミングを使って他のアプリケーションに送信できることを示すことです。また、Pythonサーバーとクライアントを使っていますが、サーバーやクライアントの開発には、Python以外のプラットフォームやフレームワークを使うことも可能です。単に、Pythonが自分にとって便利だったという理由で使用しています。
サービスプログラム
ご存知の通り、MetaTrader 5のサービスにはOnStartという関数しかなく、すべての処理はこの関数内でおこなう必要があります。サービスはどのチャートウィンドウにも結びついておらず、_Symbol、_Period、_Pointなどの組み込み関数は、スクリプトやインジケーター、EAのようには使用できません。また、サービスの実行や管理は、ナビゲータウィンドウからのみおこなうことができます。サービスをチャートウィンドウにドラッグしても実行できません。実行方法は、デモセクションのGIFファイルで確認できます。
サービスプログラムではまず、ソケット、サーバー、ポートの変数を定義します。
int socket; string server = "localhost"; int port = 9070;
SocketInit()関数はSocketCreate()メソッドを用いてソケットハンドルを作成し、サーバーアドレスとポートとともにSocketConnect()でサーバーに接続します。接続に成功するとtrueが返されます。
void SocketInit() { socket=SocketCreate(); bool connect = SocketConnect(socket, server, port, 1000); if(connect) { Print("socket is connected", " ", server, " port ", port); } }
サービスプログラムをナビゲータウィンドウから追加や実行すると、OnStart関数が呼ばれ、その中でSocketInit()が実行されます。
void OnStart() { SocketInit();
次に、開いているチャートのティック情報を格納する変数、チャートIDを格納する変数、サーバーに送信するティック情報を格納する変数の3つの変数を定義します。
MqlTick latestTick; long next; string payload;
常にサーバーへティック情報を送信するため、無限ループ(while(true))を使用し、まずソケット接続が確立しているかどうかを確認します。接続されていなければ、SocketIsConnected(socket)はfalseを返し、サービスを停止します。
while(true) { if(!SocketIsConnected(socket)){ Print("socket is not initialized yet so stopping the service"); break; }
最初のチャートIDと空の文字列でnextとpayloadを初期化します。
next = ChartFirst(); payload = "";
その後、すべてのチャートウィンドウをループして、各チャートの銘柄を取得します。
while (next != -1) { string chartSymbol = ChartSymbol(next);
各チャートのティック情報(Bid、Ask、時刻)を抽出し、
SymbolInfoTick(chartSymbol, latestTick); double bid = latestTick.bid; double ask = latestTick.ask; string tickTime = TimeToString(latestTick.time, TIME_SECONDS);
銘柄、Bid、Ask、時刻の値を使用してサーバーに送信するためのペイロードを生成します。ティック情報をサーバーに送信するためのペイロードとして、私はJSON形式の文字列を作成してみました。これは、Pythonサーバー側でJSONライブラリを使い、この文字列をJSONオブジェクトにデコードしてデータを抽出できるようにするためです。もちろん、使用するフォーマットは自分にとって便利なものであれば何でも構いません。
bool stringAdded = StringAdd(payload, StringFormat("{\"pair\": \"%s\", \"time\": \"%s\", \"bid\": %f, \"ask\": %f}", chartSymbol, tickTime, bid, ask));
複数のチャートウィンドウが開いている場合は、次のチャートウィンドウのティック情報を#@#のような区切り文字でペイロードに追加して連結します。
next = ChartNext(next); if (next != -1 && stringAdded) { stringAdded = StringAdd(payload, "#@#"); }
こうして、すべての開いているチャートウィンドウのティックデータをスキャンし、ペイロードが完成したら、サーバーに送信可能な状態になります。SocketSendメソッドでは、データの長さフィールドの値を正確に指定する必要があります。これを誤ると、余分な文字がデータに含まれ、サーバー側での解析負荷が増えるため注意が必要です。
uchar data[]; int len = StringToCharArray(payload, data); SocketSend(socket, data, len-1);
ループは、サーバーへの接続が維持されている限り、最初のチャートウィンドウから順に再度繰り返し処理されます。
Pythonサーバープログラム
プログラムフローで前述の通り、サーバーはMetaTraderからポート9070でティック情報を受信すると同時に、接続されたクライアントにポート9071でティック情報を送信する役割を持ちます。つまり、2つのソケット接続がそれぞれのポートにバインドされ、待機状態になります。
host = '127.0.0.1' MT5_RECIEVING_PORT = 9070 CLIENT_SENDING_PORT = 9071 mt5Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) mt5Socket.bind((host, MT5_RECIEVING_PORT)) mt5Socket.listen() clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) clientSocket.bind((host, CLIENT_SENDING_PORT)) clientSocket.listen()
サーバーは、MetaTrader 5からの接続は1つのみ許可するよう設計されています。これは、複数のMetaTrader 5接続による混乱や管理の不具合を避けるためです。一方で、クライアントは受信したティックデータを利用して、異なるアルゴリズムを適用したり、必要な処理をおこなうことができます。そのため、クライアント接続の管理が必要になります。
サーバー側では、接続中のクライアントを管理するためのdict型変数を使用します。
connectedClients = {}MetaTraderとクライアントの処理フローは、それぞれ別スレッドとして開始され、互いに独立して実行されるようになっています。
if __name__ == "__main__": thread = threading.Thread(target=acceptFromMt5) thread.start() thread = threading.Thread(target=acceptFromClients) thread.start()
MetaTrader 5のスレッドは、MetaTraderサービスが接続するまで待機を続けます。接続が受け入れられると、processMt5Data関数が呼ばれ、そこでデータを受信して処理し、各クライアントに送信します。この処理については後ほど詳しく説明します。
MetaTraderサービスからの接続受け入れ時にエラーが発生した場合は、次の接続を待機します。
def acceptFromMt5(): try: print("Server is listening for mt5") mt5Client, address = mt5Socket.accept() print(f"mt5 service is connected at {str(address)}") processMt5Data(mt5Client) except Exception as ex: print(f"error in accepting mt5 client {ex}") acceptFromMt5()
ティック情報の解析部分について説明する前に、クライアントがどのように接続され、管理されるかを理解することが重要です。MetaTrader 5サービスと同様に、サーバーは新しいクライアント接続を待機します。クライアントは、自身を管理できるように識別情報を送信する必要があります。この識別情報はチャートウィンドウの通貨ペアで、辞書オブジェクトのキーとして使用され、クライアントはその辞書に追加されます。複数のクライアントが接続できるように、無限ループ(whileループ)を使用しています。
def acceptFromClients(): try: while True: print("Server is listening for clients") client, address = clientSocket.accept() pair = client.recv(7).decode("ascii") print(f"{pair} client service is connected at {str(address)}") clients = connectedClients[pair] if connectedClients and pair in connectedClients.keys() else [] clients.append(client) connectedClients[pair] = clients except Exception as ex: print(f"error in accepting other clients {ex}") acceptFromClients()
これで、新規接続されたクライアントがどのように追加されるかを理解できたので、受信したティックデータがどのようにクライアントにブロードキャストされるかが理解しやすくなります。受信したティックデータはJSONオブジェクトに変換され、そこから銘柄情報が抽出されます。そして、ティック情報は、クライアントが接続時に送信した識別キーに基づいて各クライアントに配信されます。
ティックデータは、MetaTrader 5サービスプログラムで使用した同じ区切り文字で分割されます。1つのチャートに対して複数のティック情報が一度に送信される場合は、追加の分割処理が必要です。これは、PythonのJSONライブラリが、複数のティック情報がまとめて送信された場合に正しく解析できないためです。
def processMt5Data(mt5Client): data = "mt5 client connected" repeatativeEmpty = 0 while(len(data) > 0): try: data = mt5Client.recv(1024000).decode("ascii") if len(data) > 0: for jsn in data.split("#@#"): if "}{" in jsn: splittedTickData = jsn.split("}{") jsn = splittedTickData[0] + "}" jsonTickData = json.loads(jsn) pair = jsonTickData["pair"] if pair in connectedClients.keys(): broadcastToClients(pair, jsonTickData) repeatativeEmpty = repeatativeEmpty + 1 if len(data) == 0 else 0 if repeatativeEmpty > 10: print(f"data is not recieved for 10 times in a row {data}") break except Exception as ex: print(f"error in processing mt5 data {ex}") break time.sleep(0.1) acceptFromMt5()
クライアントへのデータ送信はシンプルです。送信中にエラーが発生した場合、そのクライアントは接続されていないと判断され、接続中のクライアントのリストから削除され、辞書が更新されます。
def broadcastToClients(pair, message): for client in connectedClients[pair]: try: client.send(str(message).encode("ascii")) except Exception as ex: print(f"error while sending {message} to {client} for {pair}") client.close() clients = connectedClients[pair] clients.remove(client) connectedClients[pair] = clientsこれでサーバープログラムは完了です。
Pythonクライアントプログラム
クライアントプログラムは、前述のプログラムフロー通りポート9071でサーバーに接続します。また、サーバー側でクライアントを適切に管理するためには、通貨ペア(銘柄)を表すキーが必要です。そのため、クライアント側では、サーバーに送信する識別IDとして選択できる利用可能なキーの一覧を表示するオプションが必要になります。
CURRENCY_PAIRS = [ "AUDUSD", "AUDJPY", "AUDCAD", "AUDNZD", "AUDCHF", "CADJPY", "CADCHF", "CHFJPY", "EURUSD", "EURJPY", "EURGBP", "EURCAD", "EURAUD", "EURNZD", "EURCHF", "GBPUSD", "GBPJPY", "GBPCAD", "GBPAUD", "GBPNZD", "GBPCHF", "NZDUSD", "NZDJPY", "NZDCAD", "USDCHF", "USDJPY", "USDCAD", "NZDCHF" ] server = "127.0.0.1" port = 9071
ご覧の通り、上記の一覧から1つのオプションが選択され、接続時にサーバーへ送信されます。その後、クライアントはサーバーからデータが届くのを待機します。
if __name__ == "__main__": print(f"please choose from the currency pairs given below as name \n {', '.join(CURRENCY_PAIRS)}") name = input("enter the name for the client : ") if name in CURRENCY_PAIRS: client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if client.connect_ex((server, port)) == 0: client.send(name.encode("ascii")) receiveData(client) else: print("server could not be connected.") else: print("you didn't choose from the above list")
私が作成したクライアントプログラムでは、単にティックデータを表示するだけですが、用途に応じて任意の処理をおこなうことが可能です。たとえば、利用可能なフレームワークやライブラリを使ったデータの追加処理や、機械学習ライブラリの適用などが考えられます。
def receiveData(client): repeatativeEmpty = 0 while True: data = client.recv(1024).decode("ascii") print("data received ", data) repeatativeEmpty = repeatativeEmpty + 1 if len(data) == 0 else 0 if repeatativeEmpty > 10: print(f"data is not recieved for 10 times in a row {data}") break
これでクライアントプログラムは完了です。
データフローのデモ
データをスムーズに転送するためには、まずサーバーを起動する必要があります。サーバーが起動していないと、サービスは動作しません。テスト用として、Pythonサーバーは仮想環境上で実行されています(以下の図参照)。

次に、ティックデータをサーバーで受信し、クライアントにブロードキャストするために、MetaTraderサービスプログラムを起動します(以下のスクリーンショット参照)。

MetaTraderサービスが起動していない場合、接続済みクライアントはデータの受信を待機します。クライアントがサーバーに識別キーを送信し、MetaTraderサービスからティックデータが送信されると、そのデータは現時点ではクライアントターミナルに表示されます。この例では、EUR/USDとGBP/USDのチャートウィンドウがMetaTrader 5ターミナルで開かれていたため、それぞれのティックデータがクライアントで表示されます(スクリーンショット参照)。


上記の一連のプロセスは、GIFとしてキャプチャしており、全体の流れをよりわかりやすく示しています。

結論
本記事では、ソケットプログラミングを使ってMetaTrader 5のティックデータをPythonアプリケーションに送信する方法について解説しました。MetaTraderサービスとPythonのサーバー・クライアントを利用しています。本記事で示した方法により、ティックデータだけでなく、チャートに関する他の情報も同様に送信可能です。サービスを使用しましたが、スクリプト、インジケーター、EAでも同じことができます。また、Python以外のプログラミング言語を使うことも可能です。
さらに、本記事ではWindows OS依存の解消も試みています。MetaTraderやPythonライブラリ(MetaTrader 5など)はWindowsに依存していますが、MetaTrader 5 Pythonライブラリを使用せず、ソケットプロトコルを使ってMetaTraderからデータを転送し、より扱いやすく、高度な処理が可能なライブラリで処理する方法を提案しています。
| ファイル名 | 説明 |
|---|---|
| TickSocketService.mq5 | ポート9070のソケットサーバーに接続し、ティックデータを送信するMQLファイル |
| tick_server.py | MetaTrader用ポート9070と、クライアント用ポート9071で待機するPythonサーバー |
| tick_client.py | ポート9071に接続してサーバーから送信されるデータを受信するPythonクライアント |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18680
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
初心者からエキスパートへ:MQL5を使用したアニメーションニュースヘッドライン(VII) - ニュース取引におけるポストインパクト戦略
プライスアクション分析ツールキットの開発(第33回):Candle Range Theory Tool
MQL5で他の言語の実用的なモジュールを実装する(第3回):Pythonのscheduleモジュール、強化版OnTimerイベント
MQL5取引ツール(第6回):パルスアニメーションとコントロールを備えたダイナミックホログラフィックダッシュボード
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
公開された記事MQL5サービスを使用してソケット経由でMetaTraderからPythonにティックデータを転送する:
著者:lazymesh
おめでとう。
ありがとう
興味深い仕事だ。このような方法でリスク管理サーバーを作成し、それに接続する端末のネットワークを構築することは可能ですか?
はい、可能です。
興味深い仕事だ。この方法でリスク管理サーバーを作成し、それに接続する端末のネットワークを構築することは可能ですか?