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

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

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

はじめに

連載「MQL5入門」の第31回へようこそ。前回の記事では、MQL5におけるAPIおよびWebRequest関数の基本について解説しました。具体的には、サーバーへのリクエスト送信、レスポンスの受信、そしてサーバー応答の解析による必要情報の抽出方法を紹介しました。特に前回は、Binance APIを用いてBTCUSDTの直近5本の日足ローソク足データを取得しました。また、始値、高値、安値、終値といった関連データを個別の配列に分類する方法についても説明しました。このように整理されたデータは、エキスパートアドバイザー(EA)やインジケーターの構築に活用できます。

本記事ではより発展的な内容に進みます。サーバー応答を整理した後、BTCUSDTの直近10本の30分足ローソク足データから重要な情報を抽出します。しかし単なるデータ取得にとどまらず、ローソク足形式でデータを可視化するインジケーター構築の基盤を作ります。 まずローソク足データを取得して整理し、ファイルとして保存します。その後、このファイルを読み込み、保存されたローソク足情報を用いてチャート上にローソク足を即座に描画するカスタムインジケーターを作成します。これは、インジケーターがリアルタイムでWebRequestを直接使用できないためです。この手法により、APIデータをMetaTrader 5上で実際のチャートとして表示できるようになります。

 

APIからのローソク足データ取得

プロジェクトに必要なローソク足データを取得するには、まずBinance APIへGETリクエストを送信する必要があります。取得するデータは前回と同様に、始値、高値、安値、終値です。ただし今回は、BTCUSDTの直近10本の30分足に焦点を当てます。 ソフトウェアの効率を向上させるため、MQL5のOnTickイベントハンドラを使用します。これにより、新しいティックが発生するたびにコードが実行されます。しかし、毎ティックごとにAPIリクエストを送信すると無駄が多く、API制限を超える可能性もあるため、リクエストはMetaTrader 5上で新しい30分足が確定したタイミングでのみ送信するようにします。

例:

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

datetime last_bar_time = 0;

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

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

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   datetime current_m30_time = iTime(_Symbol,PERIOD_M30,0);

   if(current_m30_time != last_bar_time)
     {

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

      Print(server_result);

      last_bar_time =  current_m30_time;

     }

  }

出力

図1:サーバー応答

説明

データとサーバー応答を保存するために、まずメソッド、URL、ヘッダ、タイムアウト、変数を指定します。Binance APIから情報を取得するので、メソッドはGETに設定します。BTCUSDTのローソク足データを取得するためのエンドポイントはURLで指定します。間隔は30分に設定され、直近10本のローソク足に限定されます。Binanceの公開APIでは追加のヘッダは不要なため、ヘッダは空のままです。  リクエストは、サーバーからの応答を最大5秒間待機します。送信データ、サーバー応答、およびレスポンスヘッダはすべて変数に格納されます。また、直近の30分足のローソク足のタイミングを監視するために変数も使用されます。

初期化関数は、エキスパートアドバイザー(EA)起動時に実行されます。この段階では、EAが正常に動作し、受信データを処理する準備ができていることが保証されますが、この場合は追加の準備は必要ありません。同様に、EAが削除されたとき、またはチャートが閉じられたときに、初期化解除関数が実行されます。現時点では空実装ですが、将来のクリーンアップ作業に対応できるよう備えられています。

新しい価格ティックが発生するたびに、主要なロジックが実行されます。まず、EAはチャート上の30分足の開始時刻を取得します。新しいローソク足が始まったかどうかを確認するために、この時刻を以前に記録されたローソク足の時刻と比較します。EAは、新しいローソク足である場合、Binance APIから最新のローソク足データを要求します。その後、サーバーからの応答は、検証用に読み取り可能な形式へ変換されます。

EAは、データ取得後に記録されたローソク足の時間を現在の時間に更新します。これにより、リクエストはティックごとに送信されるのではなく、30分ごとの新しいローソク足ごとに1回だけ送信されることが保証されます。この方法は、EAが必要な場合にのみ最新のローソク足データを取得できるようにするため、EAの効率が向上します。これは、不要なリクエストを繰り返さずに、外部APIデータに依存するインジケーターやEAを開発するのに理想的です。

比喩的な説明

Binance APIを、大量の書籍が揃った図書館のようなものだと考えてください。各書籍には、市場価格のスナップショットであるローソク足データが記録されています。私たちの目的は、必要な書籍を正確に入手することです。この場合、BTCUSDTの直近10本の30分足を取得し、後で利用できるようにデータを整理します。 まず、図書館への「リクエスト」を作成します。図書館に、必要な本の内容(BTCUSDT)、時間足(30分)、冊数(10冊)を正確に伝えるのは、これと似ています。リクエストのタイムアウトを設定するのと同様に、図書館が本を見つけるまでどれくらい待つかを決めます。

