MLモデルとストラテジーテスターの統合(第3回):CSVファイルの管理(II)

Jonathan Pereira | 20 7月, 2023

はじめに

この記事では、ストラテジーテスターとPythonの統合の3番目の部分に焦点を当てます。ここでは、CSVファイルを効率的に管理するためのCFileCSVクラスの作成について説明します。このクラスが実際にどのように実装できるかを読者がよりよく理解できるように、いくつかの例とコードを検証します。

では、CSVとは何でしょうか。

CSV (Comma Separated Values)は、データを保存・交換するためのシンプルで広く使われているファイル形式です。これは、各行がデータのセットを表し、各列がそのデータのフィールドを表す表に似ています。値は区切り文字で区切られ、異なるツールやプログラミング言語でも読み書きが容易になります。

CSVフォーマットは1970年代初頭に登場し、当初はメインフレームシステムで使われていました。CSVは広く使われているファイルタイプなので、特定の作成者を特定することはできません。

スプレッドシート、データベース、データ分析プログラムなど、さまざまなアプリケーションでデータのインポートやエクスポートによく使用されるCSVの人気の理由は、使いやすさと理解しやすさ、そして多くのシステムやツールとの互換性にあります。これは、あるシステムから別のシステムへ情報を転送するなど、異なるアプリケーション間でデータを共有する必要がある場合に特に便利です。

つまり、CSVを使用する主な利点は、使いやすさと互換性です。しかし、複雑なデータ型をサポートしていなかったり、非常に大量のデータを扱う能力が低かったりといった制限もあります。また、CSVフォーマットには普遍的な標準がないため、異なるアプリケーション間で互換性の問題が生じる可能性があります。さらに、このフォーマットでは検証をおこなわないため、誤ってデータを紛失したり修正したりする可能性があります。一般的に、CSVはデータを保存共有するための多用途で使いやすいオプションです。とはいえ、その限界を知り、十分に理解し、データの正確性を確保するための措置を講じることが重要です。


動機

CFileCSVクラスは、MetaTrader 5ストラテジーテスター環境をPythonと統合する必要性から作成されました。機械学習(ML)モデルを使った取引戦略を開発する中で、私はPythonで作成したモデルを使う難しさに直面しました。MQL5で機械学習ライブラリを作るか、エキスパートアドバイザーを完全にPythonで作るか、どちらかしかありませんでした。

MQL5言語にはMLライブラリを作成するためのリソースが用意されていますが、主な目的がデータを分析し、高速かつ効率的な方法でモデルを構築することだったので、その開発に時間と労力を費やしたくありませんでした。

そこで、中間的な解決策を見つけることが課題となりました。Pythonで作られたMLモデルを利用したかっただけでなく、MQL5を使った仕事に直接適用できるようにしたかったので、この制限を克服し、この2つの環境を統合するソリューションを見つける方法を探し始めました。

アイデアは、MetaTrader 5とPythonがタイムリーに通信できるメッセージングシステムを作ることでした。これにより、MetaTrader 5からPythonへのデータの初期化と転送、およびPythonからMetaTrader 5への予測の送信を制御できるようになります。CFileCSVクラスは、効率的なデータの保存と読み込みを可能にすることで、この相互作用を促進するために設計されました。


CFileCSVクラスの紹介

CFileCSVはCSV (Comma Separated Values)ファイルを扱うためのクラスです。このクラスはCFileから派生しており、CSVファイルを扱うための特別な機能を提供しています。このクラスの目的は、さまざまなデータ型を扱いやすくすることで、CSVファイルの読み書きを容易にすることです。

CSVファイルを使う大きなメリットのひとつは、共有が簡単で、データのインポート/エクスポートに便利なことです。このようなファイルは、ExcelやGoogle Sheetsのようなプログラムで簡単に開いて編集することができ、さまざまなプログラミング言語で読むことができます。また、特定の書式がないため、さまざまなニーズに応じて読み書きができます。

