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

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

MetaTrader 5統合 |
21 2
ALGOYIN LTD
Israel Pelumi Abioye

はじめに

MQL5入門第30回へようこそ。本記事では、MQL5における外部APIの扱いについて引き続き解説していきますが、単純なデータ抽出や基本的なリクエスト処理の範囲を超えた内容に進みます。これまでに、リクエストの送信、レスポンスの取得、生データのクリーンアップ、そして結果を利用しやすい要素に分割する方法を確認してきました。

前回の記事では、直近5本の日足データを取得して処理しました。それらの構成を分析し、不要な文字を取り除き、各ローソク足を個別の要素に分解しました。データが明確に分離されたので、それらの「素材」をより効率的に整理する段階に進みます。

各ローソク足を個別に扱うのではなく、複数のローソク足に共通する要素を、それぞれ専用の配列にまとめて整理する方法を学びます。たとえば、すべてのローソク足の開始時刻は1つの配列に格納します。同様に、始値、高値、安値、終値、出来高、さらに分析対象としたいその他のローソク足の構成要素についても同様に扱います。このように体系的に整理することで、日をまたいだ数値の比較、トレンドの特定、各種計算の実行、そしてインジケーターや売買ロジックのためのデータ準備が容易になります。 本記事を読み終える頃には、APIのローソク足データを整理するための、明確で効率的な手法を習得できるはずです。これにより、MQL5コードの可読性、拡張性、保守性が向上し、より高度な分析に向けた基盤を整えることができます。

 

すべての開始時刻を単一の配列にまとめる

まずは、複数のローソク足に含まれるすべての開始時刻を、1つの配列にまとめるところから始めます。これまでは、APIレスポンスからデータを取得してクリーンアップした際に、各ローソク足の開始時刻は個別に保持されていました。これらをより扱いやすくし、処理を簡単にするために、個別のままにしておくのではなく、構造化された単一の配列へとまとめます。 すべての開始時刻を1つの配列に格納することで、ローソク足の並び順に対応した正しいタイムスタンプのシーケンスを生成できます。これにより、パターンの確認、ローソク足間の時間の計算、日付の比較、さらには始値、高値、安値、終値といった他の要素との時刻の対応付けなど、時間ベースの処理が大幅に容易になります。

配列内の各要素は、それぞれ特定のローソク足に対応するインデックスを持ちます。たとえば、インデックス0には最初のローソク足の開始時刻、インデックス1には2番目のローソク足の開始時刻、といった具合に格納されます。すべてのデータを格納した後に配列を出力すれば、時刻が正しくグループ化され、順序どおりに並んでいるかを視覚的に確認できます。 このシンプルながら重要なステップは、他のローソク足データを整理するための基盤となります。始値、高値、安値、終値、出来高についても、開始時刻と同様の方法で処理していきます。こうして同種のデータをまとめることで、扱うローソク足の数が増えたり、より高度な処理をおこなったりする場合でも、MQL5スクリプトのスケーラビリティが向上し、コードの可読性が高まり、分析もより明確になります。

前の部分では、サーバーからのレスポンスを日足の文字列配列に分割し、不要な文字を取り除きました。また、カンマを区切り文字として使用し、各ローソク足のデータを個別の要素へと分解しました。ただし、その分割された配列のどの位置が開始時刻に対応しているのかについては、まだ特定していません。グループ化をおこなう前に、まずこれを確認する必要があります。

すべてのローソク足は同一のフォーマットで並んでいるため、開始時刻は常に同じ位置に存在します。最初のローソク足でその位置を特定すれば、残りのすべてのローソク足についても同じ方法で開始時刻を抽出できます。これにより、すべての開始時刻を1つの整理された配列にまとめることができ、後の処理で容易にアクセスおよび活用できるようになります。

//DAY 1
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);

day1_data_array[]

 1763424000000,              // opening time (0)
 92215.14000000,             // open price (1)
 93836.01000000,             // high price (2)
 89253.78000000,             // low price (3)
 92960.83000000,             // close price (4)
 39835.14769000,             // volume (5)
 1763510399999,              // closing time (6)
 3641033186.30045840,        // quote asset volume (7)
 8786593,                    // number of trades (8)
 20130.95957000,             // taker buy base asset volume (9)
 1841176605.14182350,        // taker buy quote asset volume (10)
 0                           // placeholder (11)

