English
preview
MQL5標準ライブラリエクスプローラー(第9回):ALGLIBによる多発するMAクロスオーバーシグナルのフィルタ処理

MQL5標準ライブラリエクスプローラー(第9回):ALGLIBによる多発するMAクロスオーバーシグナルのフィルタ処理

MetaTrader 5 |
21 0
Clemence Benjamin
Clemence Benjamin

内容


はじめに

移動平均クロスオーバーは、おそらくすべてのトレーダーが最初に試す戦略です。短期移動平均線が長期移動平均線を上抜けると「買い」を示し、逆の場合は「売り」を示します。これはシンプルで直感的であり、トレンド市場では驚くほど効果的です。問題は、市場が横ばいになると発生します。価格が移動平均の周辺で上下に振動し、ラインが何度も交差することで、一連の損失トレード、つまり悪名高い「ダマシ」(ホイップソー)が発生します。

多くのトレーダーは、この問題に対処するために期間を長く設定します(移動平均をより滑らかにする代わりに反応を遅くする方法です)。または、RSIやADXなどのフィルタを追加します。こうした方法は有効な場合もありますが、結局のところ元の仕組みに経験則ベースの調整を重ねているにすぎません。

ALGLIBは、MetaTrader 5のインジケータやエキスパートアドバイザー(EA)へ高度な数学計算を直接導入できる、クロスプラットフォームの数値解析ライブラリです。元々は C++、C#、Python 用に開発されたALGLIB ) は、標準ライブラリ配布の一部としてMQL5に移植されており、線形代数、最適化、信号処理、データ分析などのための数百種類の最適化されたアルゴリズムへアクセスできます。

高度な数学に不安を感じる方も心配する必要はありません。まずは簡単な概念から始め、徐々に構築していきます。ALGLIBは内部で複雑な数値計算を処理します。私たちが理解する必要があるのは、それぞれの関数が概念的に何をするのかという点だけです。これは車の運転に例えることができます。アクセルを踏むために熱力学を理解する必要はありません。

本記事では、基本的な移動平均クロスオーバーを、以下の3つのALGLIB技術を使用した数学的前処理システムへ変換します。

  • 基本的な統計フィルタ
  • 特異スペクトル解析(SSA)
  • スプラインベースの微分解析

最後には、数学的前処理によって、トレンド中の反応性を維持しながら、レンジ相場でのシグナル頻度をどのように低減できるかをご覧いただけます。


概念

ALGLIBは、数十年にわたって開発されてきた数値解析ライブラリです。ゼロから実装するには多大な時間を必要とするアルゴリズムの、最適化された実装を提供します。その幅広い関数セットの中には、以下のようなツールがあります。

  • 線形代数:行列演算、分解、方程式ソルバー
  • 最適化:制約付きおよび制約なしの最小化
  • 補間とフィッティング:スプライン、最小二乗法、有理近似
  • データ分析:クラスタリング、主成分分析
  • 信号処理:FFT、畳み込み、フィルタ処理
  • 統計:分布、仮説検定、相関分析

ALGLIBは主に、1次元配列用のCRowDouble や行列用のCMatrixDoubleなど、専用の構造体を使用して動作します。これらは、ネイティブのMQL5ベクトルおよびマトリクスタイプとの間で変換できます。

基本フィルタ(SMA、EMA、LRMA)

ALGLIBは、手動実装よりも効率的で数値的に安定した、すぐに使用できるフィルタ処理関数を提供しています。FilterSMAは、オーバーフロー保護付きの累積和を用いて単純移動平均を計算します。FilterEMAは、正確な係数処理によって指数移動平均を実装します。FilterLRMAは、スライディングウィンドウ上で線形回帰を実行します。つまり、直近K個のポイントに対して直線を適合させ、現在のポイントにおける値を返します。 

LRMA(線形回帰移動平均)の数学的背景は特に興味深いものです。各ウィンドウでは、単純な線形回帰:y = a + b*xを解きます。ここで、xはウィンドウ内での位置を表します。現在のポイントにおける値は、「a + b*(window‑1)」として計算されます。これにより、単純平均よりも線形トレンドにより素早く反応するフィルタが得られます。

最新ポイントにおける出力値は回帰直線から計算されるため、単純移動平均よりもトレンドへの反応性が高くなります。

特異スペクトル分析(SSA)

SSAは、時系列データをトレンド、周期、ノイズなどの構造的な成分へ分解します。これは、軌跡行列を構築し、特異値分解(SVD)を実行し、選択した成分を再構築することで動作します。概念的には、長期的な構造と短期的な変動を分離します。

ALGLIBのCSSAModelは、ウィンドウサイズと再構築ランクを指定することで、この処理を内部的に管理します。

スプライン補間と微分

3次スプラインは、データポイントを通過する滑らかな区分多項式であり、1次および2次微分が連続しています。1次微分は瞬間的な変化率を表します。微分値のゼロクロスは、方向転換を示す可能性があります。

以下の4つのアプローチを比較します。

  • ベースライン:生価格に対するEMA(指数移動平均)クロスオーバー
  • 基本フィルタ版:事前に平滑化した価格に対するEMAクロスオーバー
  • フィルタ付きSSA:SSAトレンドに対するEMAクロスオーバー
  • スプライン微分:方向性の手掛かりとしての微分ゼロクロス

4つすべてを1つのインジケータ内に実装することで、それぞれの動作を視覚的に比較し、前処理がシグナルタイミングと信頼性へどのような影響を与えるかを確認できます。


実装