CFileCSVクラスには、Open、WriteHeader、WriteLine、Readの4つの主要なpublicメソッドがあります。さらに、配列や行列を文字列に変換し、その値をファイルに書き出す2つのprivateヘルパーメソッドがあります。

class CFileCSV : public CFile
  {
private:
   template<typename T>
   string            ToString(const int, const T &[][]);
   template<typename T>
   string            ToString(const T &[]);
   short             m_delimiter;

public:
                     CFileCSV(void);
                    ~CFileCSV(void);
   //--- methods for working with files
   int               Open(const string,const int, const short);
   template<typename T>
   uint              WriteHeader(const T &values[]);
   template<typename T>
   uint              WriteLine(const T &values[][]);
   string            Read(void);
  };  

このクラスを使用する場合は、特定のCSVファイルで動作するように設計されていることに留意してください。ファイル内のデータが正しくフォーマットされていない場合、予期せぬ結果になることがあります。また、ファイルへの書き込みを試みる前に、そのファイルがオープンされており、書き込み権限があることを確認することも非常に重要です。

CFileCSVクラスの使用例として、データ行列からCSVファイルを作成することができます。まず、クラスのインスタンスを作成し、Openメソッドを使ってファイルを開きます。このメソッドでは、ファイル名とOpenフラグを指定します。次に、WriteHeaderメソッドを使ってヘッダーをファイルに書き込み、WriteLineメソッドを使って行列からデータ行を書き込みます。関数の例でこれらの手順を説明します。

#include "FileCSV.mqh"

void CreateCSVFile(string fileName, string &headers[], string &data[][])
  {
   // Creates an object of the CFileCSV class
   CFileCSV csvFile;

   // Checks if the file can be opened for writing in the ANSI format
   if(csvFile.Open(fileName, FILE_WRITE|FILE_ANSI))
     {
        int rows = ArrayRange(data, 0);
        int cols = ArrayRange(data, 1);
        int headerSize = ArraySize(headers);
        //Checks if the number of columns in the data matrix is equal to the number if elements in the header array and if the number of rows in the data matrix is greater than zero
        if(cols != headerSize || rows == 0)
        {
            Print("Error: Invalid number of columns or rows. Data array must have the same number of columns as the headers array and at least one row.");
            return;
        }
      // Writes header to file
      csvFile.WriteHeader(headers);
      // Writes data rows to file
      csvFile.WriteLine(data);
      // Closes the file
      csvFile.Close();
     }
   else
     {
      // Shows an error message if the file cannot be opened
      Print("Error opening file!");
     }
  }

このメソッドの目的は、ヘッダーの配列とデータの配列からCSVファイルを作成することです。まず、CFileCSVクラスのオブジェクトを作ることから始めます。次に、そのファイルがANSIフォーマットで書き込み用に開くことができるかどうかを確認します。ファイルを開くことができる場合は、データ行列の列数がヘッダー行列の要素数と等しく、データ行列の行数がゼロより大きいことを確認します。これらの条件が満たされると、このメソッドはWriteHeader()メソッドを使用してヘッダーをファイルに書き込み、 WriteLine()メソッドを使用してデータ行を書き込みます。最後に、このメソッドはファイルを閉じます。ファイルを開くことができない場合は、エラーメッセージが表示されます。

このメソッドについては、すぐに例を挙げて説明します。その実装は、他のタスクを実行するために拡張することができます。たとえば、ファイルを開こうとする前にファイルが存在するかどうかを確認したり、どの区切り文字を使うかを選択するオプションを追加したりといったようにです。

CFileCSVクラスは、CSVファイルを扱うためのシンプルで実用的な方法を提供し、CSVファイルへのデータの読み書きを容易にします。ファイルが期待されたフォーマットであることを確認し、メソッドが正常に実行されたことを確認します。


実装

上述したように、CFileCSVクラスには、Open、WriteHeader、WriteLine、Readの4つの主要なpublicメソッドがあります。また、ToStringの名前のオーバーロードを持つ2つのprivateヘルパーメソッドもあります。

