English Deutsch
preview
MQL5におけるパイプライン

MQL5におけるパイプライン

MetaTrader 5統合 |
70 0
Stephen Njuki
Stephen Njuki

はじめに:データ前処理が重要な理由

AIを用いた予測システムを設計する際、深層学習モデルの構造や取引戦略の複雑さに意識が向きがちですが、モデル性能を大きく左右するのは、しばしばニューラルネットワーク自体よりも入力データの質と一貫性です。実際、OHLCバー、ティックボリューム、スプレッドといった金融データは、そのままでは「モデルが扱いやすい形式」になっていません。スケールがまちまちであったり、市場ショックによる外れ値が含まれていたり、取引セッションのようなカテゴリ特徴量が混在していたりします。これらはそのままでは数学モデルが適切に処理できません。

たとえば、正規化した価格リターンやボラティリティ指標を入力とするシンプルなニューラルネットワークを学習するのであれば、特徴量同士のスケールを揃えられるStandardScalerが適切です。一方、Pythonでシグモイド活性化を使ったONNX LSTMを扱う場合は、活性化関数の出力レンジと整合しやすいMinMaxScalerが有利になります。さらに、高インパクトなニュースイベントのようにスプレッドや出来高が急騰する局面では、異常値の影響を抑えつつ中心的な構造を保てるRobustScalerを選択できます。

重要な点は、前処理を後回しにしないということです。スケーラーの選択は、特徴量同士の関係、そして学習アルゴリズムがそれらをどう扱うかに直結し、最終的にモデルが安定して汎化するかどうかを決めます。

前処理が不十分であれば、どれほど高度なモデルであってもパターンをうまく学習できません。たとえばスプレッドはpipsの小数単位ですが、出来高は数千単位になるため、スケール差によってスプレッドが完全に埋もれてしまいます。また、取引セッションをアジア=0、欧州=1、米国=2のように整数で扱うと、数値の大小に意味が生じてしまい、モデルを誤方向に誘導する可能性があります。

そのため、前処理は堅牢な機械学習ワークフローの基盤になります。Pythonではscikit-learnが標準化、正規化、ロバストスケーリング、One-Hotエンコーディングなどの前処理ツールを提供しており、下流のモデルに渡すデータを整形できます。各変換ステップは、特徴量が公平に学習へ貢献するためのものです。一方MQL5では、これらの前処理機能が標準では提供されていません。このギャップを埋めるため、scikit-learnの機能をある程度模倣したMQL5クラスを実装し、金融時系列予測向けの前処理パイプラインを構築することができます。



エコシステムのギャップを埋める

Pythonのscikit-learnライブラリは、事実上、機械学習の前処理における業界標準となっています。最小限のコードで、特徴量を中心化するStandardScaler、特徴量を固定範囲にスケーリングするMinMaxScaler、外れ値の過度な影響を抑えるRobustScaler、特徴量をバイナリ表現へ展開するOneHotEncoderを適用できます。さらに、scikit-learnのpipelineクラスはこれらのステップをシームレスに連結でき、モデルへ渡されるすべてのデータセットが同一の変換シーケンスを受けられます。このモジュール式でプラグアンドプレイな仕組みにより、機械学習は多くの業界で急速に普及しました。

一方で、MQL5開発者は全く異なる現実に直面します。MQL5は取引データの処理自体は比較的効率的ですが、scikit-learnに匹敵する前処理手法をネイティブには提供していません。スケーリング、エンコード、欠損値補完のいずれであれ、各変換は手作業で実装する必要があり、しかも断片化しがちです。その結果、バグが混入しやすくなるだけでなく、再現性や学習データとテストデータの一貫性確保も難しくなります。

これに対する解決策として、scikit-learnの思想を模倣した前処理パイプラインクラスをMQL5で設計することが有効だと考えます。CStandardScaler、CMinMaxScaler、CRobustScaler、COneHotEncoderといった再利用可能なモジュールを実装できれば、前処理パイプラインを1つのコンテナとして連結できます。この構造により、生の金融時系列データが深層学習モデルへ渡される前に体系的な前処理を受けられるようになります。これは、モデルをMQL5でネイティブ実装する場合でも、ONNX経由で読み込む場合でも同様です。この仕組みを利用すれば、MQL5でもPythonでおなじみのワークフローを採用でき、よりクリーンな実験、より迅速な開発、そしてより信頼性の高いAIシステムの構築が期待できます。



前処理パイプラインの構造

前処理パイプラインは、データのためのコンベアベルトのようなものと考えると分かりやすいです。生データが一方の端から入り、出口に到達する頃にはモデルが利用できる形式へと変換されています。コンベアの各ステージは、欠損値補完、カテゴリのエンコード、数値特徴量のスケーリングといった明確な処理を担当します。Pythonでは通常、これらはscikit-learnのpipelineオブジェクトにまとめられていますが、MQL5では同様の構造をカスタムクラスで設計する必要があります。本記事では、この役割を担うCPreprocessingPipelineクラスを用意します。MQL5では次のように実装します。