分割されたデータの結果を確認すると、開始時刻は配列の最初の要素として格納されていることが分かります。つまり、1日目のデータ配列を使用すれば、インデックス0に位置する最初のローソク足の開始時刻を取得できるということです。

Print(day1_data_array[0]);

図1:時間(ミリ秒)

この値は、1日目のローソク足データ配列の最初の要素から分かるように、配列のインデックス0に位置しています。出力される値は、ローソク足が正確にいつ開始したかを示しており、ミリ秒単位で表現されています。言い換えると、この数値はローソク足の正確な開始時刻を示すタイムスタンプであり、ミリ秒単位で記録されています。

現在の形式は文字列であるため、この開始時刻をMQL5で実際の日時としてそのまま使用することはできません。正しく扱ったり、他のローソク足の開始時刻と一緒に保存したりするためには、まず適切なdatetime型に変換する必要があります。 サーバーから取得した時刻はミリ秒単位であるため、最初に1000で割って秒単位に変換します。この変換は重要であり、MQL5のdatetimeが秒単位を基準としているためです。変換が完了した後は、その結果をdatetime配列に格納することができます。これにより、計算処理の実行、開始時刻の比較、あるいは追加の分析への活用が可能になります。

//+------------------------------------------------------------------+
//| 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);
//  ArrayPrint(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);

// Opening Time Array
   long day1_time_s = (long)StringToInteger(day1_data_array[0])/1000;
   datetime day1_time = (datetime)day1_time_s;
   Print(day1_time);

//---
   return(INIT_SUCCEEDED);
  }

出力

図2:日付

解説

最初の要素から取得した開始時刻はもともと文字列形式であるため、MQL5の処理でそのまま直接使用することはできません。まず数値(整数)へ変換し、その後にMQL5が正当な時刻として扱える形式へ変換します。この変換により、他のローソク足の時刻と問題なく処理、保存、比較ができるようになります。 ミリ秒を秒に変換した後、この値はlong型の整数変数に格納します。ミリ秒は年数にわたると非常に大きな数値になるため、long型を使用することで安全に扱うことができます。

その後、このlong型の値をdatetime型へ変換します。この段階で、生の数値はMQL5が理解可能な適切な日時表現へと変換されます。datetime形式に変換された後は、その値を開始時刻の配列に格納したり、計算や比較に使用したりすることが可能になります。 2日目についても同じ処理をおこないます。2日目のデータから開始時刻を取得し、それを文字列から実用的な時刻形式へ変換し、1本目のローソク足と同様に保存または出力します。これにより、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);
//  ArrayPrint(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);

// Opening Time Array

   long day1_time_s = (long)StringToInteger(day1_data_array[0])/1000;
   datetime day1_time = (datetime)day1_time_s;

   long day2_time_s = (long)StringToInteger(day2_data_array[0])/1000;
   datetime day2_time = (datetime)day2_time_s;
   Print("DAY 1 TIME: ", day1_time,"\nDAY 2 TIME: ",day2_time);
//---
   return(INIT_SUCCEEDED);
  }

出力

図3:1日目と2日目の時間

説明

文字列配列に含まれる開始時刻は、ミリ秒単位で時刻を示す非常に高精度な時計のようなものだと考えることができます。ただし、値自体は単なる数値の文字列であるため、正確に時間を表しているにもかかわらず、通常の時刻としては読み取ったり理解したりするのが困難です。

まず、この文字列を数値へ変換する必要があります。これは、ストップウォッチに表示された数値が実際に計算に使用できる値であると認識するのに似ています。さらに、多くの日時処理システムはUnixエポック以降の秒単位で時間を扱うため、ミリ秒単位の値を秒単位へ変換するために1000で割ります。これは、非常に細かい時間単位を、システムで扱える秒単位へと整える処理に相当します。 秒単位へ変換した後、その値をdatetime型へ変換します。この段階で、その値は特定の時点を示すものとしてシステムに認識され、2本目の日足の正確な開始時刻を表すことになります。