int CFileCSV::Open(const string file_name,const int open_flags, const short delimiter=';')
  {
   m_delimiter=delimiter;
   return(CFile::Open(file_name,open_flags|FILE_CSV|delimiter));
  }
template<typename T>
uint CFileCSV::WriteHeader(const T &values[])
  {
   string header=ToString(values);
//--- check handle
   if(m_handle!=INVALID_HANDLE)
      return(::FileWrite(m_handle,header));
//--- failure
   return(0);
  }
template<typename T>
uint CFileCSV::WriteLine(const T &values[][])
  {
   int len=ArrayRange(values, 0);

   if(len<1)
      return 0;

   string lines="";
   for(int i=0; i<len; i++)
      if(i<len-1)
         lines += ToString(i, values)  + "\n";
      else
         lines += ToString(i, values);

   if(m_handle!=INVALID_HANDLE)
      return(::FileWrite(m_handle, lines));
   return 0;
  }
string CFileCSV::Read(void)
  {
   string res="";
   if(m_handle!=INVALID_HANDLE)
      res = FileReadString(m_handle);

   return res;

ToStringメソッドは、CFileCSVクラスのprivateヘルパーメソッドで、行列や配列を文字列に変換し、その値をファイルに書き込むために使用されます。

template<typename T>
string CFileCSV::ToString(const int row, const T &values[][])
  {
   string res="";
   int cols=ArrayRange(values, 1);

   for(int x=0; x<cols; x++)
      if(x<cols-1)
         res+=values[row][x] + ShortToString(m_delimiter);
      else
         res+=values[row][x];

   return res;
  }
template<typename T>
string CFileCSV::ToString(const T &values[])
  {
   string res="";
   int len=ArraySize(values);

   if(len<1)
      return res;

   for(int i=0; i<len; i++)
      if(i<len-1)
         res+=values[i] + ShortToString(m_delimiter);
      else
         res+=values[i];

   return res;
  }

これらのメソッドは、WriteHeaderWrite Lineによって使用され、パラメータとして渡された値を文字列に変換し、その文字列をオープンファイルに書き込みます。これらの区切り文字は、値が期待される形式でファイルに書き込まれ、指定された区切り文字で区切られていることを確認するために使用されます。これらは、データがCSVファイルに正しく、整理された形で書き込まれるようにするための基本です。 

さらに、これらのメソッドはテンプレートとして実装されているため、CFileCSVクラスに柔軟性を与え、さまざまな種類のデータを扱うことができます。つまり、これらのメソッドは、整数、浮動小数点数、文字列など、文字列に変換できるあらゆる種類のデータに適用できます。このため、CFileCSVクラスは非常に多機能で使いやすくなっています。

これらのメソッドは主に、値が正しいフォーマットでファイルに書き込まれることを保証するためのものです。行または行列の最後の要素を除くすべての要素の末尾に区切り文字を含みます。これは、CSVファイル内の値が適切に分離されていることを保証するもので、ファイルに格納されたデータを後で読み込んで解釈する上で非常に重要です。


ToString(const int row, const T &values[][])の使用例

int data[2][3] = {{1, 2, 3}, {4, 5, 6}};
string str = csvFile.ToString(1, data);
//str -> "4;5;6"

この例では、データ行列の2行目をToStringメソッドに渡しています。このメソッドは、文字列の各要素を繰り返し処理し、結果の文字列に追加し、文字列の最後の要素を除くすべての要素の末尾に区切り文字を挿入します。結果の文字列は「4;5;6」となります。

ToString(const T &values[])の使用例

string headers[] = {"Name", "Age", "Gender"};
string str = csvFile.ToString(headers);
//str -> "Name;Age;Gender"

この例では、headers配列がToStringメソッドに渡されます。このメソッドは、配列の各要素を繰り返し処理し、結果の文字列に追加し、配列の最後の要素を除く各要素の末尾に区切り文字を挿入します。結果の文字列は「Name;Age;Gender」となります。

これらはToStringメソッドとToStringメソッドの使用例に過ぎません。文字列に変換できるデータ型であれば、どのようなものにも適用できます。ただし、これらはprivateとして宣言されているため、CFileCSVクラスの内部でしか利用できないことにご注意ください。


アルゴリズムの複雑さ 

アルゴリズムの複雑さを測定し、アルゴリズムやシステムのパフォーマンスを最適化するためにこの情報をどのように利用できるでしょうか。

ビッグオー表記法は、アルゴリズムを分析するための重要なツールであり、コンピュータサイエンスの黎明期から認識されてきました。ビッグオーの概念は1960年代に正式に定義され、現在でも広く使われています。これにより、プログラマーは入力とその実行に必要な操作に基づいて、アルゴリズムの複雑さをおおまかに見積もることができます。このツールを使うことで、異なるアルゴリズムを比較し、特定のタスクに対してより良いパフォーマンスを提供するアルゴリズムを定義することができます。

データ量と解決すべき問題の複雑さは指数関数的に増大します。だからこそ、ビッグオーという表記が重要です。日々、より多くのデータが生成される一方で、このデータを処理するために、より効率的なアルゴリズムが必要です。

ビッグオーの概念は、あるアルゴリズムにおいて、実行時間がある数学的関数(通常は多項式)に従って成長するという考えに基づいています。この関数はビッグオー表記で表され、O(f(n))と表すことができます。

では、ビッグオー記法の使用例をいくつか見てみましょう。

ビッグオーは、特定の問題を解決するためにどのアルゴリズムを選択すべきかを決定し、またシステムのパフォーマンスを最適化するのに役立ちます。



CFileCSVクラスの各メソッドの時間の複雑さは、パラメータとして提供されるデータのサイズによって異なります。

これらの複雑さは、ファイルの書き込みバッファのサイズやファイルシステムなど、他の要因に影響される可能性があるため、推定値であることに注意してください。さらに、ビッグオー表記は最悪のシナリオを推定します。メソッドに提供するデータが多すぎると、複雑さが増します。

一般的に、CFileCSVクラスは許容できる時間複雑性を持ち、それほど大きくないファイルを扱うのに効率的です。しかし、非常に大きなファイルを扱う必要がある場合は、他のアプローチを取るか、特定のユースケースを処理するためにクラスを最適化する必要があるかもしれません。

 


使用例

//+------------------------------------------------------------------+
//|                                                    exemplo_2.mq5 |
//|                                     Copyright 2022, Lethan Corp. |
//|                           https://www.mql5.com/pt/users/14134597 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Lethan Corp."
#property link      "https://www.mql5.com/pt/users/14134597"
#property version   "1.00"
#include "FileCSV.mqh"

CFileCSV csvFile;
string fileName = "dados.csv";
string headers[] = {"Timestamp", "Close", "Last"};
string data[1][3];

//The OnInit function
int OnStart(void)
  {
//Fill the 'data' array with values timestamp, Bid, Ask, Indicador1 and Indicador2
   data[0][0] = TimeToString(TimeCurrent());
   data[0][1] = DoubleToString(iClose(Symbol(), PERIOD_CURRENT, 0), 2);
   data[0][2] = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_LAST), 2);

//Open the CSV file
   if(csvFile.Open(fileName, FILE_WRITE|FILE_ANSI))
     {
      //Write the header
      csvFile.WriteHeader(headers);
      //Write data rows
      csvFile.WriteLine(data);
      //Close the file
      csvFile.Close();
     }
   else
     {
      Print("File opening error!");
     }
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+

このコードはMQL5のCFileCSVクラスの実装で、以下の機能をカバーしています。


結論

CFileCSVクラスは、CSVファイルを扱うための実用的で効率的なメソッドを提供します。ヘッダーや文字列を開いたり、書き込んだり、CSVファイルを読み込んだりするメソッドが含まれています。Open、WriteHeader、WriteLine、Readの各メソッドは、CSVファイルの正しい操作を保証し、データが読みやすい方法で書き込まれ、整理されるようにします。お時間をありがとうございました。次回は、今回紹介したCFileCSVクラスを使って、ファイル共有でMLモデルを利用する方法を紹介します。