// Preprocessing Pipeline
class CPreprocessingPipeline
{
private:
   SPreprocessorStep m_steps[];
   IPreprocessor    *m_preprocessors[];
   int              m_step_count;

public:
   CPreprocessingPipeline() : m_step_count(0) {}

   ~CPreprocessingPipeline()
   {  for(int i = 0; i < m_step_count; i++)
         delete m_preprocessors[i];
   }

   void AddImputeMedian(int column)
   {  ArrayResize(m_steps, m_step_count + 1);
      ArrayResize(m_preprocessors, m_step_count + 1);
      m_steps[m_step_count].type = PREPROCESSOR_IMPUTE_MEDIAN;
      m_steps[m_step_count].column = column;
      m_preprocessors[m_step_count] = new CImputeMedian(column);
      m_step_count++;
   }

   void AddImputeMode(int column)
   {  ArrayResize(m_steps, m_step_count + 1);
      ArrayResize(m_preprocessors, m_step_count + 1);
      m_steps[m_step_count].type = PREPROCESSOR_IMPUTE_MODE;
      m_steps[m_step_count].column = column;
      m_preprocessors[m_step_count] = new CImputeMode(column);
      m_step_count++;
   }

   void AddStandardScaler()
   {  ArrayResize(m_steps, m_step_count + 1);
      ArrayResize(m_preprocessors, m_step_count + 1);
      m_steps[m_step_count].type = PREPROCESSOR_STANDARD_SCALER;
      m_steps[m_step_count].column = -1;
      m_preprocessors[m_step_count] = new CStandardScaler();
      m_step_count++;
   }

   void AddRobustScaler()
   {  ArrayResize(m_steps, m_step_count + 1);
      ArrayResize(m_preprocessors, m_step_count + 1);
      m_steps[m_step_count].type = PREPROCESSOR_ROBUST_SCALER;
      m_steps[m_step_count].column = -1;
      m_preprocessors[m_step_count] = new CRobustScaler();
      m_step_count++;
   }

   void AddMinMaxScaler(double new_min = 0.0, double new_max = 1.0)
   {  ArrayResize(m_steps, m_step_count + 1);
      ArrayResize(m_preprocessors, m_step_count + 1);
      m_steps[m_step_count].type = PREPROCESSOR_MINMAX_SCALER;
      m_steps[m_step_count].column = -1;
      m_preprocessors[m_step_count] = new CMinMaxScaler(new_min, new_max);
      m_step_count++;
   }

   void AddOneHotEncoder(int column)
   {  ArrayResize(m_steps, m_step_count + 1);
      ArrayResize(m_preprocessors, m_step_count + 1);
      m_steps[m_step_count].type = PREPROCESSOR_ONEHOT_ENCODER;
      m_steps[m_step_count].column = column;
      m_preprocessors[m_step_count] = new COneHotEncoder(column);
      m_step_count++;
   }

   bool FitPipeline(matrix &data)
   {  matrix temp;
      temp.Copy(data);
      for(int i = 0; i < m_step_count; i++)
      {  matrix out;
         if(!m_preprocessors[i].Fit(temp)) return false;
         if(!m_preprocessors[i].Transform(temp, out)) return false;
         temp.Copy(out);
      }
      return true;
   }

   bool TransformPipeline(matrix &data, matrix &out)
   {  out.Copy(data);
      for(int i = 0; i < m_step_count; i++)
      {  matrix temp;
         if(!m_preprocessors[i].Transform(out, temp)) return false;
         out.Copy(temp);
      }
      return true;
   }

   bool FitTransformPipeline(matrix &data, matrix &out)
   {  if(!FitPipeline(data)) return false;
      return TransformPipeline(data, out);
   }
};

このクラスは、変換ステップを保持するためのコンテナとして機能します。開発者はAddStandardScaler()、AddRobustScaler()、AddMinMaxScaler()、AddOneHotEncoder()といったメソッドでステップを追加します。各ステップは独立したクラスとして実装され、たとえばCStandardScalerなどがあります。これらのクラスはFit()、Transform()、FitTransform()といった共通インターフェースを実装します。パイプラインを組み立てたら、まず学習データに対して適合し、平均値、中央値、最頻値、カテゴリマッピングなどのパラメータを取得します。学習とモデルのテストで十分な性能が得られたら、同様の前処理ステップを適用した新しいデータセットにモデルを適用できます。この一貫性によって、データリークといったテスト時の典型的な問題を避けられます。

この設計のモジュール性には複数の利点があります。まず、同じパイプラインオブジェクトを繰り返し使えるため再利用性が高まります。次に、各変換クラスが疎結合になるため、デバッグや保守が容易になります。さらに、学習、検証、テストのすべてがまったく同じ変換フローを通るため、一貫性も確保できます。言い換えると、前処理パイプラインにより、MQL5でもPythonエコシステムに近い堅牢で規律あるワークフローを実現できます。



