English Русский Deutsch
preview
MQL5入門(第29回):MQL5のAPIとWebRequest関数の習得(III)

MQL5入門(第29回):MQL5のAPIとWebRequest関数の習得(III)

MetaTrader 5統合 |
13 0
ALGOYIN LTD
Israel Pelumi Abioye

はじめに

連載「MQL5入門」の第29回へようこそ。前回の記事では、URLを構成する要素を整理し、MQL5のAPIを利用して外部プラットフォームから最新価格を取得する方法を確認しました。また、JSONレスポンスの読み取り方と、必要なデータを正確に取得する手順についても解説しました。

本記事では、より実践的な内容として一歩踏み込みます。単一の現在価格を取得するのではなく、外部プラットフォームと接続し、複数のローソク足に関する時刻、始値、高値、安値、終値を含む完全なローソク足データを取得します。さらに、取得したデータを要素ごとに分解し、それぞれを個別の配列に格納します。たとえば、始値のみを格納する配列、高値を格納する配列といった形で管理します。 このプロジェクトを通じて、構造化されたJSONレスポンスの処理、MQL5における配列操作、そして受信した市場データを効率的かつ再利用しやすい形で構築するための理解が深まるはずです。

 

WebRequestを使用したローソク足データの取得

プロジェクトの最初のステップは、WebRequest関数を使用してローソク足データを取得することです。今回は前回の記事のように現在価格のみを取得するのではなく、完全なローソク足情報をリクエストします。これには、時刻、始値、高値、安値、終値が含まれます。

本記事では、外部プラットフォームに対して直近5本の日足データをリクエストします。データを受信した後、それを分解し、すべての始値、すべての高値といったように、各価格項目ごとに個別の配列へ保存していきます。

WebRequest関数の最初のパラメータはmethodです。今回はサーバーからデータを取得するだけで、送信や更新はおこなわないため、methodはGETに設定する必要があります。これは、サーバーに対して「情報のみを要求している」ことを示します。

例:
string method = "GET";

前回の記事では、URLの各構成要素について説明しました。URLは、プロトコル、ドメイン、パス、クエリ文字列で構成されています。これらの構成要素を理解することで、リクエストがどこへ送られ、サーバーからどのリソースを取得しようとしているのかを正確に把握できます。

例:

string method = "GET";
string url = "https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1d&limit=5";

URLには、Binanceサーバーに対するすべての指示が含まれています。最初に必ず現れる「https://」は、安全な接続を示すプロトコルです。次に「api.binance.com」というドメインが続き、MetaTraderが接続すべき正確なサーバーを示します。ドメインの後に続く「/api/v3/klines」は、ローソク足データを要求していることをサーバーに伝えます。

クエリ文字列は、パスの後に続く部分です。クエリ文字列の先頭には常に疑問符(?)が付き、追加の指示が送られていることを示します。最初の指示「symbol=BTCUSDT」は、どの通貨ペアのデータを取得するかを指定しています。「?」はクエリ文字列の開始を示します。たとえば、「limit=5」は直近5本のローソク足を返すよう指示し、「interval=1d」は日足データを要求していることを示します。

比喩的な説明

巨大なオフィスビルへ特定の情報を取得しに行く場面を想像してください。最初の「https://」は、安全で暗号化された接続を示すもので、建物へ向かう主要道路のようなものです。次に「api.binance.com」があり、これはBinanceがすべてのデータサービスを保管している本館そのものです。

建物に入ると、目的地までの移動が始まります。必要な部署へたどり着くために複数の部屋を通過することを想像してください。まず/apiという部屋に入り、ここはすべてのAPIサービスの中枢です。その中にある「/v3」と書かれたドアを通ることで、APIのバージョン3にアクセスします。最後に「/klines」のドアがあり、ここがローソク足データを保管している部署です。各ステップを進むごとに、目的のデータへと近づいていきます。

正しい部屋に到達すると、クエリ文字列が?で始まります。ここで初めてサーバーに具体的な指示を出します。まず「symbol=BTCUSDT」と指定し、米ドルに対するビットコインのデータを取得したいことを示します。

各パラメータは「&」記号で区切られます。最初の銘柄指定の後に、追加のコマンドを記述するために&を使用します。「&interval=1d」は日足を要求していることを意味します。また、「&limit=5」は直近5本のローソク足を要求していることを意味します。