EAが動き始めるタイミングは、図書館に行って作業の準備をする場面に似ています。本の回収を始める前に、すべてが整っていることを確認するための準備作業があります。同様に、今回特に達成すべきことがなくても、図書館を出る際に机を片付けたり、借りていた資料を返却したりするのも良いでしょう。 市場を、絶えず変化する時計だと想像してみてください。時計の針が動くたびに、まるで図書館員が私たちに新しい知識のページを差し出してくれるようです。毎回同じ本をリクエストするのは避けたいです。むしろ、私たちは時計を見て、新しい30分の区切りが始まったかどうかを確認します。始まっていれば、図書館員に最新のタイトルを提供してもらいます。図書館から本を受け取った後、私たちは後で参照できるように、関連する情報をメモ帳に書き留めます。

データが記録されたら、付箋を使って最後に本棚から本を取り出した日時を記録します。これにより、次の30分間の区切りが始まるまで、同じ本を再度リクエストすることがなくなります。

 

各ローソク足データを並べ替える

次のステップは、サーバーからの応答を30分ごとに個別のローソク足に分割することです。最初のローソク足の始値、高値、安値、終値がすべて含まれます。残りのローソク足についても同様のプロセスが実行され、各30分足が体系的に収集されるようになります。 前回の記事で述べたように、まずJSON形式を理解する必要があります。Binanceがどのようにデータ処理をおこなっているかが分かったので、ローソク足データを正しく整理できます。Binanceは、以下のような配列の配列としてローソク足データを返します。

[
[array 1],
[array 2],
[array 3],
[array 4],
[array 5],
[array 6],
[array 7],
[array 8],
[array 9],
[array 10]
]

各内部配列は、すべての情報を含む単一の30分足チャートを表しています。次の段階は、このサーバーからの応答を個別のローソク足に分類することです。最初のローソク足の情報はすべてまとめて表示され、次に2番目のローソク足、そして最後のローソク足まで同様に続きます。この形式から明らかなように、各ローソク足データセットを区切るのに最適な文字は、配列を閉じる角括弧です。コンマはローソク足データ自体の一部であるため、使用することはできません。使用するとエラーが発生し、数値が混同されてしまうからです。

したがって、閉じ括弧を使用するのが最も安全な方法です。次のステップは、サーバーからの応答を各ローソク足ごとに個別のセクションに分割した後、各ローソク足を個別に処理することです。先頭の角括弧と、二重引用符などの不要な文字は削除されます。このクリーンアップ処理をおこなうことで、各ローソク足はよりクリーンで扱いやすくなり、配列に格納できる正確な値に変換しやすくなります。

例:
string candle_data[];
string first_bar_data;
string first_bar_data_array[];
string second_bar_data;
string second_bar_data_array[];
string third_bar_data;
string third_bar_data_array[];
string forth_bar_data;
string fourth_bar_data_array[];
string fifth_bar_data;
string fifth_bar_data_array[];
string sixth_bar_data;
string sixth_bar_data_array[];
string senventh_bar_data;
string seventh_bar_data_array[];
string eighth_bar_data;
string eighth_bar_data_array[];
string nineth_bar_data;
string nineth_bar_data_array[];
string tenth_bar_data;
string tenth_bar_data_array[];
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   datetime current_m30_time = iTime(_Symbol,PERIOD_M30,0);

   if(current_m30_time != last_bar_time)
     {

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

      Print(server_result);

      last_bar_time =  current_m30_time;


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

      //FIRST CANDLE
      first_bar_data = candle_data[0];
      StringReplace(first_bar_data,"[[","");
      StringReplace(first_bar_data,"\"","");

      StringSplit(first_bar_data,',',first_bar_data_array);

      //SECOND CANDLE
      second_bar_data = candle_data[1];
      StringReplace(second_bar_data,",[","");
      StringReplace(second_bar_data,"\"","");

      StringSplit(second_bar_data,',',second_bar_data_array);

      //THIRD CANDLE
      third_bar_data = candle_data[2];
      StringReplace(third_bar_data,",[","");
      StringReplace(third_bar_data,"\"","");

      StringSplit(third_bar_data,',',third_bar_data_array);

      //FORTH CANDLE
      forth_bar_data = candle_data[3];
      StringReplace(forth_bar_data,",[","");
      StringReplace(forth_bar_data,"\"","");

      StringSplit(forth_bar_data,',',fourth_bar_data_array);

      //FIFTH CANDLE
      fifth_bar_data = candle_data[4];
      StringReplace(fifth_bar_data,",[","");
      StringReplace(fifth_bar_data,"\"","");

      StringSplit(fifth_bar_data,',',fifth_bar_data_array);

      //SIXTH CANDLE
      sixth_bar_data = candle_data[5];
      StringReplace(sixth_bar_data,",[","");
      StringReplace(sixth_bar_data,"\"","");

      StringSplit(sixth_bar_data,',',sixth_bar_data_array);

      //SEVENTH CANDLE
      senventh_bar_data = candle_data[6];
      StringReplace(senventh_bar_data,",[","");
      StringReplace(senventh_bar_data,"\"","");

      StringSplit(senventh_bar_data,',',seventh_bar_data_array);

      //EIGHTH CANDLE
      eighth_bar_data = candle_data[7];
      StringReplace(eighth_bar_data,",[","");
      StringReplace(eighth_bar_data,"\"","");

      StringSplit(eighth_bar_data,',',eighth_bar_data_array);

      //NINETH CANDLE
      nineth_bar_data = candle_data[8];
      StringReplace(nineth_bar_data,",[","");
      StringReplace(nineth_bar_data,"\"","");

      StringSplit(nineth_bar_data,',',nineth_bar_data_array);

      //TENTH CANDLE
      tenth_bar_data = candle_data[9];
      StringReplace(tenth_bar_data,",[","");
      StringReplace(tenth_bar_data,"\"","");

      StringSplit(tenth_bar_data,',',tenth_bar_data_array);

     }
  }