ここから、インジケータを段階的に構築していきます。MetaEditorを起動し、新しいインジケータを作成して、コードを見ていきます。ファイル名はALGLIB_FilterDemo.mq5とします。このインジケータは、ALGLIB数値ライブラリによって提供される複数の高度なフィルタ処理技術を実証するために設計されています。同じ価格系列へ異なるフィルタを適用することで、それぞれの平滑化特性と、単純なクロスオーバー取引システムへの影響を比較できます。最終的なチャートには複数のラインが表示され、それぞれ異なる価格フィルタ処理を表します。また、生の移動平均から生成されたクロスオーバーシグナルと、フィルタ処理された系列から生成されたクロスオーバーシグナルの両方が表示されます。

ステップ1:フレームワークの設定

まず、ALGLIBライブラリをインクルードし、インジケータのプロパティを定義する必要があります。#include <Math/Alglib/alglib.mqh>ディレクティブによって、フィルタ処理、SSA、スプライン、線形代数に必要なすべてのALGLIB関数を読み込みます。#property文では、インジケータの基本動作を設定します。インジケータはメインチャートウィンドウ(indicator_chart_window )に描画され、10個のバッファと10個のプロットを割り当てます。10個のバッファを割り当てることで、元の価格、3つのベースラインEMA(ただしクロスオーバーでは2つのみ使用)、5つのフィルタ済み系列、そして2種類のクロスオーバーシグナルを保存および表示できます。これらすべてのラインを同時にチャートへ表示すると、少し混雑して見えるかもしれません。しかし、各フィルタが価格データをどのように変換するかを即座に視覚比較できるという利点があります。

//+------------------------------------------------------------------+
//|                                            ALGLIB_FilterDemo.mq5 |
//|                                Copyright 2026, Clemence Benjamin |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 10
#property indicator_plots   10

//--- include ALGLIB
#include <Math/Alglib/alglib.mqh>

indicator_buffersとindicator_plotsを10に設定すると、チャート上に10本の線またはマーカーが表示されます。かなりの量ですが、比較のために全てを一度に見たいのです。

ステップ2:プロットプロパティの定義

各プロットには、ラベル、タイプ、色、スタイルが必要です。連続した系列にはラインを使用し、クロスオーバーシグナルには矢印を使用します。ラベルはDataWindowに表示され、各ラインを識別するために役立ちます。異なる色を割り当てることで、チャートを読みやすくします。生の終値には黒、ベースラインEMAには青/赤、SMA(単純移動平均)フィルタ済み系列には緑、EMAフィルタ済み系列にはドジャーブルー、LRMAフィルタ済み系列にはオレンジ、SSAトレンドにはマゼンタ、スケール調整されたスプライン微分には茶色を使用します。プロット9と10は矢印プロットです。これらはクロスオーバーが発生した場所にのみ表示され、潜在的な買い(バーの下)および売り(バーの上)のポイントを示します。矢印コードと空の値は、後ほどOnInit()内で設定します。

//--- plot definitions
#property indicator_label1  "Close"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlack
#property indicator_width1  1

#property indicator_label2  "Baseline Fast EMA"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrBlue
#property indicator_width2  1

#property indicator_label3  "Baseline Slow EMA"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrRed
#property indicator_width3  1

#property indicator_label4  "SMA Filtered"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrGreen
#property indicator_width4  1

#property indicator_label5  "EMA Filtered"
#property indicator_type5   DRAW_LINE
#property indicator_color5  clrDodgerBlue
#property indicator_width5  1

#property indicator_label6  "LRMA Filtered"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrOrange
#property indicator_width6  1

#property indicator_label7  "SSA Trend"
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrMagenta
#property indicator_width7  1

#property indicator_label8  "Spline Derivative (scaled)"
#property indicator_type8   DRAW_LINE
#property indicator_color8  clrBrown
#property indicator_width8  1

#property indicator_label9  "Baseline Crossover Signals"
#property indicator_type9   DRAW_ARROW
#property indicator_color9  clrBlue
#property indicator_width9  1

#property indicator_label10 "Filtered Crossover Signals"
#property indicator_type10  DRAW_ARROW
#property indicator_color10 clrRed
#property indicator_width10 1

プロット9と10は矢印であることに注目してください。これらを使って、売買シグナルをマークします。

ステップ3:入力パラメータ

ユーザーがすべての主要なパラメータを調整できるようにします。以下を定義します。

//--- input parameters
input int      FastMAPeriod   = 5;           // Fast MA period
input int      SlowMAPeriod   = 20;          // Slow MA period
input int      FilterWindow   = 14;          // Window for SMA/LRMA and EMA alpha (alpha=2/(period+1))
input int      SSAWindow       = 30;          // Window for SSA (must be < data length)
input int      SSARank         = 5;           // Number of SSA components to keep (rank)
input double   SplineDerivScale= 10.0;        // Scaling factor for spline derivative (for visibility)
input int      LookbackBars    = 500;         // Number of recent bars to process (speed optimization)

LookbackBarsパラメータは、パフォーマンスにおいて重要です。ALGLIBの処理、特にSSAは、数千本のバーに対して実行すると計算負荷が高くなる可能性があります。直近のバーのみ(デフォルトでは500本)を処理することで、十分な履歴を表示しながら、インジケータの応答性を維持します。各入力パラメータは、インジケータの特定の要素を制御します。ベースラインのクロスオーバーではFastMAPeriodとSlowMAPeriodを使用します。FilterWindowは、価格へ適用される単純移動平均、指数移動平均、線形回帰移動平均の期間を決定します。SSAWindowは、特異スペクトル解析における軌跡行列のウィンドウ長を設定します。SSARankは、再構築に用いる上位成分の数を指定します。SplineDerivScaleは、プロット時に微分値が表示可能になるよう、単純に微分値を倍率で拡大します。そしてLookbackBarsは、ALGLIBのルーチンへ渡されるバー数を制限します。

ステップ4:バッファ宣言

データを保持するために10個のバッファが必要です。

//--- indicator buffers
double closeBuffer[];
double baselineFastBuffer[];
double baselineSlowBuffer[];
double smaFilteredBuffer[];
double emaFilteredBuffer[];
double lrmaFilteredBuffer[];
double ssaTrendBuffer[];
double splineDerivBuffer[];
double baselineSignalBuffer[];
double filteredSignalBuffer[];