StandardScaler

StandardScalerは、機械学習で最も広く使用される前処理手法の1つです。目的は、各特徴量をゼロ中心に移動させ、その標準偏差でスケーリングすることです。数学的には次の式で定義されます。

f1

ここで

  • μは特徴量の平均値 
  • σは標準偏差
  • xはデータセット中の各データ点 

この変換によって、すべての特徴量が平均0、標準偏差1にそろいます。結果として、特定の特徴量だけが学習過程を支配するリスクを減らし、データセットの均質性を高めます。MQL5でのCStandardScalerクラスの実装は以下のとおりです。

// Standard Scaler
class CStandardScaler : public IPreprocessor
{
private:
   double m_means[];
   double m_stds[];
   bool   m_is_fitted;

public:
   CStandardScaler() : m_is_fitted(false) {}

   bool Fit(matrix &data)
   {  int rows = int(data.Rows());
      int cols = int(data.Cols());
      ArrayResize(m_means, cols);
      ArrayResize(m_stds, cols);
      for(int j = 0; j < cols; j++)
      {  vector column(rows);
         for(int i = 0; i < rows; i++) column[i] = data[i][j];
         m_means[j] = column.Mean();
         m_stds[j] = column.Std();
         if(m_stds[j] == 0.0) m_stds[j] = EPSILON;
      }
      m_is_fitted = true;
      return true;
   }

   bool Transform(matrix &data, matrix &out)
   {  if(!m_is_fitted) return false;
      int rows = int(data.Rows());
      int cols = int(data.Cols());
      out.Init(rows, cols);
      for(int j = 0; j < cols; j++)
         for(int i = 0; i < rows; i++)
            out[i][j] = (!MathIsValidNumber(data[i][j]) ? DBL_MIN : (data[i][j] - m_means[j]) / m_stds[j]);
      return true;
   }

   bool FitTransform(matrix &data, matrix &out)
   {  if(!Fit(data)) return false;
      return Transform(data, out);
   }
};

上記のMQL5クラスでは、Fit()フェーズで列方向に平均値と標準偏差を計算し、内部に保持します。そしてTransform()フェーズで各データ点に対して実際に変換を適用します。ある特徴量の分散がゼロの場合は、ゼロ除算を避けるために小さな非ゼロ値epsilonを代わりに使用します。これによって数値安定性が確保されます。

取引用途では、StandardScalerはスケールの異なる特徴量を扱う際に特に有効です。たとえばスプレッドはピップの分数で記録されますが、ティックボリュームは数千単位で記録されます。このようなスケール差を放置すると、モデルは単に数値が大きい特徴量のほうに過度に注意を向けてしまいます。標準化することで両者が同じ基準で扱われ、学習が偏らなくなります。

さらに、対数リターン、移動平均、ボラティリティ指標といった入力をニューラルネットワーク用に整える際にも有効です。このスケーラを適用すると特徴量が正規化されるため、MLPやSVMといったモデルが学習時により効率的に収束しやすくなります。


MinMaxScaler

StandardScalerが特徴量を平均0、分散1に正規化するのに対し、MinMaxScalerは特徴量を特定の範囲、一般的には0〜1に再スケーリングします。変換式は次のとおりです。

f2

この変換により、すべての値が指定した範囲に収まるようになります。これは入力値のスケールに敏感なモデル、特にsigmoidやtanhといった活性化関数を用いるニューラルネットワークで有用です。MQL5でのCMinMaxScalerクラスは次のように実装されます。

// MinMax Scaler
class CMinMaxScaler : public IPreprocessor
{
private:
   double m_mins[];
   double m_maxs[];
   double m_new_min;
   double m_new_max;
   bool   m_is_fitted;

public:
   CMinMaxScaler(double new_min = 0.0, double new_max = 1.0) : m_new_min(new_min), m_new_max(new_max), m_is_fitted(false) {}

   bool Fit(matrix &data)
   {  int rows = int(data.Rows());
      int cols = int(data.Cols());
      ArrayResize(m_mins, cols);
      ArrayResize(m_maxs, cols);
      for(int j = 0; j < cols; j++)
      {  vector column(rows);
         for(int i = 0; i < rows; i++) column[i] = data[i][j];
         m_mins[j] = column.Min();
         m_maxs[j] = column.Max();
         if(m_maxs[j] - m_mins[j] == 0.0) m_maxs[j] += EPSILON;
      }
      m_is_fitted = true;
      return true;
   }

   bool Transform(matrix &data, matrix &out)
   {  if(!m_is_fitted) return false;
      int rows = int(data.Rows());
      int cols = int(data.Cols());
      out.Init(rows, cols);
      for(int j = 0; j < cols; j++)
         for(int i = 0; i < rows; i++)
         {  if(!MathIsValidNumber(data[i][j])) out[i][j] = DBL_MIN;
            else
            {  double scale = (m_new_max - m_new_min) / (m_maxs[j] - m_mins[j]);
               out[i][j] = (data[i][j] - m_mins[j]) * scale + m_new_min;
            }
         }
      return true;
   }