説明

まず、サーバーが提供するものをそのまま受け入れることから始めます。多くの場合、それはすべてのローソク足データを含む巨大なテキストファイルです。それぞれのローソク足の意味を理解するためには、まずそれぞれのローソク足間の境界を特定する必要があります。区切り文字として機能する閉じ角括弧が見つかるたびに、分割ツールを使用して長いテキストブロックを分割します。これにより、各項目が単一のローソク足のすべての生データを表す、整然としたリスト(配列)が瞬時に得られます。次に、更新されたリストの最初のローソク足に注目してみましょう。サーバーのフォーマット処理によって生じた引用符や開き角括弧など、生データには不要な文字がまだ残っています。そこで、最初のローソク足のデータから、不要な文字をすべて削除します。そうすることで、完璧に明瞭で、重要な数字だけを含むテキストが残ります。

実際の価格帯を明確に区別するためには、このような明確な用語を用いる必要があります。ここでもテキストは分割されていますが、今回はコンマが使われており、これはローソク足データを区別する上で非常に重要です。結果として得られる簡潔で使いやすいリストの各項目は、まさにそれらの値のいずれかです。残りのローソク足についても同じ手順を繰り返しました。各ローソク足について、不要な引用符と括弧を削除してから、残りのデータをカンマで区切ります。作業が終わる頃には、それぞれに個性があり、整理された10個のリストを無事作成できます。それぞれが単一のローソク足にのみ関連しており、ローソク足データの値がきちんと整理されて表示されています。データが整理されていて見やすくなったので、次に分析やグラフ作成をおこなうことができます。

比喩的な説明

サーバーからの応答全体を、無数の本がぎっしりと詰め込まれた、隙間の少ない長い本棚だと想像してみてください。1冊の本は、1本の30分足を表しています。あなたはまず、この長い本棚を10個の小さな本に分割することから始めました。つまり、巨大なデータ列が10個に分割されたということです。まるで10冊の本が棚に横一列に並べられているように、10本の異なるローソク足が今、一列に並べられています。

あなたは棚の一番上の本を選びました。表紙に貼られたプラスチック製の市販のステッカーは、JSON形式に付随する追加文字、例えば先頭の二重括弧や引用符などを代用しています。あなたは本を読む前に、シールやビニールを剥がして本をきれいにしました。表紙をきれいにした後、本を開くと、中に章が並んでいました。これらの章は、始値、高値、安値、終値を表し、コンマで区切られています。その結果、あなたはそれらの章をさらに小さなセクションに分け、最初のローソク足だけを置くための別の小さな棚にきちんと並べました。

2冊目の本については、棚から取り出し、シールをはがし、開いて、章ごとに分け、すべてを別の小さな場所にきちんと整理しました。3巻、4巻、5巻、そして10巻もすべて同じ手順で処理しました。各書籍の余分な包装は取り除かれ、その後、内容物は扱いやすい大きさに分けられました。結果として、大きくてごちゃごちゃした本棚ではなく、それぞれが1本のローソク足に対応する、非常に整然とした10個のセクションができあがります。各ローソク足内の値は容易にアクセスでき、各セグメントごとに明確に区分されています。これにより、単一の巨大なテキストブロックを扱うのではなく、各ローソク足を個別に簡単に操作できるようになります。

 

ローソク足の値を正しいデータ型に変換する