各バッファは、先に定義したプロットの1つに対応します。closeBufferは生の終値を保持します。baselineFastBufferとbaselineSlowBufferは、組み込み関数であるiMAによって値が入力されます。smaFilteredBuffer、emaFilteredBuffer、lrmaFilteredBufferは、それぞれ対応するALGLIBフィルタを適用した結果を保持します。ssaTrendBufferはSSAによって再構築されたトレンドを保存します。 splineDerivBufferは、価格にスケール調整された微分値を加えたものを保存します。そして、2つのシグナルバッファはクロスオーバー時の矢印位置を保持します。すべてのバッファは動的配列として宣言されており、サイズはターミナルによって管理されます。

ステップ5:組み込み移動平均のハンドル

ベースラインのクロスオーバーには、MQL5の組み込み関数であるiMAを使用します。これはすでに最適化されており、基準として機能します。

//--- handles for built-in iMA (for baseline)
int fastMAHandle, slowMAHandle;

iMAハンドルの使用は効率的です。これは、ターミナルが内部で移動平均を計算し、結果をキャッシュするためです。これらのハンドルはOnInit()で作成され、OnDeinit()で解放されます。

ステップ6:初期化関数

OnInit()では、バッファを設定し、矢印スタイルを構成し、MAハンドルを作成します。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- set index buffers
   SetIndexBuffer(0, closeBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, baselineFastBuffer, INDICATOR_DATA);
   SetIndexBuffer(2, baselineSlowBuffer, INDICATOR_DATA);
   SetIndexBuffer(3, smaFilteredBuffer, INDICATOR_DATA);
   SetIndexBuffer(4, emaFilteredBuffer, INDICATOR_DATA);
   SetIndexBuffer(5, lrmaFilteredBuffer, INDICATOR_DATA);
   SetIndexBuffer(6, ssaTrendBuffer, INDICATOR_DATA);
   SetIndexBuffer(7, splineDerivBuffer, INDICATOR_DATA);
   SetIndexBuffer(8, baselineSignalBuffer, INDICATOR_DATA);
   SetIndexBuffer(9, filteredSignalBuffer, INDICATOR_DATA);

   //--- set arrow codes (159 = small circle)
   PlotIndexSetInteger(8, PLOT_ARROW, 159);
   PlotIndexSetInteger(9, PLOT_ARROW, 159);
   PlotIndexSetDouble(8, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(9, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   //--- indicator short name
   IndicatorSetString(INDICATOR_SHORTNAME, "ALGLIB Filter Demo (Optimized)");

   //--- get handles for baseline MAs
   fastMAHandle = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   slowMAHandle = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   if(fastMAHandle == INVALID_HANDLE || slowMAHandle == INVALID_HANDLE)
      return(INIT_FAILED);

   return(INIT_SUCCEEDED);
}

矢印コード159を使用すると、小さな円が表示されます。シグナルバッファには空の値を設定し、明示的に値を設定した場合にのみプロットされるようにします。各バッファへINDICATOR_DATAを割り当てることで、これらの配列が表示対象となる値を保持していることをターミナルへ通知します。ベースラインとなる指数移動平均のハンドルは、ユーザーが選択した期間を使用し、現在のシンボルおよび時間足で作成されます。いずれかのハンドル作成に失敗した場合、インジケータの初期化は中止されます。

ステップ7:初期化解除

完了時には、必ずハンドルを解放します。

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(fastMAHandle != INVALID_HANDLE)  IndicatorRelease(fastMAHandle);
   if(slowMAHandle != INVALID_HANDLE)  IndicatorRelease(slowMAHandle);
}

ハンドルを解放することは、ターミナルによって割り当てられたリソースを解放するための良い習慣です。インジケータが削除された際には、ターミナルが自動的に処理をおこないますが、明示的に解放することで、コードの堅牢性と移植性が向上します。

ステップ8:フィルタ用ヘルパー関数

コードを整理して保守しやすくするために、MQL5ベクトルとALGLIBのCRowDouble間の変換をカプセル化するヘルパー関数を作成します。

//+------------------------------------------------------------------+
//| Helper to convert vector to CRowDouble and back after filter     |
//+------------------------------------------------------------------+
void ApplySMA(vector<double> &data, int window)
{
   CRowDouble row(data);
   CAlglib::FilterSMA(row, (int)data.Size(), window);
   data = row.ToVector();
}

void ApplyEMA(vector<double> &data, int window)
{
   double alpha = 2.0 / (window + 1.0);
   CRowDouble row(data);
   CAlglib::FilterEMA(row, (int)data.Size(), alpha);
   data = row.ToVector();
}

void ApplyLRMA(vector<double> &data, int window)
{
   CRowDouble row(data);
   CAlglib::FilterLRMA(row, (int)data.Size(), window);
   data = row.ToVector();
}

これらの関数は、参照渡しでベクトルを受け取り、それをCRowDoubleへ変換し、フィルタを適用した後、再びMQL5のvector型へ変換します。このパターンは重要です。なぜなら、ALGLIBはCRowDoubleオブジェクトを使用して動作しますが、私たちはMQL5のベクトル型の利便性を利用したいからです。これらの関数は元のベクトルをその場で変更するため、不要なコピーを避けることができ、メインコードの可読性も向上します。なお、EMAフィルタでは平滑化定数alphaが必要です。これは標準的な計算式:alpha = 2/(period+1)を使用してウィンドウ長から算出されます。

ステップ9:メイン計算ループ

ここからは、このインジケータの中心部分であるOnCalculate関数について説明します。ここで、ALGLIBの処理が実行されます。セクションごとに分けて説明します。

処理範囲の決定