   bool FitTransform(matrix &data, matrix &out)
   {  if(!Fit(data)) return false;
      return Transform(data, out);
   }
};

このクラスでは、Fit()フェーズで各列の最小値と最大値を取得し、それらをTransform()フェーズでの再スケーリングに利用します。開発者は範囲を自由に指定でき、典型的な[0,1]ではなく[-1,+1]といった設定も可能です。StandardScalerと同様に、特徴量がすべて同一値の場合は、ゼロ除算を防ぐためにepsilonが使われます。

実際の取引用途では、終値をONNXベースのLSTMへ入力する前にスケーリングするケースが挙げられます。ニューラルネットワークは入力が一定の狭い範囲に収まっていると勾配が安定しやすく、収束も早くなります。大きな絶対値を持つモメンタム指標やオシレーターを扱う場合にも、MinMaxScalerは値を一貫した範囲に収める助けになります。

MinMaxScalerの利点は、シンプルでありつつ元の分布形状を保てる点です。StandardScalerのように分散は変えず、単に指定区間へ値をスケーリングするだけです。ただし、外れ値に敏感で、極端な値が1つあるだけで全体のスケーリングが歪む可能性があります。そのため、値が安定したデータセットに向いており、もしくは異常値処理と併用することで、深層学習モデルの前処理として最適な選択となります。


RobustScaler

市場は予測不能で、外れ値が発生しやすいことで知られています。突発的なニュースイベントでは、スプレッドが一気に拡大したり、出来高が過去平均を大きく超えることがあります。このような状況では、StandardScalerやMinMaxScalerといった手法は歪みやすくなります。これらが平均値や極端値に強く依存するためです。こうしたケースで有効なのがRobustScalerです。

RobustScalerは、データを中央値で中心化し、四分位範囲(IQR: Interquartile Range)でスケーリングします。IQRは75パーセンタイルQ3と25パーセンタイルQ1の差として定義されます。変換式は以下のようになります。

f3

ここで

  • IQRは75パーセンタイルQ3と25パーセンタイルQ1の差
  • Medianはデータセットの中央値
  • xはデータセット内のデータポイント

このスケーラーは極端値の影響を無視するため、外れ値に強くなります。取引データでは、市場ショックが発生しても、大半の特徴量がモデル向けに適切なスケールを保てるという利点があります。MQL5での実装は次のとおりです。

// Robust Scaler
class CRobustScaler : public IPreprocessor
{
private:
   double m_medians[];
   double m_iqrs[];
   bool   m_is_fitted;

public:
   CRobustScaler() : m_is_fitted(false) {}

   bool Fit(matrix &data)
   {  int rows = int(data.Rows());
      int cols = int(data.Cols());
      ArrayResize(m_medians, cols);
      ArrayResize(m_iqrs, cols);
      for(int j = 0; j < cols; j++)
      {  vector column(rows);
         for(int i = 0; i < rows; i++) column[i] = data[i][j];
         m_medians[j] = column.Median();
         double q25 = column.Quantile(0.25);
         double q75 = column.Quantile(0.75);
         m_iqrs[j] = q75 - q25;
         if(m_iqrs[j] == 0.0) m_iqrs[j] = EPSILON;
      }
      m_is_fitted = true;
      return true;
   }

   bool Transform(matrix &data, matrix &out)
   {  if(!m_is_fitted) return false;
      int rows = int(data.Rows());
      int cols = int(data.Cols());
      out.Init(rows, cols);
      for(int j = 0; j < cols; j++)
         for(int i = 0; i < rows; i++)
            out[i][j] = (!MathIsValidNumber(data[i][j]) ? DBL_MIN : (data[i][j] - m_medians[j]) / m_iqrs[j]);
      return true;
   }

   bool FitTransform(matrix &data, matrix &out)
   {  if(!Fit(data)) return false;
      return Transform(data, out);
   }
};

上記のCRobustScalerクラスでは、Fit()フェーズで中央値と四分位数を計算し、Transform()フェーズでスケーリングを適用します。IQRがゼロになる場合に備えて、epsilonで数値安定性を確保します。この実装により、市場の異常スパイクによってモデルが誤った学習をするリスクを抑えられます。

例として、ティックボリュームをモデル学習に用いる場合を考えます。通常の時間帯では出来高は一定の範囲に収まりますが、ニュース発表時には一気に10倍に跳ね上がることがあります。StandardScalerやMinMaxScalerを使うと、分布が引き伸ばされ「通常」の値が極端に圧縮されてしまいます。RobustScalerはデータ中央50パーセントに注目するため、入力特徴量の主要パターンを保持できます。為替や暗号資産市場でよく見られる、変動が大きく裾の重い分布に対応できる点で、深層学習に有効なスケーラーといえます。