クエリ文字列を正しく構成することで、サーバーは要求されている正確なデータを理解できます。なお、APIごとにフォーマットは異なるため、必ず公式ドキュメントを確認することが重要です。 これらを設定した後、ヘッダ、タイムアウト、データ配列、結果配列、結果ヘッダといった他の引数を指定できます。すべての準備が整えば、WebRequest関数を使用してリクエストを送信し、Binance APIから直接ローソク足データを取得できます。

例:

string method = "GET";
string url = "https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1d&limit=5";
string headers = "";
int time_out = 5000;
char   data[];
char   result[];
string result_headers;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   WebRequest(method, url, headers, time_out, data, result, result_headers);
   string server_result = CharArrayToString(result);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }

プログラムがサーバーへリクエストを送信する仕組みは、企業に手紙を送って返信を待つことに似ています。封筒の中身、つまり宛先、問い合わせの種類、返信してほしい情報などが、method、URL、headers、timeout、dataに相当します。封筒が準備されると、それはサーバーへ届けられます。

大きなオフィスビルに入り、中央のメール室がすべての郵便物を受け取る様子を想像してください。郵便物は袋にまとめて入れられ、まだ中身が確認できない状態です。誰かが内容を理解する前に、これらの手紙は仕分けされる必要があります。最初の段階は、宅配業者から密封された郵便袋を受け取ることに似ています。あなたは今その袋を持っていますが、中身を読むことはまだできません。それは単なる未整理の生データの集合体です。

次に、その袋を机の上に広げ、手紙を整理して読める状態にすることを想像してください。整理されていない読みにくい塊は、正しく並べられることで明確で整理された情報へと変わります。各手紙の内容が理解できるようになり、はじめて活用できるようになります。 ここでも同じ概念です。最初に取得するのは、扱いにくい形式の生レスポンスです。その後、それを明確で読みやすい形式へ変換し、最終的にサーバーが送信した内容を理解できるようにします。

出力

図1:結果

結果

