English Русский Deutsch
preview
MetaTrader 5でL1トレンドフィルタリングを適用する

MetaTrader 5でL1トレンドフィルタリングを適用する

MetaTrader 5 |
87 8
MetaQuotes
MetaQuotes
L1トレンドフィルタリングの主な目的は、時系列データから潜在的なトレンドを以下のような形で抽出することです。
  • データの長期的ダイナミクスを維持する
  • 短期的な変動およびノイズを抑制する
  • トレンドの構造的な変化点(傾きの変化)を自動的に検出する
従来の平滑化手法とは異なり、本手法はトレンドに過度な平滑性を課しません。その代わりに、区分線形近似を生成する点が特徴であり、これは金融時系列分析において特に重要です。

L1トレンドフィルタリング



内容


はじめに

金融時系列は、高いノイズレベル、頻繁な外れ値、そして変化する市場レジームによって特徴づけられます。実務的なトレードシステムにおいて、これは次のように明確かつ定量的に現れます。すなわち、従来の「平滑化」フィルタ(移動平均、HPフィルタなど)は遅延が大きく、傾き変化のタイミングを曖昧にし、局所的な調整を反転シグナルとして誤認することが多く、その結果として誤ったエントリーやエグジットが増加し、プロフィットファクターの低下やドローダウンの増大を招きます。さらに、正則化パラメータλの選択は通常、手作業によるチューニングに依存しており、銘柄、時間軸、ヒストリ長に対して十分に汎用化されません。

本記事では、これらの問題に対する実務的解としてL1トレンドフィルタリングを提案します。すなわち、二階差分に対するL1正則化を用いた最適化により、明示的なブレークポイント(変化点)を持つ区分線形近似が自動的に得られます。本手法の主要な利点は、ブレークポイントをレジーム変化として明確に解釈できる点、また、λmaxを基準として正則化スケールを定義でき、相対形式のパラメータλ = coef · λmaxによって統一的に調整できる点にあります。さらに、MQL5での実装に適した線形計算量であることも重要な特徴です。

理論説明に加え、実務へ直接適用できる完全なワークフローも提示します。すなわち、λmaxおよびL1トレンドの計算手法、トレンド・傾き・傾き符号の3種類のインジケータ、7種類のL1トレンドベースのボラティリティ指標、エキスパートアドバイザー(EA)への統合方法、そして4種類のフィルタリングモード、バランス/エクイティ出力、可視化を含む再現可能なテストプロトコルを提示します。


1. トレンドフィルタリング問題の定式化

スカラー時系列が次の2つの成分の和として表されると考えます。

 

ここで、はトレンド成分であり、はノイズまたは不規則成分です。

目的は、観測データからトレンドを推定することです。

この問題は、元のデータへの適合度と推定トレンドの滑らかさの間のトレードオフとして表現できます。


1.1. ホドリック=プレスコット・フィルタ

ホドリック=プレスコット・フィルタは、トレンドを次の最小化問題の解として定義します。

ここで、パラメータλは平滑化の度合いを制御します。

HPフィルタの主な特性は以下の通りです。

  • データに対する線形性
  • O(n)計算量
  • λが小さい場合、トレンドは元データに近づく
  • λが大きい場合、トレンドは最良の線形近似に収束する

ただしHPフィルタは常に滑らかなトレンドを生成し、急激な傾き変化の検出が苦手です。


1.2. L1トレンドフィルタリング法

L1トレンドフィルタリングの基本的な考え方は、元データに近いトレンドを推定しつつ、傾きの変化回数をできるだけ少なくすることです。二乗誤差に基づく従来の平滑化手法が曲率の二乗を最小化するのに対し、L1アプローチでは二階差分の絶対値の総和を最小化します。

これにより、従来法とは大きく異なる結果になります。

  • ほとんどの二階差分がゼロになる
  • トレンドが自動的に線形セグメントに分割される

そのためL1フィルタはトレンドを滑らかにすることを目的とせず、観測データを説明するために必要な構造変化の数を最小化します。この特性は、金融時系列に特に適しています。金融時系列は、準線形的な上昇局面や下降局面が連続する形を取ることが多いためです。

L1トレンドフィルタリングでは、二階差分に対するL1ノルムを用いる最適化問題として定義され、凸最適化問題として表現されます。


行列表記では次のようになります。


ここで

  • yは入力時系列
  • xは推定トレンド
  • Dは二階差分行列
  • λ (≥0)は正則化パラメータ

L1ノルムを用いることで、多くの二階差分がゼロになります。これはトレンドが区分線形になることを意味します。

二階差分は次のように定義されます。

もしであれば、点は直線上にあります。

したがって、二階差分がゼロであることは線形セグメントを意味し、非ゼロであることはブレークポイントを意味します。L1ノルムはDxのスパース性を促進し、ほとんどの二階差分をゼロにします。結果として、対応する区間ではトレンドは線形になります。非ゼロの二階差分はブレークポイントとして解釈されます。

このようにして、L1トレンドフィルタリングは、構造変化点で接続された線形セグメントとしてトレンドを自動的に構築します。