One-Hotエンコーディング

取引では、すべての特徴量が数値とは限りません。一部の特徴量は、市場の特定の状態を表すために離散的またはカテゴリ型になることがあります。たとえば、時間をアジア、欧州、米国の取引セッションに分類したり、市場レジームを強気、弱気、レンジに分けたりできます。これらの例は網羅的ではありませんが、ポイントは、機械学習モデルはこのようなカテゴリ値をそのままでは解釈できないということです。さらに強調すると、アジア=0、ヨーロッパ=1、US=2のように整数を割り当てるだけでは、実際には存在しない順序性を導入し、モデルの学習を偏らせる可能性があります。

この問題の一般的な解決策がOne-Hotエンコーディングです。カテゴリごとにバイナリベクトルへ変換し、例えば先ほどの取引セッション分類であれば、アジア、ヨーロッパ、USはそれぞれ[1,0,0]、[0,1,0]、[0,0,1]になります。これによってモデルはカテゴリ同士の序列を仮定せずに識別できます。MQL5での実装は次のとおりです。

// One-Hot Encoder
class COneHotEncoder : public IPreprocessor
{
private:
   int    m_column;
   double m_categories[];
   bool   m_is_fitted;

public:
   COneHotEncoder(int column) : m_column(column), m_is_fitted(false) {}

   bool Fit(matrix &data)
   {  int rows = int(data.Rows());
      vector values;
      int unique = 0;
      for(int i = 0; i < rows; i++)
      {  if(!MathIsValidNumber(data[i][m_column])) continue;
         int idx = CVectorUtils::BinarySearch(values, data[i][m_column]);
         if(idx == -1)
         {  values.Resize(unique + 1);
            values[unique] = data[i][m_column];
            unique++;
         }
      }
      values.Swap(m_categories);
      //ArrayCopy(m_categories, values);
      m_is_fitted = true;
      return true;
   }

   bool Transform(matrix &data, matrix &out)
   {  if(!m_is_fitted) return false;
      int rows = int(data.Rows());
      int cols = int(data.Cols());
      int cat_count = ArraySize(m_categories);
      if(data.Cols() == cols - 1 + cat_count) return false;
      out.Resize( rows, cols - 1 + cat_count);
      out.Fill(0.0);
      for(int i = 0; i < rows; i++)
      {  int out_col = 0;
         for(int j = 0; j < cols; j++)
         {  if(j == m_column) continue;
            out[i][out_col] = data[i][j];
            out_col++;
         }
         for(int k = 0; k < cat_count; k++)
            if(data[i][m_column] == m_categories[k])
            {  out[i][out_col + k] = 1.0;
               break;
            }
      }
      m_is_fitted = true;
      return true;
   }

   bool FitTransform(matrix &data, matrix &out)
   {  if(!Fit(data)) return false;
      return Transform(data, out);
   }
};

上記のCOneHotEncoderクラスはこの変換を実装しています。Fit()フェーズでは指定された列のユニークなカテゴリを特定します。Transform()フェーズでは、そのカテゴリ列を複数のバイナリ列に置き換えます。カテゴリ数に応じて特徴量行列が拡張され、カテゴリ情報がモデルフレンドリーな形式に変換されます。

この仕組みが有用である例として、先ほどの取引セッション分類を考えます。0=アジア、1=ヨーロッパ、2=USといった生の数値を使うと、ニューラルネットワークがアジアとUSの差をアジアとヨーロッパの差より大きいと誤解する可能性があります。One-Hotエンコーディングを使えば、各セッションは独立した表現を持ち、モデルはそれぞれの特徴的な市場挙動を学習できます。これは取引セッションごとに流動性、ボラティリティ、方向性が大きく異なる取引のモデルでは特に重要です。



全体の統合

スケーラーやエンコーダーは個々でも強力ですが、真価を発揮するのは1つのワークフローにまとめて組み合わせたときです。これが前処理パイプラインです。複数の変換を連結し、どのデータセットに対してもモデルに渡す前に同じ処理を必ず適用できるようにします。

次のようなシナリオを考えます。4つの主要な特徴量を含むデータセットを準備しているとします。

  1. 終値
  2. ティックボリューム
  3. スプレッド
  4. 取引セッション区分(カテゴリ)

通常の最初のステップは欠損値の処理です。MQL5開発者の中にはブローカーから得たデータをそのまま使うため省略しがちな工程かもしれませんが、必要なデータが必ず揃っていると仮定するべきではありません。たとえばティックボリュームに欠損がある場合、AddImputeMedian(1)関数(1は4つの特徴量の中でティックボリュームのインデックス)を適用することで、その列の中央値で欠損を置き換えられます。同様に、取引セッション区分に欠損がある場合はAddImputeMode(3)関数を使って、最頻セッションで補完することができます。これらの選択は開発者のシステムやモデルに応じて調整されるものですが、ここではあくまで説明のための例です。 