1日目と2日目の開始時刻を出力すると、人間が読み取れる形式で明確に表示されます。これにより、それらを評価・比較したり、計算処理やインジケーターに利用したりすることが可能になります。この値は、ローソク足がチャート上で実際に形成され始めた瞬間を示す正確なタイムスタンプとなります。

最初の2本の日足について開始時刻の取得と変換方法を理解できたので、次は残りの3日分にも同じ処理を適用します。各日足に対応する文字列配列を確認し、最初の値をミリ秒から秒へ変換し、その後datetime型へキャストすることで、各ローソク足の開始時刻を正確に記録できます。これにより、5本すべての日足の開始時刻がdatetime形式で一貫して保存され、後続の処理として単一の配列へグループ化し、取引手法や追加分析に活用する準備が整います。

//+------------------------------------------------------------------+
//| 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);
//  ArrayPrint(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);

// Opening Time Array

   long day1_time_s = (long)StringToInteger(day1_data_array[0])/1000;
   datetime day1_time = (datetime)day1_time_s;

   long day2_time_s = (long)StringToInteger(day2_data_array[0])/1000;
   datetime day2_time = (datetime)day2_time_s;

   long day3_time_s = (long)StringToInteger(day3_data_array[0])/1000;
   datetime day3_time = (datetime)day3_time_s;

   long day4_time_s = (long)StringToInteger(day4_data_array[0])/1000;
   datetime day4_time = (datetime)day4_time_s;

   long day5_time_s = (long)StringToInteger(day5_data_array[0])/1000;
   datetime day5_time = (datetime)day5_time_s;

   Print("DAY 1 TIME: ", day1_time,"\nDAY 2 TIME: ",day2_time,"\nDAY 3 TIME: ",day3_time,"\nDAY 4 TIME: ",day4_time,"\nDAY 5 TIME: ",day5_time);

  }

出力

図4:1日目から5日目までの時間

最初の2本と同じ処理を残りの3本のローソク足にも適用することで、5本すべての開始時刻を取得することができました。各日の配列の最初の要素は、文字列から秒単位のlong整数へ変換された後、datetime型へキャストされています。その結果、開始時刻の完全なセットが得られ、5つすべてを出力することで確認したり、まとめて扱ったり、分析したりすることが可能になります。

これで、5本の日足それぞれの開始時刻を正しく抽出することができました。次のステップは、これらすべての開始時刻を1つのコレクションとして扱えるように、datetime配列を作成することです。

//+------------------------------------------------------------------+
//| 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);
 //  ArrayPrint(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);   
   
 // Opening Time Array
   
  long day1_time_s =  (long)StringToInteger(day1_data_array[0])/1000;
  datetime day1_time = (datetime)day1_time_s;
  
  long day2_time_s =  (long)StringToInteger(day2_data_array[0])/1000;
  datetime day2_time = (datetime)day2_time_s;
  
  long day3_time_s =  (long)StringToInteger(day3_data_array[0])/1000;
  datetime day3_time = (datetime)day3_time_s;
  
  long day4_time_s =  (long)StringToInteger(day4_data_array[0])/1000;
  datetime day4_time = (datetime)day4_time_s;
  
  long day5_time_s =  (long)StringToInteger(day5_data_array[0])/1000;
  datetime day5_time = (datetime)day5_time_s;
  
  datetime OpenTime[5] = {day1_time, day2_time, day3_time, day4_time, day5_time};
  ArrayPrint(OpenTime);
}

出力

図5.:ArrayPrint

説明

5本の日足の開始時刻を1つの配列にまとめました。毎日複数の変数を扱う代わりに、この設計により、1つの整理されたコンテナでデータを管理できるようになり、効率的に扱うことが可能になります。 配列を定義した後、ArrayPrint(OpenTime)を使用しました。この関数により、MetaTraderのログウィンドウに配列全体が出力されます。保存されたdatetime値をすべて確認することで、変換処理が正しくおこなわれ、各日の開始時刻が配列内に正確に格納されていることを確認できました。

配列は1つの関数に依存せず表示することも可能です。配列をループで回して各要素を個別に出力したり、出力形式をわかりやすく調整したりできます。より簡潔にまとめたい場合は、すべての値を1行に表示することも可能です。この柔軟性により、配列の表示方法を自由にコントロールできます。