まず、LookbackBarsに基づいて、どのバーを処理するかを決定します。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   //--- Determine the range of bars we need to process
   int startIdx = (rates_total > LookbackBars) ? rates_total - LookbackBars : 0;
   int len = rates_total - startIdx;

この簡潔な1行によって、開始インデックスが計算されます。LookbackBarsよりも多くのバーが存在する場合は、末尾からLookbackBars分だけ遡った位置から開始します。それ以外の場合は、バー0から開始します。これにより、ALGLIBのルーチンへ渡すデータ量を管理可能な範囲に制限できます。長さlenは、実際にフィルタ処理をおこなうバー数です。

データ検証

使用する最も長い期間に対して、十分な数のバーが存在することを確認する必要があります。

   //--- Need enough data for the longest period
   int maxPeriod = MathMax(SlowMAPeriod, MathMax(FilterWindow, SSAWindow));
   if(len < maxPeriod)
   {
      // Not enough bars in the lookback window – fill with EMPTY and return
      for(int i = startIdx; i < rates_total; i++)
      {
         smaFilteredBuffer[i] = EMPTY_VALUE;
         emaFilteredBuffer[i] = EMPTY_VALUE;
         lrmaFilteredBuffer[i] = EMPTY_VALUE;
         ssaTrendBuffer[i]    = EMPTY_VALUE;
         splineDerivBuffer[i] = EMPTY_VALUE;
      }
      ArrayInitialize(baselineSignalBuffer, EMPTY_VALUE);
      ArrayInitialize(filteredSignalBuffer, EMPTY_VALUE);
      return(rates_total);
   }

ルックバックウィンドウが短すぎる場合、すべてを空の値で埋めて終了します。これにより、インジケータが不完全または誤ったデータを表示することを防ぎます。maxPeriodは、3つの主要な期間(長期MA、フィルタウィンドウ、SSAウィンドウ)の中から最大の値を取得し、すべてのフィルタが意味のある出力を生成するために十分なデータを持つことを保証します。

基本データのコピー

終値を最初のバッファ(表示用)へコピーし、ベースラインとなる移動平均を取得します。

   //--- Copy close prices to buffer 0 (for plotting)
   for(int i = 0; i < rates_total; i++)
      closeBuffer[i] = close[i];

   //--- Get baseline MAs (they already cover all bars)
   if(CopyBuffer(fastMAHandle, 0, 0, rates_total, baselineFastBuffer) <= 0) return(0);
   if(CopyBuffer(slowMAHandle, 0, 0, rates_total, baselineSlowBuffer) <= 0) return(0);

終値は、最初のバッファにそのまま反映されます。2つのベースラインEMAバッファは、iMAハンドルからコピーすることで入力されます。これらのハンドルにはすべてのバーに対する値が含まれているため、対象範囲全体をコピーします。コピーに失敗した場合(戻り値が<= 0の場合)、早期終了します。

データベクトルの準備

処理対象となる価格のみを含むベクトルを作成します。

   //--- Create a vector of close prices for the lookback window
   vector<double> vecClose(len);
   for(int i = 0; i < len; i++)
      vecClose[i] = close[startIdx + i];

このベクトルvecCloseには、すべてのALGLIBフィルタへ渡される価格データが格納されます。価格配列の連続した範囲に対して処理をおこなうことで、大きな配列を渡すことを避け、メモリ使用量と計算負荷を削減します。

基本フィルタの適用

ここで、ヘルパー関数を使用して各フィルタを適用します。

   //--- 1) SMA Filter
   vector<double> vecSMA = vecClose;
   ApplySMA(vecSMA, FilterWindow);
   for(int i = 0; i < len; i++)
      smaFilteredBuffer[startIdx + i] = vecSMA[i];

   //--- 2) EMA Filter
   vector<double> vecEMA = vecClose;
   ApplyEMA(vecEMA, FilterWindow);
   for(int i = 0; i < len; i++)
      emaFilteredBuffer[startIdx + i] = vecEMA[i];

   //--- 3) LRMA Filter
   vector<double> vecLRMA = vecClose;
   ApplyLRMA(vecLRMA, FilterWindow);
   for(int i = 0; i < len; i++)
      lrmaFilteredBuffer[startIdx + i] = vecLRMA[i];

各フィルタが、それぞれ独自のデータコピーを変更している点に注目してください。元のvecCloseは、その後の処理のために変更されずに保持されます。フィルタ処理された結果は、適切なインデックス(startIdx + i)の対応するバッファへ配置されます。SMAは、ウィンドウ内のすべてのポイントに同じ重みを与えます。EMAは、直近の価格により大きな重みを与えます。LRMAは、データへ直線を適合させ、その終点をフィルタ後の値として使用します。これら3つのフィルタは一般的な手法であり、より高度なSSAやスプライン手法と比較するための基準として機能します。

特異スペクトル分析

ここが最も高度な部分です。SSAモデルを作成し、データを追加し、設定し、トレンドを抽出します。

   //--- 4) SSA Trend
   CSSAModel ssa;
   CAlglib::SSACreate(ssa);
   CRowDouble priceRow(vecClose);                     // convert once
   CAlglib::SSAAddSequence(ssa, priceRow);
   CAlglib::SSASetAlgoTopKRealtime(ssa, SSARank);
   CAlglib::SSASetWindow(ssa, SSAWindow);
   CRowDouble trend, noise;
   CAlglib::SSAAnalyzeLast(ssa, len, trend, noise);

   if(trend.Size() == len)
   {
      vector<double> vecTrend = trend.ToVector();
      for(int i = 0; i < len; i++)
         ssaTrendBuffer[startIdx + i] = vecTrend[i];
   }
   else
   {
      for(int i = 0; i < len; i++)
         ssaTrendBuffer[startIdx + i] = EMPTY_VALUE;
   }