欠損値を処理した後、次のステップはカテゴリデータを「中立的な」バイナリ形式へ変換することです。AddOneHotEncoder(3)を適用することで、セッション列はバイナリベクトルに展開され、各セッションが明確に区別できるようになります。

ステップ3としてスケーラーを適用します。データセットの性質に応じて、AddStandardScaler()、AddRobustScaler()、AddMinMaxScaler()のいずれかを選択できます。このステップの目的はすべての数値特徴量を比較可能な尺度へ調整することです。これらのステップをパイプラインに追加したら、学習データに対してFitPipeline()関数を呼び出します。これにより、平均、中央値、最頻値、カテゴリマッピングといった必要なパラメータが学習されます。その後、TransformPipeline()関数を学習データとテストデータ(または最適化データとフォワードウォークデータ)に対して呼び出すことで、未来データ漏洩なしに一貫性のある変換が適用されます。

最終的に得られるのは、深層学習モデル(MQL5で自作したものでも、ONNXでインポートしたものでも構いません)に直接入力できる、クリーンでスケーリング済み、エンコード済みの特徴量行列です。このパイプラインを使えば前処理が透明で再現可能、かつモジュール化されるため、開発者はデータの整理に時間を取られず、シグナルや戦略により集中できます。記事末尾に添付しているスクリプトでは、パイプラインクラスを使ってモデル用のデータを準備するデモを実行できます。USDJPYでテストした際のログは次のとおりです。

2025.09.12 17:05:50.150 Pipeline_Illustration (USDJPY,H4)       RAW (first 6 rows) [rows=2999, cols=4]
2025.09.12 17:05:50.150 Pipeline_Illustration (USDJPY,H4)         147.625000, 6894.000000, 20.000000, 2.000000
2025.09.12 17:05:50.150 Pipeline_Illustration (USDJPY,H4)         147.837000, 14153.000000, 20.000000, 1.000000
2025.09.12 17:05:50.150 Pipeline_Illustration (USDJPY,H4)         147.885000, 16794.000000, 20.000000, 1.000000
2025.09.12 17:05:50.150 Pipeline_Illustration (USDJPY,H4)         147.489000, 8010.000000, 20.000000, 0.000000
2025.09.12 17:05:50.150 Pipeline_Illustration (USDJPY,H4)         147.219000, 6710.000000, 20.000000, 0.000000
2025.09.12 17:05:50.150 Pipeline_Illustration (USDJPY,H4)         147.194000, 13686.000000, 20.000000, 3.000000
2025.09.12 17:05:50.163 Pipeline_Illustration (USDJPY,H4)       TRANSFORMED (FitTransform on all) [rows=2999, cols=32]
2025.09.12 17:05:50.163 Pipeline_Illustration (USDJPY,H4)         0.353976, 0.081606, 0.052632, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.163 Pipeline_Illustration (USDJPY,H4)         0.363616, 0.167533, 0.052632, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.163 Pipeline_Illustration (USDJPY,H4)         0.365798, 0.198795, 0.052632, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.163 Pipeline_Illustration (USDJPY,H4)         0.347792, 0.094816, 0.052632, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.163 Pipeline_Illustration (USDJPY,H4)         0.335516, 0.079428, 0.052632, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.163 Pipeline_Illustration (USDJPY,H4)         0.334379, 0.162005, 0.052632, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)       TRAIN (after TransformPipeline) [rows=2249, cols=32]
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.353976, 0.081606, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.363616, 0.167533, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.365798, 0.198795, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.347792, 0.094816, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.335516, 0.079428, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.334379, 0.162005, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)       TEST  (after TransformPipeline) [rows=750, cols=32]
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.538217, 0.098806, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.536307, 0.280804, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.545628, 0.163082, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.540217, 0.121817, -0.028571, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.533852, 0.093858, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)         0.532215, 0.071675, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, ...
2025.09.12 17:05:50.170 Pipeline_Illustration (USDJPY,H4)       Preprocessing pipeline demo complete.



取引におけるスケーラーの比較

どのスケーラーを使うかという選択は、常に一律で決められるものではありません。各スケーラーには長所と短所があり、最適な選択はデータの種類や対象市場の動きに依存します。以下の一覧は、それぞれのスケーラーがどこで強みを発揮するかを整理したものです。

スケーラー 強み 弱点 最適な用途
StandardScaler ガウス分布に近いデータに適する。各特徴量を平均ゼロ、分散1にする。
外れ値に敏感。
テクニカル指標やボラティリティ系特徴量の処理。
MinMaxScaler 有界活性化関数と相性が良い。典型的には[0,1]の範囲に収める。
外れ値に非常に敏感で、極端値がスケーリングを歪める。
価格系特徴量、シグモイドやtanhを使うネットワークへの入力。
RobustScaler 中央値とIQRを使うため外れ値の影響を受けづらい。
絶対的なスケールを失う可能性がある。
ヘビーテールデータ、ボラティリティの高い場面のスプレッドやティックボリューム。