現時点では、ローソク足配列内のすべてのローソク足の詳細が、個別のテキスト要素に分割されています。APIから取得したデータはすべて生の文字列として取得しているため、すべての値はテキスト形式のままです。日付や数値はデータであるため、計算に使用したり、インジケーター内に表示したりする前に、適切なデータ型に変換する必要があります。

例:
//DATETIME
long bar1_time_s;
datetime bar1_time;
long bar2_time_s;
datetime bar2_time;
long bar3_time_s;
datetime bar3_time;
long bar4_time_s;
datetime bar4_time;
long bar5_time_s;
datetime bar5_time;
long bar6_time_s;
datetime bar6_time;
long bar7_time_s;
datetime bar7_time;
long bar8_time_s;
datetime bar8_time;
long bar9_time_s;
datetime bar9_time;
long bar10_time_s;
datetime bar10_time;

//OPEN
double bar1_open;
double bar2_open;
double bar3_open;
double bar4_open;
double bar5_open;
double bar6_open;
double bar7_open;
double bar8_open;
double bar9_open;
double bar10_open;

//HIGH
double bar1_high;
double bar2_high;
double bar3_high;
double bar4_high;
double bar5_high;
double bar6_high;
double bar7_high;
double bar8_high;
double bar9_high;
double bar10_high;

//LOW
double bar1_low;
double bar2_low;
double bar3_low;
double bar4_low;
double bar5_low;
double bar6_low;
double bar7_low;
double bar8_low;
double bar9_low;
double bar10_low;

//CLOSE
double bar1_close;
double bar2_close;
double bar3_close;
double bar4_close;
double bar5_close;
double bar6_close;
double bar7_close;
double bar8_close;
double bar9_close;
double bar10_close;

//TIME
bar1_time_s = (long)StringToInteger(first_bar_data_array[0])/1000;
bar1_time = (datetime)bar1_time_s;

bar2_time_s = (long)StringToInteger(second_bar_data_array[0])/1000;
bar2_time = (datetime)bar2_time_s;

bar3_time_s = (long)StringToInteger(third_bar_data_array[0])/1000;
bar3_time = (datetime)bar3_time_s;

bar4_time_s = (long)StringToInteger(fourth_bar_data_array[0])/1000;
bar4_time = (datetime)bar4_time_s;

bar5_time_s = (long)StringToInteger(fifth_bar_data_array[0])/1000;
bar5_time = (datetime)bar5_time_s;

bar6_time_s = (long)StringToInteger(sixth_bar_data_array[0])/1000;
bar6_time = (datetime)bar6_time_s;

bar7_time_s = (long)StringToInteger(seventh_bar_data_array[0])/1000;
bar7_time = (datetime)bar7_time_s;

bar8_time_s = (long)StringToInteger(eighth_bar_data_array[0])/1000;
bar8_time = (datetime)bar8_time_s;

bar9_time_s = (long)StringToInteger(nineth_bar_data_array[0])/1000;
bar9_time = (datetime)bar9_time_s;

bar10_time_s = (long)StringToInteger(tenth_bar_data_array[0])/1000;
bar10_time = (datetime)bar10_time_s;

//OPEN
bar1_open = StringToDouble(first_bar_data_array[1]);
bar2_open = StringToDouble(second_bar_data_array[1]);
bar3_open = StringToDouble(third_bar_data_array[1]);
bar4_open = StringToDouble(fourth_bar_data_array[1]);
bar5_open = StringToDouble(fifth_bar_data_array[1]);
bar6_open = StringToDouble(sixth_bar_data_array[1]);
bar7_open = StringToDouble(seventh_bar_data_array[1]);
bar8_open = StringToDouble(eighth_bar_data_array[1]);
bar9_open = StringToDouble(nineth_bar_data_array[1]);
bar10_open = StringToDouble(tenth_bar_data_array[1]);

//HIGH
bar1_high = StringToDouble(first_bar_data_array[2]);
bar2_high = StringToDouble(second_bar_data_array[2]);
bar3_high = StringToDouble(third_bar_data_array[2]);
bar4_high = StringToDouble(fourth_bar_data_array[2]);
bar5_high = StringToDouble(fifth_bar_data_array[2]);
bar6_high = StringToDouble(sixth_bar_data_array[2]);
bar7_high = StringToDouble(seventh_bar_data_array[2]);
bar8_high = StringToDouble(eighth_bar_data_array[2]);
bar9_high = StringToDouble(nineth_bar_data_array[2]);
bar10_high = StringToDouble(tenth_bar_data_array[2]);

//LOW
bar1_low = StringToDouble(first_bar_data_array[3]);
bar2_low = StringToDouble(second_bar_data_array[3]);
bar3_low = StringToDouble(third_bar_data_array[3]);
bar4_low = StringToDouble(fourth_bar_data_array[3]);
bar5_low = StringToDouble(fifth_bar_data_array[3]);
bar6_low = StringToDouble(sixth_bar_data_array[3]);
bar7_low = StringToDouble(seventh_bar_data_array[3]);
bar8_low = StringToDouble(eighth_bar_data_array[3]);
bar9_low = StringToDouble(nineth_bar_data_array[3]);
bar10_low = StringToDouble(tenth_bar_data_array[3]);