[

  [

1763337600000, "94261.45000000", "96043.00000000", "91220.00000000", "92215.14000000", "39218.59806000", 1763423999999, "3674562070.23860600", 8134322, "18690.19245000", "1750979467.78626070", "0"

  ],

  [

1763424000000, "92215.14000000", "93836.01000000", "89253.78000000", "92960.83000000", "39835.14769000", 1763510399999, "3641033186.30045840", 8786593, "20130.95957000", "1841176605.14182350", "0"

  ],

  [

1763510400000, "92960.83000000", "92980.22000000", "88608.00000000", "91554.96000000", "32286.63760000", 1763596799999, "2925773651.25595790"

MetaTrader 5のエキスパートログの制限により、サーバーからの完全なレスポンスをすべて表示することはできませんでしたが、プログラム自体は完全なデータを読み取ることが可能です。

 

返されたローソク足データの形式を理解する

ローソク足データを取得した後の次のステップは、サーバーが使用しているJSONのパターンを理解することです。これは非常に重要です。なぜなら、システムごとにデータの送信形式が異なるからです。パターンを事前に確認しなければ、正しい値をどのように取得すればよいか分かりません。抽出ロジックを実装する前に、必ずレスポンスの構造を確認することが重要です。

MetaTrader 5が出力できたレスポンスの一部を見ると、Binanceが使用しているパターンが明確に分かります。各ローソク足は1つの配列として表現され、その配列内の各値にはそれぞれ固有の意味があります。形式は以下のようになっています。

[

  [

    1763337600000, "94261.45000000", "96043.00000000", "91220.00000000", "92215.14000000", "39218.59806000",

    1763423999999, "3674562070.23860600", 8134322, "18690.19245000", "1750979467.78626070", "0"

  ],

  [

    1763424000000, "92215.14000000", "93836.01000000", "89253.78000000", "92960.83000000", "39835.14769000",

    1763510399999, "3641033186.30045840", 8786593, "20130.95957000", "1841176605.14182350", "0"

  ],

  [

    1763510400000, "92960.83000000", "92980.22000000", "88608.00000000", "91554.96000000", "32286.63760000",

    1763596799999, "2925773651.25595790"

各内部配列は1本のローソク足データを表しており、すべて同じ順序で並んでいます。最初にローソク足の開始時刻、続いて始値、高値、安値、終値、そして出来高が配置されています。その後に、終了時刻、クオート資産建ての出来高、取引回数などの追加情報が続きます。MetaTrader 5は文字数制限のためレスポンス全体を表示できませんでしたが、表示された部分だけでも形式を理解するには十分です。

形式

[

  [array 1],

  [array 2],

  [array 3],

  [array 4],

  [array 5]

]

これらの内部配列はカンマで区切られており、それぞれが1本の完全なローソク足を表します。各配列には、Binanceが定義した特定の順序に従って実際のデータが格納されています。

たとえば、ある配列は次のようになっています。

[

  1763337600000, "94261.45000000", "96043.00000000",

  "91220.00000000", "92215.14000000", "39218.59806000",

  1763423999999, "3674562070.23860600", 8134322,

  "18690.19245000", "1750979467.78626070", "0"

]

この配列全体が1本の完全なローソク足を表しています。最初の数値はローソク足の開始時刻(ミリ秒単位)です。2番目が始値、その後に高値、安値、終値が続きます。 さらに進むと、1763423999999がローソク足の終了時刻を示します。その後の大きな数値「3674562070.23860600」はクオート資産建ての出来高を表し、8134322は取引回数です。続いて、テイカー買いのベース資産出来高「18690.19245000」、テイカー買いのクオート資産出来高「1750979467.78626070」が並びます。最後の「0」は、互換性維持のために予約されている未使用フィールドです。

このパターンを理解しておくことで、始値、高値、安値、終値といった必要な値を正確に抽出し、それぞれを個別の配列へ格納することが容易になります。


ローソク足の値を個別の配列へ分離する

サーバーから取得したレスポンスの構造を理解し、各ローソク足データが特定の順序で並んでいることを把握したので、次のステップはそれらの値を個別の配列へ分割することです。これにより、開始時刻、始値、高値、安値、終値、出来高をそれぞれ別々に保存でき、後でインジケーターやエキスパートアドバイザー(EA)内で分析・利用しやすくなります。

各内部配列は1本のローソク足データを表しているため、インデックス0を最新のローソク足、インデックス1をその1本前、それ以降も同様の順序で扱うことができます。この構造により、各ローソク足へ個別にアクセスし、必要な値だけを取得することが容易になります。

生のサーバーレスポンスを個別の配列へ変換する最初のステップは、「完全なローソク足同士を区別できる文字」を選択することです。 カンマは各ローソク足内の値の区切りとしても使用されているため、区切り文字として使用することはできません。これは、すべてのデータが分散してしまい、正しい値を再び正確な形に組み直すことが不可能になることを意味します。私たちが必要としているのは、各ローソク足データセットの末尾にのみ現れ、その値の内部には一切含まれない文字です。

結果

[

  [

    1763337600000, "94261.45000000", "96043.00000000", "91220.00000000", "92215.14000000", "39218.59806000",

    1763423999999, "3674562070.23860600", 8134322, "18690.19245000", "1750979467.78626070", "0"

  ],

  [

    1763424000000, "92215.14000000", "93836.01000000", "89253.78000000", "92960.83000000", "39835.14769000",

    1763510399999, "3641033186.30045840", 8786593, "20130.95957000", "1841176605.14182350", "0"

  ],

  [

    1763510400000, "92960.83000000", "92980.22000000", "88608.00000000", "91554.96000000", "32286.63760000",

    1763596799999, "2925773651.25595790"

配列の要素を分離するためにカンマを使用することはできません。なぜなら、私たちは各日のデータを1つのローソク足単位としてまとめて扱いたいからです。各ローソク足には、開始時刻、始値、高値、安値、終値、出来高など多くの数値が含まれており、それら自体がカンマで区切られています。カンマを使ってローソク足を分離しようとすると、ローソク足全体ではなく、その内部の各値ごとに分割されてしまいます。その結果、どの数値がどのローソク足に対応しているのかを判別することが非常に困難になります。

カンマを区切り文字として使用すると、データが分散し、各ローソク足の値がばらばらに混在してしまいます。その後で各ローソク足の正しい数値の並びを再構築することは難しく、エラーも発生しやすくなります。これでは、分析のためにローソク足データを整理するという本来の目的が達成できません。各ローソク足を独立したデータ集合として維持することができなくなるからです。

この問題を解決するために、ローソク足の実際の数値の一部ではない文字を使用する必要があります。その文字は、各ローソク足ごとに1回だけ現れ、できればそのデータブロックの終わり付近に存在するのが理想です。そのような文字を選択することで、値そのものを損なうことなく、各ローソク足を明確に区別できます。

閉じ角括弧(])は、この目的に最適です。各ローソク足ブロックの末尾には必ず「]」が現れます。そのため、これを区切り文字として使用すれば、各ローソク足を個別の配列として分離できます。データ内部に含まれるカンマによる混乱を避けながら、ローソク足データの構造を維持し、それぞれに個別にアクセスし、処理や分析をおこないやすくなります。

例:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   WebRequest(method, url, headers, time_out, data, result, result_headers);
   string server_result = CharArrayToString(result);
// Print(server_result);

   string candle_data[];
   int array_count = StringSplit(server_result,']', candle_data);
   
   Print(array_count);

//---
   return(INIT_SUCCEEDED);
  }

説明

最初に、candle_dataという動的な文字列配列を宣言しました。これは、Binance APIから取得した大きなレスポンスを小さな単位に分割した後、各ローソク足を個別に保持するためです。

次に、MQL5に対してserver_result文字列を「]」文字で分割するよう指示します。返されたデータパターンでは、各ローソク足は必ず「]」で終了するため、この文字を区切り文字として使用することにしました。「]」は各ローソク足ブロックの終了を一貫して示す唯一の明確な文字です。カンマは始値、高値、安値、終値などのローソク足内部の値にも使われているため、区切り文字として使用できません。しかし、「]」は各ローソク足配列の末尾にのみ現れるため、最も安全な選択です。