たとえば、正規化した価格リターンやボラティリティ指標を入力とするシンプルなニューラルネットワークを学習するのであれば、特徴量同士のスケールを揃えられるStandardScalerが適切です。一方、Pythonでシグモイド活性化を使ったONNX LSTMを扱う場合は、活性化関数の出力レンジと整合しやすいMinMaxScalerが有利になります。さらに、高インパクトなニュースイベントのようにスプレッドや出来高が急騰する局面では、異常値の影響を抑えつつ中心的な構造を保てるRobustScalerを選択できます。

重要な点は、前処理を後回しにしないということです。スケーラーの選択は、特徴量同士の関係、そして学習アルゴリズムがそれらをどう扱うかに直結し、最終的にモデルが安定して汎化するかどうかを決めます。


深層学習のデータの準備

いわゆる深層学習モデル、あるいはニューラルネットワークは、MQL5でコーディングする場合でもONNXでインポートする場合でも、入力データに対して厳密な前提条件を持ちます。たとえば、Python側で正規化されたデータを使って学習したモデルに、MQL5側で未加工・非スケーリングの特徴量を入力すると、モデルは性能を発揮できない、もしくはまったく動作しないことがあります。このため、前処理パイプラインは「任意」ではなく、実運用レベルの取引やワークフローにおいては必須であると言えます。

たとえば、TensorFlowでmin-max正規化した価格データを使って学習したONNXモデルを、後にMQL5で展開する際、もし生のOHLCデータをそのまま入力した場合、入力スケールに整合性がないため、学習時の重みに対して不適切な値が渡され、予測品質は著しく低下します。Python側の学習時と同一のmin/maxパラメータを用いてCMinMaxScaler()を適用すれば、両環境で一貫した入力スケーリングを保証できます。

スケーリング以外にも、カテゴリエンコーディングは重要な役割を果たします。  たとえば、取引セッションを例に挙げると、one-hotエンコーディングで学習したニューラルネットワークは、推論時にも同じバイナリ形式の入力を期待します。もし学習時にはアジア = [0,1,0]であったものが、推論時に[1,0,0]として渡されると、モデルの予測は意味を成さなくなります。学習時に利用したカテゴリ情報を記録するパイプラインを導入することで、このような不整合を防ぐことができます。

さらに、前処理パラメータの永続化も不可欠です。平均値、最小値、最大値、カテゴリマッピングなどは、学習後に保存し、推論時に再適用する必要があります。これらの一貫性が失われると、再学習や推論時にドリフトが発生するリスクがあります。Pythonではscikit-learnがjoblibやpickleによるシリアライズ手段を提供していますが、MQL5でも同様に、パイプラインの状態を配列形式からbinやcsvとして保存し、エキスパートアドバイザー(EA)の初期化時に再ロードすることで同じ効果を得ることができます。



課題とベストプラクティス

これらの前処理パイプラインによってMQL5のワークフローに一定の厳密性が確保されていますが、信頼性を保つためにはいくつかの課題に対処する必要があります。ここでは代表的な5つを考えます。まず1つ目は、NaNの扱いと欠損データの処理です。金融データセットは、市場の休場、ティックデータの欠落、不規則なブローカーフィードなどが原因で欠損を含むことが一般的です。MQL5のパイプラインでは、欠損を示すためにDBL_MINのようなプレースホルダーが用いられることがあります。そのため、数値データであれば中央値、離散/カテゴリデータであれば最頻値など、一貫した方法でこれらの値を補完することが重要です。これにより、モデルに無効なデータが入力されることを防ぎます。

MQL5におけるパイプラインの2つ目の問題点は、データリークを防ぐことです。よくある誤りとして、学習、検証、テストの各データセットに分割する前に、スケーラーやエンコーダーをデータ全体に対して適合してしまうケースがあります。このような運用は、将来の情報を学習過程に漏えいさせてしまい、パフォーマンス指標を不正に押し上げる可能性があります。MQL5のストラテジーテスターは本来、現在または過去の価格情報のみを逐次読み取るように設計されていますが、補助データをモデルに入力する場合、このようなリーケージを防ぐための追加チェックが必要になります。ベストプラクティスは、必ず学習データのみに対してFit()を実行し、学習セットとテストセットにはそれぞれ別個にTransform()を適用することです。

3つ目として、混合データ型のスケーリングは難易度が高い場合があります。データセットに数値特徴量とカテゴリ特徴量の両方が含まれる場合、変換処理は慎重におこなう必要があります。エンコーダーはスケーラーよりも先に適用し、新たに生成されるバイナリ列が適切に数値行列へ統合されるようにする必要があります。