重要な行は SSASetAlgoTopKRealtime(ssa, SSARank) です。これは、再構築に使用する上位SSARank個の成分(最大の固有値)をSSAへ指定します。最大の成分のみを保持することで、実質的にノイズを除去しています。ウィンドウサイズSSAWindowは、軌跡行列を構築する際に考慮される過去のポイント数を決定します。より大きなウィンドウでは長期的なパターンを捉えることができますが、より多くのデータが必要になります。分析後、トレンドが期待される長さになっているかを確認します。SSAが失敗した場合(データ不足や数値的な問題によって発生する可能性があります)、空の値で埋めます。生成されたトレンドラインは、単純移動平均のどれよりも滑らかになり、潜在的な周期的要素を明らかにすることができます。

スプライン微分計算

スプライン微分を計算するためには、バーのインデックスを表すx軸が必要です。

   //--- 5) Spline derivative
   // X axis = bar index within the lookback window (0..len-1)
   vector<double> xVec(len);
   for(int i = 0; i < len; i++) xVec[i] = i;

   vector<double> priceVec = priceRow.ToVector();   // already from vecClose

   double xArr[], priceArr[];
   ArrayResize(xArr, len);
   ArrayResize(priceArr, len);
   for(int i = 0; i < len; i++)
   {
      xArr[i] = xVec[i];
      priceArr[i] = priceVec[i];
   }

   CSpline1DInterpolantShell spline;
   CAlglib::Spline1DBuildCubic(xArr, priceArr, len, 0, 0.0, 0, 0.0, spline);

ベクトルを単純な配列へ変換します。これはSpline1DBuildCubicがdouble型のパラメータを必要とするためです。境界条件パラメータ(0, 0.0, 0, 0.0)は、端点で2次微分がゼロになる自然3次スプラインを指定します。これは、ほとんどの価格系列で適切に機能する標準的な選択です。スプライン補間関数は、その後splineへ保存されます。

次に、各ポイントにおける微分を計算します。

   for(int i = 0; i < len; i++)
   {
      double val, deriv, deriv2;
      CAlglib::Spline1DDiff(spline, i, val, deriv, deriv2);
      splineDerivBuffer[startIdx + i] = close[startIdx + i] + deriv * SplineDerivScale;
   }

スケール調整された微分値を実際の価格へ加えることで、ラインが価格水準付近に表示されるようにします。この調整をおこなわない場合、微分値はほぼゼロ付近となり、視認することが難しくなります。微分値は、平滑化された曲線の変化率を示します。正の値は上昇方向の勢いを示し、負の値は下降方向の勢いを示します。スケーリング係数によって、これらの変動が価格の周辺で振動するラインとして視覚的に確認できるようになります。

クロスオーバーシグナル検出

ここで、取引シグナルを生成します。まず、シグナルバッファを空の値で初期化します。

   //--- 6) Crossover signals (only within lookback window)
   ArrayInitialize(baselineSignalBuffer, EMPTY_VALUE);
   ArrayInitialize(filteredSignalBuffer, EMPTY_VALUE);

フィルタ済みクロスオーバーでは、EMAフィルタ済み系列(vecEMA)に対する短期および長期の指数移動平均が必要です。これらをベクトルへ再帰的に計算します。

   double alphaFast = 2.0 / (FastMAPeriod + 1.0);
   double alphaSlow = 2.0 / (SlowMAPeriod + 1.0);

   vector<double> fastFilteredVec(len);
   vector<double> slowFilteredVec(len);

   if(len > 0)
   {
      fastFilteredVec[0] = vecEMA[0];
      slowFilteredVec[0] = vecEMA[0];
   }
   for(int i = 1; i < len; i++)
   {
      fastFilteredVec[i] = alphaFast * vecEMA[i] + (1 - alphaFast) * fastFilteredVec[i-1];
      slowFilteredVec[i] = alphaSlow * vecEMA[i] + (1 - alphaSlow) * slowFilteredVec[i-1];
   }

この再帰的な計算式は、標準的なEMA計算です。系列全体を事前に計算しておくことで、各ティックごとに再計算する必要がなくなり、コードもより整理されたものになります。ここでは、ベースラインと同じ短期および長期の期間を使用していますが、適用対象は生の終値ではなく、EMAフィルタ済み価格(vecEMA)です。これにより、元となる系列がすでに平滑化されているため、ノイズの少ないクロスオーバーシグナルが生成される可能性があります。

次に、クロスオーバーを検出します。

   for(int i = 1; i < len; i++)
   {
      int idx = startIdx + i;

      // Baseline crossover signals (using the built‑in MA buffers)
      if(baselineFastBuffer[idx] > baselineSlowBuffer[idx] && baselineFastBuffer[idx-1] <= baselineSlowBuffer[idx-1])
         baselineSignalBuffer[idx] = low[idx] - 10 * _Point; // buy arrow below price
      if(baselineFastBuffer[idx] < baselineSlowBuffer[idx] && baselineFastBuffer[idx-1] >= baselineSlowBuffer[idx-1])
         baselineSignalBuffer[idx] = high[idx] + 10 * _Point; // sell arrow above price

      // Filtered crossover signals
      if(fastFilteredVec[i] > slowFilteredVec[i] && fastFilteredVec[i-1] <= slowFilteredVec[i-1])
         filteredSignalBuffer[idx] = low[idx] - 20 * _Point; // buy
      if(fastFilteredVec[i] < slowFilteredVec[i] && fastFilteredVec[i-1] >= slowFilteredVec[i-1])
         filteredSignalBuffer[idx] = high[idx] + 20 * _Point; // sell
   }

買いの場合、矢印はバーの安値の下に配置します。売りの場合は、高値の上に配置します。フィルタ済みの矢印は20ポイント離して配置します(ベースラインでは10ポイント)。これにより、視覚的に区別しやすくなります。条件判定では、短期および長期のラインの現在値および前回値を比較することで、クロスオーバーを検出します。この標準的なロジックにより、短期ラインが長期ラインをクロスした瞬間に正確にシグナルが生成されます。