//CLOSE
bar1_close = StringToDouble(first_bar_data_array[4]);
bar2_close = StringToDouble(second_bar_data_array[4]);
bar3_close = StringToDouble(third_bar_data_array[4]);
bar4_close = StringToDouble(fourth_bar_data_array[4]);
bar5_close = StringToDouble(fifth_bar_data_array[4]);
bar6_close = StringToDouble(sixth_bar_data_array[4]);
bar7_close = StringToDouble(seventh_bar_data_array[4]);
bar8_close = StringToDouble(eighth_bar_data_array[4]);
bar9_close = StringToDouble(nineth_bar_data_array[4]);
bar10_close = StringToDouble(tenth_bar_data_array[4]);

説明

各ローソク足の値に対応する変数を生成します。MQL5で適切な日時値を作成するには、まずミリ秒を表すテキスト形式の数値を、long型の数値に変換する必要があります。数値価格をテキスト形式から倍精度浮動小数点数に変換する必要があります。そのため、いくつかの変数は事前に設定されます。後ほど、これらの各変数の変換値が取得されます。文字列の値はまずlong型の数値に変換され、その後1,000で割る必要があります。これは、各ローソク足の時刻がAPIからミリ秒単位で受信されるためです。結果はMQL5が理解できる正しい日時値に変換され、long型の変数に格納されます。これにより、チャートに適したローソク足の時間が得られます。10本のローソク足はすべて同じ手順を経ます。

時間値を設定した後、次に始値値を変換します。すべての始値は文字列の位置から抽出され、変換関数を使用して倍精度浮動小数点数に変換されます。各ローソク足が実際の数値の始値を持つことを保証するために、この処理は各ローソク足に対して実行されます。高値も同様のプロセスを経ます。すべての高値は、テキスト上の位置から変換関数に転送され、その後、関連するdouble型の変数に転送されます。その後、この手順を安値に対して繰り返し、最後に終値について同様に処理します。いずれの場合も、目的はすべての価格をプレーンテキストから適切な数値形式に変換し、計算、ローソク足の描画、価格変動の比較に利用できるようにすることです。

比喩的な説明

それぞれのローソク足を、章がすでに整理され、きれいにされた本だと想像してみてください。唯一の問題点は、その本は図書館が理解できない言語で書かれているということです。本は完成しましたが、その内容を私たちのシステムが理解できる言語に翻訳しない限り、何の役にも立ちません。なので、翻訳作業を進めなければなりません。ローソク足の本には必ず5つの重要なページがあります。ローソク足の開始時刻が1ページ目に表示され、続いて2ページ目に始値、3ページ目に高値、4ページ目に安値、5ページ目に終値が表示されます。しかし、これらのページは正しい数値を使用する代わりに、言語で書かれています。本を開いたとき、その数字が数字ではなく言葉で書かれていると想像してみてください。図書館システムがこれらの数値を保存したり計算したりできるようにするには、それぞれの数値を適切な形式に書き換える必要があります。変換段階とは、書き換え手順のことです。

どの本にも最初のページにミリ秒単位で時間が書かれていますが、これは図書館が理解できないカレンダーシステムを使って日付を印刷するのと似ています。日付を秒単位に書き換えるのが最初のステップです。これは、外国のカレンダーを自国のカレンダーに変換するのと似ています。最終的に、結果を再度datetime形式に変換する際に、日付はライブラリの公式な日付形式で書き込まれます。ローソク足関連の書籍ごとに、この手順を繰り返し、1冊目から10冊目までの書籍に対応する日時を図書館システムに入力します。

その後、残りのページに進みます。始値、高値、安値、終値は各価格ページに保存されますが、それらはすべて文字列です。それらを、図書館のスキャナーでは読み取れない手書きのページだと想像してみてください。アルゴリズムが各価格を完全に理解できるようにするには、価格をきれいな数字を使って書き直す必要があります。図書館システムは、変換後も問題なく価格比較、値の保存、計算を実行できるようになりました。各ローソク足の本の各ページについて、この手順を実行します。この工程を経た時点で、それぞれのローソク足の本はもはや手書きの外国語文書ではなくなっています。すべて、図書館の索引システムが理解できる正確な日付と数値に変換されています。これで、ローソク足の棚にある読みやすい書籍を使って、指標を作成したり、ローソク足を描いたり、必要な計算を実行したりできるようになりました。

 

ローソク足の値を個別の配列にグループ化する