Print("DAY 1 TIME: ", OpenTime[0],"\nDAY 2 TIME: ",OpenTime[1],"\nDAY 3 TIME: ",OpenTime[2],"\nDAY 4 TIME: ",OpenTime[3],"\nDAY 5 TIME: ",OpenTime[4]);

配列の各数値は、1日目から5日目までそれぞれのローソク足の開始時刻に対応しています。要素を個別に表示することで、どの開始時刻がどのローソク足に対応しているかを簡単に把握できます。特定の項目だけを確認したりデバッグしたりしたい場合、この方法は非常に便利です。

 

すべての始値を単一の配列にまとめる

日足データの並びを見ると、始値は各文字列配列において、開始時刻の直後に位置する要素であることが分かります。そこで、このパートでは各ローソク足の始値を特定することに注力しました。

文字列配列内の適切なインデックスを使って始値を特定した後、文字列の値をMQL5の計算で利用できるようにdouble型へ変換しました。変換後、同じ手順を5本のローソク足すべてに適用し、各日の始値を取得しました。これにより、開始時刻と同様に、すべての始値を一貫して、整理された形で1つの配列にまとめる準備が整いました。

  1763424000000,       // opening time (0)
  92215.14000000,      // open price (1)
  93836.01000000,      // high price (2)
  89253.78000000,      // low price (3)
  92960.83000000,      // close price (4)
  39835.14769000,      // volume (5)
  1763510399999,       // closing time (6)
  3641033186.30045840, // quote asset volume (7)
  8786593,             // number of trades (8)
  20130.95957000,      // taker buy base asset volume (9)
  1841176605.14182350, // taker buy quote asset volume (10)
  0                    // placeholder (11)

//+------------------------------------------------------------------+
//| 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);
//  ArrayPrint(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);

// Opening Time Array

   long day1_time_s = (long)StringToInteger(day1_data_array[0])/1000;
   datetime day1_time = (datetime)day1_time_s;

   long day2_time_s = (long)StringToInteger(day2_data_array[0])/1000;
   datetime day2_time = (datetime)day2_time_s;

   long day3_time_s = (long)StringToInteger(day3_data_array[0])/1000;
   datetime day3_time = (datetime)day3_time_s;

   long day4_time_s = (long)StringToInteger(day4_data_array[0])/1000;
   datetime day4_time = (datetime)day4_time_s;

   long day5_time_s = (long)StringToInteger(day5_data_array[0])/1000;
   datetime day5_time = (datetime)day5_time_s;

   datetime OpenTime[5] = {day1_time, day2_time, day3_time, day4_time, day5_time};

// Opening Price Array
   double day1_open = StringToDouble(day1_data_array[1]);

//---
   return(INIT_SUCCEEDED);
  }

説明

まず、1日目のデータ配列の中から始値を特定しました。日足データの構造を確認した結果、始値はインデックス1に格納されていることが分かりました。これは理にかなっています。なぜなら、開始時刻がインデックス0にあり、始値は論理的にその次であるインデックス1に対応するためです。 インデックスを特定した後、文字列をdouble型に変換しました。価格は通常、小数点を含むため、整数や文字列として保存してしまうと正しい計算ができません。そのため、平均値の計算や比較、追加分析などの数値計算に使用できるようにdouble型へ変換しました。

よりよく理解するために、APIから取得した生データを手書きの情報のように考えてみてください。始値は文字列として書かれており、一見数字に見えてもコンピュータは文字列として認識します。私たちは、変換関数を使ってこの文字列を「計算機が使える数字」に変換し、実際の計算に利用できるようにしました。この段階で、単なるテキスト表記だった値を有効な数値として扱えるようにしました。変換後、正しいdouble値をday1_openに格納でき、残りのローソク足についても同じ手順で処理できます。