クリーンアップ

最後に、ルックバックウィンドウより前のすべてのバーについて、フィルタ済みラインを空の値で埋めます。

   //--- Fill any bars before the lookback window with EMPTY_VALUE for the filtered lines
   for(int i = 0; i < startIdx; i++)
   {
      smaFilteredBuffer[i] = EMPTY_VALUE;
      emaFilteredBuffer[i] = EMPTY_VALUE;
      lrmaFilteredBuffer[i] = EMPTY_VALUE;
      ssaTrendBuffer[i]    = EMPTY_VALUE;
      splineDerivBuffer[i] = EMPTY_VALUE;
   }

   //--- return value of prev_calculated for next call
   return(rates_total);
}

これで完了です。インジケータが完成しました。

MetaEditorでコンパイルした後、インジケータはエラーなく読み込まれます。デフォルトパラメータを使用してUSDJPYのM15へ適用すると、LookbackBarsの最適化により初期化は効率的に実行されます。チャート上には、生の終値(黒)、2つのベースラインEMA(青と赤)、そして5つのフィルタ済みライン(緑、ドジャーブルー、オレンジ、マゼンタ、茶色)が表示されます。青と赤の矢印はベースラインEMAからのクロスオーバーを示し、赤い矢印(より離れた位置に配置)はフィルタ済みEMAからのクロスオーバーを示します。2種類のシグナルを比較することで、追加の平滑化が過剰なシグナルの削減に役立つのか、または遅延を大きく発生させるのかを判断できます。

実装内容は以下のとおりです。

  • ベースラインEMAクロスオーバーバッファでは、ターミナルによって計算される標準的な短期および長期EMAを使用し、一般的な基準を提供します。
  • フィルタ済みシリーズバッファ(SMA、EMA、LRMA)はALGLIBを介して適用される3つの古典的な移動平均線で、それぞれがどのように価格を平滑化するかを示します。
  • SSAトレンド再構築では、系列をトレンドとノイズへ分解する最新のデータ適応型手法を使用し、隠れたパターンを明らかにします。
  • スプライン微分計算では、平滑化された価格の変化率を連続的に測定し、視認性を高めるためにスケール調整します。
  • シグナル可視化ロジックでは、2種類のクロスオーバーシグナル(ベースラインおよびフィルタ済み)を矢印として表示し、取引タイミングと頻度を直接比較できるようにします。

パフォーマンス最適化では、再計算を最小限に抑え、SSAおよびスプライン計算を直近のバーに限定することへ重点を置いています。LookbackBarsパラメータによって、速度と履歴の深さのバランスを調整できます。これらの構成要素を使用することで、異なるフィルタパラメータを試すことができ、さらにEMAフィルタ済みクロスオーバーを他のフィルタ済み系列へ置き換えて、それらがシグナル生成へどのような影響を与えるかを検証できます。


テスト

インジケータのコンパイルが成功したので、実際に動作させてみましょう。このプロセスが初めての場合でも心配する必要はありません。一緒にすべての手順を確認していきます。ここでの目的は収益性を評価することではありません(覚えておいてください。これは取引ロボットではなく、視覚的なツールです)。目的は、数学的前処理によって移動平均クロスオーバーの動作がどのように変化するのか、特に横ばい相場でどのように変わるのかを観察することです。

ステップ1:チャートにインジケータを表示する

MetaEditorでコードをコンパイルした後、インジケータALGLIB_FilterDemo.ex5はナビゲータウィンドウ(表示 → ナビゲータ、またはCtrl+N)の「インジケータ」フォルダ内に表示されます。表示されない場合は、ナビゲータ上で右クリックし、[更新]を選択してください。

チャートへ適用する方法:

  • 新しいチャートを開きます。ここでは例としてUSDJPY、M15(15分足)を使用します。
  • ナビゲータからインジケータをチャートへドラッグするか、ダブルクリックします。
  • インジケータの入力パラメータウィンドウが表示されます。今回は、すべてデフォルト値のままにします。
    • 短期MA期間:5
    • 長期MA期間:20
    • フィルタウィンドウ:14
    • SSAウィンドウ:30
    • SSAランク:5
    • スプライン微分スケール:10.0
    • ルックバックバー:500
  • [OK]をクリックします。

インジケータはすぐにチャート上へ複数のラインを描画します。最初は表示が複雑に見えるかもしれませんが、それぞれのラインには役割があり、ここで確認していきます。

ステップ2:インジケータの動作を確認する(ライブチャート)

少し時間を取って、チャート上に表示される内容を確認してください。以下が表示されるはずです。

  • 黒線:生の終値(基準となる価格)
  • 青と赤の線:ベースラインとなる短期(5)および長期(20)の指数移動平均。いわゆる、よく知られた定番のクロスオーバーです。
  • 緑の線:ALGLIBによる単純移動平均(SMA)でフィルタ済みの価格
  • ドジャースブルー色の線:ALGLIBを用い指数移動平均(EMA)でフィルタ処理した価格
  • 橙色の線:ALGLIBを用いてLRMAでフィルタ処理した価格
  • マゼンタの線:特異スペクトル分析(SSA)によって抽出されたトレンド成分
  • 茶色の線:スプライン微分・これは平滑化された価格の変化率を示し、チャート上で確認できるようにスケール調整され、価格へ加算されています。
  • 青い矢印:ベースラインEMAによるクロスオーバーシグナル(買いはバーの下、売りはバーの上に配置)
  • 赤い矢印:フィルタ済みEMAによるクロスオーバーシグナル(区別しやすいよう、少し離れた位置に配置)。