4つ目として、変換処理のデバッグにも注意を払う必要があります。変換結果の正しさを検証するために、変換後データの先頭数行を出力して確認することが有用です。Print()などのデバッグツールを利用することで、エンコーディングやスケーリングが期待どおりに機能しているかを迅速に把握できます。

最後に、再現性の確保は常に保証されるわけではありません。実験間の一貫性を保つためには、パイプラインのパラメータ、最小値、中央値、カテゴリマッピングなどを、学習済みモデルとともに保存しておく必要があります。これにより、バックテスト、ライブ運用、再学習のいずれにおいても、まったく同一の前処理を適用できるようになります。

以上の5つの注意点を守ることで、開発者は一般的な落とし穴を回避し、MQL5のパイプラインをPythonのそれと同等に堅牢なものにすることができます。



結論

前処理は機械学習において決して華やかな工程ではありませんが、その重要性は疑いようがありません。慎重な準備がなければ、どれほど高度な深層学習モデルであっても、生のままのスケール調整されていない、あるいは不一致なエンコードが施された取引データに直面したときに失敗してしまいます。MetaTrader 5に精通した開発者にとって、この課題はこれまで機械学習ワークフローを最大限に活用するうえで障壁となってきました。scikit-learnのような強力なツールキットを備えるPythonとは異なり、MQL5には標準の前処理パイプラインが存在しません。この問題の解決策は、MQL5内でモジュール式かつ再利用可能な前処理パイプラインを構築することにあると考えられます。

前処理を単なる補助機能ではなく、AIワークフローの不可欠な構成要素として扱うことで、開発者はトレーディングシステムを機械学習の厳密な実践と整合させることができます。その結果、ネイティブモデルであれ、ONNXベースのモデルであれ、可能な限り最良の環境で動作させることができるようになります。

名前 説明
PipeLine.mqh パイプライン機能の基本クラス
Pipeline_Illustration.mq5 基本クラスを参照して使用方法を示すスクリプト

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

添付されたファイル |
PipeLine.mqh (14.96 KB)
カスタム口座パフォーマンス行列インジケーターの開発 カスタム口座パフォーマンス行列インジケーターの開発
このインジケーターは、口座エクイティ、損益、ドローダウンをリアルタイムで監視し、パフォーマンスダッシュボードとして可視化することで、規律の維持を促す役割を果たします。トレーダーが取引の一貫性を保ち、過剰取引を避け、自己勘定取引会社評価チャレンジ(プロップファームチャレンジ)のルールを遵守するための支援ツールとして機能します。
初心者からエキスパートへ:NFP発表後の市場取引におけるフィボナッチ戦略の実装 初心者からエキスパートへ:NFP発表後の市場取引におけるフィボナッチ戦略の実装
金融市場において、リトレースメントの法則は最も否定しがたい力の一つです。価格は必ずリトレースするというのが経験則であり、大きな値動きにおいても、最小のティックパターンにおいても、ジグザグの形で現れることが多くあります。しかし、リトレースメントのパターン自体は固定されておらず、不確実で予測が難しいのが現状です。この不確実性があるため、トレーダーは複数のフィボナッチレベルを参照し、それぞれの影響力を確率的に考慮します。本記事では、主要経済指標発表後の短期売買における課題に対処するため、フィボナッチ手法を応用した精緻な戦略を紹介します。リトレースメントの原則とイベントドリブンの市場動向を組み合わせることで、より信頼性の高いエントリーおよびエグジットの機会を見出すことを目指します。ディスカッションに参加し、フィボナッチをイベント後取引にどのように適応できるかをご覧ください。
Parafrac V2オシレーター:パラボリックSARとATRの統合 Parafrac V2オシレーター:パラボリックSARとATRの統合
Parafrac V2オシレーターは、パラボリックSARとATR(Average True Range、平均真の範囲)を統合した高度なテクニカル分析ツールです。前バージョンのParafracオシレーターではフラクタルを使用していたため、過去や現在のシグナルを覆い隠すようなスパイクが発生しやすいという課題がありました。Parafrac V2ではATRによるボラティリティ測定を活用することで、トレンドや反転、ダイバージェンスの検出をより滑らかで信頼性の高い方法で行えるようになり、チャートの混雑や分析の過負荷を軽減できます。
MQL5での取引戦略の自動化(第32回):プライスアクションに基づくファイブドライブハーモニックパターンシステムの作成 MQL5での取引戦略の自動化(第32回):プライスアクションに基づくファイブドライブハーモニックパターンシステムの作成
本記事では、MQL5においてピボットポイントとフィボナッチ比率に基づいて強気、弱気双方のファイブドライブ(5-0)ハーモニックパターンを識別し、ユーザーが選択できるカスタムエントリー、ストップロス、テイクプロフィット設定を用いて取引を実行するファイブドライブパターンシステムを開発します。また、A-B-C-D-E-Fパターン構造やエントリーレベルを表示するために、三角形やトレンドラインなどのチャートオブジェクトを使った視覚的フィードバックでトレーダーの洞察力を高めます。