最初のローソク足の始値を特定・変換する方法が分かったので、残りの4本のローソク足についても同じ手順を踏みました。手順はまったく同じで、以前のパターンに従って処理を進めただけです。 各日の始値を正しく取得するため、まず文字列配列内のインデックスを確認しました。1日目と同様に、2日目、3日目、4日目、5日目の始値もそれぞれ対応する配列のインデックス1に格納されていました。インデックスを確認した後、値を文字列形式からdouble型に変換する必要があります。 価格は通常小数を含むため、この変換は不可欠です。たとえば、1.08512や154.72のような価格を計算に使ったり、他の価格と比較したりする場合、double型として記録しておかなければなりません。文字列のままでは、MetaTraderは数値として扱うことができません。

この処理は、印刷された数字を電卓に入力することに例えられます。紙に書かれた数字が有効に見えても、電卓は実際の数値でしか計算できません。私たちは、各ローソク足の始値を文字列からdouble型に変換することで、MQL5が将来的に計算に利用できるよう準備をしていた、ということです。

// Opening Price Array
 double day1_open = StringToDouble(day1_data_array[1]);
 double day2_open = StringToDouble(day2_data_array[1]);
 double day3_open = StringToDouble(day3_data_array[1]);
 double day4_open = StringToDouble(day4_data_array[1]);
 double day5_open = StringToDouble(day4_data_array[1]);
 
 double OpenPrice[5] = {day1_open, day2_open, day3_open, day4_open, day5_open};
 
 Print("DAY 1 OPEN PRICE: ", OpenPrice[0],"\nDAY 2 OPEN PRICE: ",OpenPrice[1],"\nDAY 3 OPEN PRICE: "
  ,OpenPrice[2],"\nDAY 4 OPEN PRICE: ",OpenPrice[3],"\nDAY 5 OPEN PRICE: ",OpenPrice[4]);

出力

図6:始値

説明

特定の日のデータ配列から始値の文字列を取得し、各行の処理で小数点を保持する数値に変換して、その日のdouble変数に格納します。価格は小数を含むため、文字列からdouble型に変換し、浮動小数点値として扱う必要があります。これにより、算術計算、比較、インジケーター計算などが正しくおこなえるようになります。各日の分割データの2番目の要素(インデックス1)がローソク足構造における始値の位置であることは前の段階で確認済みであり、変換処理はその位置から値を読み取ります。

5日目の変換は、最初の4日とは大きく異なります。最初の4日目までは、それぞれのデータ配列から始値を取得していますが、5日目では誤って再びday4_data_arrayから読み取っている可能性があります。このままでは、5日目の始値が4日目と同じ値になってしまいます。各日の価格が正しい要素から取得されるようにするためには、5日目の変換で使用する元の配列を確認し、5日目のデータ配列を使用するように修正する必要があります。

各日の始値を数値に変換した後、5本すべての価格を1つの配列にまとめます。すべての値が1つの配列に揃っていれば、計算処理やトレンド分析、インジケーターでの利用が簡単におこなえます。価格を個別の文字列として管理する場合と比べて、より信頼性が高く効率的です。 各日の始値を数値に変換した後、5本の価格を1つの配列にまとめることで、計算やトレンド分析、インジケーターでの利用が容易になり、個別の文字列として扱うよりも信頼性と効率性が向上します。扱うよりも、信頼性が高く効果的です。

最後に、各日の始値は、配列の要素をインデックス順に出力することで、分かりやすい形で表示されます。この出力を確認することで、すべての変換とグループ化が正しくおこなわれたか、データの順序が期待どおりかを素早く検証できます。計算で元のインデックスや配列名を使用する前に、それらが正確であることを必ず確認してください。

 

すべての終値を単一の配列にまとめる

終値を抽出する際には、以前に始値を扱った際と同じ体系的な手順を用います。各日足のデータは一貫した構造で整理されているため、終値が配列内のどの位置にあるかも正確に把握しています。この予測可能な構造により、要素を探したり推測したりする必要がなく、抽出作業が簡単になります。各ローソク足が同じパターンに従うことが分かっているため、適切な位置を参照するだけで済みます。この構造が変わらない限り、任意の本数のローソク足に対しても信頼できるコードを作成することが可能です。

次のステップは、各日のデータのどの位置に終値が格納されているかを特定した上で、その値を取得することです。この時点では、データはまだ文字列配列として保持されており、テキスト形式です。一見価格のように見えても、計算に使用するには数値型に変換する必要があります。MQL5で値を実数として扱うためには、配列から値を取り出し、double型に変換する必要があります。