次に、明確なトレンドがなく価格が横方向へ動いているレンジ部分を確認してください。以下の動きを観察します。

  • ベースラインEMA(青と赤)は頻繁に上下へクロスし、そのたびに青い矢印が表示されます。レンジ相場では、短時間のうちにこうした矢印が何度も現れることがあります。これが、先ほど説明した「過剰なシグナル」の問題です。
  • 次に、フィルタ済みラインを確認します。緑(SMA)、ドジャーブルー(EMA)、オレンジ(LRMA)のラインは、生の価格よりも滑らかになっていることが分かります。これらは小さな価格変動すべてには反応しません。
  • 特に赤い矢印(フィルタ済みクロスオーバー)に注目してください。同じ横ばい区間で数えてみると、青い矢印よりも赤い矢印の数が少ないことが確認できるでしょう。前処理によって、多くの偽シグナルが除去されています。
  • マゼンタのSSAラインは、多くの場合、最も滑らかなラインになります。横ばい期間ではほとんど動かず、クロスオーバーをほとんど発生させない場合があります。
  • 茶色のスプライン微分ラインは、価格周辺で振動します。これが上下にクロスすると、モメンタム変化を示します。実際のクロスオーバーが発生する前に反応しているように見える場合があります。これは、微分が傾きの変化を早期に検出しているためです。

ライブチャート

図1:USDJPY M15ライブチャート

ステップ3:ストラテジーテスタ(ビジュアルモード)でのテスト

ライブチャートでは最近の動きを確認できます。しかし、数か月間のような長期間の履歴データでインジケータの動作を確認したい場合はどうでしょうか。その場合、ストラテジーテスタを使用します。EAをテストしているわけではありませんが、ビジュアルモードでインジケータを実行し、異なる市場環境でのシグナルを観察できます。

設定方法は以下のとおりです。

  • ストラテジーテスタを開きます(表示 → ストラテジーテスタ、またはCtrl+R)。
  • テスタウィンドウで:
    • EA:空欄のままにします(EAは実行していません)。
    • 銘柄:USD/JPY(またはお好みの通貨ペア)
    • 期間:M15
    • モデル:「ティックごと」または「1分足OHLC」を選択します(どちらも視覚的な観察に適しています)。
    • 日付範囲:トレンド相場と横ばい相場の両方を含む期間を選択します。例として、過去6〜12か月など。
    • ビジュアルモード:チェックを入れます。これは重要です。テスタ実行中にインジケータの描画をリアルタイムで確認できます。
  • [開始]をクリックします。テスタが開始され、インジケータがティックごと、またはバーごとに適用されるチャートウィンドウが開きます。

実行中は以下を確認してください。

  • 強いトレンド期間では、ベースライン矢印(青)とフィルタ済み矢印(赤)は同じ方向に表示されます。フィルタ済み矢印は多少遅れて表示される場合があります(滑らかさの代償として遅延が発生します)が、基本的にはトレンド方向と一致します。
  • 横ばいや不安定な期間では違いを確認してください。青い矢印は短期EMAがあらゆる小さな価格変動を追うため、上下方向に繰り返し発生します。一方、赤い矢印は大幅に少なくなります。横ばい区間によっては、赤い矢印がまったく表示されない場合もあります。これがフィルタ処理の効果です。
  • SSAライン(マゼンタ)の動作にも注目してください。横ばい期間では、ほぼ水平になることが多く、クロスオーバーを生成しません。いわばノイズに付き合わず、様子見しているような状態です。
  • スプライン微分(茶色)は横ばい市場でも動きを示す場合があります。ただし、これをスケール調整して価格へ加算しているため、価格水準周辺の振動として確認できます。SplineDerivScaleパラメータを変更することで、表示を強めたり弱めたりできます。

図2:ストラテジーテスタのビジュアルモード

ステップ4:パラメータ調整

学習する最も良い方法の1つは、入力値を変更して、その効果をすぐに確認することです。チャートからインジケータを削除し(右クリック → インジケータ → 選択 → 削除)、再度追加して、一度に1つのパラメータだけ変更してください。そして変化を観察します。

  • FilterWindowを増加(例:14から30):すべてのフィルタ済みライン(緑、ドジャーブルー、オレンジ)がより滑らかになります。価格変化への反応は遅くなります。クロスオーバーシグナル(赤い矢印)は少なくなりますが、遅延が増えます。これは典型的なトレードオフです。滑らかさと反応性のバランスです。
  • FilterWindowを減少(例:5):フィルタ済みラインは生の価格へより近づきます。赤い矢印が増えます。一部は青い矢印とほぼ同時に表示される場合があります。フィルタ効果は弱まります。
  • SSAWindowを調整(20、50などを試す):マゼンタのSSAラインの滑らかさが変化します。小さいウィンドウでは反応性が高くなります(ただし生価格よりは滑らかです)。大きいウィンドウでは非常にゆっくり動くラインとなり、長期的な動きのみを捉えます。ウィンドウを大きくしすぎると、チャート左側でラインが消える場合があります。SSAには計算に十分なデータが必要です。
  • SSARankを変更(3、8などを試す):これは再構築時に保持する成分数を制御します。低いランク=より滑らか(より強いフィルタ処理)。高いランク=より多くの詳細を保持(元の価格に近い)。M15のFXデータでは、4〜7程度の値が適している場合が多いです。
  • SplineDerivScaleを変更:大きくすると(例:20)、茶色ラインの振動が大きくなります。確認しやすくなりますが、価格から大きく離れる場合があります。小さくすると(例:2)、価格へ近づき、微分ゼロクロスの確認が難しくなります。視認できるが邪魔にならない値を見つけてください。

この実践的な試行を通じて、それぞれの数学的手法がデータへどのような影響を与えるのか直感的に理解できるようになります。あなたはもうALGLIBについて単に読んでいるだけではありません。自分自身のチャート上で、リアルタイムにその動作を確認しているのです。


結論

これまで、ライブチャート上で、そしてストラテジーテスタによる過去データ上でインジケータの動作を確認してきました。ここで、一緒に達成したことを振り返ってみましょう。

