MQL5入門(第37回):MQL5のAPIとWebRequest関数の習得(XI)
はじめに
連載「MQL5入門」の第37回へようこそ。前回の記事では、API署名の基本概念を紹介し、MQL5でハッシュおよびHMACベースの技術を用いて安全なリクエストを生成する方法について解説しました。署名がなぜ必要なのか、どのように機密性の高いリクエストを保護するのか、そしてMQL5が概念的にどのように署名を生成するのかに焦点を当てました。
本記事では、その知識を基に、さらに実践的な内容へ進みます。MQL5スクリプトを作成し、認証付きAPIリクエストをBinanceに送信して、取得可能なすべての資産の残高情報を取得します。ここでは、これまで学んできたWebRequestの利用、タイムスタンプの取得と処理、署名の生成、そしてプライベートAPIエンドポイントとの通信といった要素を実際に組み合わせて活用します。この記事を読み終える頃には、MQL5からBinanceの認証付きAPIを安全に利用し、実際のアカウント情報を取得する方法をしっかり理解できるようになるでしょう。
署名付きリクエストのためのBinanceサーバー時刻の取得
前回の記事で説明したように、Binance向けの安全な署名を生成するためには、メッセージと秘密鍵の2つが必要です。今回のケースでは、メッセージとして使用するのがタイムスタンプです。ただし、このタイムスタンプはローカルPCの時刻ではなく、Binanceサーバーの現在時刻でなければなりません。 タイムスタンプはリクエストのセキュリティを確保する上で重要な役割を果たします。各リクエストを一意にし、第三者によるリプレイ攻撃を防止するためです。タイムスタンプは常に変化するため、リクエストが送信された正確な時刻を示します。Binanceはミリ秒単位のタイムスタンプを使用しており、認証付きリクエストの正確な検証を可能にしています。残高照会や注文発注などのプライベートエンドポイントへアクセスする際、正しいサーバー時刻を使用することで、生成した署名がBinance側で有効と認識されます。
サーバー時刻を取得するためには、まずBinance APIにリクエストを送信する必要があります。ローカルPCの時計は設定やタイムゾーンによって誤差が生じる可能性がありますが、Binanceサーバー時刻はBinanceがすべての認証処理で利用する公式な基準時刻です。そのため、MQL5プログラムは署名を生成する前にBinanceサーバーへ接続し、現在のサーバー時刻を取得しなければなりません。この手順を省略すると、生成したタイムスタンプがBinanceの期待値と一致せず、署名は無効として拒否されてしまいます。 現在のサーバー時刻を取得するには、特定のBinance APIエンドポイントへGETリクエストを送信します。このエンドポイントは認証不要の公開APIであるため、誰でもアクセス可能です。リクエストに対してBinanceはJSONオブジェクトを返し、その中に現在のサーバー時刻(ミリ秒単位)が含まれています。MQL5スクリプトでは、このレスポンスからタイムスタンプ値を抽出し、署名生成時のメッセージとして使用します。
リクエストを適切に構築し、レスポンスを処理してタイムスタンプを正しく解釈することで、その後に送信するすべてのAPIリクエストをBinanceのサーバークロックと同期させることができます。一見すると単純な手順に思えるかもしれませんが、これは非常に重要です。正確なタイムスタンプがなければ、プライベートAPIへのリクエストも、署名の生成も正常に機能しません。そのため、本記事の後半でタイムスタンプと秘密鍵を組み合わせて正しい署名付きリクエストを生成する前に、まずはサーバー時刻を確実に取得する方法を習得する必要があります。 これについては以前にも説明しましたが、重要なポイントなので改めて触れておきます。MQL5スクリプトからBinanceへWebRequestを送信できるようにするには、事前にプラットフォームの設定で許可を有効にしておく必要があります。Ctrl+Oキーを押してオプション画面を開き、[エキスパートアドバイザ]タブの許可URLリストに以下のURLを追加してください。この設定により、スクリプトがBinanceサーバーへのWebRequestを許可された形で送信できるようになります。認証付きAPIリクエストを送信する前に、必ず実施しておく必要がある設定です。
例://+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string url_t = "https://api.binance.com/api/v3/time"; string headers_t = ""; char result_t[]; string response_headers_t; char data_t[]; int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t); string time = CharArrayToString(result_t); Print(time); }
出力:

解説:
スクリプトはまず、現在のサーバー時刻を返すBinanceエンドポイントのURLを定義することから始まります。MQL5プログラムはこのURLにリクエストを送信し、Binanceの公式タイムスタンプを取得します。サーバー時刻を利用することは非常に重要です。なぜなら、後で送信する署名付きリクエストがBinanceのサーバークロックと同期されることを保証し、タイムスタンプの不一致によるエラーやリクエスト拒否を防ぐことができるからです。 続いて、スクリプトはヘッダー用の空文字列を作成します。このリクエストは公開APIに対するものであるため認証は不要であり、ヘッダーは空のままで問題ありません。一方、プライベートAPIを利用する場合には、APIキーやその他の認証情報を含むヘッダーが必要になります。しかし、サーバータイムスタンプを取得するだけであれば、ヘッダーを空にしておいて問題ありません。
さらに、スクリプトではリクエストデータとレスポンスデータを格納するための配列を宣言します。GETリクエストにはデータが含まれないため、リクエストボディ用の配列は空のままとなります。一方で、レスポンス用の配列にはサーバーから返された生データが格納されます。また、サーバーが返すヘッダー情報は別の変数に保存されます。これは必須ではありませんが、デバッグしたり、Binanceが返した追加情報を確認したりする際に役立ちます。 その後、WebRequest関数を使用してBinanceにGETリクエストを送信します。この関数は、リクエストメソッド、URL、ヘッダー、ミリ秒単位のタイムアウト時間、リクエストデータ、レスポンス保存用の配列、およびレスポンスヘッダーを格納する変数などを引数として受け取ります。関数はリクエストの成否を示すステータスコードを返し、通常、リクエストが正常に成功した場合は200が返されます。
サーバーからレスポンスを受信すると、生のバイト配列は読み取り可能な文字列へ変換されます。この変換により、レスポンス内容を人が読める文字列として扱えるようになります。BinanceサーバーはタイムスタンプをJSON形式のバイト列として返すため、この処理が必要になります。最後に、スクリプトは取得したタイムスタンプを出力します。これにより、リクエストが正常に実行されたこと、および現在のBinanceサーバー時刻をミリ秒単位で正しく取得できたことを確認できます。
比喩的な説明
友人の住む街の時刻を確認してから通話の予定を立てる場面を想像してください。まず、その相手に手紙を届けるためには住所が必要です。これは、スクリプトでBinanceエンドポイントのURLを指定することに似ています。URLは、リクエストをどこへ送信すべきかをプログラムに伝える役割を果たします。つまり、MQL5プログラムに対して、公式なサーバー時刻を取得するためにどこへリクエストを送るべきかを指示しているのです。このサーバー時刻を利用することは重要です。なぜなら、後で何らかの処理をおこなう際に、Binanceの時計と時刻を一致させることができるからです。もし自分のローカルマシンの時計を使用した場合、時刻のずれによって計画が狂ったり、Binanceにリクエストを拒否されたりする可能性があります。
次に、封筒の中に追加の指示やメモを入れる場面を考えてみてください。これはWebRequestのヘッダーに相当します。今回のサーバー時刻取得リクエストでは、Binanceは何を要求しているのかをすでに理解しているため、補足情報は必要ありません。しかし、機密情報を扱うリクエストなどでは、ヘッダーにAPIキーや認証情報を含める必要があります。これは、封筒の中に秘密のコードや身分証明書を同封するようなものです。
Binanceから返されるレスポンスは、返送されてくる荷物や手紙だと考えることができます。スクリプト内のレスポンス配列は、その荷物を受け取って保管する場所です。今回は単に問い合わせを行っているだけで追加データを送信しないため、リクエストボディは空のままです。レスポンスがどのように送られてきたのかについて詳しく知りたい場合は、返されたヘッダーを確認できます。これは荷物に添えられた追加のメモのようなものです。 WebRequest関数は、実際に手紙を送る行為に相当します。郵便の種類、宛先、返送された手紙をどこに保管するか、そして郵便局が配達を諦めるまでどれくらい待つかを指定します。リクエストが完了すると、ステータスコードは配達完了通知のような役割を果たし、処理が成功したかどうかを知らせてくれます。ステータスコード200が返された場合は、すべてが正常に完了したことを意味します。
最後に荷物を開封すると、中に情報が入っています。ただし、その情報は最初は理解できない形式で記録されているかもしれません。バイト配列を読み取り可能な文字列へ変換する処理は、この暗号のようなメッセージを通常の言葉へ翻訳することに似ています。そして、タイムスタンプを出力することは、Binanceから届いた通知を読むことに相当します。これにより、現在のサーバー時刻をミリ秒単位で確認でき、自分の時刻がBinanceの時計と一致していることを把握したうえで、次の処理へ進むことができます。 また、WebRequestの出力結果から、サーバーのレスポンスがJSON形式であることも確認できます。レスポンスには単なる数値だけでなく、キーと値のペアによって構成された構造化データが含まれています。このエンドポイントの場合、JSONオブジェクトにはserverTimeというキーが1つ存在し、その値として現在のサーバータイムスタンプがミリ秒単位で格納されています。そのため、必要なタイムスタンプを簡単に取り出すことができます。
API署名を生成する際には、serverTimeに対応する値をメッセージとして使用するため、必要なのはこの数値だけです。この値を抽出することで、その後に送信する署名付きリクエストをBinanceサーバーの時刻と同期させることができ、結果としてプライベートエンドポイントから正常に受け入れられるようになります。
例://+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string url_t = "https://api.binance.com/api/v3/time"; string headers_t = ""; char result_t[]; string response_headers_t; char data_t[]; int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t); string time = CharArrayToString(result_t); string pattern = "{\"serverTime\":"; int pattern_lenght = StringFind(time,pattern); pattern_lenght += StringLen(pattern); int end = StringFind(time,"}",pattern_lenght + 1); string server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght); Print(server_time); }
解説:
最初の行では、patternという文字列変数を定義し、その中に「serverTime」を格納しています。これはBinanceサーバーから返されたJSONオブジェクト内に含まれている文字列です。このパターンを識別することで、JSONレスポンス全体の中で実際のタイムスタンプ値がどこから始まるのかを特定できます。
次に、このパターンをレスポンス文字列の中から検索します。この関数は、レスポンス内でパターンが最初に現れる位置のインデックスを返します。私たちが必要としているのはパターンそのものではなく、その直後に続く値です。そのため、取得した位置にパターンの文字数を加算します。これにより、文字列内でサーバータイムスタンプが始まる位置を取得できます。
比喩的な説明
友人から小さな箱とメモを受け取ったと想像してください。そのメモには、最初に「serverTime:」という見出しがあり、その後ろに長い数字が書かれています。あなたにとって必要なのはその数字だけであり、見出しやその他の文字は必要ありません。JSONレスポンスからserverTimeを探す作業は、この見出しを見つけることと同じです。見出しを見つけたら、その部分は無視して、本当に必要な数字だけに注目します。次に、その数字がどこで終わるのかを探します。このJSONでは、数値の直後に閉じ波括弧(})があるため、それが終端の目印になります。これは、箱の中のメモがどこで終わるのかを確認する作業に似ています。数字の開始位置と終了位置の両方が分かれば、見出しや余計な文字を除外して、必要な数字だけを正確に取り出すことができます。
最後に、その数字をメモに書き写したり声に出して読んだりすると考えてみてください。コードでは、これがサーバー時刻の数値だけを含む部分文字列を抽出する処理に相当します。そして、その値を出力することは、その数字を紙に書き留めて次の作業に利用できるようにすることと同じです。友人がメモに書いた正確な時刻を基に予定を立てるのと同様に、この方法で数値だけを抽出することで、それをAPI署名を生成する際のメッセージとして直接利用できるようになります。
Binance APIキーと秘密鍵の生成
MQL5を使用してBinanceと通信し、認証付きリクエストを送信するには、APIキーと秘密鍵が必要です。これら2つのキーはユーザー名とパスワードのような役割を果たし、プログラムがBinanceに安全に接続して、残高確認や取引実行などアカウント上の操作をおこなえるようにします。APIキーはアカウントの識別に使用され、秘密鍵はリクエストが実際に自分から送信されたことを証明するための安全な署名生成に使用されます。実際には、MQL5スクリプトが署名生成に使用するのはこの秘密鍵です。
まずBinanceアカウントにログインし、APIキーを生成します。ログイン後、プロフィールから[API Management]を選択します。ここで新しいAPIキーを作成するオプションが表示されます。APIキーの名前(ラベル)の入力を求められます。「MQL5 Trading Bot」のような分かりやすい名前を付けることで、将来的に複数のAPIキーを作成した場合でも簡単に識別できます。名前を付けるとBinanceがAPIキーと秘密鍵を生成します。APIキーはすぐに表示されますが、秘密鍵は一度しか表示されません。
そのため、秘密鍵は必ず安全に保存する必要があります。パスワードマネージャーや保護されたファイルに保管することが推奨されます。秘密鍵を第三者と共有したり、他人が閲覧できるスクリプト内に直接記述したりしてはいけません。キー生成後は、APIキーに対して権限設定を行う必要があります。通常、MQL5から残高情報を取得したり問い合わせを行う場合は「Read Info」を有効化する必要があります。ボットが取引をおこなう場合は、取引権限も有効化する必要があります。ただし資金の不正送金を防ぐため、特別な理由がない限り出金権限は有効にすべきではありません。
APIキーと秘密鍵を取得すれば、MQL5プログラムは署名を生成し、Binanceへ安全なリクエストを送信できるようになります。秘密鍵は特に重要で、タイムスタンプなどのメッセージと組み合わせて一意の署名を生成するために使用されます。これによりリクエストの改ざんを防ぎ、あなたのソフトウェアのみがアカウントへアクセスできることを保証します。
MQL5におけるBinance API署名の生成
次のステップは署名の生成です。すでにBinanceサーバー時刻を取得するためのAPI GETリクエストの使用方法を理解し、APIキーと秘密鍵の生成も完了しているためです。署名とは、Binanceへ送信されるリクエストが正当であり、途中で改ざんされていないことを証明するための安全な手段です。 前回説明した通りですが、今回はBinanceのプライベートAPIで要求される正確なパラメータを使って署名を生成します。ミリ秒単位のサーバータイムスタンプと秘密鍵を組み合わせることで、ユニークな署名が生成されます。この署名をAPIリクエストに付与することで、Binanceはリクエストの正当性とデータの整合性を検証できます。
署名されていない、または不正に署名されたリクエストはBinanceのプライベートエンドポイントで拒否されるため、このステップは必須です。手順に従い、サーバー時刻と秘密鍵を正しく使用することで、MQL5スクリプトがBinanceと安全かつ正しく通信できることを保証できます。 まず最初にMQL5プログラム内で2つのstring変数を宣言します。そのうち1つには、先ほどAPIリクエストで取得したBinanceサーバー時刻を格納します。このサーバー時刻はミリ秒単位のメッセージとして署名生成に使用されます。もう1つのstring変数には、Binanceダッシュボードで生成されたプライベートキー(秘密鍵)を格納します。この秘密鍵はリクエストが本人のものであることを証明する重要な要素であり、厳重に管理する必要があります。
次の段階では、文字列として保存された両方の値を文字配列に変換します。署名を作成する前にこの変換が必要となるのは、MQL5の暗号化アルゴリズムが文字列を直接処理するのではなく、バイトレベルでデータを扱うためです。この変換の際には、整合性を確保し、文字コードに関する問題を防ぐためにUTF-8エンコーディングを使用します。つまり、UTF-8エンコーディングを用いてサーバー時刻と秘密鍵を文字列から文字配列へ変換することで、ハッシュ化および署名生成の準備を整えることができます。これにより、暗号化処理に必要な形式でデータを扱うことが可能となり、Binance APIが要求する認証用署名を正しく生成できるようになります。
例:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string url_t = "https://api.binance.com/api/v3/time"; string headers_t = ""; char result_t[]; string response_headers_t; char data_t[]; int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t); string time = CharArrayToString(result_t); string pattern = "{\"serverTime\":"; int pattern_lenght = StringFind(time,pattern); pattern_lenght += StringLen(pattern); int end = StringFind(time,"}",pattern_lenght + 1); string server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght); string message = "timestamp=" + server_time; string secrete_key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE"; uchar uMsg[], uSKey[]; StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8); StringToCharArray(secrete_key, uSKey, 0, StringLen(secrete_key), CP_UTF8); }
解説:
この時点では、まず署名対象のメッセージを指定するところから始めます。先ほどBinance APIレスポンスから取得したサーバー時刻の値は、「timestamp=」という文字列と結合されます。これはBinanceの署名付きリクエストが厳密なパラメータ構文に従う必要があるためで、このプレフィックスの付与は必須です。単なる数値だけではサーバー側で署名対象として扱われません。キーと値の形式で指定する必要があります。そのため、サーバー時刻の値だけをそのまま使い、「timestamp=」を付けずに送信すると、生成される署名はBinance側の計算結果と一致せず、リクエストは拒否されます。このため、メッセージはBinance API仕様で定められた形式に正確に従う必要があります。
次に、秘密鍵は文字列変数として保存します。この値はBinanceダッシュボードで生成されるプライベートキーであり、常に安全に管理する必要があります。Binanceはこの秘密鍵をリクエストの中で受け取りません。署名はMQL5アプリケーション側で生成されます。Binance側では登録済みのシークレットキー情報を用いて署名を検証できます。メッセージと秘密鍵を文字列として定義した後、それぞれを格納するための2つの文字配列を用意します。秘密鍵とメッセージのバイト表現はこれらの配列に保存されます。この変換は、MQL5の暗号処理が文字列ではなくバイト単位でおこなわれるため必要です。
次に、メッセージ文字列をUTF-8エンコーディングで文字配列に変換します。これにより、各文字が一貫したバイト列として表現されます。秘密鍵についても同様にUTF-8へ変換します。この処理は重要で、MQL5で生成されるバイト表現がBinance側で同じ署名計算をおこなう際の入力と一致することを保証します。
比喩的な説明
セキュリティオフィスに登録郵便を送ると考えてみます。このオフィスは特定のフォーマットに従った手紙しか受け付けません。まず、手紙の内容を書くことから始めます。この場合の内容は「時刻」です。ただし、単なる数字だけを紙に書いてもオフィスでは受理されません。オフィスは「timestamp equals this time」のように、時刻が明確にラベル付けされていることを期待しています。もし数字だけを送ってラベルを付けていない場合、その手紙は必要な形式に従っていないため拒否されます。このため、サーバー時刻の前には必ず「timestamp=」を付ける必要があります。この形式になっていなければ、その申請は決して承認されません。
次に、秘密鍵はあなただけが持っている個人用のスタンプのようなものとして考えます。このスタンプは封筒の中には入れられません。代わりに、あなたは送信前に作業場でそのスタンプを使って手紙に印を付けます。このスタンプがあることで、その郵便が本当にあなたから送られたものであることを証明できます。もし他人がこのスタンプなしで同じものを偽造しようとしても、オフィスはすぐに偽物だと判断します。さらに、オフィスは手書きの文字を読むのではなく、特定のキーボード配列で入力されたテキストしか理解しません。そのため、メッセージと秘密のスタンプ情報も同じ形式で書き直す必要があります。混乱が起きないように、メッセージと秘密鍵はUTF-8エンコーディングで文字配列に変換されます。これは、すべてを共通の文字コードで表現するようなものです。
例:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string url_t = "https://api.binance.com/api/v3/time"; string headers_t = ""; char result_t[]; string response_headers_t; char data_t[]; int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t); string time = CharArrayToString(result_t); string pattern = "{\"serverTime\":"; int pattern_lenght = StringFind(time,pattern); pattern_lenght += StringLen(pattern); int end = StringFind(time,"}",pattern_lenght + 1); string server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght); string message = "timestamp=" + server_time; string secrete_key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE"; uchar uMsg[], uSKey[]; StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8); StringToCharArray(secrete_key, uSKey, 0, StringLen(secrete_key), CP_UTF8); int blockSize = 64; uchar ipad[], opad[]; ArrayResize(ipad, blockSize); ArrayResize(opad, blockSize); for(int i = 0; i < blockSize; i++) { ipad[i] = uSKey [i] ^ 0x36; opad[i] = uSKey [i] ^ 0x5C; } uchar innerData[], innerHash[]; ArrayCopy(innerData, ipad); ArrayCopy(innerData, uMsg, blockSize); CryptEncode(CRYPT_HASH_SHA256, innerData, uSKey, innerHash); uchar outerData[], finalHash[]; ArrayCopy(outerData, opad); ArrayCopy(outerData, innerHash, blockSize); CryptEncode(CRYPT_HASH_SHA256, outerData, uSKey, finalHash); string signature = ""; for(int i = 0; i < ArraySize(finalHash); i++) signature += StringFormat("%02x", finalHash[i]); Print(signature); }
解説:
まずブロックサイズを64バイトに設定します。この値はハッシュアルゴリズムの要件によって決まります。適切なブロックサイズを使用することで、入力データがアルゴリズムの期待する形式と一致します。これはアルゴリズムが固定長のデータ単位で処理をおこなうためです。次に2つのバイト配列を宣言します。一方はインナーパッド、もう一方はアウターパッドとして使用します。どちらもブロックサイズに合わせてサイズを調整します。現時点では、これらの配列はそれぞれ64バイトの容量を持つ空のコンテナです。これらのパッドは内部ハッシュ処理と外部ハッシュ処理を分離するために重要であり、今後秘密鍵から得られる値で埋められます。
パッドの実際の準備は、その後のループ処理でおこないます。ループは最初のバイトから64バイト目まで実行されます。各位置で、秘密鍵のバイトと固定定数をXOR演算で結合します。インナーパッドには一方の定数を使用し、アウターパッドには別の定数を使用します。この処理により、秘密鍵は統一的かつ制御された方法で変換されます。その結果、内側のハッシュ処理用と外側のハッシュ処理用に分かれた2つのキー表現が生成されます。この分離はセキュリティ上重要であり、改ざん耐性の向上につながります。
パッドの準備が完了した後、次はインナーハッシュ用のデータを構築します。インナーパッドは新しいバイト配列へコピーされ、この配列は結合データを格納するために使用されます。その後、メッセージのバイト列がブロックサイズの位置から挿入されます。これにより、インナーパッドの後ろにメッセージが続く単一の連続したバイト配列が作成されます。この結合データは暗号ハッシュアルゴリズムに渡され、ハッシュ値が生成されます。この結果は、秘密鍵を用いてメッセージを初期変換したものを表しており、インナーハッシュと呼ばれます。
続いて、インナーハッシュ生成後にアウターハッシュの処理へ進みます。アウターパッドは新しいバイト配列へコピーされます。この配列にインナーハッシュが追加されます。つまり、アウターパッドと一次ハッシュの結果を結合した新しいバイト列が構成されます。このデータを再度ハッシュアルゴリズムに入力することで、最終的なハッシュが生成されます。この最終ハッシュが、実際の署名出力となるバイト列です。
この時点では、署名はまだバイト列の状態で、そのままAPI呼び出しには使用できません。そのため、空の文字列を用意して可読形式へ変換します。その後、生成されたハッシュの各バイトを順にループで処理します。各バイトは2桁の16進数表現へ変換され、その結果が署名文字列に順次追加されます。この処理をすべてのバイトが処理されるまで繰り返します。
ループが完了すると、署名は単一の16進数文字列として完成します。この値はAPIリクエストにそのまま使用でき、出力して検証することも可能です。この文字列はBinanceに対して、リクエストが正当であり、正しい秘密鍵によって生成されたことを証明するために送信されます。
比喩的な説明
封蝋にたとえると、重要な書類に強固な封印を施すことを考えます。この封印は2つの目的を持ちます。1つ目は、その文書を実際に作成した本人であることを証明することです。2つ目は、配送中に内容が一切改ざんされていないことを保証することです。まず、固定サイズの型(モールド)を作成します。この型は、メッセージや秘密のフレーズの長さに関係なく常に同じサイズです。封印手法そのものがそのサイズで動作するよう設計されているため、型のサイズは固定されています。この「ブロックサイズ」が、封印処理がおこなわれる範囲を決定します。
次に、2種類の蝋を用意します。どちらも秘密の言葉から生成されますが、処理方法が異なります。1つのシートには最初の標準的な要素が混ぜられ、もう1つには別の標準的な要素が混ぜられます。この混合の段階で、秘密のフレーズは2つの異なる方向にわずかに変化します。その結果として、同じ秘密から生まれているにもかかわらず、封印の中で異なる役割を持つ2種類の蝋ができます。これがインナーパッドとアウターパッドです。
蝋の準備ができたら、最初の封印工程に進みます。まず1種類目の蝋を型に入れ、その上に紙を置きます。それらは重なって一つの層になります。この重なった状態をスタンプ機で押しつぶすことで、中間的な封印が作られます。これは最終的な封印ではなく、次の工程で使うための一時的な結果です。続いて第二の封印工程に移ります。2種類目の蝋を型に入れ、その上に先ほどの中間封印を重ねます。そして同じスタンプ機で再び圧力をかけます。この二度目の圧縮によって最終的な封蝋が完成します。この封印は、文書と秘密の両方を強く結びつけるもので、どちらか一方でも少し違えばまったく別の結果になります。
この時点で封印はまだ物理的な形のままで、そのままではデジタル的に送信したり添付したりすることができません。そのため、各構成要素を2文字ずつのコードとして丁寧に変換していきます。封印全体を一つひとつ順にたどりながら書き起こし、最終的に一つの読み取り可能な文字列にします。最後にそのコードを確認して完成させます。それをメールで送る際に文書へ添付します。受信者は同じ秘密の言葉を使って、まったく同じ封印の手順を再現します。その結果が一致すれば、その文書は正しく作成され、改ざんされていないことが確認できます。
Binance APIへの認証付きリクエストの送信
Binance APIに認証付きリクエストを送信することは、MQL5でAPI署名を構築する方法を学んだ次のステップです。残高取得のようなアカウント関連のエンドポイントは認証が必要ですが、公開エンドポイントは追加の認証なしで利用できます。ここでは、サーバー時刻、作成した署名、そしてAPIキーがすべて組み合わさります。Binanceの各資産の残高を取得するためには、署名付きリクエストをプライベートエンドポイントに送信する必要があります。リクエストのヘッダーにはAPIキーが含まれ、パラメータにはタイムスタンプと署名が含まれます。リクエストが正当であり、データが改ざんされていないことを確認するために、Binanceはこれらの情報を検証します。
リクエストが正しく送信されると、サーバーはJSONオブジェクトを返します。この中には、各資産の利用可能残高やロック中残高など、アカウント内のすべての資産情報が含まれています。その後、このレスポンスから必要な残高データをMQL5プログラム内で抽出し、処理することができます。ここで初めて、コードは実際にBinanceアカウントと通信する段階に入ります。
例://+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string url_t = "https://api.binance.com/api/v3/time"; string headers_t = ""; char result_t[]; string response_headers_t; char data_t[]; int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t); string time = CharArrayToString(result_t); string pattern = "{\"serverTime\":"; int pattern_lenght = StringFind(time,pattern); pattern_lenght += StringLen(pattern); int end = StringFind(time,"}",pattern_lenght + 1); string server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght); string message = "timestamp=" + server_time; string secrete_key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE"; uchar uMsg[], uSKey[]; StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8); StringToCharArray(secrete_key, uSKey, 0, StringLen(secrete_key), CP_UTF8); int blockSize = 64; uchar ipad[], opad[]; ArrayResize(ipad, blockSize); ArrayResize(opad, blockSize); for(int i = 0; i < blockSize; i++) { ipad[i] = uSKey [i] ^ 0x36; opad[i] = uSKey [i] ^ 0x5C; } uchar innerData[], innerHash[]; ArrayCopy(innerData, ipad); ArrayCopy(innerData, uMsg, blockSize); CryptEncode(CRYPT_HASH_SHA256, innerData, uSKey, innerHash); uchar outerData[], finalHash[]; ArrayCopy(outerData, opad); ArrayCopy(outerData, innerHash, blockSize); CryptEncode(CRYPT_HASH_SHA256, outerData, uSKey, finalHash); string signature = ""; for(int i = 0; i < ArraySize(finalHash); i++) signature += StringFormat("%02x", finalHash[i]); Print(signature); string query_string = ""; query_string += "&signature=" + signature; string time_stamp = "timestamp=" + server_time; string url = "https://api.binance.com/api/v3/account?"+ time_stamp + query_string; string API_KEY = "ABcdeviQHg3LD9ncT0mabcdepfz2a3qkNvrI0abcdeIZYU5umZgeowabcdeMbq8A"; string headers = "X-MBX-APIKEY: " + API_KEY + "\r\n"; char result[]; string response_headers; char datas[]; int status = WebRequest("GET", url, headers, 5000, datas, result, response_headers); string server_result = CharArrayToString(result); Print(server_result); }
解説:
認証付きリクエストに必要なクエリパラメータは、コードの最初のセクションで準備します。署名は、追加のクエリ値を格納するために作成された空の文字列に追加されます。署名はヘッダーやボディには決して含めず、URLクエリの一部として送信しなければなりません。一方で、先ほどBinanceから取得したサーバー時刻は、正しい形式でタイムスタンプを構築するために使用されます。このタイムスタンプはリプレイ攻撃を防ぐ役割を持ち、リクエストが最近作成されたものであることを示します。最終的にBinanceへ送信するリクエストURLは、ベースエンドポイントURLにタイムスタンプと署名を結合することで作成されます。
次にAPIキーを準備します。APIキーは署名のようにURLへ直接追加されるものではありません。Binanceが指定する専用のヘッダー名を使って、リクエストヘッダーに含めます。署名は正しい秘密鍵によって承認されたリクエストであることを証明し、このヘッダーはどのアカウントからのリクエストであるかをBinanceに伝えます。WebRequestではHTTPヘッダーが正しい形式である必要があるため、ヘッダー文字列の末尾にある改行は重要です。
その後、サーバーからのレスポンスを受け取るための変数を宣言します。これには、WebRequest関数内部で使用される追加の文字配列、サーバーが返すレスポンスヘッダーを保存する文字列、そしてレスポンス本文を格納する文字配列が含まれます。リクエスト処理後、MetaTraderはこれらの変数をBinanceから返ってくるデータの格納先として使用します。残高取得ではリクエストボディを送信する必要がないため、WebRequestはGETメソッドで呼び出されます。関数には最終的なURL、タイムアウト値、そしてAPIキーを含むヘッダーが渡されます。Binanceはリクエストを検証し、署名とタイムスタンプが正しいことを確認したうえで、問題がなければアカウント情報を返します。
受信した生のレスポンスは文字配列として取得されるため、MQL5内で扱いやすくするために最終的に文字列へ変換されます。これにより、Binanceから返された完全なJSONレスポンスを確認でき、そこにはアカウント内の各資産の残高情報が含まれています。その後、この出力から利用可能残高、ロック中残高、各資産の詳細などを抽出することができます。
比喩的な説明
銀行から残高一覧が記載された正式な通知を受け取る場面を想像してください。その手紙を後で確認できるように保存したいと考え、まず表紙に分かりやすいラベルを付けたノートを用意します。次に必要なときにすぐ見つけられるように、そのノートに適切な名前を付けます。タイムスタンプと署名は、このメモにおいて非常に重要な2つの情報です。タイムスタンプは、あなたが手紙を書いた正確な時刻を示すもので、郵便の消印のような役割を果たします。これにより、銀行はその手紙が最新のものであり、誰かが古い手紙を再利用しようとしているわけではないことを確認できます。
署名は、あなただけがこのメッセージを送信する権限を持っていることを証明するもので、個人用の封蝋に似ています。銀行があなたのリクエストを受け取ると、タイムスタンプと署名の両方を使って内容を検証します。その後、あなたはAPIキーを表す身分証明書(IDカード)を準備します。これは、リクエストがどのアカウントに関するものなのかを銀行に伝えるために、封筒の見える場所に入れられます。銀行はあなたのIDを見ることはできますが、秘密の封蝋を知らないため、あなたの署名を偽造することはできません。
次に、その封筒を信頼できる配達員に渡します。この配達員が、MQL5におけるWebRequest関数に相当します。配達員はあなたの手紙を銀行へ届け、その後、返事を待ちます。銀行はあなたの本人確認を行い、タイムスタンプと封蝋を検証したうえで、口座内のすべての資産残高を含む返答を書きます。配達員が戻ってきたら、あなたは封筒を開封します。その返答は、JSONという構造化形式で記録されています。そのため、残高を簡単に確認できるよう、人が読める形に変換する必要があります。プログラムから内容を表示することは、まるで台帳を机の上に広げて各項目を確認するようなものです。
リクエストが送信され、サーバーから応答を受け取った後は、そのデータをファイルに保存する必要があります。応答内容を保存することで、結果のデバッグ、口座残高の永続的な記録の保持、あるいはBinanceへ何度も問い合わせることなく後でデータを処理することが可能になります。MQL5の標準的なファイル操作関数を使用すれば、サーバーから返された応答をファイルへ書き込むことができます。
例:
string filename = "Binace_Balance.txt"; int handle = FileOpen(filename, FILE_WRITE|FILE_TXT|FILE_SHARE_READ|FILE_ANSI); if(handle != INVALID_HANDLE) { FileWrite(handle, server_result); FileClose(handle); Print("EA successfully wrote the data to " + filename); } else { Print("Error opening file for writing. Error code: ", + GetLastError()); }
説明:
銀行から、あなたのすべての残高が記載された正式な通知書を受け取ったと想像してください。後で参照できるように保管したいので、あなたはノートを用意し、表紙にわかりやすいラベルを付けます。そのラベルがあるおかげで、後で必要になったときにすぐ見つけることができます。これは、サーバーからの応答に対して分かりやすいファイル名を付けることに似ています。次に、ノートを開いて白紙のページを用意し、読みやすく書き込みができる状態であることを確認します。同様に、プログラムでは適切な設定でファイルを開き、読みやすいテキストデータを正しく保存できるようにします。
ノートを開いたら、通知書の内容をそのページに書き写します。すべてを書き終えたら、内容を保存するためにノートを閉じます。ノートを閉じることで、記録した内容が安全に保管されていることを確認できます。ソフトウェアでファイルを閉じる操作も同じ役割を果たし、サーバーからの応答が正確に保存されることを保証します。ノートブックが開けない場合は、その理由を書き留めて問題を解決できるようにします。同様に、プログラムではファイルを開くことに失敗した際にエラーコードが返され、その原因を理解して修正するのに役立ちます。
結論
この記事では、MQL5でBinance APIを利用するための次の大きなステップについて学びました。APIキー、秘密鍵、サーバー時刻、そして生成した署名を使用して認証付きリクエストを送信し、すべての資産の口座残高を取得する方法を学びました。また、サーバーから返される応答を処理し、後で利用したり追加処理を行ったりできるよう、安全にファイルへ保存する方法についても解説しました。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20978
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5取引ツール(第14回):アンチエイリアシングと角丸スクロールバーを備えたピクセルパーフェクトなスクロール対応テキストキャンバス
MQL5標準ライブラリエクスプローラー(第6回):生成されたエキスパートアドバイザーの最適化
MQL5でカスタムインジケータを作成する(第6回):平滑化、色相シフト、マルチタイムフレーム対応を備えたRSI計算の拡張
データサイエンスとML(第48回):Transformerは取引において重要なのか
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索