この時点で、各ローソク足の値は適切なデータ型に正しく変換されており、価格はdouble型、タイミングはdatetime型になっています。次の段階は、これらの値を整理して、同種のデータをグループ化することです。つまり、10本のローソク足それぞれの始値を1つの配列に、終値を別の配列に、といった具合に、高値、安値、時間値についても同様に配列に格納します。 指標を作成したり計算を実行したりする際に、このように変数をグループ化することで作成される構造を扱いやすくなります。これで、毎回個々のローソク足にアクセスする代わりに、始値の配列、終値の配列、または時刻の配列を参照するだけで、データセット全体を一度に扱うことができるようになりました。

さらに、このアプローチは効率性と明確性を向上させます。たとえば、このソフトウェアは、チャート上にローソク足を表示しながら配列を順次読み取ることで、各ローソク足の始値、高値、安値、終値を表現することができます。同じ型の値はすべてグループ化されているため、移動平均、ローソク足の比較、その他のテクニカル分析操作などの計算も簡素化されます。

例:
datetime OpenTime[10] = {bar1_time, bar2_time, bar3_time, bar4_time, bar5_time,bar6_time, bar7_time, bar8_time, bar9_time, bar10_time};
double   OpenPrice[10] = {bar1_open, bar2_open, bar3_open, bar4_open, bar5_open,bar6_open, bar7_open, bar8_open, bar9_open, bar10_open};
double   ClosePrice[10] = {bar1_close, bar2_close, bar3_close, bar4_close, bar5_close,bar6_close, bar7_close, bar8_close, bar9_close, bar10_close};
double   LowPrice[10] = {bar1_low, bar2_low, bar3_low, bar4_low, bar5_low,bar6_low, bar7_low, bar8_low, bar9_low, bar10_low};
double   HighPrice[10] = {bar1_high, bar2_high, bar3_high, bar4_high, bar5_high,bar6_high, bar7_high, bar8_high, bar9_high, bar10_high};

説明

この手順では、個々のローソク足の値をすべて配列にグループ化し、類似するデータをすべてまとめて保存します。以前はローソク足ごとに個別の変数を使っていましたが、今ではあらゆる種類のデータに対して単一の配列を使用するようになりました。その結果、プログラムの後半でローソク足の情報を扱う作業がはるかに簡単になります。 10本のローソク足の始値は、取得時の順序と同じ順序で保持します。各ローソク足の時間は対応する位置に格納されます。この設定では、10個の異なる時間変数を管理する代わりに、インデックスを指定するだけで任意のローソク足の時間を参照することが容易になります。

価格値に関しては、この手法は変更されていません。ローソク足1本目から10本目まで、始値は単一の配列にまとめられています。終値、高値、安値の保存にも同じ方法が用いられます。最初のローソク足は常に最初のインデックスで参照され、残りのインデックスも同じ順序で参照されます。これにより、各価格配列の要素がすべて同じローソク足番号に対応するようになります。 配列を使用することで、ローソク足データを整理された信頼性の高い方法で処理できます。各ローソク足を個別に処理するのではなく、ループで配列を反復処理することで、各ローソク足を自動的に処理できます。同様に、すべての価格値が同じローソク足に対して同じインデックスを共有することで、混乱を防ぎ、適切な整合性を維持します。指標を作成したり、値を計算したり、グラフを作成したりする際には、この配置が非常に重要です。

比喩的な説明

大量の書籍を想像してみてください。それぞれの本はローソク足の例えです。どの本にも、ローソク足に関する詳細情報がすべて記載されており、使用時間や価格帯なども含まれています。あなたはこれまで、情報をページごとに、書籍ごとに確認してきました。機能はしますが、本が増えるにつれて、一つ一つを個別に扱うのが難しくなり、処理速度が低下します。 そこで、あなたは、データを1冊の本にすべてまとめるのではなく、カテゴリー別に分類します。1つの棚は営業時間全体を表示するように作られており、別の棚は始値を表示するように作られており、さらに別の棚は終値、高値、安値を表示するように作られています。棚に並んでいるローソク足の番号は、各列で表されています。したがって、各棚の1列目にはローソク足1が、2列目にはローソク足2が、といった具合に配置されます。

これにより、比較したり類似の内容を読むために、各書籍を個別に開く必要がなくなります。価格表を読むには、始め値の棚を確認します。決済時間をすべて確認したい場合は、時間棚にアクセスします。この構成により、データの効率的な計算、比較、および表示が容易になります。

 

ローソク足情報を可視化のために保存する

次のステップは、配列にグループ化された関連するすべてのローソク足データを格納するファイルを作成することです。このファイルの機能は、すべてのローソク足データを整理された形で保存し、後でチャート上に表示できるようにすることです。インジケーターまたはプログラムは、データをファイルに保存することで容易にデータにアクセスでき、サーバーに繰り返しデータを要求する必要がなくなります。