終値をdouble型に変換しておくことで、計算や比較、その他の処理を正しくおこなうことができます。MQL5は文字列と数値を明確に区別して扱うため、数学的な操作をおこなう前に正しいデータ型に変換しておくことが非常に重要です。 次のステップは、各終値を有効な数値に変換した後、それらを整理して配列にまとめることです。5日間の終値をそれぞれ別々の変数に分散させるのではなく、1つの配列に格納することで、データへのアクセスが容易になり、より整理されたコードを書くことが可能になります。

さらに、配列に格納することで、値をループで処理したり、計算をおこなったり、インジケーターに渡したり、1日分の終値を別の日と比較したり、構造化データが必要な分析をおこなったりすることが簡単になります。配列を使用すると、個々の要素を独立して扱うのではなく、関連する複数の値を1つの整理された単位として管理できるため、作業効率が向上します。 配列が作成され、すべての終値が格納されたら、出力して確認することで、すべてが正しく記録されたことを検証できます。この重要な確認ステップにより、抽出と変換が正確におこなわれたことが確認できます。また、すべての終値を一度に表示することで、データを視覚的にチェックし、誤りがないかを簡単に確認することも可能です。

 1763424000000,     // opening time (0)
 92215.14000000,    // open price (1)
 93836.01000000,    // high price (2)
 89253.78000000,    // low price (3)
 92960.83000000,    // close price (4)
 39835.14769000,    // volume (5)

この配置を見ると、終値は配列のインデックス4、つまりデータ配列の5番目の要素に格納されていることが分かります。終値を含むインデックスを特定したら、文字列の値をdouble型に変換し、同じ体系的な手順で新しい配列に格納します。価格は小数点を含む値であるため、数値型で表現する必要があり、double型への変換が不可欠です。

各日の終値を特定した後、5日分の終値を1つの配列にまとめます。始値と同様に、この方法により、すべての終値をまとめて扱うことが可能になります。このグループ化されたデータを使うことで、配列のインデックスに応じて任意の日の終値を簡単に取得でき、計算や比較、グラフ描画、インジケーターやスクリプト内でのその他の処理に利用することができます。

// Opening Time Array
  long day1_time_s =  (long)StringToInteger(day1_data_array[0])/1000;
  datetime day1_time = (datetime)day1_time_s;
  
  long day2_time_s =  (long)StringToInteger(day2_data_array[0])/1000;
  datetime day2_time = (datetime)day2_time_s;
  
  long day3_time_s =  (long)StringToInteger(day3_data_array[0])/1000;
  datetime day3_time = (datetime)day3_time_s;
  
  long day4_time_s =  (long)StringToInteger(day4_data_array[0])/1000;
  datetime day4_time = (datetime)day4_time_s;
  
   long day5_time_s =  (long)StringToInteger(day5_data_array[0])/1000;
  datetime day5_time = (datetime)day5_time_s;
      
  datetime OpenTime[5] = {day1_time, day2_time, day3_time, day4_time, day5_time};
   
 // Opening Price Array
 double day1_open = StringToDouble(day1_data_array[1]);
 double day2_open = StringToDouble(day2_data_array[1]);
 double day3_open = StringToDouble(day3_data_array[1]);
 double day4_open = StringToDouble(day4_data_array[1]);
 double day5_open = StringToDouble(day4_data_array[1]);
 
 double OpenPrice[5] = {day1_open, day2_open, day3_open, day4_open, day5_open}; 

 // Closing Price Array
 double day1_close = StringToDouble(day1_data_array[4]);
 double day2_close = StringToDouble(day2_data_array[4]);
 double day3_close = StringToDouble(day3_data_array[4]);
 double day4_close = StringToDouble(day4_data_array[4]);
 double day5_close = StringToDouble(day4_data_array[4]);
 
 double ClosePrice[5] = {day1_close, day2_close, day3_close, day4_close, day5_close};
 Print("DAY 1 CLOSE PRICE: ", ClosePrice[0],"\nDAY 2 CLOSE PRICE: ",ClosePrice[1],"\nDAY 3 CLOSE PRICE: "
,ClosePrice[2],"\nDAY 4 CLOSE PRICE: ",ClosePrice[3],"\nDAY 5 CLOSE PRICE: ",ClosePrice[4]);