私たちは、フィルタ済み移動平均クロスオーバーを横並びで視覚的に比較できる、実用的なインジケータを完成させました。チャート上では、ベースラインEMAがフィルタ済みEMAと比較してどのように動作するのかを明確に確認できます。横ばい市場では、フィルタ済みクロスオーバーの発生頻度が低下しています。これは、数学的前処理によって実現したかった結果そのものです。

私たちは、パラメータを調整することで、それぞれのフィルタの動作を確認できることを学びました。FilterWindow、SSAWindow、SSARank、SplineDerivScaleを変更することで、滑らかさ、遅延、シグナル頻度への影響をすぐに確認できます。このような実践的な探索によって、単に理論を読むだけでは得られない直感的な理解を深めることができます。

私たちは、数学を取引へ応用する具体的な例を確認しました。SMA、EMA、LRMAフィルタでは、馴染みのある平滑化の概念を確認しました。特異スペクトル解析(SSA)では、データ適応型の分解によって潜在的なトレンドを抽出する方法を確認しました。スプライン微分では、変化率を価格チャート上へ直接視覚化する方法を確認しました。これらを実現するために、数学者になる必要はありませんでした。ALGLIBが複雑な計算を処理し、私たちは結果の解釈に集中することができました。

単純な移動平均クロスオーバーから始まり、私たちは複数のフィルタを備えた分析プラットフォームを構築しました。元の戦略自体は変わっていません。依然として、2つの移動平均がクロスするだけの仕組みです。しかし、それへ入力されるデータは変換されています。これが今回一緒に探求してきた中心的な考え方です。前処理は重要です。同じ取引ロジックであっても、異なるフィルタ処理を施したデータへ適用すると、異なるシグナルが生成されます。そして今、私たちはその違いを実際に探索できるツールを手にしています。

コードのモジュール構造により、以下のような拡張が可能です。

  • EMAフィルタ済みクロスオーバーを、他のフィルタ済み系列(SMAフィルタ、LRMAフィルタ、SSAトレンドなど)によるクロスオーバーへ置き換え、それらを比較できます。
  • フィルタ済みシグナルを抽出し、クロスオーバー発生時にアラートや通知を生成できます。
  • ここで紹介した前処理技術をEAの基盤として使用し、フィルタ済みクロスオーバーロジックを自動化してバックテストやフォワードテストを実行できます。
  • まったく異なるALGLIB関数、例えば最適化ルーチン、クラスタリングアルゴリズム、高度な統計テストなどを試し、それらを自分たちの取引アイデアへ適用できます。

この記事を通して、 ALGLIBがMQL5を単なる組み込みインジケータに限定されたプラットフォームから、本格的な数値解析研究環境へ変えることを確認しました。よりクリーンなシグナルを求めるトレーダー、分析ツールを構築する開発者、あるいは応用数学に興味を持つ人にとって、ALGLIBはこれまで利用できなかった可能性への扉を開きます。

以下の添付ファイルからインジケータをダウンロードしてください。お気に入りの通貨ペアや時間足へ適用してみてください。パラメータをいろいろいじってみてください。壊して、直して、改良してみてください。視覚的なフィードバックを、自分自身の理解を深めるための手掛かりとして利用してください。そして準備ができたら、次のステップへ進みましょう。そのロボットを作り、その最適化を実行し、新しいフィルタを探索してください。

ディスカッションで、皆さんの観察、質問、実験結果をぜひ共有してください。コードは自由に使用し、変更し、学習するために利用できます。皆さんの貢献が、これらのアイデアをさらに発展させ、コミュニティ全体を強化していきます。

皆さん、コーディングを楽しんでください。


添付ファイル

ソースファイル名 種別 バージョン  説明
ALGLIB_FilterDemo インジケータ  1.00 MetaTrader 5用の標準ALGLIBライブラリを使用した、高度なトレンドフィルタ処理手法を紹介するデモンストレーションインジケータです。従来のEMAクロスオーバーと線形回帰移動平均(LRMA)クロスオーバーを比較し、さらに微分値に基づく傾きの可視化を備えた3次スプラインを組み込んでいます。これにより、トレンド検出を改善し、レンジ相場におけるダマシシグナルを低減します。

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

添付されたファイル |
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
ダイナミックマルチペアEAの形成(第7回):リアルタイムトレードフィルタリングのためのクロスペア相関マッピング ダイナミックマルチペアEAの形成(第7回):リアルタイムトレードフィルタリングのためのクロスペア相関マッピング
このパートでは、複数銘柄型エキスパートアドバイザー(EA)にリアルタイム相関行列を統合し、冗長な取引やリスクが積み重なるポジションを防ぐ仕組みを構築します。複数ペア間の相互関係を動的に測定することで、EAは既存エクスポージャーと競合するエントリーをフィルタリングし、ポートフォリオ全体のバランスを改善します。その結果、システミックリスクを低減し、より質の高いトレード判断を実現します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
プライスアクション分析ツールキットの開発(第63回):MQL5での上昇・下降ウェッジ検出の自動化 プライスアクション分析ツールキットの開発(第63回):MQL5での上昇・下降ウェッジ検出の自動化
連載「プライスアクション分析ツールキットの開発」の今回の記事では、上昇ウェッジおよび下降ウェッジパターンをリアルタイムで自動検出するMQL5インジケーターを開発します。このシステムは、ピボット構造を確認し、境界線の収束を数学的に検証し、パターン同士の重複を防止しながら、ブレイクアウトおよび失敗条件を正確な視覚的フィードバックとともに監視します。クリーンなオブジェクト指向アーキテクチャを使用して構築されたこの実装は、主観的なウェッジ認識を、規律あるプライスアクション分析を強化するために設計された、状態を認識して扱える構造化分析コンポーネントへと変換します。