L1トレンドフィルタリングの主な特性は以下の通りです。

    • トレンドは線形セグメントで構成される
    • ブレークポイントは構造変化として解釈される
    • λ = 0の場合、トレンドは元データと一致する
    • 十分に大きいλでは、トレンドは厳密に最良の線形近似になる
    • 計算量は観測数に対して線形である


    1.3. 正則化パラメータλの役割

    パラメータλは、近似精度とトレンドの複雑さのトレードオフを制御します。

    λの値 解の性質
    λ=0
    x=y、平滑化なし
    小さいλ
    弱い平滑化、多数のブレークポイント
    中程度のλ
    区分線形トレンド
    大きいλ
    ほぼ線形トレンド
    λ≥λmax​
    厳密に線形トレンド

    表1:L1トレンドにおけるλ依存性

    したがって、λはブレークポイントの数と位置を制御します。


    1.4. 問題の幾何学的解釈

    推定するトレンドxはn次元空間内の点として解釈できます。目的関数の第1項(データへの適合度)は、観測点yを中心とするユークリッド球を定義します。つまり、xがyに近いほど誤差は小さくなります。

    一方でL1ノルムによる正則化項は凸多面体を定義します。L2正則化で現れる滑らかな楕円体とは異なり、この多面体は鋭い頂点を持ちます。これらの頂点は、一部の二階差分がゼロになる状態に対応します。

    L1ノルムの角構造によりスパース解が誘導され、最適解はしばしば制約が一部のみ有効となる頂点に位置します。これは多くの二階差分がゼロとなり、トレンドが区分線形になることを意味します。

    最適解は、ユークリッド球とL1多面体が最初に接触する点として得られます。この点においてトレンドは、限られたブレークポイントで接続された線形セグメントとなります。

    パラメータλmaxは、ユークリッド球がL1多面体に対して頂点ではなく、線形関数で構成される部分空間上で接触する場合に対応します。このときすべての二階差分がゼロとなり、トレンドは厳密に線形になります。

    λ ≥ λmaxの場合、それ以上正則化を増やしても解は変化せず、トレンドは線形のままになります。

    1.5. λmaxの計算アルゴリズム

    入力ベクトルy(長さN)に対する最大正則化パラメータλmaxの計算を説明します。

    1. サイズ(N−2)×Nの二階差分行列Dを構築します。


    2. 曲率ベクトルDyを計算します。

    3. 次の線形方程式系を解きます。

    4. ベクトルvの要素のうち、絶対値最大の値を取ります。

    金融時系列データにおいて、パラメータλmaxは重要な実務的意味を持ちます。

    • 正則化パラメータの正規化を可能にする
    • データスケールに依存しないλの選択を可能にする
    • 異なる時系列間の比較を容易にする
    • λを最大正則化の比率として解釈できるようにする

    したがって、λ=coef_lambda_max⋅λmax⁡⁡ (coef_lambda_max ∈ (0,1))の形式の相対パラメータを用いることで実運用しやすくなります。

    以降のインジケータおよびEAでは、λはλmax基準で扱い、パラメータは係数coef_lambda_maxにより指定します。


    2. L1トレンドを計算するためのMQL5メソッド

    L1トレンドフィルタリングを実務で使用するために、double型およびfloat型ベクトルに対して2つのメソッドが実装されています。

    • L1TrendFilterLambdaMaxは最大正則化パラメータを計算する
    • L1TrendFilterは、正則化パラメータλを与えてL1トレンドを計算する(λはλmax単位でも指定可能)

    2.1. L1TrendFilterLambdaMax

    データベクトルに対する最大正則化パラメータλmaxを計算するメソッドです。

    vector<double>の場合:

    bool  vector::L1TrendFilterLambdaMax(
       double          &lambda_max       // the maximum value of the regularization parameter lambda
       )
    vector<float>の場合:
    bool  vectorf::L1TrendFilterLambdaMax(
       float           &lambda_max       // the maximum value of the regularization parameter lambda
       );

    パラメータ

    lambda

    [out] 正則化パラメータλmaxの値。エラーの場合は-1が返されます。

    戻り値

    成功した場合trueを返します。

    注意

    メモリ消費量はベクトルサイズに対して線形に増加します。


    2.2. L1TrendFilter

    データベクトルに対するL1トレンドを計算するメソッドです。

    vector<double>の場合:

    bool  vector::L1TrendFilter(
       double          lambda,         // regularization parameter
       bool            relative,       // flag indicating lambda is in λmax units
       vector&         result          // output vector with L1 filtering result
       );

    vector<float>の場合:

    bool  vectorf::L1TrendFilter(
       float           lambda,         // regularization parameter
       bool            relative,       // flag indicating lambda is in λmax units
       vectorf&        result          // output vector with L1 filtering result
       );

    パラメータ

    lambda

    [in] 正則化パラメータλの値です。relative = trueの場合、λは[0, 1]の範囲でλmaxに対する比率として指定されます。

    relative

    [in] λの指定方法を示すフラグです。trueの場合、λはλmax単位で指定されます。falseの場合、絶対値として指定されます。

    result

    [out] L1フィルタリング結果を格納するベクトルです。

    戻り値

    成功した場合trueを返します。

    注意

    メモリ消費量はベクトルサイズに対して線形に増加します。


    以下は、推奨されるλの範囲(相対モード)です。

    λ倍率 特性
    0.005~0.015 ほぼL2(ノイズが多い)
    0.02~0.04 マイクロセグメント
    0.04~0.07 シグナル抽出に最適
    0.07~0.12 中期トレンド
    0.12~0.25 市場レジーム
    > 0.3 セグメント数が少ない

    表2:λmax単位におけるλの動作レンジ


    実務的には、λは0.04〜0.25の範囲の倍率で使用することが推奨されます。



    3. 応用例

    このセクションでは、シミュレーションによるブラウン運動データ、S&P 500の価格データにおけるL1トレンド計算、およびブラウン運動とFOREX市場データの両方におけるλmaxのスケーリング特性について考察します。

    また、特定の銘柄および時間足に対して最適なL1トレンド分解を得るために、λmaxの最適な正則化パラメータ(倍率)を決定するための3種類のインジケータバリエーションを提示します。

    さらに、移動平均、MACD、ADX、EMA戦略における売買シグナル(L1トレンドとの整合性)フィルタリング結果についても示します。


    3.1. シミュレーションデータ(ランダムウォーク)におけるL1トレンド計算

    例として、シミュレーションされたブラウン運動データ上で、異なる正則化パラメータλを用いてL1トレンドを計算するケースを考えます。

    スクリプトコード:

    //+------------------------------------------------------------------+
    //|                                                  TestL1Trend.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property script_show_inputs
    #include <Graphics\Graphic.mqh>
    //+------------------------------------------------------------------+
    //| Generate Brown movement data                                     |
    //+------------------------------------------------------------------+
    void BMData(vector<double> &data,int &data_count)
      {
       data.Resize(data_count);
       data[0] = 0.0;
       for(int i=1; i<data_count; i++)
          data[i] = data[i-1] + (MathRand()/32767.0 - 0.5);
      }
    //+------------------------------------------------------------------+
    //| CopyValues                                                       |
    //+------------------------------------------------------------------+
    bool CopyValues(vector<double> &data_v,double &data[])
      {
       int data_count=(int)data.Size();
       if(data_count==0)
          return(false);
       ArrayResize(data,data.Size());
       for(int i=0; i<data_count; i++)
          data[i]=data_v[i];
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       MathSrand(1);
       int data_count=1000;
       vector<double> data_test;
       BMData(data_test,data_count);
    //--- prepare arrays for chart
       double x[],y[];
       ArrayResize(x,data_count);
       ArrayResize(y,data_count);
       for(int i=0; i<data_count; i++)
          x[i]=i;
    //---
       CGraphic graphic;
       long chart=0;
       string name="test";
       if(ObjectFind(chart,name)<0)
          graphic.Create(chart,name,0,0,0,1000,600);
       else
          graphic.Attach(chart,name);
       graphic.BackgroundMain("L1 Trend filtering (random walk) with different lambda");
       graphic.BackgroundMainSize(16);
       graphic.HistoryNameWidth(60);
       graphic.HistoryColor(ColorToARGB(clrGray,255));
       graphic.XAxis().AutoScale(false);
       graphic.XAxis().Min(0);
       graphic.XAxis().Max(data_count);
    //---
       CopyValues(data_test,y);
       graphic.CurveAdd(x,y,CURVE_LINES,"Data").LinesWidth(1);
    //--- L1TrendFilterLambdaMax
       double lambda_max=0.0;
       if(data_test.L1TrendFilterLambdaMax(lambda_max))
          PrintFormat("lambda_max=%f",lambda_max);
    //---
       vector<double> data_l1;
       const double lambda_factors[]= {1.0,0.9,0.8,0.5,0.25,0.1,0.01,0.05,0.001,0.0005};
       for(int i=0; i<ArraySize(lambda_factors); i++)
         {
          double lambda=lambda_max*lambda_factors[i];
          PrintFormat("%d. lambda=%f",i+1,lambda);
          bool res=data_test.L1TrendFilter(lambda_factors[i],true,data_l1);
          if(res)
            {
             CopyValues(data_l1,y);
             graphic.CurveAdd(x,y,CURVE_LINES,"lambda="+DoubleToString(lambda,0)).LinesWidth(3);
            }
         }
    //---
       graphic.CurvePlotAll();
       graphic.Update();
       DebugBreak();
      }
    //+------------------------------------------------------------------+
    
    出力:
    TestL1Trend (EURUSD,H1) lambda_max=51703.353749
    TestL1Trend (EURUSD,H1) 1. lambda=51703.353749
    TestL1Trend (EURUSD,H1) 2. lambda=46533.018374
    TestL1Trend (EURUSD,H1) 3. lambda=41362.682999
    TestL1Trend (EURUSD,H1) 4. lambda=25851.676874
    TestL1Trend (EURUSD,H1) 5. lambda=12925.838437
    TestL1Trend (EURUSD,H1) 6. lambda=5170.335375
    TestL1Trend (EURUSD,H1) 7. lambda=517.033537
    TestL1Trend (EURUSD,H1) 8. lambda=2585.167687
    TestL1Trend (EURUSD,H1) 9. lambda=51.703354
    TestL1Trend (EURUSD,H1) 10. lambda=25.851677
    

    この例から分かるように、正則化パラメータλを小さくすると、トレンドがより細かいセグメントに分解されるようになります(図1)。

    一方でλ ≥ λmaxの場合、解は直線となり、線形回帰(グローバルトレンド)に対応します。


    図1:ブラウン運動データに対する異なるλ値でのL1フィルタ計算例

    図1:ブラウン運動データに対する異なるλ値でのL1フィルタ計算例


    L1トレンドを計算する関数は、double型ベクトルおよびfloat型ベクトルの両方に対して利用可能です。

    計算結果を比較するためのテストスクリプトを以下に示します。

    //+------------------------------------------------------------------+
    //|                                       TestL1TrendFloatDouble.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #include <Graphics\Graphic.mqh>
    
    uint32_t ExtSeed=1;
    //+------------------------------------------------------------------+
    //| Generate Brown movement data                                     |
    //+------------------------------------------------------------------+
    template<typename T>
    void BMData(vector<T> &data,uint64_t data_count)
      {
       MathSrand(ExtSeed);
    
       data.Resize(data_count);
       data[0] = 0.0;
    
       for(uint64_t i=1; i<data_count; i++)
          data[i] = data[i-1] + T(MathRand()/32767.0 - 0.5);
      }
    //+------------------------------------------------------------------+
    //| CopyValues                                                       |
    //+------------------------------------------------------------------+
    template<typename T>
    bool CopyValues(double &data[],const vector<T> &data_v)
      {
       if(ArrayResize(data,data.Size())!=data.Size())
          return(false);
       for(uint64_t i=0; i<data.Size(); i++)
          data[i]=data_v[i];
    
       return(true);
      }
    //+------------------------------------------------------------------+
    //| L1TrendCalculate                                                 |
    //+------------------------------------------------------------------+
    template<typename T>
    bool L1TrendCalculate(double &result[],uint64_t data_count,double lambda,bool lambda_is_relative)
      {
       vector<T> data_test;
       BMData(data_test,data_count);
    
       vector<T> vres;
       if(!data_test.L1TrendFilter((T)lambda,lambda_is_relative,vres))
          return(false);
       if(ArrayResize(result,(uint32_t)vres.Size())!=vres.Size())
          return(false);
       for(uint64_t n=0; n<result.Size(); n++)
          result[n]=vres[n];
    
       return(true);
      }
    //+------------------------------------------------------------------+
    //| TestRun                                                          |
    //+------------------------------------------------------------------+
    bool TestRun(uint32_t data_count,uint32_t mode)
      {
    //--- create graph
       CGraphic graphic;
       long     chart=0;
       string   name="L1TrendTest";
    
       if(ObjectFind(chart,name)<0)
          graphic.Create(chart,name,0,0,0,1280,600);
       else
          graphic.Attach(chart,name);
    
       string mode_name="(";
       if((mode&1)==1)
          mode_name+="DOUBLE";
       if((mode&3)==3)
          mode_name+=" & ";
       if((mode&2)==2)
          mode_name+="FLOAT";
       mode_name+=")";
    
       graphic.BackgroundMain("L1Trend filtering (random walk) with different lambda "+mode_name);
       graphic.BackgroundMainSize(16);
       graphic.HistoryNameWidth(60);
       graphic.HistoryColor(ColorToARGB(clrGray,255));
       graphic.XAxis().AutoScale(false);
       graphic.XAxis().Min(0);
       graphic.XAxis().Max(data_count);
    //--- prepare arrays
       double x[];
       double y[];
    
       if(ArrayResize(x,data_count)!=data_count)
          return(false);
      
       for(uint32_t i=0; i<data_count; i++)
          x[i]=i;
    
       vector<double> v;
       BMData(v,data_count);
       v.Swap(y);
       graphic.CurveAdd(x,y,CURVE_LINES,"Data").LinesWidth(1);
    //--- calculate
       const double lambda_factors[]= {1.0,0.9,0.8,0.5,0.25,0.1,0.01,0.05,0.001,0.0005};
    //--- double
       if((mode&1)==1)
         {
          for(uint64_t i=0; i<lambda_factors.Size(); i++)
            {
             if(L1TrendCalculate<double>(y,data_count,lambda_factors[i],true))
                graphic.CurveAdd(x,y,CURVE_LINES,"DBL="+DoubleToString(lambda_factors[i],4)).LinesWidth(4);
            }
         }
    //--- float
       if((mode&2)==2)
         {
          for(uint64_t i=0; i<lambda_factors.Size(); i++)
            {
             if(L1TrendCalculate<float>(y,data_count,(float)lambda_factors[i],true))
                graphic.CurveAdd(x,y,CURVE_LINES,"FLT="+DoubleToString(lambda_factors[i],4)).LinesWidth(2);
            }
         }
    //--- update
       graphic.CurvePlotAll();
       graphic.Update();
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       for(uint32_t n=0; !IsStopped(); n++,Sleep(1000))
         {
          TestRun(1000,1+n%3);
    
          if((n%3)==2)
             ExtSeed++;
         }
      }
    //+------------------------------------------------------------------+

    出力:


    3.2. S&P 500価格系列におけるL1トレンド計算

    S&P 500の元データからlog(S&P 500)を計算するケースを考えます。これは、S.J. Kim, K. Koh, S. Boyd, and D. Gorinevskyによる論文l_1 Trend Filtering(SIAM Review, Problems and Techniques section, 51(2):339–360, 2009)で扱われている例に対応します。 

    スクリプトを実行するには、ファイル「snp500.txt」のデータを使用します。このファイルは「terminal_data_folder\MQL5\Files」フォルダに配置する必要があります。

    //+------------------------------------------------------------------+
    //|                                       TestL1TrendFilterSP500.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property script_show_inputs
    #include <Graphics\Graphic.mqh>
    //+------------------------------------------------------------------+
    //| LoadData                                                         |
    //+------------------------------------------------------------------+
    void LoadData(string filename,vector<double> &data,int &data_count)
      {
       data_count=0;
       ResetLastError();
       int file_handle=FileOpen(filename,FILE_READ|FILE_TXT|FILE_ANSI);
       if(file_handle!=INVALID_HANDLE)
         {
          while(!FileIsEnding(file_handle))
            {
             string str=FileReadString(file_handle);
             if(data.Size()<=(ulong)data_count)
                data.Resize(data_count+1);
             data[data_count]=StringToDouble(str);
             data_count++;
            }
          FileClose(file_handle);
         }
       else
          PrintFormat("Failed to open %s file, Error code = %d",filename,GetLastError());
    //---
       data.Resize(data_count);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       long chart=0;
       string name="log SP500";
       int data_count=0;
       vector<double> data_sp500;
       LoadData("snp500.txt",data_sp500,data_count);
       vector<double> data_l1_sp500;
       data_l1_sp500.Resize(data_count);
    //--- L1TrendFilterLambdaMax
       double lambda_max=0.0;
       if(data_sp500.L1TrendFilterLambdaMax(lambda_max))
          PrintFormat("Lambda_max=%f",lambda_max);
       double lambda=50;
    //--- L1TrendFilter
       if(data_sp500.L1TrendFilter(lambda,false,data_l1_sp500))
         {
          //--- prepare arrays for chart
          double x[],y[],y2[];
          ArrayResize(x,data_count);
          ArrayResize(y,data_count);
          ArrayResize(y2,data_count);
          for(int i=0; i<data_count; i++)
            {
             x[i]=i;
             y[i]=data_sp500[i];
             y2[i]=data_l1_sp500[i];
            }
          //---
          CGraphic graphic;
          if(ObjectFind(chart,name)<0)
             graphic.Create(chart,name,0,0,0,1000,600);
          else
             graphic.Attach(chart,name);
          graphic.BackgroundMain("log SP500 L1 trend filtering");
          graphic.BackgroundMainSize(16);
          graphic.HistoryNameWidth(60);
          graphic.HistoryColor(ColorToARGB(clrGray,255));
          graphic.XAxis().AutoScale(false);
          graphic.XAxis().Min(0);
          graphic.XAxis().Max(data_count);
          graphic.XAxis().DefaultStep(100);
          graphic.CurveAdd(x,y,CURVE_LINES,"SP500").LinesWidth(1);
          graphic.CurveAdd(x,y2,CURVE_LINES,"L1 trend").LinesWidth(3);
          graphic.CurvePlotAll();
          graphic.Update();
          DebugBreak();
         }
      }
    //+------------------------------------------------------------------+
    

    スクリプト実行結果は図2に示します。

    図2:S&P 500指数の対数価格系列に対するL1トレンド推定の例

    図2:S&P 500指数の対数価格系列に対するL1トレンド推定の例


    このスクリプトの実行により、[エキスパート]タブには与えられた時系列に対するλmaxの値が表示されます。

    TestL1TrendFilterSP500 (EURUSD,H1)      Lambda_max=37394.835512
    

    このスクリプトは、L1TrendFilterLambdaMaxおよびL1TrendFilterメソッドの使用方法を示しており、正則化パラメータλ = 50を固定値として用いる点は、手法の著者による原論文と同様です。

    以降の例では、正則化パラメータλの絶対値ではなく、relative = trueフラグを用いて、λmax単位での相対値を使用します。


    3.3. λmaxのスケーリング特性

    L1フィルタリングにおいてパラメータλmaxは重要な役割を持ちます。これは、解が大域的な線形近似へと退化する正則化の上限を定義するためです。この量の興味深い性質として、時系列長に対するスケーリング依存性があります。

    数値実験によれば、λmaxは観測数に対してべき乗則に従って増加します。

    ここでTは時系列の長さ、αはスケーリング指数です。

    ランダムウォーク(ブラウン運動)の場合、指数はα ≈ 2.5に近い値になることが示されます。ブラウン運動の振幅はに従って増加し、二階差分演算子はのスケールで変化します。 λmaxの計算では、時系列の曲率に関連する量の最大値を評価することになります。


    その結果、これらのスケーリングが組み合わさることで次の関係が得られます。

    これは指数α ≈ 2.5に対応します。

    したがって、時系列の長さが増加するとλmaxは線形よりもはるかに速い速度で増加します。


    3.3.1.ブラウン運動に対する数値実験

    スケーリング則を検証するために数値実験をおこないます。

    異なる時系列長Tに対してブラウン運動を生成し、それぞれのλmaxの平均値を計算します。

    対数近似を用いることで:

    線形回帰によってスケーリング指数αを推定できます。

    実験コードを以下に示します。

    //+------------------------------------------------------------------+
    //|                            TestScalingLambdaMaxBrownMovement.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #include <Graphics\Graphic.mqh>
    //+------------------------------------------------------------------+
    //| Generate Brownian motion                                         |
    //+------------------------------------------------------------------+
    void GenerateBrownian(int N,vector<double> &data)
      {
       data.Resize(N);
       data[0] = 0.0;
       for(int i=1; i<N; i++)
          data[i] = data[i-1] + (MathRand()/32767.0 - 0.5);
      }
    //+------------------------------------------------------------------+
    //| LinearRegression                                                 |
    //+------------------------------------------------------------------+
    void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b)
      {
       double sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0;
       for(int i = 0; i < n; i++)
         {
          sx  += x[i];
          sy  += y[i];
          sxx += x[i] * x[i];
          sxy += x[i] * y[i];
         }
       double denom = n * sxx - sx * sx;
       a = (n * sxy - sx * sy) / denom;
       b = (sy - a * sx) / n;
      }
    //+------------------------------------------------------------------+
    //| TestScaling with statistics                                      |
    //+------------------------------------------------------------------+
    void TestScalingStatistics()
      {
       MathSrand(42);
       int RUNS = 10;     //
       int MC   = 10;   // Monte Carlo
       double alpha_values[];
       ArrayResize(alpha_values, RUNS);
    // --- geometric grid of T
       int nT = 8;
       int Tvals[];
       ArrayResize(Tvals, nT);
       int T0 = 64;
       for(int i = 0; i < nT; i++)
          Tvals[i] = T0 << i;
       Print("Scaling test with statistics");
    //---
       double logT[];
       double logLambda[];
       vector<double> bm;
       vector<double> l1_trend;
       for(int run = 0; run < RUNS; run++)
         {
          ArrayResize(logT, nT);
          ArrayResize(logLambda, nT);
          //---
          for(int i = 0; i < nT; i++)
            {
             int T = Tvals[i];
             double lambda_sum = 0.0;
             l1_trend.Resize(T);
             for(int k = 0; k < MC; k++)
               {
                GenerateBrownian(T, bm);
                double lambda_max=0.0;
                if (bm.L1TrendFilterLambdaMax(lambda_max))
                   lambda_sum += lambda_max;
                bm.L1TrendFilter(0.2,true,l1_trend);
               }
             double lambda_avg = lambda_sum / MC;
             logT[i]      = MathLog((double)T);
             logLambda[i] = MathLog(lambda_avg);
            }
          // --- regression
          double alpha, c;
          LinearRegression(logT, logLambda, nT, alpha, c);
          alpha_values[run] = alpha;
          PrintFormat("run %d -> alpha = %.6f", run+1, alpha);
         }
    //--- statistics
       double mean = 0.0;
       for(int i=0;i<RUNS;i++)
          mean += alpha_values[i];
       mean /= RUNS;
    // --- standard deviation
       double var = 0.0;
       for(int i=0;i<RUNS;i++)
          var += (alpha_values[i]-mean)*(alpha_values[i]-mean);
       var /= (RUNS - 1);
       double stddev = MathSqrt(var);
    // --- standard error of mean
       double sem = stddev / MathSqrt((double)RUNS);
    // --- theoretical comparison
       double alpha_theory=2.5;
       double percent_error=MathAbs(mean-alpha_theory)/alpha_theory*100.0;
    //--- results
       PrintFormat("mean alpha = %.6f", mean);
       PrintFormat("std deviation = %.6f", stddev);
       PrintFormat("standard error = %.6f", sem);
       PrintFormat("theory = %.4f", alpha_theory);
       PrintFormat("percent error from theory = %.4f %%", percent_error);
      }
    //+------------------------------------------------------------------+
    //| TestScaling                                                      |
    //+------------------------------------------------------------------+
    void TestScaling()
      {
       MathSrand(1);
    // --- geometric grid of T
       int nT = 8;
       int Tvals[];
       ArrayResize(Tvals,nT);
    //---
       int T0 = 64;
       for(int i=0; i<nT; i++)
          Tvals[i]=T0<<i;   // 64 * 2^i
    //---
       double logT[], logLambda[];
       ArrayResize(logT,nT);
       ArrayResize(logLambda,nT);
    //---
       Print("scaling test for lambda_max");
       for(int i=0; i<nT; i++)
         {
          int T = Tvals[i];
          //--- Monte-Carlo simulations
          int MC=1000;
          double lambda_sum = 0.0;
          for(int k=0; k<MC; k++)
            {
             vector<double> bm;
             GenerateBrownian(T, bm);
             double lambda_max=0.0;
             if(bm.L1TrendFilterLambdaMax(lambda_max))
                lambda_sum += lambda_max;
            }
          double lambda_avg=lambda_sum/MC;
          logT[i]= MathLog((double)T);
          logLambda[i]=MathLog(lambda_avg);
          PrintFormat("T=%5d   <lambda_max>=%.6f",T,lambda_avg);
         }
    // --- linear regression in log-log
       double alpha, c;
       LinearRegression(logT,logLambda,nT,alpha,c);
    //---
       PrintFormat("estimated scaling exponent alpha = %.4f",alpha);
       double alpha_theory=2.5;
       PrintFormat("theoretical value = %.4f",alpha_theory);
    //--- plot scaling law
       CGraphic g;
       g.Create(0, "ScalingLaw",0,0,0,1000,600);
       g.BackgroundMain("Scaling law of lambda_max (Brownian motion)");
       g.BackgroundMainSize(16);
       g.CurveAdd(logT, logLambda, CURVE_POINTS, "Simulation");
    //---
       double xfit[2], yfit[2];
       xfit[0] = logT[0];
       xfit[1] = logT[nT-1];
    //---
       yfit[0] = alpha*xfit[0] + c;
       yfit[1] = alpha*xfit[1] + c;
    //---least squares fit
       g.CurveAdd(xfit, yfit, CURVE_LINES, "LS fit");
       g.CurvePlotAll();
       g.Update();
       DebugBreak();
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
    //--- calculate scaling with statistics
       TestScalingStatistics();
    //--- show sample results
       TestScaling();
      }
    //+------------------------------------------------------------------+

    出力:

    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   Scaling test with statistics    
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 1 -> alpha = 2.480774       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 2 -> alpha = 2.530977       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 3 -> alpha = 2.435511       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 4 -> alpha = 2.461984       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 5 -> alpha = 2.467093       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 6 -> alpha = 2.487965       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 7 -> alpha = 2.532371       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 8 -> alpha = 2.455831       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 9 -> alpha = 2.483485       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 10 -> alpha = 2.420283      
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   mean alpha = 2.475627   
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   std deviation = 0.036281        
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   standard error = 0.011473       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   theory = 2.5000 
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   percent error from theory = 0.9749 %    
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   scaling test for lambda_max     
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T=   64   <lambda_max>=97.302362        
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T=  128   <lambda_max>=566.626861       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T=  256   <lambda_max>=3162.076116      
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T=  512   <lambda_max>=18271.204936     
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T= 1024   <lambda_max>=100057.796790    
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T= 2048   <lambda_max>=578620.887399    
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T= 4096   <lambda_max>=3192555.936035   
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T= 8192   <lambda_max>=17895314.647170  
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   estimated scaling exponent alpha = 2.4967       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   theoretical value = 2.5000      
    

    両対数プロット(対数スケール)では、ブラウン運動においてデータ点数に対するλmaxの関数がべき乗則に従うことが示されます。

    図3:ブラウン運動におけるLambdaMaxのべき乗依存性

    図3:ブラウン運動におけるLambdaMaxのべき乗依存性


    シミュレーション結果は以下の通りです。

    mean alpha = 2.4756
    std deviation = 0.036
    theory = 2.5
    percent error ≈ 1%

    したがって、この実験は次の理論関係を裏付けています。



    両対数プロット(対数スケール)では、log(λmax)とlog(T)の間に線形関係が成立することが示されます。


    3.3.2. 金融時系列におけるスケーリング

    同様の実験を外国為替市場の価格系列に対して実施します。異なる通貨ペアおよび時間足に対して指数αを推定します。

    結果として、実データにおいても指数αはα ≈ 2.45~2.60の範囲に収まり、これはブラウン運動の理論値に非常に近い値となります。これはλmaxのスケーリング特性がほぼ普遍的であり、異なる市場および時間足に対しても成立することを意味します。

    スクリプトTestScalingLambdaMaxSymbol.mq5は、標準時間足M1〜H1において指定銘柄のλmaxの指数を計算します。

    //+------------------------------------------------------------------+
    //|                                   TestScalingLambdaMaxSymbol.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property script_show_inputs
    //--- input parameters
    input string WorkSymbol   = "EURUSD";     // Symbol
    input int    YearStart    = 2024;
    input int    YearEnd      = 2025;
    #include <Graphics\Graphic.mqh>
    //+------------------------------------------------------------------+
    //| GetHistoricalData                                                |
    //+------------------------------------------------------------------+
    bool GetHistoricalData(double &data[], string symbol, ENUM_TIMEFRAMES tf, int year_start, int year_end)
      {
       datetime from = StringToTime(IntegerToString(year_start) + ".01.01 00:00");
       datetime to   = StringToTime(IntegerToString(year_end)   + ".12.31 23:59");
       int copied = CopyClose(symbol, tf, from, to, data);
       if(copied <= 0)
         {
          Print("Error in CopyClose: ", GetLastError());
          ArrayResize(data, 0);
          return false;
         }
    //PrintFormat("Loaded bars: %d (%s %s)", ArraySize(data), symbol, EnumToString(tf));
       return true;
      }
    //+------------------------------------------------------------------+
    //| LinearRegression                                                 |
    //+------------------------------------------------------------------+
    void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b)
      {
       double sx = 0, sy = 0, sxx = 0, sxy = 0;
       for(int i = 0; i < n; i++)
         {
          sx  += x[i];
          sy  += y[i];
          sxx += x[i] * x[i];
          sxy += x[i] * y[i];
         }
       double denom = n*sxx - sx*sx;
       if(denom!=0)
         {
          a = (n*sxy-sx*sy)/denom;
          b = (sy-a*sx)/n;
         }
      }
    //+------------------------------------------------------------------+
    //| Scaling test for one timeframe                                   |
    //+------------------------------------------------------------------+
    bool TestScalingLambaMaxTF(string symbol, ENUM_TIMEFRAMES tf, double &logT_out[], double &logLambda_out[], double &alpha_out)
      {
       MathSrand(42);
       double prices[];
       if(!GetHistoricalData(prices, symbol, tf, YearStart, YearEnd))
          return false;
       int Tvals[];
       int nT=8;
       int T0=64;
       ArrayResize(Tvals, nT);
       for(int i = 0; i < nT; i++)
          Tvals[i] = T0 << i;
       ArrayResize(logT_out, nT);
       ArrayResize(logLambda_out, nT);
       int data_size = ArraySize(prices);
       vector<double> data_prices;
       for(int i = 0; i < nT; i++)
         {
          int T = Tvals[i];
          int MC = 1000;
          double lambda_sum = 0.0;
          for(int k = 0; k < MC; k++)
            {
             if(data_size < T)
                break;
             int start = MathRand() % (data_size - T);
             data_prices.Resize(T);
             for(int j=0; j<T;  j++)
                data_prices[j]=prices[start+j];
             double lambda_max=0.0;
             if(data_prices.L1TrendFilterLambdaMax(lambda_max))
                lambda_sum += lambda_max;
            }
          double lambda_avg = lambda_sum / MC;
          logT_out[i]=MathLog((double)T);
          logLambda_out[i]=MathLog(lambda_avg);
          //PrintFormat("TF=%s T=%5d   <lambda_max>=%.6f", EnumToString(tf), T, lambda_avg);
         }
       double c;
       LinearRegression(logT_out, logLambda_out, nT, alpha_out, c);
       PrintFormat("%s (%s) estimated scaling exponent = %.4f", symbol,EnumToString(tf), alpha_out);
       return true;
      }
    //+------------------------------------------------------------------+
    //| TestScalingLambdaMaxSymbol                                       |
    //+------------------------------------------------------------------+
    void TestScalingLambdaMaxSymbol(string symbol)
      {
       ENUM_TIMEFRAMES timeframes[] = {PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6,
                                       PERIOD_M10, PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30, PERIOD_H1
                                      };
       uint colors[] = {clrRed,clrBlue,clrGreen,clrOrange,clrPurple,clrDarkGreen,clrCyan,
                        clrNavy,clrOrangeRed,clrDodgerBlue,clrCrimson,clrDarkRed
                       };
    //---
       CGraphic g;
       g.Create(0,"ScalingLawTest",0,0,0,1000,600);
       g.BackgroundMain("Scaling law of lambda_max ("+symbol+")");
       g.BackgroundMainSize(16);
       PrintFormat("%s scaling test for standard timeframes",symbol);
       for(int i = 0; i < ArraySize(timeframes); i++)
         {
          double logT[], logLambda[], alpha;
          // Print("processing timeframe: ", EnumToString(timeframes[i]), " -----");
          if(TestScalingLambaMaxTF(symbol,timeframes[i],logT,logLambda,alpha))
            {
             g.CurveAdd(logT,logLambda,ColorToARGB(colors[i % ArraySize(colors)],255),CURVE_POINTS_AND_LINES,EnumToString(timeframes[i]));
            }
         }
       g.CurvePlotAll();
       g.Update();
    //---
       DebugBreak();
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
    //--- estimate lambda_max scale exponent for price data
       TestScalingLambdaMaxSymbol(WorkSymbol);
      }
    //+------------------------------------------------------------------+
    

    EURUSDに対する結果

    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD scaling test for standard timeframes     
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M1) estimated scaling exponent = 2.5038  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M2) estimated scaling exponent = 2.5350  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M3) estimated scaling exponent = 2.5034  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M4) estimated scaling exponent = 2.5422  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M5) estimated scaling exponent = 2.5341  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M6) estimated scaling exponent = 2.5132  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M10) estimated scaling exponent = 2.5188 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M12) estimated scaling exponent = 2.5126 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M15) estimated scaling exponent = 2.5208 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M20) estimated scaling exponent = 2.4887 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M30) estimated scaling exponent = 2.5695 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_H1) estimated scaling exponent = 2.6118  

    EURUSD(標準時間足M1〜H1)に対する結果を図4に示します。

    図4:異なるEURUSD時間足におけるλmaxのべき乗依存性

    図4:異なるEURUSD時間足におけるλmaxのべき乗依存性

    同様に、他の通貨ペアについても解析することができます。

    USDJPYの場合:

    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY scaling test for standard timeframes     
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M1) estimated scaling exponent = 2.5851  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M2) estimated scaling exponent = 2.5825  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M3) estimated scaling exponent = 2.4889  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M4) estimated scaling exponent = 2.5099  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M5) estimated scaling exponent = 2.5059  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M6) estimated scaling exponent = 2.4939  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M10) estimated scaling exponent = 2.5548 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M12) estimated scaling exponent = 2.5641 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M15) estimated scaling exponent = 2.5525 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M20) estimated scaling exponent = 2.5390 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M30) estimated scaling exponent = 2.5805 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_H1) estimated scaling exponent = 2.4645  
    

    USDJPYにおいても、結果はべき乗則でよく近似されます。

    図5:異なるUSDJPY時間足におけるλmaxのべき乗依存性

    図5:異なるUSDJPY時間足におけるλmaxのべき乗依存性

    GBPUSDの場合:

    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD scaling test for standard timeframes     
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M1) estimated scaling exponent = 2.5235  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M2) estimated scaling exponent = 2.5449  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M3) estimated scaling exponent = 2.5439  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M4) estimated scaling exponent = 2.5427  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M5) estimated scaling exponent = 2.5248  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M6) estimated scaling exponent = 2.5308  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M10) estimated scaling exponent = 2.5293 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M12) estimated scaling exponent = 2.5235 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M15) estimated scaling exponent = 2.5069 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M20) estimated scaling exponent = 2.4977 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M30) estimated scaling exponent = 2.5659 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_H1) estimated scaling exponent = 2.5524  

    同様の状況はGBPUSDの価格系列でも確認されます(図6)。

    図6:異なるGBPUSD時間足におけるλmaxのべき乗依存性

    図6:異なるGBPUSD時間足におけるλmaxのべき乗依存性


    今回扱ったEURUSD、USDJPY、GBPUSDの各系列においても、推定された指数はおおむね2.5に近い値となっています。

    複数の時間足および通貨ペアにおけるλmaxの対数スケールでの線形関係は、λmaxが観測数に対してべき乗的に依存することを示しています。


    3.3.3. スケーリングの実務的含意

    λmaxに対するべき乗依存性の存在は、重要な実務的意味を持ちます。

    λmax ∝ T^2.5であるため、λの絶対値は以下に強く依存します。

    1. データウィンドウの長さ
    2. 時間足
    3. 時系列のスケール

    したがって、λの絶対値を用いることは実務上あまり適していません。

    よりロバストなアプローチは、λ=c⋅λmax (0<c<1)という相対パラメータを用いる方法です。

    この方法には以下の利点があります。

    • 正則化パラメータのスケール不変性を実現する
    • 異なる銘柄間でのパラメータ移植を容易にする
    • 異なる時間足でも同一設定を使用できるようにする

    この理由により、以降のすべての例では、パラメータλはλmax単位で指定されます。


    3.4. L1トレンドインジケータ

    このセクションでは以下の3種類のインジケータを扱います。

    • 終値に基づくL1トレンドの計算
    • L1トレンドの線形成長係数(スロープ)の計算
    • L1トレンドスロープの符号の計算
    これらのインジケータは、トレンド分解の視覚的分析に利用でき、またトレード戦略で使用する際の正則化パラメータλの適切な値を決定するのにも役立ちます。

    3.4.1. L1TrendFilter.mq5:L1トレンドインジケータ

    この例では、指定された本数(例ではBarsToShow = 1000)の終値を用いてL1フィルタを計算します。λ係数はλmax単位で指定されます。

    計算はL1TrendFilter(relative = true)メソッド呼び出しを用いておこなわれます。この場合、パラメータλはλmax単位で定義されます。インジケータ値はチャートウィンドウに直接表示されます。

    L1TrendFilter.mq5インジケータのコードを以下に示します。

    //+------------------------------------------------------------------+
    //|                                                L1TrendFilter.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_chart_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1TrendFilter"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrDodgerBlue
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Trend[];
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Trend,INDICATOR_DATA);
       ArrayInitialize(Trend,EMPTY_VALUE);
    //---
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| 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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total < BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Trend,EMPTY_VALUE);
          return(0);
         }
    //--- check new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
    //--- range
       int start=rates_total-BarsToShow;
    //--- hide old bars
       for(int i=0; i<start; i++)
          Trend[i]=EMPTY_VALUE;
    //---
       int data_count=BarsToShow;
    //--- copy Close
       vector<double> DataClose;
       DataClose.Resize(data_count);
       for(int i=0; i<data_count; i++)
          DataClose[i]=close[start+i];
    //--- lambda max
       double lambda_max=0.0;
       bool res=DataClose.L1TrendFilterLambdaMax(lambda_max);
       if(res)
         {
          PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f",
                      lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda);
         }
    //--- L1 trend filtering
       vector<double> filtered_data;
       filtered_data.Resize(data_count);
       if(DataClose.L1TrendFilter(CoefLambda,true,filtered_data))
         {
          for(int i=0; i<data_count; i++)
             Trend[start+i]=filtered_data[i];
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    図7では、CoefLambda = 0.015を用いてL1TrendFilter.mq5インジケータを計算した例を示します。


    図7:CoefLambda = 0.015によるL1TrendFilter.mq5インジケータ計算例

    図7:CoefLambda = 0.015によるL1TrendFilter.mq5インジケータ計算例


    比較のため、異なる正則化パラメータを用いた複数のバリエーションを計算することができます。

    図8では、CoefLambda = 0.015、CoefLambda = 0.025、およびCoefLambda = 0.055を用いた計算結果を示します。


    図8:異なるCoefLambda値によるL1TrendFilter.mq5インジケータ計算例

    図8:異なるCoefLambda値によるL1TrendFilter.mq5インジケータ計算例



    3.4.2. L1TrendFilterSlope.mq5:L1トレンド傾きインジケータ

    トレンドの傾きを表示するためには、L1TrendFilterインジケータ値の増分を用いることができます。

    例として、L1TrendFilterSlopeインジケータを考えます。このインジケータは別ウィンドウに値を表示します。

    インジケータのコードを以下に示します。

    //+------------------------------------------------------------------+
    //|                                           L1TrendFilterSlope.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1TrendFilterSlope"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrDodgerBlue
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Trend[];
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Trend,INDICATOR_DATA);
       ArrayInitialize(Trend,EMPTY_VALUE);
    //---
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| 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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total < BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Trend,EMPTY_VALUE);
          return(0);
         }
    //--- check  new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc= (prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
    //---
       int start=rates_total-BarsToShow;
       int data_count=BarsToShow;
    //--- hide old bars
       for(int i=0;i<start;i++)
          Trend[i]=EMPTY_VALUE;
    //--- copy Close
       vector<double> DataClose;
       DataClose.Resize(data_count);
       for(int i=0;i<data_count;i++)
          DataClose[i]=close[start+i];
    //--- lambda max
       double lambda_max=0.0;
       if(DataClose.L1TrendFilterLambdaMax(lambda_max))
         {
          PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f",
                      lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda);
         }
    //--- L1 filtering
       vector<double> filtered_data;
       filtered_data.Resize(data_count);
       bool res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data);
       if(res)
         {
          //--- slope (first difference)
          for(int i=1; i<data_count; i++)
            {
             double delta=filtered_data[i]-filtered_data[i-1];
             Trend[start+i]=delta;
            }
          //--- copy first element
          Trend[start]=Trend[start+1];
         }
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    L1TrendFilter.mq5とL1TrendFilterSlope.mq5を組み合わせた結果を図9に示します。

    図9:CoefLambda = 0.015によるL1TrendFilter.mq5およびL1TrendFilterSlope.mq5インジケータ計算例

    図9:CoefLambda = 0.015によるL1TrendFilter.mq5およびL1TrendFilterSlope.mq5インジケータ計算例


    3.4.3. L1TrendFilterSlopeSign.mq5:トレンド方向インジケータ

    同様に、L1TrendFilterSlope.mq5インジケータの増分の符号を表示するインジケータを計算することができます。

    L1TrendFilterSlopeSign.mq5インジケータのコードを以下に示します。

    //+------------------------------------------------------------------+
    //|                                       L1TrendFilterSlopeSign.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1TrendFilterSlope"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrDodgerBlue
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Trend[];
    //+------------------------------------------------------------------+
    //| Signum                                                           |
    //+------------------------------------------------------------------+
    double Signum(const double value)
      {
       return((value>0)-(value<0));
      }
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Trend,INDICATOR_DATA);
       ArrayInitialize(Trend,EMPTY_VALUE);
    //---
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| 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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total < BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Trend,EMPTY_VALUE);
          return(0);
         }
    //--- check new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
    //---
       int start=rates_total-BarsToShow;
       int data_count=BarsToShow;
    //--- hide old bars
       for(int i=0; i<start; i++)
          Trend[i]=EMPTY_VALUE;
    //--- copy Close
       vector<double> DataClose;
       DataClose.Resize(data_count);
       for(int i=0; i<data_count; i++)
          DataClose[i]=close[start+i];
    //--- lambda max
       double lambda_max=0.0;
       bool res=DataClose.L1TrendFilterLambdaMax(lambda_max);
       if(res)
         {
          PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f",
                      lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda);
         }
    //--- L1 filtering
       vector<double> filtered_data;
       filtered_data.Resize(data_count);
       res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data);
       if(res)
         {
          Trend[start]=0;
          for(int i=1; i<data_count; i++)
            {
             double delta=filtered_data[i]-filtered_data[i-1];
             Trend[start+i]=Signum(delta);
            }
         }
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    図10では、CoefLambda = 0.015を用いて3つのインジケータ(L1TrendFilter.mq5、L1TrendFilterSlope.mq5、L1TrendFilterSlopeSign.mq5)を同時に表示した例を示します。

    図10:CoefLambda = 0.015によるL1TrendFilter.mq5、L1TrendFilterSlope.mq5、およびL1TrendFilterSlopeSign.mq5インジケータ計算例

    図10:CoefLambda = 0.015によるL1TrendFilter.mq5、L1TrendFilterSlope.mq5、およびL1TrendFilterSlopeSign.mq5インジケータ計算例




    3.4.4. L1トレンドに基づくボラティリティ指標

    このセクションでは、L1トレンドに基づいて金融商品のボラティリティを評価するためのインジケータを紹介します。

    これらのツールにより、市場の不安定期間および安定期間の識別、現在の市場ダイナミクスの分析、ならびにより適切なトレード判断が可能になります。

    ここで扱うインジケータは以下の通りです。

    • L1Volatility.mq5:L1トレンドに対する残差ボラティリティ
    • L1VolatilitySmoothed.mq5:平滑化残差ボラティリティ
    • L1VolatilityAbsolute.mq5:絶対ボラティリティ
    • L1VolatilityNormalized.mq5:正規化ボラティリティ
    • L1VolatilityNormalizedSmoothed.mq5:平滑化された正規化ボラティリティ
    • L1VolatilityRegime.mq5:ボラティリティに基づく市場レジーム検出

    これらのインジケータはすべて統一されたL1トレンドフレームワーク上に構築されており、解析結果の一貫性を確保し、解釈を容易にします。

    これらのインジケータを使用することで、高ボラティリティおよび低ボラティリティの期間を視覚的に識別でき、さらにレンジ、トレンド、拡張、パニックといった市場レジームの判定も可能になります。

    その結果、トレーダーは市場環境に応じて戦略を適応させることができます。たとえば、低ボラティリティ期間にはより保守的な戦略を適用し、強いトレンドが発生している局面ではよりアクティブな戦略を適用するといった運用が可能になります。


    3.4.4.1. L1Volatility.mq5:残差ボラティリティ指標

    このインジケータは、終値と対応するL1トレンド値との差として残差ボラティリティを計算します。

    このアプローチにより、不安定な市場局面の特定や、正確なエントリーおよびエグジットタイミングの把握が可能になります。

    視覚的には、このインジケータは別チャートウィンドウにオレンジ色のラインとして表示されます。

    このインジケータは以下の用途に役立ちます。

    • L1トレンドからの価格乖離を評価し、価格変動の強さを測定する
    • 局所的なボラティリティスパイクを検出し、リスク管理を改善する
    • 同一時間足における異なる銘柄のダイナミクスを比較する

    このインジケータは、短期的なボラティリティ変化を監視しつつ、広いトレンドコンテキストを維持する必要があるシステムに特に有用です。

    L1Volatility.mq5インジケータのコードを以下に示します。

    //+------------------------------------------------------------------+
    //|                                                 L1Volatility.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1Volatility"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrOrangeRed
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Volatility[];
    //---
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Volatility,INDICATOR_DATA);
       ArrayInitialize(Volatility,EMPTY_VALUE);
    //---
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| 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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total<BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Volatility,EMPTY_VALUE);
          return(0);
         }
    //--- check new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
    //---
       int start=rates_total-BarsToShow;
       int data_count=BarsToShow;
    //--- hide old bars
       for(int i=0;i<start;i++)
          Volatility[i]=EMPTY_VALUE;
    //--- copy Close
       vector<double> DataClose;
       DataClose.Resize(data_count);
       for(int i=0; i<data_count; i++)
          DataClose[i]=close[start+i];
    //--- lambda max
       double lambda_max=0.0;
       bool res=DataClose.L1TrendFilterLambdaMax(lambda_max);
       if(res)
         {
          PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f",
                      lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda);
         }
    //--- L1 filter
       vector<double> filtered_data;
       filtered_data.Resize(data_count);
       res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data);
       if(res)
         {
          for(int i=0; i<data_count; i++)
            {
             double residual=close[start+i]-filtered_data[i];
             Volatility[start+i]=residual;
            }
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    計算結果を図11に示します。


    図11:L1Volatility.mq5インジケータ

    図11:L1Volatility.mq5インジケータ



    3.4.4.2. L1VolatilitySmoothed.mq5:平滑化残差ボラティリティ指標

    このインジケータは、L1Volatilityの平滑化バージョンであり、単純移動平均(SMA)を適用したものです。

    平滑化により、以下が可能になります。

    • 短期的ノイズおよび外れ値の低減
    • より明確で解釈しやすい可視化
    • ボラティリティの持続的な変化へのフォーカス

    このインジケータは、長期的なボラティリティトレンドを評価する必要がある戦略に有用です。たとえば、適応型トレードシステムや、トレンド相場およびレンジ相場の両方における偽シグナルのフィルタリングなどに利用されます。

    L1VolatilitySmoothed.mq5インジケータのコードを以下に示します。

    //+------------------------------------------------------------------+
    //|                                         L1VolatilitySmoothed.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    #property indicator_label1  "L1VolatilitySmoothed"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrMediumVioletRed
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    input int    SmoothPeriod = 10;  // Smooth period
    //---
    double VolSmoothed[];
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0, VolSmoothed, INDICATOR_DATA);
       ArrayInitialize(VolSmoothed, EMPTY_VALUE);
       PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| 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[])
      {
       if(rates_total<BarsToShow)
         {
          ArrayInitialize(VolSmoothed,EMPTY_VALUE);
          return(0);
         }
    //--- recalc only on new bar
       static datetime last_bar_time = 0;
       if(time[0] == last_bar_time && prev_calculated > 0)
          return(prev_calculated);
       last_bar_time=time[0];
    //---
       int start=rates_total-BarsToShow;
       for(int i=0; i<start; i++)
          VolSmoothed[i]=EMPTY_VALUE;
    //--- copy close prices
       vector<double> price(BarsToShow);
       for(int i=0; i<BarsToShow; i++)
          price[i] = close[start+i];
       vector<double> l1(BarsToShow);
       bool res=price.L1TrendFilter(CoefLambda,true,l1);
       if(res)
         {
          //--- calculate raw volatility
          vector<double> rawVol(BarsToShow);
          for(int i=0; i<BarsToShow; i++)
             rawVol[i]=close[start+i]-l1[i];
          //--- apply simple moving average smoothing
          for(int i=0; i<BarsToShow; i++)
            {
             double sum = 0.0;
             int count = 0;
             for(int j=MathMax(0,i-SmoothPeriod+1); j<=i; j++)
               {
                sum+=rawVol[j];
                count++;
               }
             VolSmoothed[start+i]=sum/count;
            }
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    図12では、L1Volatility.mq5とL1VolatilitySmoothed.mq5の両方のインジケータを示します。

    図12:L1Volatility.mq5およびL1VolatilitySmoothed.mq5インジケータ

    図12:L1Volatility.mq5およびL1VolatilitySmoothed.mq5インジケータ



    3.4.4.3. L1VolatilityAbsolute.mq5:絶対ボラティリティ指標

    このインジケータは、終値とL1トレンドとの差の絶対値を計算します。

    特徴および用途は以下の通りです。

    • 方向性を無視し、変動の大きさのみを評価する
    • トレンド方向に依存せず、価格振幅の分析に適している
    • 極値統計やリスク分析に基づくシステムで有用

    絶対ボラティリティは価格変動の純粋な大きさを反映し、トレーダーが方向性に左右されず市場の変動強度を観察できるようにします。

    L1VolatilityAbsolute.mq5インジケータのコードを以下に示します。

    //+------------------------------------------------------------------+
    //|                                         L1VolatilityAbsolute.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1VolatilityAbsolute"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrOrange
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Vol[];
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Vol,INDICATOR_DATA);
       ArrayInitialize(Vol,EMPTY_VALUE);
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| 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[])
      {
    //---
       static bool warned=false;
       if(rates_total < BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting bars ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Vol,EMPTY_VALUE);
          return(0);
         }
       static datetime last_bar=0;
       bool new_bar=(time[0]!=last_bar);
    //---
       if(!(prev_calculated==0 || new_bar || rates_total!=prev_calculated))
          return(prev_calculated);
    //---
       last_bar=time[0];
       int start=rates_total-BarsToShow;
       int N=BarsToShow;
       for(int i=0; i<start; i++)
          Vol[i]=EMPTY_VALUE;
    //---
       vector<double> price;
       price.Resize(N);
       for(int i=0; i<N; i++)
          price[i]=close[start+i];
       vector<double> l1;
       l1.Resize(N);
       bool res=price.L1TrendFilter(CoefLambda,true,l1);
       if(res)
         {
          for(int i=0; i<N; i++)
             Vol[start+i]=MathAbs(close[start+i]-l1[i]);
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    インジケータ計算の例を図13に示します。


    図13:L1VolatilityAbsolute.mq5インジケータ

    図13:L1VolatilityAbsolute.mq5インジケータ


    3.4.4.4. L1VolatilityNormalized.mq5:正規化ボラティリティ指標

    このインジケータは、ATRとL1トレンドを組み合わせてボラティリティを正規化します。

    トレンドからの絶対価格乖離と、ATR期間における平均価格変動幅の比率を計算します。正規化により価格スケールへの依存性が除去され、異なる銘柄および時間足間での比較が可能になります。

    用途は以下の通りです。

    • 相対的に強い/弱い市場変動の識別
    • 異なる資産間のボラティリティ比較
    • 価格レベルに依存しない市場環境の評価

    L1VolatilityNormalized.mq5インジケータのコードを以下に示します。

    //+------------------------------------------------------------------+
    //|                                       L1VolatilityNormalized.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    #property indicator_label1  "L1VolatilityNormalized"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrDodgerBlue
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double VolNormalized[];
    //---
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- prepare
       SetIndexBuffer(0, VolNormalized,INDICATOR_DATA);
       ArrayInitialize(VolNormalized,EMPTY_VALUE);
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| 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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total<BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(VolNormalized,EMPTY_VALUE);
          return(0);
         }
    //--- check new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
       int start=rates_total-BarsToShow;
    //---
       for(int i=0; i<start; i++)
          VolNormalized[i]=EMPTY_VALUE;
    //--- copy close prices
       vector<double> price(BarsToShow);
       for(int i=0; i<BarsToShow; i++)
          price[i]=close[start+i];
    //---
       vector<double> l1(BarsToShow);
       bool res=price.L1TrendFilter(CoefLambda,true,l1);
       if(res)
         {
          //--- compute normalized volatility
          double mean=0.0;
          double stddev=0.0;
          for(int i=0; i<BarsToShow; i++)
             mean+=close[start+i]-l1[i];
          mean/=BarsToShow;
          //---
          for(int i=0; i<BarsToShow; i++)
             stddev+=MathPow(close[start+i]-l1[i]-mean,2);
          stddev=MathSqrt(stddev/BarsToShow);
          //---
          for(int i=0; i<BarsToShow; i++)
             VolNormalized[start+i]=stddev>0?(close[start+i]-l1[i])/stddev:0;
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    計算結果を図14に示します。

    図14:L1VolatilityNormalized.mq5インジケータ

    図14:L1VolatilityNormalized.mq5インジケータ


      3.4.4.5. L1VolatilityNormalizedSmoothed.mq5:平滑化された正規化ボラティリティ指標

      このインジケータは、正規化アプローチを拡張し、指数移動平均(EMA)による平滑化を追加したものです。

      利点は以下の通りです。

      • 短期的ノイズおよび急激なスパイクの影響を低減する
      • より明確で解釈しやすいボラティリティプロファイルを生成する
      • 持続的なボラティリティおよび現在の市場レジームの評価を容易にする

      このインジケータは、安定したボラティリティ推定を必要とする適応型戦略に特に有用です。たとえば、取引モードを自動的に選択する場合などに利用されます。

      L1VolatilityNormalizedSmoothed.mq5インジケータのコードを以下に示します。

      //+------------------------------------------------------------------+
      //|                               L1VolatilityNormalizedSmoothed.mq5 |
      //|                             Copyright 2000-2026, MetaQuotes Ltd. |
      //|                                             https://www.mql5.com |
      //+------------------------------------------------------------------+
      #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
      #property link      "https://www.mql5.com"
      #property version   "1.00"
      #property indicator_separate_window
      #property indicator_buffers 1
      #property indicator_plots   1
      
      #property indicator_label1  "L1VolatilityNormalizedSmoothed"
      #property indicator_type1   DRAW_LINE
      #property indicator_color1  clrDeepSkyBlue
      #property indicator_width1  2
      //---
      input int    BarsToShow   = 1000;  // Number of bars to calculate L1
      input double CoefLambda   = 0.015; // Lambda in lambda_max units
      input int    SmoothPeriod = 10;    // EMA smoothing period (1=no smoothing)
      //---
      double NormVolSmooth[];
      //+------------------------------------------------------------------+
      //| Indicator initialization function                                |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //--- prepare
         SetIndexBuffer(0,NormVolSmooth,INDICATOR_DATA);
         ArrayInitialize(NormVolSmooth,EMPTY_VALUE);
         PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
         IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
      //---
         return(INIT_SUCCEEDED);
        }
      //+------------------------------------------------------------------+
      //| 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[])
        {
      //--- check bars
         static bool warned=false;
         if(rates_total < BarsToShow)
           {
            if(!warned)
              {
               Print("Waiting for enough bars: ",BarsToShow);
               warned=true;
              }
            ArrayInitialize(NormVolSmooth,EMPTY_VALUE);
            return(0);
           }
      //--- check  new bar
         static datetime last_bar_time=0;
         bool new_bar=(time[0]!=last_bar_time);
         bool need_recalc= (prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
         if(!need_recalc)
            return(prev_calculated);
         last_bar_time=time[0];
         int start=rates_total-BarsToShow;
      //---
         for(int i=0; i<start; i++)
            NormVolSmooth[i]=EMPTY_VALUE;
      //--- copy close prices
         vector<double> price(BarsToShow);
         for(int i=0; i<BarsToShow; i++)
            price[i]=close[start+i];
      //---
         vector<double> l1(BarsToShow);
         bool res=price.L1TrendFilter(CoefLambda,true,l1);
         if(res)
           {
            //--- compute normalized volatility
            vector<double> VolNormalized(BarsToShow);
            double mean = 0, stddev = 0;
            for(int i=0; i<BarsToShow; i++)
               mean += close[start+i]-l1[i];
            mean /= BarsToShow;
            //---
            for(int i=0; i<BarsToShow; i++)
               stddev += MathPow(close[start+i]-l1[i]-mean,2);
            stddev = MathSqrt(stddev/BarsToShow);
            //---
            for(int i=0; i<BarsToShow; i++)
               VolNormalized[i]=stddev>0 ? (close[start+i]-l1[i])/stddev: 0;
            //--- EMA smoothing
            vector<double> Smooth(BarsToShow);
            double alpha=(SmoothPeriod<=1) ? 1.0: 2.0/(SmoothPeriod+1.0);
            //---
            Smooth[0] = VolNormalized[0];
            for(int i=1; i<BarsToShow; i++)
               Smooth[i]=alpha*VolNormalized[i]+(1.0-alpha)*Smooth[i-1];
            //--- copy to indicator buffer
            for(int i=0; i<BarsToShow; i++)
               NormVolSmooth[start+i]=Smooth[i];
           }
      //---
         return(rates_total);
        }
      //+------------------------------------------------------------------+
      

      計算結果を図15に示します。

      図15:L1VolatilityNormalized.mq5およびL1VolatilityNormalizedSmoothed.mq5インジケータ

      図15:L1VolatilityNormalized.mq5およびL1VolatilityNormalizedSmoothed.mq5インジケータ



      3.4.4.6. L1VolatilityRegime.mq5:ボラティリティに基づく市場レジーム検出

      このインジケータは、正規化および平滑化されたボラティリティに基づいて現在の市場レジームを分類し、4つの市場状態を識別します。

      インジケータの特徴は以下の通りです。

      • 完全に自律的であり、外部データを必要としない
      • 適応型戦略のための市場ダイナミクスを明確に可視化する
      • LowVolThreshおよびHighVolThreshの閾値パラメータは、銘柄および時間足に応じて調整可能
      レジーム 説明
      0 レンジ
      低ボラティリティ、横ばい市場
      1 トレンド
      中程度のボラティリティ、トレンドの存在
      2 エクスパンション
      強い値動き、市場の拡張
      3 パニック 極端なボラティリティ、急激な値動き

      表3:L1VolatilityRegime.mq5インジケータのレジーム分類

      • 現在の市場レジームを迅速に判定する
      • 市場状況に応じてトレード戦略を適応させる
      • 極端な値動き時のリスクを低減し、取引効率を向上させる

      L1VolatilityRegime.mq5インジケータのコードを以下に示します。

      //+------------------------------------------------------------------+
      //|                                           L1VolatilityRegime.mq5 |
      //|                             Copyright 2000-2026, MetaQuotes Ltd. |
      //|                                             https://www.mql5.com |
      //+------------------------------------------------------------------+
      #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
      #property link      "https://www.mql5.com"
      #property version   "1.00"
      #property indicator_separate_window
      #property indicator_buffers 1
      #property indicator_plots   1
      //---
      #property indicator_label1  "L1 Volatility Regime"
      #property indicator_type1   DRAW_LINE
      #property indicator_color1  clrRoyalBlue
      #property indicator_width1  2
      //--- input parameters
      input int    BarsToShow    = 1000;  // Number of bars to calculate L1
      input double CoefLambda    = 0.015; // Lambda in lambda_max units
      input int    ATRPeriod     = 14;    // ATR period
      input int    SmoothPeriod  = 10;    // Smooth period
      input double L1MoveThresh  = 0.0;   // Move volatility
      input double LowVolThresh  = 0.5;   // Low volatility
      input double HighVolThresh = 1.5;   // High volatility
      input double PanicMult     = 2.0;   // Panic volatility
      //---
      double Regime[];
      //+------------------------------------------------------------------+
      //| Indicator initialization function                                |
      //+------------------------------------------------------------------+
      int OnInit()
        {
         SetIndexBuffer(0, Regime, INDICATOR_DATA);
         PlotIndexSetDouble(0,PLOT_EMPTY_VALUE, EMPTY_VALUE);
         IndicatorSetInteger(INDICATOR_DIGITS, 0);
      //---
         return(INIT_SUCCEEDED);
        }
      //+------------------------------------------------------------------+
      //| 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[])
        {
      //--- check bars
         if(rates_total<BarsToShow+ATRPeriod)
           {
            ArrayInitialize(Regime,EMPTY_VALUE);
            return(0);
           }
      //--- check new bar
         static datetime last_bar_time=0;
         bool new_bar=(time[0]!=last_bar_time);
         bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
         if(!need_recalc)
            return(prev_calculated);
         last_bar_time=time[0];
      //---
         int start=rates_total-BarsToShow;
         int count=BarsToShow;
      //---
         for(int i=0; i<start; i++)
            Regime[i]=EMPTY_VALUE;
      //---
         vector<double> DataClose(count),DataHigh(count),DataLow(count);
         for(int i=0; i<count; i++)
           {
            DataClose[i]=close[start+i];
            DataHigh[i]=high[start+i];
            DataLow[i]=low[start+i];
           }
      //---
         vector<double> L1(count);
         bool res=DataClose.L1TrendFilter(CoefLambda,true,L1);
         if(!res)
            return(prev_calculated);
      //---
         vector<double> TR(count),ATR(count);
         for(int i=0; i<count; i++)
           {
            if(i==0)
               TR[i]=DataHigh[i]-DataLow[i];
            else
              {
               double h_l=DataHigh[i]-DataLow[i];
               double h_pc=MathAbs(DataHigh[i]-DataClose[i-1]);
               double l_pc=MathAbs(DataLow[i]-DataClose[i-1]);
               TR[i]=MathMax(h_l,MathMax(h_pc,l_pc));
              }
            int from=MathMax(0,i-ATRPeriod+1);
            double sumTR=0.0;
            int n = i-from+1;
            for(int j=from; j<=i;j++)
               sumTR += TR[j];
            ATR[i]=sumTR/n;
           }
      //---
         vector<double> NormVol(count), SmoothVol(count);
         for(int i=0; i<count; i++)
            NormVol[i]=(ATR[i]>0) ? MathAbs(DataClose[i]-L1[i])/ATR[i] : 0;
         double alpha=2.0/(SmoothPeriod+1.0);
         SmoothVol[0]=NormVol[0];
         for(int i=1; i<count; i++)
            SmoothVol[i]=alpha*NormVol[i]+(1.0-alpha)*SmoothVol[i-1];
      //---
         for(int i=0; i<count; i++)
           {
            double vol=SmoothVol[i];
            double deltaL1=(i>0) ? (L1[i]-L1[i-1]): 0.0;
            if(vol<LowVolThresh)
               Regime[start+i]=0; // Range
            else
               if(vol>=LowVolThresh && vol<HighVolThresh)
                  Regime[start+i]=(MathAbs(deltaL1)>L1MoveThresh) ? 1:0; // Trend/Range
               else
                  if(vol>=HighVolThresh && vol<HighVolThresh*PanicMult)
                     Regime[start+i]=2; // Expansion
                  else
                     Regime[start+i]=3; // Panic
           }
      //---
         return(rates_total);
        }
      //+------------------------------------------------------------------+
      

      計算結果を図16に示します。

      図16:L1VolatilityRegime.mq5インジケータ

      図16:L1VolatilityRegime.mq5インジケータ

        利便性のために、レジームをカラーで可視化するバージョンも使用することができます。

        L1VolatilityRegimeColor.mq5インジケータのコードを以下に示します。

        //+------------------------------------------------------------------+
        //|                                      L1VolatilityRegimeColor.mq5 |
        //|                             Copyright 2000-2026, MetaQuotes Ltd. |
        //|                                             https://www.mql5.com |
        //+------------------------------------------------------------------+
        #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
        #property link      "https://www.mql5.com"
        #property version   "1.00"
        #property indicator_separate_window
        #property indicator_buffers 4
        #property indicator_plots   4
        //---
        #property indicator_label1  "Range"
        #property indicator_type1   DRAW_LINE
        #property indicator_color1  clrDodgerBlue
        #property indicator_width1  2
        //---
        #property indicator_label2  "Trend"
        #property indicator_type2   DRAW_LINE
        #property indicator_color2  clrLime
        #property indicator_width2  2
        //---
        #property indicator_label3  "Expansion"
        #property indicator_type3   DRAW_LINE
        #property indicator_color3  clrOrange
        #property indicator_width3  2
        //---
        #property indicator_label4  "Panic"
        #property indicator_type4   DRAW_LINE
        #property indicator_color4  clrRed
        #property indicator_width4  2
        //--- input parameters
        input int    BarsToShow    = 1000;  // Number of bars to calculate L1
        input double CoefLambda    = 0.015; // Lambda in lambda_max units
        input int    ATRPeriod     = 14;    // ATR period
        input int    SmoothPeriod  = 10;    // Smooth period
        input double L1MoveThresh  = 0.0;   // Move volatility
        input double LowVolThresh  = 0.5;   // Low volatility
        input double HighVolThresh = 1.5;   // High volatility
        input double PanicMult     = 2.0;   // Panic volatility
        //--- buffers
        double Regime[];
        double BufRange[], BufTrend[], BufExpansion[], BufPanic[];
        //+------------------------------------------------------------------+
        //| Indicator initialization function                                |
        //+------------------------------------------------------------------+
        int OnInit()
          {
           SetIndexBuffer(0,BufRange,INDICATOR_DATA);
           SetIndexBuffer(1,BufTrend,INDICATOR_DATA);
           SetIndexBuffer(2,BufExpansion,INDICATOR_DATA);
           SetIndexBuffer(3,BufPanic,INDICATOR_DATA);
        //---
           PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
           PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
           PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,EMPTY_VALUE);
           PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,EMPTY_VALUE);
        //---
           IndicatorSetInteger(INDICATOR_DIGITS,0);
           return(INIT_SUCCEEDED);
          }
        //+------------------------------------------------------------------+
        //| 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[])
          {
           if(rates_total<BarsToShow+ATRPeriod)
             {
              ArrayInitialize(Regime,EMPTY_VALUE);
              ArrayInitialize(BufRange,EMPTY_VALUE);
              ArrayInitialize(BufTrend,EMPTY_VALUE);
              ArrayInitialize(BufExpansion,EMPTY_VALUE);
              ArrayInitialize(BufPanic,EMPTY_VALUE);
              return(0);
             }
        //--- new bars
           static datetime last_bar_time=0;
           bool new_bar=(time[0]!=last_bar_time);
           bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
           if(!need_recalc)
              return(prev_calculated);
           last_bar_time=time[0];
        //---
           int start=rates_total-BarsToShow;
           int count=BarsToShow;
        //---
           ArrayResize(Regime,rates_total);
           for(int i=0;i<start;i++)
              Regime[i]=EMPTY_VALUE;
        //---
           vector<double> DataClose(count),DataHigh(count),DataLow(count);
           for(int i=0; i<count; i++)
             {
              DataClose[i]=close[start+i];
              DataHigh[i]=high[start+i];
              DataLow[i]=low[start+i];
             }
        //---
           vector<double> L1(count);
           bool res=DataClose.L1TrendFilter(CoefLambda,true,L1);
           if(!res)
              return(prev_calculated);
        //---
           vector<double> TR(count),ATR(count);
           for(int i=0; i<count; i++)
             {
              if(i==0)
                 TR[i]=DataHigh[i]-DataLow[i];
              else
                {
                 double h_l = DataHigh[i]-DataLow[i];
                 double h_pc = MathAbs(DataHigh[i]-DataClose[i-1]);
                 double l_pc = MathAbs(DataLow[i]-DataClose[i-1]);
                 TR[i] = MathMax(h_l, MathMax(h_pc, l_pc));
                }
              int from=MathMax(0,i-ATRPeriod+1);
              double sumTR=0;
              int n=i-from+1;
              for(int j=from; j<=i; j++)
                 sumTR+=TR[j];
              ATR[i]=sumTR/n;
             }
        //---
           vector<double> NormVol(count), SmoothVol(count);
           for(int i=0;i<count;i++)
              NormVol[i]=(ATR[i]>0) ? MathAbs(DataClose[i]-L1[i])/ATR[i] : 0;
        //---
           double alpha=2.0/(SmoothPeriod+1.0);
           SmoothVol[0]=NormVol[0];
           for(int i=1; i<count; i++)
              SmoothVol[i]=alpha*NormVol[i]+(1.0-alpha)*SmoothVol[i-1];
        //--- calc Regime[]
           for(int i=0; i<count; i++)
             {
              double vol=SmoothVol[i];
              double deltaL1=(i>0) ? (L1[i]-L1[i-1]):0.0;
              if(vol<LowVolThresh)
                 Regime[start+i]=0;
              else
                 if(vol<HighVolThresh)
                    Regime[start+i]=(MathAbs(deltaL1)>L1MoveThresh) ? 1:0;
                 else
                    if(vol<HighVolThresh*PanicMult)
                       Regime[start+i]=2;
                    else
                       Regime[start+i]=3;
             }
        //--- buffers
           for(int i=0; i<rates_total; i++)
             {
              BufRange[i] = (Regime[i]==0) ? Regime[i]: EMPTY_VALUE;
              BufTrend[i] = (Regime[i]==1) ? Regime[i]: EMPTY_VALUE;
              BufExpansion[i] = (Regime[i]==2) ? Regime[i]: EMPTY_VALUE;
              BufPanic[i] = (Regime[i]==3) ? Regime[i]: EMPTY_VALUE;
             }
        //---
           return(rates_total);
          }
        //+------------------------------------------------------------------+
        

        総合的な計算結果を図17に示します。

        図17:L1VolatilityRegime.mq5およびL1VolatilityRegimeColor.mq5インジケータ

        図17:L1VolatilityRegime.mq5およびL1VolatilityRegimeColor.mq5インジケータ


        図18〜20では、EURGBP、AUDCAD、およびCHFJPYにおける全ボラティリティインジケータの同時表示例を示します。

        図18:EURGBPのボラティリティインジケータ

        図18:EURGBPのボラティリティインジケータ


        図19:AUDCADのボラティリティインジケータ

        図19:AUDCADのボラティリティインジケータ


        図20:CHFJPYのボラティリティインジケータ

        図20:CHFJPYのボラティリティインジケータ


        3.5. トレード戦略におけるL1トレンドの応用

        このセクションでは、移動平均、MACD、ADX、およびEMA戦略において、異なる売買シグナルフィルタの適用方法について検討します。

        売買シグナルにフィルタを追加することで、トレードシステムの特性を改善できます。本記事で提示するEAのフィルタ有無による有効性を分析するため、各新規バーごとにバランスおよびエクイティデータを別ファイルへ保存し、Pythonスクリプトを用いて異なるモードにおけるシステムの結果を可視化します。

        すべてのEAは同一のアーキテクチャを持ちます。

        L1トレンドと売買シグナルの整合性の基本原理

        • オープンフィルタ(L1FilterOpen = true)は、ポジションを支配的トレンド方向のみに限定して建てることを可能にする
        • クローズフィルタ(L1FilterClose = true)は、強いトレンド局面ではポジションを保持し、局所的な調整による早期決済を抑制する

        以下は、共通入力パラメータ(すべてのEA共通)です。

        //--- L1 filter parameters
        input int    L1TotalBars    = 1000;   // Total bars for L1 filter
        input bool   L1FilterOpen   = false;  // Use filter for Open
        input bool   L1FilterClose  = false;  // Use filter for Close
        input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
        //--- save statistics
        input bool   SaveStatistics = false;  // Save statistics to file

        バランスおよびエクイティデータのファイル保存

        inputパラメータSaveStatisticsは、時間、終値、バランス、エクイティなどの現在値を、新しいバーが形成されるたびにファイルへ保存する機能を有効化します。データはパス「terminal_data_folder\Tester\Agent-127.0.0.1-3000\MQL5\Files」に保存されます。

        データ保存処理はOnTick()内で呼び出され、inputパラメータbool SaveStatisticsの値に依存して実行されます。

        //+------------------------------------------------------------------+
        //| Expert OnTick function                                           |
        //+------------------------------------------------------------------+
        void OnTick()
          {
        //--- trade only at new bar
           if(!IsNewBar())
              return;
        //--- check trade conditions
           if(SelectPosition())
              CheckForClose();
           else
              CheckForOpen();
        //--- save account statistics
           if(SaveStatistics)
              SaveAccountStatistics();
          }

        保存されるファイル名の接頭辞は、L1FilterOpenとL1FilterCloseの組み合わせに依存します。

        ファイル名は戦略および銘柄に依存し、EAの初期化関数内で生成されます。

        //+------------------------------------------------------------------+
        //| PrepareStrategyFileName                                          |
        //+------------------------------------------------------------------+
        string PrepareStrategyFileName(string strategy_name)
          {
           int v=0;
           if(L1FilterOpen)
              v=v | 1;
        //---
           if(L1FilterClose)
              v=v | 2;
        //---
           string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
           return filename;
          }
        //+------------------------------------------------------------------+
        //| Expert initialization                                            |
        //+------------------------------------------------------------------+
        int OnInit()
          {
        //--- prepare filename
           ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
        //--- delete old file if exists
           if(FileIsExist(ExtStrategyFileName))
              FileDelete(ExtStrategyFileName);
        //---
           return INIT_SUCCEEDED;
          }

        売買シグナルフィルタは、さまざまなモードで適用することができます。

        //+------------------------------------------------------------------+
        //| Save account statistics to file                                  |
        //+------------------------------------------------------------------+
        void SaveAccountStatistics()
          {
        //--- check file name
           if(ExtStrategyFileName=="")
              return;
        //---
           int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
           if(file==INVALID_HANDLE)
             {
              Print("File open error: ",GetLastError());
              return;
             }
        //--- append
           FileSeek(file,0,SEEK_END);
        //--- account data
           double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
           double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
           double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
           double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
           double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
        //--- volume
           double volume=0.0;
           if(PositionSelect(_Symbol))
              volume=PositionGetDouble(POSITION_VOLUME);
        //--- time
           datetime t[1];
           if(CopyTime(_Symbol,_Period,0,1,t)<=0)
             {
              FileClose(file);
              return;
             }
           double current_close[1];
           if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
             {
              FileClose(file);
              return;
             }
           string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                    balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
        //---
           FileWrite(file,line);
        //---
           FileClose(file);
          }

        異なるモードでの売買シグナルフィルタの適用

        ストラテジーテスターでEAを順次実行し、以下の4つの組み合わせをすべて評価します。

        1. L1FilterOpen = false, L1FilterClose = false(フィルタなしの取引)
        2. L1FilterOpen = true, L1FilterClose = false(エントリーフィルタ)
        3. L1FilterOpen = false, L1FilterClose = true(エグジットフィルタ)
        4. L1FilterOpen = true, L1FilterClose = true(エントリーおよびエグジットの両方にフィルタ適用)

        この実行により、0_MA_EURUSD.txt、1_MA_EURUSD.txt、2_MA_EURUSD.txt、3_MA_EURUSD.txtのようなファイルが生成されます。

        これらのファイルにはバランスやエクイティのデータが含まれており、L1トレンドとの整合性による売買シグナルフィルタリングの有効性を比較するために使用できます。

        データの可視化

        チャートを作成するには、これら4つのファイルを別フォルダ(例:「C:\data\」)にコピーし、Pythonスクリプトを実行します。

        import pandas as pd
        import matplotlib.pyplot as plt
        import os
        
        # --- folder for charts
        output_dir = "C:\\data\\charts\\"
        os.makedirs(output_dir, exist_ok=True)
        
        symbol = "EURUSD" 
        name_strategy = "MA"
        file_strategy = name_strategy+"_"+symbol 
        title_strategy = " ("+symbol+" "+name_strategy+" strategy+filters)"
        file_prefix = symbol+"_"+name_strategy+"_"
        
        # --- files
        files = [
            "C:\\data\\0_"+file_strategy+".txt",
            "C:\\data\\1_"+file_strategy+".txt",
            "C:\\data\\2_"+file_strategy+".txt",
            "C:\\data\\3_"+file_strategy+".txt"
        ]
        
        # --- labels
        labels = [
            "No filters",
            "Open L1 filter",
            "Close L1 filter",
            "Open+Close L1 filter"
        ]
        
        # --- load data
        def load_file(filename):
            df = pd.read_csv(
                filename,
                sep=";",
                header=None,
                names=[
                    "time",
                    "balance",
                    "equity",
                    "margin",
                    "free_margin",
                    "margin_level",
                    "volume",
                    "close"
                ]
            )
            df["time"] = pd.to_datetime(df["time"])
            return df
        
        # --- close price chart
        plt.figure(figsize=(10,6), dpi=100)
        for file, label in zip(files, labels):
            df = load_file(file)
        plt.plot(df["time"], df["close"], color='gray')
        plt.title(symbol+" Close Price")
        plt.xlabel("Time")
        plt.ylabel("closing price")
        plt.legend()
        plt.grid()
        plt.tight_layout()
        plt.savefig(output_dir + file_prefix+"close_price.png", dpi=100)
        plt.show()
            
        # --- balance chart
        plt.figure(figsize=(10,6), dpi=100)
        for file, label in zip(files, labels):
            df = load_file(file)
            plt.plot(df["time"], df["balance"], label=label)
        plt.title("Balance" + title_strategy)
        plt.xlabel("Time")
        plt.ylabel("Balance")
        plt.legend()
        plt.grid()
        plt.tight_layout()
        plt.savefig(output_dir + file_prefix+"balance.png", dpi=100)
        plt.show()
        plt.close()
        
        # --- equity chart
        plt.figure(figsize=(10,6), dpi=100)
        for file, label in zip(files, labels):
            df = load_file(file)
            plt.plot(df["time"], df["equity"], label=label)
        plt.title("Equity" + title_strategy)
        plt.xlabel("Time")
        plt.ylabel("Equity")
        plt.legend()
        plt.grid()
        plt.tight_layout()
        plt.savefig(output_dir + file_prefix+"equity.png", dpi=100)
        plt.show()
        plt.close()
        
        #--- balance + equity chart
        plt.figure(figsize=(10,6), dpi=100)
        for i, (file, label) in enumerate(zip(files, labels)):
        
            df = load_file(file)
            # --- get matplotlib color
            color = plt.rcParams["axes.prop_cycle"].by_key()["color"][i % 10]
            #--- equity — solid line
            plt.plot(
                df["time"],
                df["equity"],
                color=color,
                linestyle="-",
                label=f"{label} equity"
            )
            #--- balance — dashed line
            plt.plot(
                df["time"],
                df["balance"],
                color=color,
                linestyle="--",
                label=f"{label} balance"
            )
        plt.title("Balance + Equity" + title_strategy)
        plt.xlabel("Time")
        plt.ylabel("Value")
        plt.legend()
        plt.grid()
        plt.tight_layout()
        plt.savefig(output_dir+file_prefix+"balance_equity.png", dpi=100)
        plt.show()
        plt.close()


        売買シグナルフィルタの実装

        パラメータL1TotalBars、L1FilterOpen、L1FilterClose、L1CoefLambdaは、L1トレンドの計算設定および売買シグナルのフィルタリングにおけるその利用方法を定義します。

        L1トレンド計算と売買シグナルの整合性

        すべてのEAにおいて、L1トレンド解析を用いた追加フィルタリングが適用されます。

          1. 直近のL1TotalBars本のデータを用いて平滑化された価格系列を構築します。
          2. トレンドの成長係数deltaを、フィルタ後の直近2点の差として計算します。
            delta > 0の場合は上昇トレンド、delta < 0の場合は下降トレンドと判定されます。
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1]-data_filtered[data_count-2];
            //---
               return(dp);
              }
            

            パラメータL1CoefLambdaはλmax単位で指定されており、これによりボラティリティやバー数の変化に対してロバストなフィルタリングが可能になります。

            トレードエントリーフィルタ

            L1FilterOpen = trueの場合:

            • L1トレンドが負の場合、BUYシグナルは無視
            • L1トレンドが正の場合、SELLシグナルは無視

            この条件により、トレードは支配的なトレンド方向にのみ建てられます。

            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
               if(signal == WRONG_VALUE)
                  return;
            //--- L1 filter
               if(L1FilterOpen)
                 {
                  double delta = CheckTrendL1();
                  if(signal == ORDER_TYPE_BUY && delta < 0)
                    {
                     signal = WRONG_VALUE;
                     PrintFormat("Open BUY signal cancelled by L1 trend delta=%.5f", delta);
                    }
                  if(signal == ORDER_TYPE_SELL && delta > 0)
                    {
                     signal = WRONG_VALUE;
                     PrintFormat("Open SELL signal cancelled by L1 trend delta=%.5f", delta);
                    }
                 }
            //---
               if(signal == WRONG_VALUE)
                  return;
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars)
                  return;
            //---
               double price = (signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK): SymbolInfoDouble(_Symbol,SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0);
              }


            トレードエグジットフィルタ

            L1FilterClose = trueの場合:

            • L1トレンドが上昇中である限り、BUYポジションは逆シグナルによって決済されない
            • L1トレンドが下降中である限り、SELLポジションは決済されない

            これにより、強いトレンド局面ではポジションを保持できるようになり、局所的な調整による早期決済を抑制できます。

            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=MA_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type = PositionGetInteger(POSITION_TYPE);
               bool close_signal = false;
            //---
               if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL)
                  close_signal = true;
               if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY)
                  close_signal = true;
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double delta = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && delta > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend delta=%.5f", delta);
                    }
                  if(type == POSITION_TYPE_SELL && delta < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend delta=%.5f", delta);
                    }
                 }
            //---
               if(close_signal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>=L1TotalBars)
                  ExtTrade.PositionClose(_Symbol,3);
              }



            3.5.1. 移動平均トレード戦略

            最初の例として、古典的な移動平均に基づくトレンドフォロー型戦略を用いたEAを考えます。

            売買シグナルは、終値と移動平均線のクロスオーバーに基づいて生成されます。

            • BUY:終値が移動平均線を下から上へクロスした場合
            • SELL:終値が移動平均線を上から下へクロスした場合

            シグナルは直近2本の確定バーを用いて評価されます。これにより、現在形成中の未確定バーの影響を排除し、偽シグナルを低減します。

            さらに、L1トレンド設定(L1TotalBars、L1FilterOpen、L1FilterClose、L1CoefLambda)に基づいたエントリーおよびイグジットフィルタが適用されます。

            MovingAverageFilteredL1.mq5のコードを以下に示します。

            //+------------------------------------------------------------------+
            //|                                      MovingAverageFilteredL1.mq5 |
            //|                             Copyright 2000-2026, MetaQuotes Ltd. |
            //|                                             https://www.mql5.com |
            //+------------------------------------------------------------------+
            #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
            #property link      "https://www.mql5.com"
            #property version   "1.00"
            //--- best MovingAverage parameters for EURUSD,H1,2025
            input int    MovingPeriod   = 61;     // MA period
            input int    MovingShift    = 0;      // MA shift
            //--- trade volume
            input double TradeLot       = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;   // Total bars for L1 filter
            input bool   L1FilterOpen   = false;  // Use filter for Open
            input bool   L1FilterClose  = false;  // Use filter for Close
            input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false;  // Save statistics to file
            //---
            #define MA_MAGIC 1234501
            #include <Trade\Trade.mqh>
            CTrade ExtTrade;
            int    ExtHandle = INVALID_HANDLE;
            bool   ExtHedging = false;
            string ExtStrategyName="MA";
            string ExtStrategyFileName="";
            //+------------------------------------------------------------------+
            //| Check new bar                                                    |
            //+------------------------------------------------------------------+
            bool IsNewBar()
              {
               static datetime last_time=0;
               datetime t[1];
            //---
               if(CopyTime(_Symbol,_Period,0,1,t)>0)
                 {
                  if(t[0]!=last_time)
                    {
                     last_time=t[0];
                     return(true);
                    }
                 }
               return(false);
              }
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1]-data_filtered[data_count-2];
            //---
               return(dp);
              }
            //+------------------------------------------------------------------+
            //| GetTradeSignal                                                   |
            //+------------------------------------------------------------------+
            bool GetTradeSignal(ENUM_ORDER_TYPE &signal)
              {
               signal = WRONG_VALUE;
               MqlRates bars[];
               double ma[];
               ArraySetAsSeries(bars,true);
               ArraySetAsSeries(ma,true);
               ArrayResize(bars,2);
               ArrayResize(ma,2);
            //-- two last closed bars
               if(CopyRates(_Symbol,_Period,2,2,bars) != 2)
                 {
                  Print("CopyRates failed");
                  return(false);
                 }
               if(CopyBuffer(ExtHandle,0,2,2,ma) != 2)
                 {
                  Print("CopyBuffer failed");
                  return(false);
                 }
               double close_prev = bars[1].close;
               double close_last = bars[0].close;
               double ma_prev = ma[1];
               double ma_last = ma[0];
            //--- check MA crossover
               if(close_prev < ma_prev && close_last > ma_last)
                  signal = ORDER_TYPE_BUY;
               else
                  if(close_prev > ma_prev && close_last < ma_last)
                     signal = ORDER_TYPE_SELL;
            //--- log
            //   PrintFormat("PrevBar: time=%s close=%.5f ma=%.5f | LastBar: time=%s close=%.5f ma=%.5f | Signal=%s",
            //               TimeToString(bars[0].time,TIME_DATE|TIME_MINUTES), close_prev, ma_prev,
            //               TimeToString(bars[1].time,TIME_DATE|TIME_MINUTES), close_last, ma_last,
            //               (signal==ORDER_TYPE_BUY?"BUY":signal==ORDER_TYPE_SELL?"SELL":"NONE"));
            //---
               return(true);
              }
            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
               if(signal == WRONG_VALUE)
                  return;
            //--- L1 filter
               if(L1FilterOpen)
                 {
                  double delta = CheckTrendL1();
                  if(signal == ORDER_TYPE_BUY && delta < 0)
                    {
                     signal = WRONG_VALUE;
                     PrintFormat("Open BUY signal cancelled by L1 trend delta=%.5f", delta);
                    }
                  if(signal == ORDER_TYPE_SELL && delta > 0)
                    {
                     signal = WRONG_VALUE;
                     PrintFormat("Open SELL signal cancelled by L1 trend delta=%.5f", delta);
                    }
                 }
            //---
               if(signal == WRONG_VALUE)
                  return;
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars)
                  return;
            //---
               double price = (signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK): SymbolInfoDouble(_Symbol,SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0);
              }
            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=MA_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type = PositionGetInteger(POSITION_TYPE);
               bool close_signal = false;
            //---
               if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL)
                  close_signal = true;
               if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY)
                  close_signal = true;
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double delta = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && delta > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend delta=%.5f", delta);
                    }
                  if(type == POSITION_TYPE_SELL && delta < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend delta=%.5f", delta);
                    }
                 }
            //---
               if(close_signal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>=L1TotalBars)
                  ExtTrade.PositionClose(_Symbol,3);
              }
            //+------------------------------------------------------------------+
            //| SelectPosition                                                   |
            //+------------------------------------------------------------------+
            bool SelectPosition()
              {
               bool res = false;
               if(ExtHedging)
                 {
                  uint total = PositionsTotal();
                  for(uint i=0; i<total; i++)
                    {
                     string sym = PositionGetSymbol(i);
                     if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==MA_MAGIC)
                       {
                        res = true;
                        break;
                       }
                    }
                 }
               else
                 {
                  if(PositionSelect(_Symbol))
                     res = (PositionGetInteger(POSITION_MAGIC)==MA_MAGIC);
                 }
               return(res);
              }
            //+------------------------------------------------------------------+
            //| Expert initialization function                                   |
            //+------------------------------------------------------------------+
            int OnInit()
              {
            //--- check parameters
               if(MovingPeriod<=0)
                 {
                  Print("Error: MovingPeriod parameter must be positive");
                  return(INIT_PARAMETERS_INCORRECT);
                 }
               ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
               ExtTrade.SetExpertMagicNumber(MA_MAGIC);
               ExtTrade.SetMarginMode();
               ExtTrade.SetTypeFillingBySymbol(_Symbol);
            //--- prepare indicator
               ExtHandle = iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
               if(ExtHandle==INVALID_HANDLE)
                 {
                  Print("Failed to create MA handle");
                  return(INIT_FAILED);
                 }
            //--- prepare filename
               ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
            //--- delete old file if exists
               if(FileIsExist(ExtStrategyFileName))
                  FileDelete(ExtStrategyFileName);
            //---
               return(INIT_SUCCEEDED);
              }
            //+------------------------------------------------------------------+
            //| PrepareStrategyFileName                                          |
            //+------------------------------------------------------------------+
            string PrepareStrategyFileName(string strategy_name)
              {
               int v=0;
               if(L1FilterOpen)
                  v=v | 1;
            //---
               if(L1FilterClose)
                  v=v | 2;
            //---
               string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
               return(filename);
              }
            //+------------------------------------------------------------------+
            //| Save account statistics to file                                  |
            //+------------------------------------------------------------------+
            void SaveAccountStatistics()
              {
            //--- check file name
               if(ExtStrategyFileName=="")
                  return;
            //---
               int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
               if(file==INVALID_HANDLE)
                 {
                  Print("File open error: ",GetLastError());
                  return;
                 }
            //--- append
               FileSeek(file,0,SEEK_END);
            //--- account data
               double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
               double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
               double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
               double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
               double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
            //--- volume
               double volume=0.0;
               if(PositionSelect(_Symbol))
                  volume=PositionGetDouble(POSITION_VOLUME);
            //--- time
               datetime t[1];
               if(CopyTime(_Symbol,_Period,0,1,t)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               double current_close[1];
               if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                        balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
            //---
               FileWrite(file,line);
            //---
               FileClose(file);
              }
            //+------------------------------------------------------------------+
            //| Expert OnTick function                                           |
            //+------------------------------------------------------------------+
            void OnTick()
              {
            //--- trade only at new bar
               if(!IsNewBar())
                  return;
            //--- check trade conditions
               if(SelectPosition())
                  CheckForClose();
               else
                  CheckForOpen();
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
              }
            //+------------------------------------------------------------------+
            //| Expert deinitialization function                                 |
            //+------------------------------------------------------------------+
            void OnDeinit(const int reason)
              {
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
            //---
               IndicatorRelease(ExtHandle);
              }
            //+------------------------------------------------------------------+
            

            3.5.1.1. L1トレンドフィルタリングの有効性評価の一般的方法論

            L1フィルタの有効性を評価するためには、以下の手順が必要です。

            1. まず、最大利益を得るためのトレード戦略の最適パラメータセットを見つける必要があります。
              その上で、最もパフォーマンスの良い戦略について、売買シグナルの改善を検討することが望まれます。

            2. L1FilterOpen/L1FilterCloseの設定により、以下の4種類のフィルタ適用パターンを比較します。
              • フィルタなしのトレード
              • エントリーフィルタありのトレード
              • エグジットフィルタありのトレード
              • エントリーおよびエグジット両方のフィルタありのトレード
              テスト結果はSaveStatistics = trueを設定することでファイルに保存されます。生成されたファイルはC:\Data\などの別フォルダにコピーします。

            3. その後、Pythonスクリプトを実行して統合プロットを生成します。

            これらの手順は、MovingAverageFilteredL1.mq5EAの例を用いて考えます。このEAはEURUSDを対象に、時間足H1で取引をおこない、テスト期間は2025年とします。

            フィルタ処理では、トレンド計算に固定バー数を使用し、L1TotalBars = 1000とします。正則化パラメータλはλmax単位で指定され、固定値としてL1CoefLambda = 0.2を用います。

            入力パラメータ:

            //--- best MovingAverage parameters for EURUSD,H1,2025
            input int    MovingPeriod   = 64;     // MA period
            input int    MovingShift    = 0;      // MA shift
            //--- trade volume
            input double TradeLot       = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;   // Total bars for L1 filter
            input bool   L1FilterOpen   = false;  // Use filter for Open
            input bool   L1FilterClose  = false;  // Use filter for Close
            input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false;  // Save statistics to file

            最適化設定では、銘柄EURUSD、時間足H1、およびテスト期間2025.01.01から2025.12.31を指定します。

            図21:MovingAverageFilteredL1.mq5の最適化設定

            図21:MovingAverageFilteredL1.mq5の最適化設定


            高速最適化のために、「1分OHLC」モードを使用します(本戦略は新しいバーでのみ動作するため、この近似は許容されます)。また、最適化アルゴリズムとして「高速遺伝的アルゴリズム」を選択し、評価基準として「バランス最大」を設定します。

              図22:MovingAverageFilteredL1.mq5の最適化パラメータ

            図22:MovingAverageFilteredL1.mq5の最適化パラメータ


            この段階の目的はトレード戦略の最適パラメータを見つけることなので、最適化パラメータではすべてのフィルタおよびファイル保存を無効化します。

            簡略化のため、「MA Period」パラメータのみを1から800の範囲、ステップ1で最適化します。

            図23:MovingAverageFilteredL1.mq5の最適化結果

            図23:MovingAverageFilteredL1.mq5の最適化結果


            「MA Period」パラメータの最適化は1分未満で完了し、結果および上位パラメータの一覧が図23に示されます。

            より正確なテストのために、テスト設定で[Every tick based on real ticks]を選択します。


            図24:MovingAverageFilteredL1.mq5のテスト設定

            図24:MovingAverageFilteredL1.mq5のテスト設定


            最良パラメータ「MA Period = 64」で単体テストを実行します。

            図25:MovingAverageFilteredL1.mq5の最適な最適化パラメータ

            図25:図25:MovingAverageFilteredL1.mq5の最適な最適化パラメータ


            図26:MovingAverageFilteredL1.mq5の最適パラメータを使用したテスト結果

            図26:MovingAverageFilteredL1.mq5の最適パラメータを使用したテスト結果


            次に、ファイルへのデータ保存を有効にしてテストを実行する必要があります。

            そのためにSaveStatistics = trueを設定し、売買シグナルフィルタの4つの組み合わせでテストを実行します。 

            図27:MovingAverageFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ

            図27:図27:MovingAverageFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ


            4つすべての組み合わせでテストを実行すると、テスターのディレクトリにファイル0_MA_EURUSD.txt、1_MA_EURUSD.txt、2_MA_EURUSD.txt、および3_MA_EURUSD.txtが生成されます。

            これらのファイルには、テスト区間の各バーにおける時間、終値、バランス、エクイティが含まれます。

            これらを別ディレクトリにコピーし、本例ではC:\Dataに配置します。


            図28:MovingAverageFilteredL1.mq5の4つのフィルタモードにおけるテスト結果ファイル

            図28:MovingAverageFilteredL1.mq5の4つのフィルタモードにおけるテスト結果ファイル


            データ解析はPythonスクリプトを用いて実行します。

            import pandas as pd
            import matplotlib.pyplot as plt
            import os
            
            # --- folder for charts
            output_dir = "C:\\data\\charts\\"
            os.makedirs(output_dir, exist_ok=True)
            
            
            symbol = "EURUSD" 
            name_strategy = "MA"
            file_strategy = name_strategy+"_"+symbol 
            title_strategy = " ("+symbol+" "+name_strategy+" strategy+filters)"
            file_prefix = symbol+"_"+name_strategy+"_"
            
            # --- files
            files = [
                "C:\\data\\0_"+file_strategy+".txt",
                "C:\\data\\1_"+file_strategy+".txt",
                "C:\\data\\2_"+file_strategy+".txt",
                "C:\\data\\3_"+file_strategy+".txt"
            ]
            
            # --- labels
            labels = [
                "No filters",
                "Open L1 filter",
                "Close L1 filter",
                "Open+Close L1 filter"
            ]
            
            # --- load data
            def load_file(filename):
                df = pd.read_csv(
                    filename,
                    sep=";",
                    header=None,
                    names=[
                        "time",
                        "balance",
                        "equity",
                        "margin",
                        "free_margin",
                        "margin_level",
                        "volume",
                        "close"
                    ]
                )
                df["time"] = pd.to_datetime(df["time"])
                return df
            
            # --- close price chart
            plt.figure(figsize=(10,6), dpi=100)
            for file, label in zip(files, labels):
                df = load_file(file)
            plt.plot(df["time"], df["close"], color='gray')
            plt.title(symbol+" Close Price")
            plt.xlabel("Time")
            plt.ylabel("closing price")
            plt.legend()
            plt.grid()
            plt.tight_layout()
            plt.savefig(output_dir + file_prefix+"close_price.png", dpi=100)
            plt.show()
                
            # --- balance chart
            plt.figure(figsize=(10,6), dpi=100)
            for file, label in zip(files, labels):
                df = load_file(file)
                plt.plot(df["time"], df["balance"], label=label)
            plt.title("Balance" + title_strategy)
            plt.xlabel("Time")
            plt.ylabel("Balance")
            plt.legend()
            plt.grid()
            plt.tight_layout()
            plt.savefig(output_dir + file_prefix+"balance.png", dpi=100)
            plt.show()
            plt.close()
            
            # --- equity chart
            plt.figure(figsize=(10,6), dpi=100)
            for file, label in zip(files, labels):
                df = load_file(file)
                plt.plot(df["time"], df["equity"], label=label)
            plt.title("Equity" + title_strategy)
            plt.xlabel("Time")
            plt.ylabel("Equity")
            plt.legend()
            plt.grid()
            plt.tight_layout()
            plt.savefig(output_dir + file_prefix+"equity.png", dpi=100)
            plt.show()
            plt.close()
            
            
            #--- balance + equity chart
            plt.figure(figsize=(10,6), dpi=100)
            for i, (file, label) in enumerate(zip(files, labels)):
            
                df = load_file(file)
                # --- get matplotlib color
                color = plt.rcParams["axes.prop_cycle"].by_key()["color"][i % 10]
                #--- equity — solid line
                plt.plot(
                    df["time"],
                    df["equity"],
                    color=color,
                    linestyle="-",
                    label=f"{label} equity"
                )
                #--- balance — dashed line
                plt.plot(
                    df["time"],
                    df["balance"],
                    color=color,
                    linestyle="--",
                    label=f"{label} balance"
                )
            plt.title("Balance + Equity" + title_strategy)
            plt.xlabel("Time")
            plt.ylabel("Value")
            plt.legend()
            plt.grid()
            plt.tight_layout()
            plt.savefig(output_dir+file_prefix+"balance_equity.png", dpi=100)
            plt.show()
            plt.close()
            
            

            MetaEditorでPythonスクリプトを実行するには、[コンパイル]を押します(図29)。


            図29:MetaEditor内のPlotData.pyスクリプト

            図29:MetaEditor内のPlotData.pyスクリプト


            PlotData.pyを実行すると、以下のチャートが表示されます。

            1. EURUSDの価格系列(各バーの終値)
            2. すべてのフィルタモードにおけるバランス曲線
            3. すべてのフィルタモードにおけるエクイティ曲線
            4. バランス+エクイティチャート(ドローダウン低減の評価用)

            これらのチャートはC:\Data\Charts\にPNG形式で保存されます。


            図30:テスト期間におけるEURUSD価格チャート

            図30:テスト期間におけるEURUSD価格チャート


            図31:異なるフィルタモードにおけるMovingAverageFilteredL1.mq5のバランス曲線

            図31:異なるフィルタモードにおけるMovingAverageFilteredL1.mq5のバランス曲線


            図32:MovingAverageFilteredL1.mq5のエクイティ曲線(フィルタモード別)

            図32:MovingAverageFilteredL1.mq5のエクイティ曲線(フィルタモード別)


            図33:異なるフィルタモードにおけるバランス+エクイティチャート

            図33:異なるフィルタモードにおけるバランス+エクイティチャート


            MetaEditorでPythonスクリプトを実行するためには、Python(例ではバージョン3.14)をインストールし、そのパスを設定で指定する必要があります。


            図34:MetaEditorのPython設定

            図34:MetaEditorのPython設定


            スクリプトはpandasおよびmatplotlibライブラリを使用します。インストールされていない場合:

            pip install pandas
            pip install matplotlib


            3.5.1.2. 移動平均戦略にL1フィルタを適用した結果

            結果(バランス、エクイティ、およびチャート)を図35〜37に示します

            色の定義:

            1. 青(フィルタなしの戦略)
            2. 緑(エグジットにL1フィルタ適用)
            3. 赤(エントリーおよびエグジット両方にL1フィルタ適用)
            4. オレンジ(エントリーにL1フィルタ適用)

            図35:異なるフィルタモードにおけるMovingAverageFilteredL1.mq5のバランス曲線

            図35:異なるフィルタモードにおけるMovingAverageFilteredL1.mq5のバランス曲線



            図35:異なるフィルタモードにおけるMovingAverageFilteredL1.mq5のエクイティ曲線

            図36:異なるフィルタモードにおけるMovingAverageFilteredL1.mq5のエクイティ曲線


            図37:異なるフィルタモードにおけるMovingAverageFilteredL1.mq5のバランス+エクイティチャート

            図37:異なるフィルタモードにおける MovingAverageFilteredL1.mq5のバランス+エクイティチャート



            3.5.2. MACDトレード戦略

            別の例として、MACD (Moving Average Convergence/Divergence)インジケータに基づく売買シグナルを用いるEAを考えます。

            売買シグナル

            シグナルはMACDメインラインとシグナルラインのクロスオーバーで生成されます。

            1. BUY:MACDメインラインがシグナルラインを下から上へクロスした場合
            2. SELL:MACDメインラインがシグナルラインを上から下へクロスした場合

            シグナルはバー確定時のみ評価され、バー内部の誤ったトリガーを減少させます。

            さらに、L1トレンドフィルタ設定(L1TotalBars、L1FilterOpen、L1FilterClose、L1CoefLambda)に基づくエントリーおよびエグジットフィルタが使用されます。

            MACDFilteredL1.mq5のコードを以下に示します。

            //+------------------------------------------------------------------+
            //|                                               MACDFilteredL1.mq5 |
            //|                             Copyright 2000-2026, MetaQuotes Ltd. |
            //|                                             https://www.mql5.com |
            //+------------------------------------------------------------------+
            #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
            #property link      "https://www.mql5.com"
            #property version   "1.00"
            //--- best MACD parameters for USDCHF,H1,2025
            input int    FastEMA        = 43;     // Fast EMA
            input int    SlowEMA        = 59;     // Slow EMA
            input int    SignalEMA      = 37;     // SignalEMA
            //--- trade volume
            input double TradeLot       = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;   // Total bars for L1 filter
            input bool   L1FilterOpen   = false;  // Use filter for Open
            input bool   L1FilterClose  = false;  // Use filter for Close
            input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false;  // Save statistics to file
            //---
            #define MACD_MAGIC 1234502
            #include <Trade\Trade.mqh>
            int    ExtHandle = INVALID_HANDLE;
            bool   ExtHedging = false;
            CTrade ExtTrade;
            string ExtStrategyName="MACD";
            string ExtStrategyFileName="";
            //+------------------------------------------------------------------+
            //| Check new bar                                                    |
            //+------------------------------------------------------------------+
            bool IsNewBar()
              {
               static datetime last_time=0;
               datetime t[1];
            //---
               if(CopyTime(_Symbol,_Period,0,1,t)>0)
                 {
                  if(t[0]!=last_time)
                    {
                     last_time=t[0];
                     return(true);
                    }
                 }
               return(false);
              }
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1]-data_filtered[data_count-2];
            //---
               return(dp);
              }
            //+------------------------------------------------------------------+
            //| GetTradeSignal(MACD)                                             |
            //+------------------------------------------------------------------+
            bool GetTradeSignal(ENUM_ORDER_TYPE &signal)
              {
               signal = WRONG_VALUE;
               double macd_main[];
               double macd_signal[];
            //---
               ArrayResize(macd_main,2);
               ArrayResize(macd_signal,2);
            //---
               ArraySetAsSeries(macd_main, true);
               ArraySetAsSeries(macd_signal, true);
            //--- buffer 0 = MACD main, buffer 1 = signal line
               if(CopyBuffer(ExtHandle,0,1,2,macd_main)!=2)
                  return(false);
               if(CopyBuffer(ExtHandle,1,1,2,macd_signal)!=2)
                  return(false);
            //---
               double main_prev   = macd_main[1];
               double main_last   = macd_main[0];
               double signal_prev = macd_signal[1];
               double signal_last = macd_signal[0];
            //--- MACD crossover
               if(main_prev < signal_prev && main_last > signal_last)
                  signal = ORDER_TYPE_BUY;
               else
                  if(main_prev > signal_prev && main_last < signal_last)
                     signal = ORDER_TYPE_SELL;
            //---
               return(true);
              }
            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal) || signal == WRONG_VALUE)
                  return;
            //---
               if(L1FilterOpen)
                 {
                  double dp = CheckTrendL1();
                  if(signal == ORDER_TYPE_BUY && dp < 0)
                     return;
                  if(signal == ORDER_TYPE_SELL && dp > 0)
                     return;
                 }
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol, _Period) < L1TotalBars)
                  return;
            //---
               double price = (signal == ORDER_TYPE_BUY)
                              ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                              : SymbolInfoDouble(_Symbol, SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0);
              }
            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=MACD_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type = PositionGetInteger(POSITION_TYPE);
               bool close_signal = false;
            //---
               if(type == POSITION_TYPE_BUY  && signal == ORDER_TYPE_SELL)
                  close_signal = true;
               if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY)
                  close_signal = true;
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double dp = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && dp > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp);
                    }
                  if(type == POSITION_TYPE_SELL && dp < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp);
                    }
                 }
            //---
               if(close_signal)
                  ExtTrade.PositionClose(_Symbol, 3);
              }
            //+------------------------------------------------------------------+
            //| SelectPosition                                                   |
            //+------------------------------------------------------------------+
            bool SelectPosition()
              {
               bool res = false;
               if(ExtHedging)
                 {
                  uint total = PositionsTotal();
                  for(uint i=0; i<total; i++)
                    {
                     string sym = PositionGetSymbol(i);
                     if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==MACD_MAGIC)
                       {
                        res = true;
                        break;
                       }
                    }
                 }
               else
                 {
                  if(PositionSelect(_Symbol))
                     res = (PositionGetInteger(POSITION_MAGIC)==MACD_MAGIC);
                 }
               return(res);
              }
            //+------------------------------------------------------------------+
            //| Expert initialization                                            |
            //+------------------------------------------------------------------+
            int OnInit()
              {
            //--- check parameters
               if(FastEMA <= 0 || SlowEMA <= 0 || SignalEMA <= 0)
                 {
                  Print("Error: MACD parameters must be positive");
                  return(INIT_PARAMETERS_INCORRECT);
                 }
               if(FastEMA >= SlowEMA)
                 {
                  Print("FastEMA must be less than SlowEMA");
                  return(INIT_PARAMETERS_INCORRECT);
                 }
            //---
               ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
               ExtTrade.SetExpertMagicNumber(MACD_MAGIC);
               ExtTrade.SetMarginMode();
               ExtTrade.SetTypeFillingBySymbol(_Symbol);
            //--- prepare indicator
               ExtHandle=iMACD(_Symbol,_Period,FastEMA,SlowEMA,SignalEMA,PRICE_CLOSE);
               if(ExtHandle==INVALID_HANDLE)
                 {
                  Print("Failed to create MACD handle");
                  return(INIT_FAILED);
                 }
            //--- prepare filename
               ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
            //--- delete old file if exists
               if(FileIsExist(ExtStrategyFileName))
                  FileDelete(ExtStrategyFileName);
            //---
               return(INIT_SUCCEEDED);
              }
            //+------------------------------------------------------------------+
            //| PrepareStrategyFileName                                          |
            //+------------------------------------------------------------------+
            string PrepareStrategyFileName(string strategy_name)
              {
               int v=0;
               if(L1FilterOpen)
                  v=v | 1;
            //---
               if(L1FilterClose)
                  v=v | 2;
            //---
               string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
               return(filename);
              }
            //+------------------------------------------------------------------+
            //| Save account statistics to file                                  |
            //+------------------------------------------------------------------+
            void SaveAccountStatistics()
              {
            //--- check file name
               if(ExtStrategyFileName=="")
                  return;
            //---
               int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
               if(file==INVALID_HANDLE)
                 {
                  Print("File open error: ",GetLastError());
                  return;
                 }
            //--- append
               FileSeek(file,0,SEEK_END);
            //--- account data
               double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
               double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
               double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
               double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
               double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
            //--- volume
               double volume=0.0;
               if(PositionSelect(_Symbol))
                  volume=PositionGetDouble(POSITION_VOLUME);
            //--- time
               datetime t[1];
               if(CopyTime(_Symbol,_Period,0,1,t)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               double current_close[1];
               if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                        balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
            //---
               FileWrite(file,line);
            //---
               FileClose(file);
              }
            //+------------------------------------------------------------------+
            //| Expert OnTick function                                           |
            //+------------------------------------------------------------------+
            void OnTick()
              {
            //--- trade only at new bar
               if(!IsNewBar())
                  return;
            //--- check trade conditions
               if(SelectPosition())
                  CheckForClose();
               else
                  CheckForOpen();
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
              }
            //+------------------------------------------------------------------+
            //| Expert deinitialization                                          |
            //+------------------------------------------------------------------+
            void OnDeinit(const int reason)
              {
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
            //---
               if(ExtHandle != INVALID_HANDLE)
                  IndicatorRelease(ExtHandle);
              }
            //+------------------------------------------------------------------+
            

            テスト設定を図38に示します。

            図38:MACDFilteredL1.mq5のストラテジーテスター設定

            図38:MACDFilteredL1.mq5のストラテジーテスター設定


            図39:MACDFilteredL1.mq5のテストパラメータ

            図39:MACDFilteredL1.mq5のテストパラメータ


            図40:MACDFilteredL1.mq5のテスト結果

            図40:MACDFilteredL1.mq5のテスト結果


            図41:MACDFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ

            図41:MACDFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ


            ストラテジーテスターで異なるフィルタ設定によるテストを実行すると、testerディレクトリにx_MACD_EURUSD.txtファイルが生成されます。

            これらのファイルはC:\Data\にコピーし、その後PlotData.pyスクリプトを実行します。


            図42:異なるフィルタモードにおけるMACDFiltered.mq5のテスト結果ファイル

            図42:異なるフィルタモードにおけるMACDFiltered.mq5のテスト結果ファイル



            3.5.2.1. MACD戦略にL1フィルタを適用した結果

            結果を図43~45に示します。

            図43:異なるフィルタモードにおけるMACDFilteredL1.mq5のバランス曲線

            図43:異なるフィルタモードにおけるMACDFilteredL1.mq5のバランス曲線


            図44:異なるフィルタモードにおけるMACDFilteredL1.mq5のエクイティ曲線

            図44:異なるフィルタモードにおけるMACDFilteredL1.mq5のエクイティ曲線


            図45:異なるフィルタモードにおけるMACDFilteredL1.mq5のバランス+エクイティチャート

            図45:異なるフィルタモードにおけるMACDFilteredL1.mq5のバランス+エクイティチャート


            3.5.3. ADXトレード戦略

            別の例として、ADX(Average Directional Movement Index)に基づくトレンドフォロー戦略を実装したADXFilteredL1.mq5を考えます。

            主要な売買シグナルは、+DIおよび−DIラインの分析と、トレンド強度を示すADXレベルに基づいています。

            シグナルは以下のように定義されます。

            • BUY:+DIが−DIを下から上へクロスした場合
            • SELL:+DIが−DIを上から下へクロスした場合

            さらに、ADX値も考慮されます。ADXが閾値ADXTrendLevelを下回る場合、市場は弱いトレンドまたはレンジ相場とみなされ、そのシグナルは無視されます。

            シグナルの評価には直近2本の確定バーのインジケータ値が使用され、現在形成中の未確定バーの影響は除外されることで、偽シグナルを低減します。

            エントリーおよびイグジットフィルタは、L1トレンド設定(L1TotalBars、L1FilterOpen、L1FilterClose、L1CoefLambda)に従って適用されます。

            ADXFilteredL1.mq5のコードを以下に示します。

            //+------------------------------------------------------------------+
            //|                                                ADXFilteredL1.mq5 |
            //|                             Copyright 2000-2026, MetaQuotes Ltd. |
            //|                                              http://www.mql5.com |
            //+------------------------------------------------------------------+
            #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
            #property link      "https://www.mql5.com"
            #property version   "1.00"
            //--- best ADX parameters for EURUSD,H1,2025
            input int    ADXPeriod      = 65;     // ADX Period
            input double ADXTrendLevel  = 7;      // ADX Trend Level
            //--- trade volume
            input double TradeLot       = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;   // Total bars for L1 filter
            input bool   L1FilterOpen   = false;  // Use filter for Open
            input bool   L1FilterClose  = false;  // Use filter for Close
            input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false;  // Save statistics to file
            //---
            #define ADX_MAGIC 1234503
            #include <Trade\Trade.mqh>
            CTrade ExtTrade;
            int    ExtHandle = INVALID_HANDLE;
            bool   ExtHedging = false;
            string ExtStrategyName="ADX";
            string ExtStrategyFileName="";
            //+------------------------------------------------------------------+
            //| Check new bar                                                    |
            //+------------------------------------------------------------------+
            bool IsNewBar()
              {
               static datetime last_time=0;
               datetime t[1];
            //---
               if(CopyTime(_Symbol,_Period,0,1,t)>0)
                 {
                  if(t[0]!=last_time)
                    {
                     last_time=t[0];
                     return(true);
                    }
                 }
               return(false);
              }
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1] - data_filtered[data_count-2];
            //---
               return(dp);
              }
            //+------------------------------------------------------------------+
            //| GetTradeSignal (ADX)                                             |
            //+------------------------------------------------------------------+
            bool GetTradeSignal(ENUM_ORDER_TYPE &signal)
              {
               signal=WRONG_VALUE;
               double adx[],plusdi[],minusdi[];
               ArrayResize(adx,2);
               ArrayResize(plusdi,2);
               ArrayResize(minusdi,2);
            //---
               ArraySetAsSeries(adx,true);
               ArraySetAsSeries(plusdi,true);
               ArraySetAsSeries(minusdi,true);
            //--- buffer0 = ADX
               if(CopyBuffer(ExtHandle,0,1,2,adx)!=2)
                  return(false);
            //--- buffer1 = +DI
               if(CopyBuffer(ExtHandle,1,1,2,plusdi)!=2)
                  return(false);
            //--- buffer2 = -DI
               if(CopyBuffer(ExtHandle,2,1,2,minusdi)!=2)
                  return(false);
               double adx_last=adx[0];
               double plus_prev=plusdi[1];
               double plus_last=plusdi[0];
               double minus_prev=minusdi[1];
               double minus_last=minusdi[0];
            //--- strong trend required
               if(adx_last<ADXTrendLevel)
                  return(true);
            //--- +DI cross -DI
               if(plus_prev<minus_prev && plus_last>minus_last)
                  signal=ORDER_TYPE_BUY;
               else
                  if(plus_prev>minus_prev && plus_last<minus_last)
                     signal=ORDER_TYPE_SELL;
            //---
               return(true);
              }
            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
            //---
               if(!GetTradeSignal(signal) || signal==WRONG_VALUE)
                  return;
            //---
               if(L1FilterOpen)
                 {
                  double dp=CheckTrendL1();
                  if(signal==ORDER_TYPE_BUY && dp<0)
                     return;
                  if(signal==ORDER_TYPE_SELL && dp>0)
                     return;
                 }
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars)
                  return;
            //---
               double price=(signal==ORDER_TYPE_BUY)
                            ? SymbolInfoDouble(_Symbol,SYMBOL_ASK)
                            : SymbolInfoDouble(_Symbol,SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol,signal,TradeLot,price,0,0);
              }
            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=ADX_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type=PositionGetInteger(POSITION_TYPE);
               bool close_signal=false;
            //---
               if(type==POSITION_TYPE_BUY && signal==ORDER_TYPE_SELL)
                  close_signal=true;
            //---
               if(type==POSITION_TYPE_SELL && signal==ORDER_TYPE_BUY)
                  close_signal=true;
            //--- check L1 filter
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double dp = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && dp > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp);
                    }
                  if(type == POSITION_TYPE_SELL && dp < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp);
                    }
                 }
            //---
               if(close_signal)
                  ExtTrade.PositionClose(_Symbol,3);
              }
            //+------------------------------------------------------------------+
            //| SelectPosition                                                   |
            //+------------------------------------------------------------------+
            bool SelectPosition()
              {
               bool res = false;
               if(ExtHedging)
                 {
                  uint total = PositionsTotal();
                  for(uint i=0; i<total; i++)
                    {
                     string sym = PositionGetSymbol(i);
                     if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==ADX_MAGIC)
                       {
                        res = true;
                        break;
                       }
                    }
                 }
               else
                 {
                  if(PositionSelect(_Symbol))
                     res = (PositionGetInteger(POSITION_MAGIC)==ADX_MAGIC);
                 }
               return(res);
              }
            //+------------------------------------------------------------------+
            //| Expert initialization                                            |
            //+------------------------------------------------------------------+
            int OnInit()
              {
               ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
               ExtTrade.SetExpertMagicNumber(ADX_MAGIC);
               ExtTrade.SetMarginMode();
               ExtTrade.SetTypeFillingBySymbol(_Symbol);
            //--- prepare indicator
               ExtHandle=iADX(_Symbol,_Period,ADXPeriod);
               if(ExtHandle==INVALID_HANDLE)
                 {
                  Print("ADX handle error");
                  return(INIT_FAILED);
                 }
            //--- prepare filename
               ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
            //--- delete old file if exists
               if(FileIsExist(ExtStrategyFileName))
                  FileDelete(ExtStrategyFileName);
            //---
               return(INIT_SUCCEEDED);
              }
            //+------------------------------------------------------------------+
            //| PrepareStrategyFileName                                          |
            //+------------------------------------------------------------------+
            string PrepareStrategyFileName(string strategy_name)
              {
               int v=0;
               if(L1FilterOpen)
                  v=v | 1;
            //---
               if(L1FilterClose)
                  v=v | 2;
            //---
               string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
               return(filename);
              }
            //+------------------------------------------------------------------+
            //| Save account statistics to file                                  |
            //+------------------------------------------------------------------+
            void SaveAccountStatistics()
              {
            //--- check file name
               if(ExtStrategyFileName=="")
                  return;
            //---
               int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
               if(file==INVALID_HANDLE)
                 {
                  Print("File open error: ",GetLastError());
                  return;
                 }
            //--- append
               FileSeek(file,0,SEEK_END);
            //--- account data
               double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
               double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
               double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
               double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
               double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
            //--- volume
               double volume=0.0;
               if(PositionSelect(_Symbol))
                  volume=PositionGetDouble(POSITION_VOLUME);
            //--- time
               datetime t[1];
               if(CopyTime(_Symbol,_Period,0,1,t)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               double current_close[1];
               if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                        balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
            //---
               FileWrite(file,line);
            //---
               FileClose(file);
              }
            //+------------------------------------------------------------------+
            //| Expert OnTick function                                           |
            //+------------------------------------------------------------------+
            void OnTick()
              {
            //--- trade only at new bar
               if(!IsNewBar())
                  return;
            //--- check trade conditions
               if(SelectPosition())
                  CheckForClose();
               else
                  CheckForOpen();
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
              }
            //+------------------------------------------------------------------+
            //| Expert deinitialization                                          |
            //+------------------------------------------------------------------+
            void OnDeinit(const int reason)
              {
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
            //--- release indicator handle
               if(ExtHandle!=INVALID_HANDLE)
                  IndicatorRelease(ExtHandle);
              }
            //+------------------------------------------------------------------+
            

            テスト設定、パラメータ、および結果を図46~48に示します。


            図46:ADXFilteredL1.mq5のテスター設定

            図46:ADXFilteredL1.mq5のテスター設定


            図47:ADXFilteredL1.mq5のテストパラメータ

            図47:ADXFilteredL1.mq5のテストパラメータ


            図48:ADXFilteredL1.mq5のテスト結果

            図48:ADXFilteredL1.mq5のテスト結果


            テストのために、異なるフィルタ設定で4回の実行を行う必要があります。

            図49:ADXFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ

            図49:図49:ADXFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ

            その後、testerディレクトリにファイルが生成されます。これらはC:\Dataにコピーし、PlotData.pyで処理します。


            図50:異なるフィルタモードにおけるADXFiltered.mq5のテスト結果ファイル

            図50:図50:異なるフィルタモードにおけるADXFiltered.mq5のテスト結果ファイル



            3.5.3.1. ADX戦略にL1フィルタを適用した結果

            結果を図51~53に示します。

            図51:異なるフィルタモードにおけるADXFilteredL1.mq5のバランス曲線

            図51:異なるフィルタモードにおけるADXFilteredL1.mq5のバランス曲線


            図52:異なるフィルタモードにおけるADXFilteredL1.mq5のエクイティ曲線

            図52:図52:異なるフィルタモードにおけるADXFilteredL1.mq5のエクイティ曲線


            図53:異なるフィルタモードにおけるADXFilteredL1.mq5のバランス+エクイティチャート

            図53:異なるフィルタモードにおけるADXFilteredL1.mq5のバランス+エクイティチャート



            3.5.4. EMAクロスオーバーに基づくトレード戦略

            EMAFilteredL1.mq5は、2本の指数移動平均線(EMA)のクロスオーバーに基づくトレンドフォロー戦略を実装します。

            この戦略では、2つの移動平均を使用します。

            1. FastEMA:短期指数移動平均
            2. SlowEMA:長期指数移動平均

            トレードシグナルは以下のように形成されます。

            • BUY:FastEMAがSlowEMAを下から上へクロスした場合
            • SELL:FastEMAがSlowEMAを上から下へクロスした場合

            解析には直近2本の確定バーのインジケータ値を使用し、現在形成中の未確定バーの影響を除外することで、偽シグナルを減少させます。

            さらに、エントリーおよびエグジットフィルタは、L1トレンド設定(L1TotalBars、L1FilterOpen、L1FilterClose、L1CoefLambda)に従って適用されます。

            EMAFilteredL1.mq5のコードを以下に示します。

            //+------------------------------------------------------------------+
            //|                                                EMAFilteredL1.mq5 |
            //|                             Copyright 2000-2026, MetaQuotes Ltd. |
            //|                                              http://www.mql5.com |
            //+------------------------------------------------------------------+
            #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
            #property link      "https://www.mql5.com"
            #property version   "1.00"
            //--- best EMA parameters for EURUSD,H1,2025
            input int    FastEMA       = 29;     // Fast EMA
            input int    SlowEMA       = 101;     // Slow EMA
            //--- trade volume
            input double TradeLot      = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;  // Total bars for L1 filter
            input bool   L1FilterOpen  = false;  // Use filter for Open
            input bool   L1FilterClose = false;  // Use filter for Close
            input double L1CoefLambda  = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false; // Save statistics to file
            //---
            #define EMA_MAGIC 1234503
            #include <Trade\Trade.mqh>
            CTrade ExtTrade;
            int    ExtHandle = INVALID_HANDLE;
            bool   ExtHedging = false;
            int    FastHandle, SlowHandle;
            string ExtStrategyName="EMA";
            string ExtStrategyFileName="";
            //+------------------------------------------------------------------+
            //| Check new bar                                                    |
            //+------------------------------------------------------------------+
            bool IsNewBar()
              {
               static datetime last_time=0;
               datetime t[1];
            //---
               if(CopyTime(_Symbol,_Period,0,1,t)>0)
                 {
                  if(t[0]!=last_time)
                    {
                     last_time=t[0];
                     return(true);
                    }
                 }
               return(false);
              }
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1] - data_filtered[data_count-2];
            //---
               return(dp);
              }
            //+------------------------------------------------------------------+
            //| GetTradeSignal (2EMA crossover)                                  |
            //+------------------------------------------------------------------+
            bool GetTradeSignal(ENUM_ORDER_TYPE &signal)
              {
               signal=WRONG_VALUE;
            //---
               double fast[],slow[];
               ArrayResize(fast,2);
               ArrayResize(slow,2);
            //---
               ArraySetAsSeries(fast,true);
               ArraySetAsSeries(slow,true);
            //---
               if(CopyBuffer(FastHandle,0,1,2,fast)!=2)
                  return(false);
            //---
               if(CopyBuffer(SlowHandle,0,1,2,slow)!=2)
                  return(false);
            //---
               if(fast[1]<slow[1] && fast[0]>slow[0])
                  signal=ORDER_TYPE_BUY;
               if(fast[1]>slow[1] && fast[0]<slow[0])
                  signal=ORDER_TYPE_SELL;
            //---
               return(true);
              }
            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
            //---
               if(!GetTradeSignal(signal) || signal==WRONG_VALUE)
                  return;
            //---
               if(L1FilterOpen)
                 {
                  double dp=CheckTrendL1();
            //---
                  if(signal==ORDER_TYPE_BUY && dp<0)
                     return;
                  if(signal==ORDER_TYPE_SELL && dp>0)
                     return;
                 }
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars)
                  return;
            //---
               double price=(signal==ORDER_TYPE_BUY)
                            ? SymbolInfoDouble(_Symbol,SYMBOL_ASK)
                            : SymbolInfoDouble(_Symbol,SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol,signal,TradeLot,price,0,0);
              }
            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=EMA_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type=PositionGetInteger(POSITION_TYPE);
               bool close_signal=false;
            //---
               if(type==POSITION_TYPE_BUY && signal==ORDER_TYPE_SELL)
                  close_signal=true;
            //---
               if(type==POSITION_TYPE_SELL && signal==ORDER_TYPE_BUY)
                  close_signal=true;
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double dp = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && dp > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp);
                    }
                  if(type == POSITION_TYPE_SELL && dp < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp);
                    }
                 }
            //---
               if(close_signal)
                  ExtTrade.PositionClose(_Symbol,3);
              }
            //+------------------------------------------------------------------+
            //| SelectPosition                                                   |
            //+------------------------------------------------------------------+
            bool SelectPosition()
              {
               bool res = false;
               if(ExtHedging)
                 {
                  uint total = PositionsTotal();
                  for(uint i=0; i<total; i++)
                    {
                     string sym = PositionGetSymbol(i);
                     if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==EMA_MAGIC)
                       {
                        res = true;
                        break;
                       }
                    }
                 }
               else
                 {
                  if(PositionSelect(_Symbol))
                     res = (PositionGetInteger(POSITION_MAGIC)==EMA_MAGIC);
                 }
               return(res);
              }
            //+------------------------------------------------------------------+
            //| Expert initialization                                            |
            //+------------------------------------------------------------------+
            int OnInit()
              {
               ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
               ExtTrade.SetExpertMagicNumber(EMA_MAGIC);
               ExtTrade.SetMarginMode();
               ExtTrade.SetTypeFillingBySymbol(_Symbol);
            //--- prepare indicators
               FastHandle=iMA(_Symbol,_Period,FastEMA,0,MODE_EMA,PRICE_CLOSE);
               SlowHandle=iMA(_Symbol,_Period,SlowEMA,0,MODE_EMA,PRICE_CLOSE);
               if(FastHandle==INVALID_HANDLE||SlowHandle==INVALID_HANDLE)
                  return(INIT_FAILED);
            //--- prepare filename
               ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
            //--- delete old file if exists
               if(FileIsExist(ExtStrategyFileName))
                  FileDelete(ExtStrategyFileName);
            //---
               return(INIT_SUCCEEDED);
              }
            //+------------------------------------------------------------------+
            //| PrepareStrategyFileName                                          |
            //+------------------------------------------------------------------+
            string PrepareStrategyFileName(string strategy_name)
              {
               int v=0;
               if(L1FilterOpen)
                  v=v | 1;
            //---
               if(L1FilterClose)
                  v=v | 2;
            //---
               string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
               return(filename);
              }
            //+------------------------------------------------------------------+
            //| Save account statistics to file                                  |
            //+------------------------------------------------------------------+
            void SaveAccountStatistics()
              {
            //--- check file name
               if(ExtStrategyFileName=="")
                  return;
            //---
               int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
               if(file==INVALID_HANDLE)
                 {
                  Print("File open error: ",GetLastError());
                  return;
                 }
            //--- append
               FileSeek(file,0,SEEK_END);
            //--- account data
               double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
               double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
               double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
               double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
               double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
            //--- volume
               double volume=0.0;
               if(PositionSelect(_Symbol))
                  volume=PositionGetDouble(POSITION_VOLUME);
            //--- time
               datetime t[1];
               if(CopyTime(_Symbol,_Period,0,1,t)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               double current_close[1];
               if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                        balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
            //---
               FileWrite(file,line);
            //---
               FileClose(file);
              }
            //+------------------------------------------------------------------+
            //| Expert OnTick function                                           |
            //+------------------------------------------------------------------+
            void OnTick()
              {
            //--- trade only at new bar
               if(!IsNewBar())
                  return;
            //--- check trade conditions
               if(SelectPosition())
                  CheckForClose();
               else
                  CheckForOpen();
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
              }
            //+------------------------------------------------------------------+
            //| Expert deinitialization                                          |
            //+------------------------------------------------------------------+
            void OnDeinit(const int reason)
              {
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
            //--- release indicator handles
               if(FastHandle!=INVALID_HANDLE)
                  IndicatorRelease(FastHandle);
            //---
               if(SlowHandle!=INVALID_HANDLE)
                  IndicatorRelease(SlowHandle);
              }
            //+------------------------------------------------------------------+

            EMAFilteredL1.mq5の設定、パラメータ、およびテスト結果を図54~56に示します。

            図54:EMAFilteredL1.mq5のテスター設定

            図54:EMAFilteredL1.mq5のテスター設定


            図55:EMAFilteredL1.mq5のテストパラメータ

            図55:EMAFilteredL1.mq5のテストパラメータ


            図56:EMAFilteredL1.mq5のテスト結果

            図56:EMAFilteredL1.mq5のテスト結果


            L1フィルタの有効性を分析するためには、異なるフィルタモードでEAをテスター内で順次実行する必要があります。

            図57:EMAFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ

            図57:EMAFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ


            その結果、テスターフォルダ内にファイルが作成されます。これらをPythonスクリプトPlotData.pyで指定されたディレクトリへコピーし、設定「symbol = "EURUSD", name_strategy = "EMA"」で実行します。


            図58:異なるトレードシグナルフィルタモードにおけるEMAFilteredL1.mq5のテスト結果ファイル

            図58:異なるトレードシグナルフィルタモードにおけるEMAFilteredL1.mq5のテスト結果ファイル



            3.5.4.1. EMA戦略売買シグナルにL1フィルタを適用した結果

            結果を図59~61に示します。

            図59:異なるフィルタモードにおけるEMAFilteredL1.mq5のバランス曲線

            図59:異なるフィルタモードにおけるEMAFilteredL1.mq5のバランス曲線


            図60:異なるフィルタモードにおけるEMAFilteredL1.mq5のエクイティ曲線

            図60:異なるフィルタモードにおけるEMAFilteredL1.mq5のエクイティ曲線


            図61:異なるフィルタモードにおけるEMAFilteredL1.mq5のバランス+エクイティチャート

            図61:異なるフィルタモードにおけるEMAFilteredL1.mq5のバランス+エクイティチャート




            3.5.5. 移動平均、MACD、ADX、EMA戦略におけるL1フィルタ利用のまとめ

            検討した例では、EURUSD通貨ペア、H1時間足、2025年を対象としてトレード戦略をテストしました(図62)。

            図62:テスト期間におけるEURUSD価格チャート(2025年、H1、終値)

            図62:テスト期間におけるEURUSD価格チャート(2025年、H1、終値)


            図63:異なるフィルタモードにおける移動平均、MACD、ADX、およびEMA戦略のバランス曲線

            図63:異なるフィルタモードにおける移動平均、MACD、ADX、およびEMA戦略のバランス曲線


            移動平均、MACD、ADX、およびEMA戦略の分析では、ポジション決済段階でL1フィルタを適用した場合(チャート上では緑色で表示)が最も良い結果を示しました。エグジット時のフィルタ適用は、ノイズによる反転や偽シグナルを効果的に抑制し、安定したトレンド方向へのポジション保持を可能にします。その結果、利益およびプロフィットファクターが向上し、最大ドローダウンも低減されました。

            一方、ポジションオープン段階でのL1フィルタ適用(オレンジ色で表示)は、それほど効果的ではありませんでした。追加フィルタによってエントリー数が制限され、取引品質の改善に見合うほどではない形で利益機会の一部を逃す結果となったためです。


            図64:異なるフィルタモードにおける移動平均、MACD、ADX、およびEMA戦略のバランス+エクイティ曲線

            図64:異なるフィルタモードにおける移動平均、MACD、ADX、およびEMA戦略のバランス+エクイティ曲線


            このように、ポジション決済時にトレードシグナルへL1フィルタを適用することで、トレードシステムの安定性が向上し、短期的な価格変動への感度が低下し、利益/リスク比が改善されます。古典的な移動平均と比較して、L1フィルタは一時的な調整と実際のトレンド反転をより適切に区別できるため、トレンドフォロー戦略をより効率的に利用できます。

            また、L1トレンドフィルタと整合したシグナルで取引した場合のバランスおよびエクイティ曲線の挙動にも注目する必要があります(図64)。トレンド方向への取引では、エクイティがバランスを上回ることが多く、これによりリスク指標が大きく改善され、ドローダウンが低減されます。したがって、トレンドとの整合性はトレードシステムの特性(ドローダウンおよびリスク)も改善します。

            さらに、L1トレンドと売買シグナルを整合させることで、取引回数は減少する一方で取引品質は向上し、これもトレード戦略全体の統計特性に対して好影響を与えます。


            番号 戦略 総純利益(米ドル) Buy and Hold比率
            1  Buy and Hold 1363.8 100 %
            2  移動平均(フィルタなし) 1001.03 73.4 %
            3  移動平均(L1エントリフィルタ) 107.65 7.89 %
            4  移動平均(L1エグジットフィルタ) 1342.5 98.43 %
            5  移動平均(L1エントリー+エグジット) 986.16 72.31 %
            6  MACD(フィルタなし) 997.79 73.16 %
            7  MACD(L1エントリーフィルタ) 140.13 10.27 %
            8  MACD(L1エグジットフィルタ) 1359.52 99.69 %
            9  MACD(L1エントリー+エグジット) 697.54 51.15 %
            10  ADX(フィルタなし) 791.99 58.07 %
            11  ADX(L1エントリーフィルタ) -50.9 -3.73 %
            12  ADX(L1エグジットフィルタ) 940.39 68.95 %
            13  ADX(L1エントリー+エグジット) 430.05 31.53 %
            14  EMA(フィルタなし) 957.3 70.19 %
            15  EMA(L1エントリーフィルタ) -173.35 -12.71 %
            16  EMA(L1エグジットフィルタ) 1258.99 92.31 %
            17  EMA(L1エントリー+エグジット) -131.41 -9.64%

            表4:移動平均線、MACD、ADX、およびEMA戦略におけるL1フィルタ利用時の総利益結果とBuy and Holdの比較


            表4によると、ポジション決済時にL1フィルタを使用することで、すべての戦略において収益性が改善されました。

            Buy and Hold戦略の結果(1363.8ドル)をトレンド全体の値動きに対する100%とした場合、以下の結果が得られます。

            1. 移動平均の利益は73.4%から98.43%に増加
            2. MACDの利益は73.16%から99.69%に増加
            3. ADXの利益は58.07%から68.5%に増加
            4. EMAの利益は70.19%から92.31%に増加

            このように、L1フィルタを使用することで、移動平均、MACD、およびEMA戦略では利益が22〜26%増加し、トレンド全体の大部分(98.43%、99.69%、92.31%)を捉えることができ、Buy and Holdの結果に近づきました。ADX戦略では利益が10%増加しました。

            これらの例では、最も高いバランス値を示したパラメータ、すなわち可能な解の中でも最良クラスの設定を持つ戦略を対象としています。これらは青色で示されています。結果から、こうした最も収益性の高い解であっても、L1トレンドとの整合性による追加フィルタリングをおこなうことで、さらに改善できることが分かります。一部の戦略では改善幅は小さく(ADXでは緑の曲線が青の曲線に近く、最適なバランス解に近いことを示しています)、L1フィルタ適用後にどれだけ改善するかによって、その戦略の売買シグナル品質(選択されたパラメータの最適性)を評価することができます。

            なお、本例では強いトレンドを持つEURUSD市場を対象としています(図62)。他の市場レジームや金融商品では結果は異なります。また、L1トレンドはH1時間足において、正則化パラメータλ = 0.2·λmaxを用いて構築されました。他の金融商品や時間足については、適切な係数値を L1トレンドインジケータを用いて推定することができます。


            結論

            L1トレンドフィルタリングは、局所的なノイズと実際のトレンド変化を分離するためのツールとして、実務上の有用性が確認されています。

            この手法は、自動的にブレークポイントを持つ区分線形のトレンドを生成し、さらにλmaxを基準とした便利なチューニングスケールを提供するため、手動でのパラメータ調整の問題を解消できます。

            実践的な統合レベルでは、完全なツールキットが提供されています。具体的には、λmaxおよびL1フィルタを計算する関数、3種類のインジケータ(L1Trend、L1TrendSlope、L1TrendSlopeSign)、7種類のL1トレンドボラティリティ指標(L1Volatility、L1VolatilitySmoothed、L1VolatilityAbsolute、L1VolatilityNormalized、L1VolatilityNormalizedSmoothed、L1VolatilityRegime、L1VolatilityRegimeColor)、EAテンプレート、さらに再現可能なテストプロトコル(4モード:フィルタなし、エントリーフィルタ、エグジットフィルタ、両方のフィルタ適用、結果保存、および Python 可視化スクリプト)が含まれています。

            また、L1 トレンドフィルタは機械学習におけるデータラベリングにも利用可能である点に注意すべきです。特に、記事「機械学習を用いたトレンド取引戦略の開発」では、Savitzky–Golay フィルタで平滑化した価格系列の微分を用いてトレンド判定をおこなっています。同様のアプローチはL1フィルタリングでも実装可能であり、この場合、トレンドは区分線形関数として近似され、各セグメントにおけるトレンド強度は対応する区間の傾きによって自然に表現されます。


            実践上の推奨事項

            • 相対正則化を使用します(λ = coef_lambda_max · λmax)。多くの用途では、coefは0.04〜0.25の範囲を使用します。より細かい変動を捉えたい場合は0.02〜0.04程度、より粗い近似やレジーム検出には0.12〜0.25程度が適しています。
            • 多くの場合、L1フィルタはポジション決済(利益トレンドを維持し、早すぎる決済を減らす)に適用したときに最も効果的です。エントリー側に適用すると、取引回数は減少する一方で、品質改善が比例しないケースがあります。
            • 現在のトレンド分析には、単純なルール「delta = x_filtered[last] − x_filtered[last−1]」を使用してください。deltaの符号が、支配的な L1 トレンドの方向を示します。

            制限事項:効果は銘柄、時間足、市場レジームに依存するため、適切な評価指標を用いたヒストリカルデータ検証が必要です。

            提案されているMQL5モジュールおよびテストプロトコルにより、特定の売買システムに対する仮説検証や有効パラメータの選定を迅速におこなうことができます。

            記事中のすべてのコードは、公開プロジェクトMQL5\Shared Projects\L1Trendでも利用可能です。


            サンプル一覧

            種別 ファイル 説明
            スクリプト MQL5\Scripts\TestL1Trend.mq5
            モデルデータ(ランダムウォーク)に対してL1トレンドを計算するテストスクリプト
            スクリプト MQL5\Scripts\TestL1TrendFloatDouble.mq5 doubleおよびfloatベクトルを用いて、モデルデータ(ランダムウォーク)のL1トレンドを計算するテストスクリプト
            スクリプト MQL5\Scripts\TestL1TrendFilterSP500.mq5 SP500指数の価格データに対してL1トレンドを計算するテストスクリプト
            データファイル MQL5\Files\snp500.txt
            テストスクリプト用のデータファイル(S&P500指数価格系列のログ)
            スクリプト MQL5\Scripts\TestScalingBrownianMotion.mq5 ブラウン運動に対するλmaxのべき乗則依存性を計算するスクリプト
            スクリプト MQL5\Scripts\TestScalingSymbol.mq5 指定シンボルの価格系列に対するλmaxのべき乗則依存性を計算するスクリプト
            インジケータ MQL5\Indicators\L1TrendFilter.mq5 L1トレンドを計算するインジケータ
            インジケータ
            MQL5\Indicators\L1TrendFilter_Slope.mq5 L1トレンドの変化率を計算するインジケータ
            インジケータ
            MQL5\Indicators\L1TrendFilter_SlopeSign.mq5 L1トレンド変化の符号を計算するインジケータ
            インジケータ
            MQL5\Indicators\L1Volatility.mq5 残差ボラティリティ(終値とL1トレンド値との差分)を計算するインジケータ
            インジケータ
            MQL5\Indicators\L1VolatilitySmoothed.mq5 平滑化残差ボラティリティを計算するインジケータ
            インジケータ
            MQL5\Indicators\L1VolatilityAbsolute.mq5 絶対変動率を計算するインジケータ
            インジケータ
            MQL5\Indicators\L1VolatilityNormalized.mq5 ATRとL1トレンドを用いて正規化ボラティリティを計算するインジケータ
            インジケータ
            MQL5\Indicators\L1VolatilityNormalizedSmoothed.mq5 平滑化正規化ボラティリティを計算するインジケータ
            インジケータ
            MQL5\Indicators\L1VolatilityRegime.mq5 市場レジームを検出するインジケータ
            インジケータ
            MQL5\Indicators\L1VolatilityRegimeColor.mq5 市場レジーム検出インジケータのカラー版
            EA MQL5\Experts\MovingAverageFilteredL1.mq5 L1フィルタを適用した移動平均戦略ベースのEA
            EA
            MQL5\Experts\MACDFilteredL1.mq5 L1フィルタを適用したMACD戦略ベースのEA
            EA MQL5\Experts\ADXFilteredL1.mq5 L1フィルタを適用したADX戦略ベースのEA
            EA MQL5\Experts\EMAFilteredL1.mq5 L1フィルタを適用したEMAクロスオーバー戦略ベースのEA
            Python script MQL5\Scripts\PlotData.py L1フィルタ適用時のトレードシグナル有効性を分析するためのPythonスクリプト

            表5:記事で使用されているプログラムコードの説明



            MetaQuotes Ltdによってロシア語から翻訳されました。
            元の記事: https://www.mql5.com/ru/articles/21142

            添付されたファイル |
            最後のコメント | ディスカッションに移動 (8)
            Renat Akhtyamov
            Renat Akhtyamov | 20 4月 2026 において 19:09

            こんな感じだ:


            Quantum
            Quantum | 20 4月 2026 において 20:12
            Renat Akhtyamov #:

            こんな感じだ:

            トレンドへの分割は正則化パラメータλに大きく依存し、λが小さいほど、より短いトレンドを捉えることができる。
            考慮した例では、λ=0.2*λ_maxの単位でλの固定値を使用した。λ_max単位での計算は、部分的にデータに適応することができます。lambda_max の値自体は、系列の形状(相対スプレッド)、すなわちボラティリティに依存する。

            トレンドにはさまざまなフェーズとライフサイクルがあることを念頭に置く必要があります。したがって、現在のトレンドに適応するための何らかのメカニズムが必要である。すなわち、何らかの方法でラムダを管理し、最適なトレンドの分割を見つけることである。


            最良の結果は理想的なトレンド市場であるべきで、その例は次のとおりである: EURUSD, 2025, H1 (the best parameters MovingAverage period=61).

            ユーロドル

            テスター・オプション

            テスターパラメーター

            フィルターなしのテスター結果

            L1終値フィルター

            L1クローズフィルター

            ここでは、エグジットフィルターがトレンドエリアでの利益を増加させるのに役立っていることがわかる。


            同じストラテジーのバリエーションで、修正時にポジションを追加したもの:

            テスターオプション

            テスターオプション ポジション追加

            追加なし:

            追加あり:



            また、平坦な市場区間におけるMovingAverageパラメータの最適値は異なるはずである。すなわち、2つ目の区間における平均の最適な期間は変化しています(しかし、テスターで最適化した場合、見つかったパラメータは最適化の全区間において他のすべてのパラメータの中で最高の利益をもたらします)。

            Quantum
            Quantum | 20 4月 2026 において 21:08

            異なるラムダでフラット・インターバルの結果をチェックしてみよう。

            フィルターなし

            出力フィルターあり lambda=0.2*lambda_max

            フィルターあり λ=0.001λ_max(傾向が小さい)。

            このように、λ=0.001 lambda_maxの平坦区間では、フィルタなしで結果を改善し、局所的な小さな傾向を考慮することができます。

            しかしながら、フィルターλ=0.2*λ_maxを用いたバリエーションは、フィルターなしの戦略よりも低い収益性を示しました。

            Quantum
            Quantum | 20 4月 2026 において 21:30

            フラットネス内の局所的なトレンドにポジション(異なるラムダ)を追加したバリエーション

            フィルターなし:


            Cフィルター lambda=0.2*lambda_maxで補正を加えたもの:

            λ=0.001*λ_maxのCフィルターと補正を加えたもの:

            λ=0.2*λ_maxのフィルタと補正の追加:λ=0.001*λ_maxのフィルタと補正の追加を加えたバリエーションは、フィルタなしのバリエーションよりも良い結果を示した。

            補正の平坦区間内に局所的な小さなトレンド(λ=0.001*λ_max)を追加することで、フィルターなしのオリジナル戦略の利益を増加させることができた(利益という点ではλ=0.2*λ_maxの変種を改善)。

            Renat Akhtyamov
            Renat Akhtyamov | 21 4月 2026 において 08:06
            Quantum #:

            フラットネス内の局所的なトレンドにポジション(異なるラムダ)を加えたバリエーション

            フィルターなし


            Cフィルター lambda=0.2*lambda_maxで補正を加えたもの:

            Cフィルター lambda=0.001*lambda_maxに補正を加える:

            λ=0.2*λ_maxのフィルタと補正の加算を用いた変種は、フィルタを用いなかった変種よりも良い結果を示した。

            補正の平坦区間内に局所的な小さなトレンド(λ=0.001*λ_max)を加えることで、フィルターなしのオリジナル戦略の利益を増加させることができた(そして、利益という点ではλ=0.2*λ_maxの変種を改善した)。

            少なくともデモでは

            理解するには、経験を積み、10ヶ月間無駄な作業をした後に、理解できるようになるだろう。

            ルーチン作業なしのアルゴリズム取引:MetaTrader 5におけるSQLiteを用いた高速取引分析 ルーチン作業なしのアルゴリズム取引:MetaTrader 5におけるSQLiteを用いた高速取引分析
            本記事では、MQL5におけるSQLiteを用いた取引ジャーナル管理のための「最小実用構成」を紹介します。内容には、取引、シグナル、イベント用テーブル構造、インデックス設計、プリペアドステートメントによる高速かつ安全なデータ記録、さらに標準的な分析用SQLクエリが含まれます。また、MetaTrader 5の統計ダッシュボードとの統合方法や、MetaEditor上でデータベースを操作する手法についても解説します。このアプローチにより、取引ジャーナルの自動化、計算処理の高速化、そしてEAコードを複雑化させることなく高度な分析を実現できます。
            Python + MetaTrader 5:データ、機能、プロトタイプのための高速研究フレームワーク Python + MetaTrader 5:データ、機能、プロトタイプのための高速研究フレームワーク
            本記事では、PythonとMetaTrader 5の統合によって、研究の柔軟性と取引実行を単一のワークフローに統合できることを示しています。Pythonはデータ分析、特徴量選択、モデル学習に使用され、MetaTrader 5はテストおよび取引自動化に使用されます。このアプローチにより、ソリューションを実運用へ移行するプロセスが簡素化され、再現性が向上し、トレードシステムの開発がより迅速かつ構造化されます。
            OpenCLを用いたMQL5におけるCPUからGPUへの実践的移行パス OpenCLを用いたMQL5におけるCPUからGPUへの実践的移行パス
            MQL5でCPUからGPUへの移行方法を実用的に構築する方法を解説します。本記事では、コンテキストの初期化、バッファ構造の設計、大規模バッチ処理、カーネルの起動、データ転送の最小化に焦点を当てます。また、典型的なエラーとその解決方法についても取り上げます。ローソク足パターンの例を通じて、このアプローチの実用的な効果も示します。
            決定論的振動型探索(DOS) 決定論的振動型探索(DOS)
            決定論的振動型探索(DOS, Deterministic Oscillatory Search)アルゴリズムは、乱数を使用せずに勾配法と群知能アルゴリズムの利点を組み合わせた、革新的な大域最適化手法です。適応度の振動と勾配状態メカニズムによって、DOSは複雑な探索空間を決定論的に探索することができます。