出力

図7:終値

説明

まず、どのインデックスが終値を表すかを特定することが最初のステップです。値の並びを見ると、終値は各ローソク足で常に同じ位置にあることが分かり、毎日正確に抽出できることが確認できます。 終値の位置を確認した後、各値を文字列から計算に利用可能な数値型に変換する必要があります。比較や計算などの操作には数値型が必要であり、サーバーから取得したデータはもともと文字列形式であるため、この変換は非常に重要です。5日間の日足すべてに同じ手順を適用し、各終値を数値形式に変換しました。

各日の終値をdouble型に変換した後、すべての終値をまとめるためにClosePriceという1つの配列を作成しました。配列の最初の要素が最新のローソク足を、最後の要素がシリーズ内の5日目のローソク足を示すように順序を保っています。このデータ整理の方法により、任意のローソク足の終値を簡単に取得したり、複数のローソク足に対して分析をおこなったりすることが可能になります。 各日足の終値を取得した後、それを文字列から計算に利用できる数値型に変換することで、文字列データによる問題を防ぎ、5本すべての終値を分析、比較、その他の処理に利用可能な形式で整えることができます。

 

すべての高値を単一の配列にまとめる

このセクションの目的は、各日足の高値をすべて1つの配列にまとめることです。最初のステップは、始値や終値のときと同様に、各ローソク足配列のどの要素が高値に対応するかを特定することです。ローソク足データのパターンを見ることで、各日の高値が格納されている適切なインデックスを確認できます。

 1763424000000,     // opening time (0)
 92215.14000000,    // open price (1)
 93836.01000000,    // high price (2)
 89253.78000000,    // low price (3)
 92960.83000000,    // close price (4)
 39835.14769000,    // volume (5)

インデックスを特定したら、各日の高値を文字列から数値に変換します。価格は小数点を含むことが多く、計算や比較には数値データが必要なため、この変換は必須です。すべての高値を変換した後、それらを1つの配列にまとめます。 各要素が特定の日の高値を表すことで、複数の日足のトレンド分析や値の比較、インジケーターや取引ロジックでの利用が容易になります。この手順は、始値や終値を処理した方法と同様に、ローソク足データの処理を統一するためにおこないます。

// High Price Array
 double day1_high = StringToDouble(day1_data_array[2]);
 double day2_high = StringToDouble(day2_data_array[2]);
 double day3_high = StringToDouble(day3_data_array[2]);
 double day4_high = StringToDouble(day4_data_array[2]);
 double day5_high = StringToDouble(day4_data_array[2]);
 
 double HighPrice[5] = {day1_high, day2_high, day3_high, day4_high, day5_high};

説明

ここでは、各日足の高値を1つの配列にまとめています。最初のステップとして、各日の文字列配列から高値を表す要素を特定します。データの分析によれば、各日足配列の3番目の要素(インデックス2)が高値に対応しています。

各日について、この文字列値をdouble型に変換します。価格は小数点を含むことが多いため、数値型として保存することで、後で計算、比較、分析をおこなうことが可能になります。たとえば、1日目のローソク足配列の3番目の要素をdoubleに変換し、day1_highを生成します。この処理を残りの4日についても繰り返します。

各日の高値を変換した後、5日分の値を1つの配列にまとめます。配列の各要素が特定の日の最高値を表すため、複数の日足にまたがる高値を整理された形で簡単に取得、評価、変更することができます。

 

すべての安値を単一の配列にまとめる

ローソク足データを整理する次のステップは、各日の安値を1つの配列にまとめることです。始値や高値のときと同様に、まず各日足配列の中で安値がどこにあるかを特定します。データによれば、安値はインデックス3、つまり各ローソク足配列の4番目の要素に格納されています。