ファイルはシンプルな表形式で構成されています。最初の行にある見出しには、「時間」「始値」「高値」「安値」「終値」などがあり、各列が何を表しているかが一目でわかるようになっています。各行には、見出しの下に1本のローソク足に関する詳細情報が記載されています。すべてのローソク足の時刻は最初の列に格納され、続いて2番目の列に始値、3番目の列に高値、といった具合に格納されます。最初のデータ行は1本目のローソク足を表し、次のデータ行は2本目のローソク足を表し、以降の各行は順次次のローソク足を表します。 この表形式のレイアウトにより、ローソク足に関する情報が整理され、簡単にアクセスできるようになります。このインジケーターは、各列を読み取ることで、チャート上のローソク足を正確に再現できます。このフレームワークを使用することで、新しいローソク足を追加したり、データを更新したりといった今後の作業も容易になります。

例:

string filename;
int handle;
filename = "BTCUSDTM30_MQL5.csv";
handle = FileOpen(filename, FILE_WRITE|FILE_CSV|FILE_SHARE_READ|FILE_ANSI, ',');

if(handle != INVALID_HANDLE)
  {
// Write a header row
   FileWrite(handle, "Time", "Open", "High", "Low", "Close");

// Write the 5 days of data row by row
   for(int i = 0; i < 10; i++)
     {
      FileWrite(handle, OpenTime[i], OpenPrice[i], HighPrice[i], LowPrice[i], ClosePrice[i]);
     }

   FileClose(handle);
   Print("EA successfully wrote the data to " + filename);
  }
else
  {
   Print("Error opening file for writing. Error code: ", + GetLastError());
  }

説明

ローソク足データを保存する最初のステップは、ファイル名を選択することです。簡潔なファイル名は、ローソク足の銘柄やタイムラインなど、ファイルに含まれるデータの概要を素早く把握できるため、複数のファイルを扱いやすくなります。 次に、プログラムはファイルへの書き込みを試みます。適切な動作を保証するために、特定の設定が使用されます。カンマ区切り値(CSV)形式でファイルを開くと、データはプログラムと人間の両方にとって理解しやすい表形式に整理されます。さらに、共有およびエンコードのパラメータは、さまざまなプログラムによる安全なアクセスと適切な解釈を可能にするように構成されています。次に、プログラムはファイルを開いた後にヘッダ行を書き込みます。この行は、ファイルに記録されているデータの種類を明確に示し、各列を識別します。ヘッダは後々の読み取りを容易にし、ファイルの構造を維持する役割を果たします。

このプログラムは、ヘッダーの後、ローソク足データを1行ずつファイルに書き込んでいきます。関連するすべての値が列にきちんと整理されており、各行は1本のローソク足を表しています。10本のローソク足すべてを正しい順序で取得するために、プログラムはチャートと同じ順序で配列を反復処理します。そして、すべてのローソク足データが追加されると、不要なリソースが保持されず、すべてのデータが保存されるように、ファイルは閉じられます。ファイルが正しく処理されたことを示すため、アプリケーションは成功メッセージを表示します。 

コードのコンパイルと実行は、コードを作成した後におこなわれます。EAは、チャート上で初期化されるとすぐにファイル作成プロセスを開始します。すべてのローソク足データは、ソフトウェアによって作成される構造化されたCSVファイルに保存されます。 お使いのコンピューターでは、ファイルはMQL5ディレクトリ内のファイルフォルダに生成されます。MQL5はプログラム生成ファイルをこのデフォルトディレクトリに保存するため、後で簡単に見つけることができます。ファイルが届いたら、それを使ってシグナルを作成したり、その他の分析を実行したりする前に、すべてのローソク足データが正確に保存されていることを確認できます。

比喩的な説明

棚に10冊の本が並べられていて、それぞれの本にはローソク足1本1本に関する情報が書かれたページが入っていると想像してみてください。ページは種類に応じて、いくつかの山に分けられています。これらの書類をすべて新しいバインダーにまとめて、必要なときにいつでも簡単にデータにアクセスできるようにすることを想像してみてください。 バインダーにどのような情報が含まれているか(たとえば、ローソク足の銘柄や期間など)を明確にするために、まずバインダーに分かりやすいラベルを貼ります。その後、バインダーを開き、ページをきちんと整理して収納できるように準備します。中を見る人の手引きとなるよう、巻頭には各欄の意味を説明するタイトルページが設けられています。

その後、バインダーにページを1枚ずつ並べ、各行をローソク足1本に見立てます。最初の行は最初のローソク足を表し、2番目の行は2番目のローソク足を表す、といった具合です。このページ構成により、ローソク足に関するすべての情報が整理され、必要なときにすぐに参照できるようになっています。 すべてのデータが安全に保管されるように、整理が終わったらバインダーを閉じます。これは、ソフトウェアがファイルを完成させて、情報がコンピュータに永続的に保存されるようにするのと似ています。