StringSplit関数には3つのパラメータが必要です。1つ目は分割対象の文字列、ここではserver_resultです。2つ目は分割に使用する文字、ここでは「]」です。3つ目は分割後の要素を格納する配列、ここではcandle_dataになります。StringSplit関数は、文字列を分割した後に識別された要素の総数を返します。その返り値はarray_countに保存されます。

この動作を理解するために、単純な例を考えてみましょう。文字列「MQL5, ALGO, TRADING」をカンマで分割した場合、文字列内にはカンマが2つあります。しかし、StringSplitが返す要素数は3になります。これは、StringSplitが「区切り文字の後にも要素がある」と仮定して数えるためです。最後の部分が空であっても1つの要素としてカウントされます。ローソク足データでも同じロジックが適用されます。StringSplitは「]」を見つけるたびに前のローソク足の終了として扱い、その次のローソク足を新しい要素としてカウントします。

出力

図2:分割カウント

この場合、StringSplit関数は7を返します。サーバーレスポンス内の「]」の数を確認すると理由が理解できます。リクエストした5本のローソク足それぞれが「]」で終了するため、すでに5つの閉じ括弧があります。さらに、最初のローソク足の前にはレスポンス全体の先頭に「[」があり、全体構造の最後にももう1つの「]」があります。したがって、レスポンスには合計で6つの閉じ括弧が存在します。

StringSplitは「区切り文字の後にも要素がある」と仮定して数えるため、最後の']'の後にも1つ余分な要素を作ります。その結果、レスポンス内の閉じ括弧は6つであるにもかかわらず、返り値は7となります。つまり、6回の分割に加え、最後に空のセグメントが1つ付加された形でカウントされるわけです。これが、5本のローソク足しか要求していないのにarray_countが7になる理由です。

次のステップでは、StringSplitでレスポンスを個別の部分に分割した後、配列の各要素を個別に出力します。これにより、分割が意図どおりに機能しているかを確認でき、各要素にどのデータが含まれているかを明確に把握できます。また、どの部分がローソク足に対応するもので、どの部分が分割過程で生じた空のセグメントであるかも、個別に確認することが可能です。

例:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   WebRequest(method, url, headers, time_out, data, result, result_headers);
   string server_result = CharArrayToString(result);
// Print(server_result);

   string candle_data[];
   int array_count = StringSplit(server_result,']', candle_data);
 
   Print(candle_data[0]);

//---
   return(INIT_SUCCEEDED);
  }