各日の安値を文字列からdouble型に変換します。これにより、計算や比較、その他の分析に値を利用できるようになります。変換後、5日分すべての安値を1つの配列にまとめます。この配列では、各インデックスが特定の日に対応しているため、個々の安値にアクセスしたり操作したりするのが簡単になります。 このように安値をまとめることで、各ローソク足の構成要素を別々の配列に整理した体系的な構造が完成します。データ管理が容易になるだけでなく、この方法により、分析やインジケーター、取引ロジックで利用する準備も整います。

// Low Price Array

 double day1_low = StringToDouble(day1_data_array[3]);
 double day2_low = StringToDouble(day2_data_array[3]);
 double day3_low = StringToDouble(day3_data_array[3]);
 double day4_low = StringToDouble(day4_data_array[3]);
 double day5_low = StringToDouble(day4_data_array[3]);


  double LowPrice[5] = {day1_low, day2_low, day3_low, day4_low, day5_low};

説明

5日間の安値を、各日のローソク足データから取得し、文字列からdouble型に変換した後、1つの配列に記録しました。配列を出力して確認することで、値が正しく準備され、インジケーター、取引戦略、エキスパートアドバイザーで利用できる順序で整理されていることが確認できます。配列の各スロットには、対応する特定の日が割り当てられています。                                                                                                         



結論

本記事では、複数の日足から類似の要素を抽出して専用の配列にまとめることで、APIローソク足データの整理を次の段階に進めることに成功しました。各ローソク足の開始時刻、始値、高値、安値、終値を取得し、変換して構造化された配列に格納しました。この方法を用いることで、特定のデータポイントへのアクセス、計算処理、インジケーターやエキスパートアドバイザーでの利用が非常に簡単になります。記事を読み終える頃には、MQL5で複数のローソク足を効率的に管理する明確かつ効果的な方法を理解でき、より高度な分析や取引戦略の自動化に向けた準備が整います。

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

添付されたファイル |
最後のコメント | ディスカッションに移動 (2)
Jiang Huang
Jiang Huang | 4 12月 2025 において 11:49
WebRequestシリーズが気に入っています。バックテストやライブ取引に使用する方法を教えてください。カスタムシンボルを 使うのですか?
ALGOYIN LTD
Israel Pelumi Abioye | 4 12月 2025 において 14:27
Jiang Huang カスタムシンボルを 使用していますか?
ありがとうございます。次の記事をお楽しみに
取引戦略の開発:出来高制限アプローチの使用 取引戦略の開発:出来高制限アプローチの使用
テクニカル分析の世界では、価格がしばしば中心的な役割を果たします。トレーダーはサポートやレジスタンス、パターンを綿密に描きますが、多くの場合、これらの動きを駆動する重要な力である「出来高」を見落としています。本記事では、新しい出来高分析のアプローチであるVolume Boundaryインジケーターについて解説します。この指標は、バタフライ曲線やトリプルサイン曲線といった高度な平滑化関数を用いることで変換をおこない、より明確な解釈と体系的な取引戦略の構築を可能にします。
MQL5における取引戦略の自動化(第45回):逆フェアバリューギャップ(IFVG) MQL5における取引戦略の自動化(第45回):逆フェアバリューギャップ(IFVG)
本記事では、MQL5において逆フェアバリューギャップ(IFVG, Inverse Fair Value Gap)検出システムを構築します。このシステムは、直近のバーにおける強気/弱気のFVG(フェアバリューギャップ)を最小ギャップサイズフィルターを適用して識別し、価格との相互作用に基づき、その状態をnormal(通常)、mitigated(解消)、inverted(反転)として追跡します(遠側ブレイクによるミティゲーション、再エントリー時のリトレース(押し戻し)、内側から遠側をブレイクしてクローズすることによるインバージョンを含みます)。また、重複は無視し、追跡するFVGの数を制限します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
プロップファームチャレンジをクリアするための自動リスク管理 プロップファームチャレンジをクリアするための自動リスク管理
本記事では、GOLD向けのプロップファーム用エキスパートアドバイザー(EA)の設計について解説します。このEAは、ブレイクアウトフィルター、マルチタイムフレーム分析、堅牢なリスク管理、そして厳格なドローダウン制御を特徴としています。ルール違反を回避し、ボラティリティの高い市場環境下でも安定した取引実行を維持することで、トレーダーがプロップファームのチャレンジをクリアするのを支援します。