図書館システムがこのバインダーを使用するには、プロシージャをコンパイルして実行する必要があります。これはプログラミング用語で言うと、「EAをコンパイルしてチャート上で初期化する」ということです。バインダー(すべてのローソク足データを含むファイル)は、EAが動作を開始するとすぐにコンピュータ上に作成されます。それは、MQL5ディレクトリ内のファイルフォルダにあります。これでバインダーを開いて使用できるようになり、インジケーターやプログラムから整理されたローソク足データにアクセスして分析や可視化をおこなうことができます。

パス:
C:\Users\Dell\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files

図2:ファイル内のローソク足データ

一度に多くの情報をお伝えすることを避けるため、ここで一旦中断します。次の記事では、さらに一歩進み、ファイルからのデータを使用してチャート上のローソク足を正しいローソク足形式で可視化するインジケーターを開発します。これにより、保存して整理したデータを実際のローソク足として表示できるようになります。


結論

Binance APIデータの処理を改善するため、本記事では直近10本の30分足チャートを取得し、サーバーからの応答を個々のチャートに分割しました。関連する値を配列にまとめ、適切なデータ型に変換することで、データの取り扱いを容易にしました。ローソク足データを整理された表形式で保存し、将来的に簡単にアクセスできるようにするため、最終的にファイルを作成しました。このプロセスは、データを視覚化できるように準備し、指標やEAを構築するための構造化された基盤を提供します。次の記事では、このファイルを使用して、チャート上にローソク足が適切な形式で表示されるインジケーターを作成します。これにより、市場の動きを視覚的に効率的に分析できるようになります。

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

ラリー・ウィリアムズの『市場の秘密』(第1回):MQL5でスイングストラクチャーインジケーターを構築する ラリー・ウィリアムズの『市場の秘密』(第1回):MQL5でスイングストラクチャーインジケーターを構築する
MQL5でラリー・ウィリアムズ式の市場構造インジケーターを構築するための実践的なガイドです。バッファの設定、スイングポイントの検出、チャートの設定、そしてトレーダーがテクニカル市場分析でこのインジケーターをどのように活用できるかについて解説します。
MQL5における取引戦略の自動化(第46回):Liquidity Sweep on Break of Structure (BoS) MQL5における取引戦略の自動化(第46回):Liquidity Sweep on Break of Structure (BoS)
MQL5においてLiquidity Sweep on Break of Structure (BoS)システムを構築します。このシステムは、ユーザーが定義した期間に基づいてスイングハイとスイングローを検出し、それらをHH (Higher High) / HL (Higher Low) /LH (Lower High) /LL (Lower Low)としてラベル付けすることでBoS(上昇トレンドにおけるHH、下降トレンドにおけるLL)を識別します。また、価格がスイングをヒゲで一時的にブレイクした後、再び終値がスイング内に戻る場合を流動性スイープとして検出します。
MQL5でカスタムインジケーターを作成する(第1回):Canvasグラデーションを使用したピボットベースのトレンドインジケーターの構築 MQL5でカスタムインジケーターを作成する(第1回):Canvasグラデーションを使用したピボットベースのトレンドインジケーターの構築
本記事では、ユーザーが定義した期間にわたって高速ピボットラインと低速ピボットラインを計算し、これらのラインに対する価格の位置に基づいてトレンドの方向を検出し、矢印でトレンドの開始を知らせるとともに、必要に応じて現在のバーを超えてラインを延長するピボットベースのトレンドインジケーターを、MQL5で作成します。このインジケーターは、カスタマイズ可能な色で表示される個別の上昇線と下降線、トレンドの変化に応じて色が変わる点線の高速線、そしてトレンド領域の強調表示を強化するためのCanvasオブジェクトを使用した、線間のオプションのグラデーション塗りつぶしによる動的な可視化をサポートしています。
Codexパイプライン:PythonからMQL5へ ― FXI ETFを対象とした複数四半期の指標分析 Codexパイプライン:PythonからMQL5へ ― FXI ETFを対象とした複数四半期の指標分析
MetaTraderを本来のFX取引という「コンフォートゾーン」の外でどのように活用できるかという検討を継続し、FXI ETFという別の取引可能資産に着目します。前回の記事では、指標の選定にとどまらず、指標間のパターンの組み合わせにまで踏み込み、やや過度に複雑化した側面がありました。本記事では一歩引いて、指標選定そのものに焦点を当てます。最終的には、十分な価格履歴データが存在する場合に、さまざまな資産に対して適切な指標を推奨できるパイプラインの構築を目指します。