candle_data[0]:

[[1763424000000,

  "92215.14000000",

  "93836.01000000",

  "89253.78000000",

  "92960.83000000",

  "39835.14769000",

  1763510399999,

  "3641033186.30045840",

  8786593,

  "20130.95957000",

  "1841176605.14182350",

  "0"

candle_data[1]:

,[

  1763510400000,

  "92960.83000000",

  "92980.22000000",

  "88608.00000000",

  "91554.96000000",

  "32286.63760000",

  1763596799999,

  "2925773651.25595790",

  6822174,

  "15060.08451000",

  "1365200809.17455710",

  "0"

candle_data[2]:

[

  1763596800000,

  "91554.96000000",

  "93160.00000000",

  "86100.00000000",

  "86637.23000000",

  "39733.19073000",

  1763683199999,

  "3548950335.09842180",

  7841395,

  "18283.84047000",

  "1634256490.95743210",

  "0"


最初のローソク足データから時刻とOHLC値を抽出する

サーバーレスポンスを分割した後、配列には元のデータの各部分が複数の要素として格納されています。この段階で、どの要素がどのローソク足に対応するかを正確に把握することが重要です。各ローソク足には、開始時刻、始値、高値、安値、終値といった特定の値が含まれており、これらは予測可能な順序で並んでいます。順序を正確に理解せずに値を割り当てようとすると、どの数値がローソク足のどの特性に対応するのかを判断するのは非常に困難です。

まず、配列の最初の要素のパターンを確認します。この要素の構造を調べることで、各値がどのように並んでいるかを把握できます。通常、この最初の要素は最新のローソク足を表しています。値の順序を理解することで、それぞれの数値をローソク足の各特性(開始時刻、始値、高値、安値、終値)に正しく割り当てることができ、データを正確に解釈することが可能になります。 同じ考え方を、配列内の他の要素にも適用できます。サーバーのレスポンスはすべてのローソク足に対して同じ構造を持つため、次の要素も同様の順序で値が格納されています。この一貫性により、各ローソク足のデータを正確にマッピングでき、識別処理の自動化も容易になります。

最終的に、各要素を正確に特定した上で、処理や分析を行う準備が整います。値を正しくローソク足に割り当てれば、計算を実行したり、関連する値を比較のためにまとめたり、文字列として取得された値を数値へ変換することも可能です。このステップは、MQL5でAPIレスポンスを扱う上で非常に重要であり、ローソク足データに対するすべての後続操作の基盤となります。

candle_data[0]:

[[

  1763424000000,       // opening time

  "92215.14000000",    // open price

  "93836.01000000",    // high price

  "89253.78000000",    // low price

  "92960.83000000",    // close price

  "39835.14769000",    // volume

  1763510399999,       // closing time

  "3641033186.30045840", // quote asset volume

  8786593,             // number of trades

  "20130.95957000",    // taker buy base asset volume

  "1841176605.14182350", // taker buy quote asset volume

  "0"                  // placeholder

ローソク足データのインデックス0からは、不要な文字を取り除き、文字列をカンマで分割するだけで、開始時刻とOHLC値を簡単に抽出することができます。

例:
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   WebRequest(method, url, headers, time_out, data, result, result_headers);
   string server_result = CharArrayToString(result);
// Print(server_result);

   string candle_data[];
   int array_count = StringSplit(server_result,']', candle_data);

   string day1_data = candle_data[0];
   StringReplace(day1_data,"[[","");
   StringReplace(day1_data,"\"","");

   Print(day1_data);

//---
   return(INIT_SUCCEEDED);
  }

説明

ローソク足データ配列の最初の要素の情報を扱うために、まず文字列変数を作成します。これにより、その1本のローソク足だけを操作でき、他のデータに影響を与えずに処理できます。有用な値を抽出する前に、この変数に含まれる不要な文字、例えば二重括弧や引用符などを取り除く必要があります。

不要な文字を削除するには、StringReplace関数を使用します。この関数には3つのパラメータが必要です。1つ目はクリーニング対象の文字列です。2つ目は削除したい文字または文字列の指定です。3つ目は置換後の文字列です。置換後の文字列を空文字列にすると、対象の文字は単純に削除されます。 この方法で、テキスト内の引用符や二重括弧を削除します。クリーニングが完了すると、ローソク足データは扱いやすくなり、開始時刻、始値、高値、安値、終値など、異なる項目ごとに分割して使用できるようになります。

比喩的な説明

図書館で、何枚かのページが貼り合わさった古い本を手に取ったと想像してください。最初のページをはっきりと読むためには、実際の内容を覆っている層を慎重に剥がす必要があります。  「string day1_data = candle_data[0];」は、この本の中から最初のページを取り出すのに似ています。大きな本はAPIからのサーバーレスポンス全体で、各ページが1本のローソク足に対応しています。ここではcandle_data[0]を選ぶことで、最初のページだけを独立して取り出し、処理できるようにしています。

次に、「StringReplace(day1_data, "[[", "");」は、ページ上部に貼り付けられた余計なラッパーを取り除く作業に似ています。古い本では、初めのページに付箋やラベル、テープが貼られていることがあります。これを取り除かないと正しく読めません。同様に、データ内の「[[」は必要な情報の一部ではないため、慎重に削除します。

さらに、「StringReplace(day1_data, "\"", "");」 は、ページ全体に散らばった小さな汚れや印を拭き取るような作業です。値を正しく扱うためには、文字列を囲む二重引用符は不要です。これを削除することで、ページが読みやすくなります。 最後に、「Print(day1_data);」で整えたページを明るい光にかざして確認するイメージです。ほこりが取り除かれ、ラッパーも外され、残った文字列は読みやすく整理されています。これにより、次の処理に利用可能な形で文字列を正しく把握できます。

出力

1763510400000,

92960.83000000,

92980.22000000,

88608.00000000,

91554.96000000,

32286.63760000,

1763596799999,

2925773651.25595790,

6822174,

15060.08451000,

1365200809.17455710,

0

生のローソク足データは正しく取得できましたが、依然として1つの長い文字列として結合されたままです。このままでは、開始時刻や始値、高値、安値、終値など、各値を個別に扱うことができないため、1つのテキストブロックとして保持することはできません。そのため、このクリーニング済みの文字列を分割して配列に変換する必要があります。こうすることで、各構成要素に個別にアクセスでき、計算や分析に自由に利用できるようになります。

例:
WebRequest(method, url, headers, time_out, data, result, result_headers);
string server_result = CharArrayToString(result);
// Print(server_result);

string candle_data[];
int array_count = StringSplit(server_result,']', candle_data);

string day1_data = candle_data[0];
StringReplace(day1_data,"[[","");
StringReplace(day1_data,"\"","");

string day1_data_array[];
StringSplit(day1_data,',',day1_data_array);
ArrayPrint(day1_data_array);

出力

図3:1日目

説明

まず、不要な文字を取り除いた後の長い文字列を保持するために、動的配列変数を宣言しました。この文字列は配列にする必要があります。なぜなら、後で文字列の内容を個別の要素に変換するからです。文字列はカンマで区切られているため、これを利用して文字列を個別の要素に分割することが有効です。

ローソク足データを、いくつもの連結された車両からなる長い列車に例えてみてください。各車両は時刻、始値、高値、安値、終値などの値を表しています。この列車を扱うためには、まずすべての車両を分離する必要があります。最初の行「string day1_data_array[];」は、各車両を分離した後にそれぞれ配置するための、大きくて空の駐車場を準備するようなイメージです。

次の行「StringSplit(day1_data, ',', day1_data_array);」は、各カンマで列車を切り離す装置のような役割を果たします。列車はカンマで区切られ、各車両(開始時刻や始値、高値、安値、終値などを表す値)は、分割後に駐車場の自分の位置を与えられます。これにより、すべての情報が簡単にアクセス可能で、個別に分離されます。 最後の行、「ArrayPrint(day1_data_array);」は、駐車場の前に立ち、すべての車両が正しく分離され、配置されているかをリストで確認するイメージです。


2本目のローソク足データから時刻とOHLC値を抽出する

最初のローソク足の処理が完了したら、次は2本目のローソク足を扱います。処理の手順は最初のローソク足と同じにすることで、精度を確保し、データ構造を統一します。各ローソク足を独立して処理することで、値が混ざる可能性を減らし、正確なデータの分析や保存ができるようになります。

2本目のローソク足のデータを分割する前に、生のデータパターンを詳細に確認することが重要です。生のレスポンスを出力することで、分割の妨げとなる不要な文字、例えばカンマや括弧、引用符などを特定できます。この作業をおこなうことで、意図せず不要な文字が配列に追加され、数値の抽出に影響することを防げます。

また、2本目のローソク足の生データの正確な形式を理解することで、サーバーレスポンスが一貫した構造を保持していることを確認できます。最初のローソク足と比較することで、開始時刻、始値、高値、安値、終値などの値の順序が一定であることも確認できます。 この一貫性により、データ形式の違いに対応するための調整を行うことなく、同じクリーニングと分割の手順を適用することができます。

例:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   WebRequest(method, url, headers, time_out, data, result, result_headers);
   string server_result = CharArrayToString(result);
// Print(server_result);

   string candle_data[];
   int array_count = StringSplit(server_result,']', candle_data);

//DAY 1
   string day1_data = candle_data[0];
   StringReplace(day1_data,"[[","");
   StringReplace(day1_data,"\"","");

   string day1_data_array[];
   StringSplit(day1_data,',',day1_data_array);

//DAY 2
   Print(candle_data[1]);

//---
   return(INIT_SUCCEEDED);
  }

出力

, [

1763596800000, // Opening time

"91554.96000000", // Open price

"93160.00000000", // High price

"86100.00000000", // Low price

"86637.23000000", // Close price

"39733.19073000",  

1763683199999,

"3548950335.09842180",

7841395,

"18283.84047000",

"1634256490.95743210",

"0"

フォーマットを確認したところ、削除すべき特定の文字(カンマの直後に続く開き角括弧や、二重引用符など)があることが分かりました。これらの不要な文字を取り除いたうえで、2日目のデータを格納するための文字列配列を宣言してもよいでしょう。

例:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   WebRequest(method, url, headers, time_out, data, result, result_headers);
   string server_result = CharArrayToString(result);
// Print(server_result);

   string candle_data[];
   int array_count = StringSplit(server_result,']', candle_data);

//DAY 1
   string day1_data = candle_data[0];
   StringReplace(day1_data,"[[","");
   StringReplace(day1_data,"\"","");

   string day1_data_array[];
   StringSplit(day1_data,',',day1_data_array);

//DAY 2
   string day2_data = candle_data[1];
   StringReplace(day2_data,",[","");
   StringReplace(day2_data,"\"","");
   Print(day2_data);

   string day2_data_array[];
   StringSplit(day2_data,',',day2_data_array);
//---
   return(INIT_SUCCEEDED);
  }

出力

1763596800000,

91554.96000000,

93160.00000000,

86100.00000000,

86637.23000000,

39733.19073000,

1763683199999,

3548950335.09842180,

7841395,

18283.84047000,

1634256490.95743210,

0

出力結果から、余分な文字がすべて取り除かれていることが分かります。その後、カンマを区切り文字として StringSplit 関数を使用し、データを分割して単一の配列内の独立した要素に分けました。これにより、それぞれの値を個別に扱いやすくなりました。

 

3本目のローソク足データから時刻とOHLC値を抽出する

3本目のローソク足のデータについても、最初の2本と同じ方法で整理します。まず出力してフォーマットを確認し、不要な文字を取り除きます。 不要な文字を削除した後、3本目のローソク足のデータを格納するための文字列配列を宣言します。その後、StringSplit 関数を使用し、カンマを区切り文字としてデータを個別の要素に分割します。前のローソク足と同様に、これにより開始時刻、始値、高値、安値、終値といった各変数に個別にアクセスできるようになります。この方法により、すべてのローソク足データが適切に整理され、追加の処理に備えた状態になります。

Print(candle_data[2]);

出力

,[

1763683200000,

"86637.22000000",

"87498.94000000",

"80600.00000000",

"85129.43000000",

"72256.12679000",

1763769599999,

"6061348756.34156410",

11826480,

"34071.85828000",

"2859133223.22405230",

"0"
例:
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   WebRequest(method, url, headers, time_out, data, result, result_headers);
   string server_result = CharArrayToString(result);
// Print(server_result);

   string candle_data[];
   int array_count = StringSplit(server_result,']', candle_data);

//DAY 1
   string day1_data = candle_data[0];
   StringReplace(day1_data,"[[","");
   StringReplace(day1_data,"\"","");

   string day1_data_array[];
   StringSplit(day1_data,',',day1_data_array);

//DAY 2
   string day2_data = candle_data[1];
   StringReplace(day2_data,",[","");
   StringReplace(day2_data,"\"","");

   string day2_data_array[];
   StringSplit(day2_data,',',day2_data_array);

//DAY 3
   string day3_data = candle_data[2];
   StringReplace(day3_data,",[","");
   StringReplace(day3_data,"\"","");

   string day3_data_array[];
   StringSplit(day3_data,',',day3_data_array);

//---
   return(INIT_SUCCEEDED);
  }

ローソク足のフォーマットは一定しているため、その形式を利用して数値の前に付いている不要な文字や二重引用符を簡単に取り除くことができます。クリーンアップされた値はその後、分割して配列に格納することで、より簡単に数値処理をおこなえるようになります。

例:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   WebRequest(method, url, headers, time_out, data, result, result_headers);
   string server_result = CharArrayToString(result);
// Print(server_result);

   string candle_data[];
   int array_count = StringSplit(server_result,']', candle_data);

//DAY 1
   string day1_data = candle_data[0];
   StringReplace(day1_data,"[[","");
   StringReplace(day1_data,"\"","");

   string day1_data_array[];
   StringSplit(day1_data,',',day1_data_array);

//DAY 2
   string day2_data = candle_data[1];
   StringReplace(day2_data,",[","");
   StringReplace(day2_data,"\"","");

   string day2_data_array[];
   StringSplit(day2_data,',',day2_data_array);

//DAY 3

   string day3_data = candle_data[2];
   StringReplace(day3_data,",[","");
   StringReplace(day3_data,"\"","");

   string day3_data_array[];
   StringSplit(day3_data,',',day3_data_array);

//DAY 4

   string day4_data = candle_data[3];
   StringReplace(day4_data,",[","");
   StringReplace(day4_data,"\"","");

   string day4_data_array[];
   StringSplit(day4_data,',',day4_data_array);

//DAY 5

   string day5_data = candle_data[4];
   StringReplace(day5_data,",[","");
   StringReplace(day5_data,"\"","");

   string day5_data_array[];
   StringSplit(day5_data,',',day5_data_array);

//---
   return(INIT_SUCCEEDED);
  }

混乱を避けるために、ここでいったん本記事は区切りとします。次回の記事では、複数の日足ローソク足から取得した類似データを、それぞれ1つの配列にまとめていきます。たとえば、すべての開始時刻を1つの配列にまとめ、高値、安値、終値についても同様に整理します。これにより、ローソク足データの取り扱いがより管理しやすく、構造化されたものになります。


結論

本記事では、MQL5のWebRequest関数を使用して外部プラットフォームの API からローソク足データを取得する方法と、そのデータを整理し始める方法について解説しました。サーバーからのレスポンスを分析してそのパターンを把握し、不要な文字を削除しながら、各ローソク足ごとにデータを個別の配列へ分割する方法を確認しました。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20375

添付されたファイル |
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MQL5でかぎ足をマスターする(第1回):インジケーターの作成 MQL5でかぎ足をマスターする(第1回):インジケーターの作成
MQL5で完全なかぎ足エンジンを構築する方法を学びましょう。価格の反転の構築、動的な線分の生成、そしてかぎ足の構造をリアルタイムで更新する方法を扱います。本連載第1回では、MetaTrader 5上にかぎ足を直接描画する方法を解説します。これにより、トレーダーはトレンドの転換や市場の強さを明確に把握できるようになり、第2部で扱うかぎ足ベースの自動売買ロジックの準備が整います。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5での取引戦略の自動化(第42回):セッションベースのオープニングレンジブレイクアウト(ORB)システム MQL5での取引戦略の自動化(第42回):セッションベースのオープニングレンジブレイクアウト(ORB)システム
MQL5で完全にカスタマイズ可能なセッションベースのオープニングレンジブレイクアウト(ORB)システムを作成します。このシステムでは、任意のセッション開始時刻とレンジの期間を設定でき、指定したオープニング期間の高値と安値を自動計算し、かつ動きの方向に沿った確定ブレイクアウトのみを取引します。