MetaTrader 5でL1トレンドフィルタリングを適用する
- データの長期的ダイナミクスを維持する
- 短期的な変動およびノイズを抑制する
- トレンドの構造的な変化点(傾きの変化)を自動的に検出する

内容
- はじめに
- 1. トレンドフィルタリングの問題定式化
1.1. ホドリック=プレスコット・フィルタ
1.2. L1トレンドフィルタリング法
1.3. 正則化パラメータλの役割
1.4. 幾何学的解釈
1.5. λmaxの計算アルゴリズム - 2. L1トレンドを計算するためのMQL5メソッド
2.1. L1TrendFilterLambdaMax
2.2. L1TrendFilter - 3. L1トレンド計算の例
3.1. 合成データ(ランダムウォーク)におけるL1トレンド
3.2. S&P 500価格系列におけるL1トレンド
3.3. λmaxのスケーリング特性
3.3.1. ブラウン運動に対する数値実験
3.3.2. 金融時系列におけるスケーリング
3.3.3. スケーリングの実務的含意
3.4. L1トレンドインジケータ
3.4.1. L1TrendFilter.mq5:L1トレンドインジケータ
3.4.2. L1TrendFilterSlope.mq5:L1トレンド傾きインジケータ
3.4.3. L1TrendFilterSlopeSign.mq5:トレンド方向インジケータ - 3.4.4. L1トレンドに基づくボラティリティ指標
3.4.4.1. L1Volatility.mq5:残差ボラティリティ指標
3.4.4.2. L1VolatilitySmoothed.mq5:平滑化残差ボラティリティ指標
3.4.4.3. L1VolatilityAbsolute.mq5:絶対ボラティリティ指標
3.4.4.4. L1VolatilityNormalized.mq5:正規化ボラティリティ指標
3.4.4.5. L1VolatilityNormalizedSmoothed.mq5:平滑化された正規化ボラティリティ指標
3.4.4.6. L1VolatilityRegime.mq5:ボラティリティに基づく市場レジーム検出 -
3.5. トレード戦略におけるL1トレンドの応用
3.5.1. 移動平均戦略
3.5.1.1. L1トレンドフィルタリングの有効性評価の一般的方法論
3.5.1.2. 移動平均戦略における結果
3.5.2. MACD戦略
3.5.2.1. MACD戦略の結果
3.5.3. ADX戦略
3.5.3.1. ADX戦略の結果
3.5.4. EMA戦略
3.5.4.1. EMA戦略の結果
3.5.5. 移動平均、MACD、ADX、EMA戦略における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フィルタ計算例
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トレンド推定の例
このスクリプトの実行により、[エキスパート]タブには与えられた時系列に対するλmaxの値が表示されます。
TestL1TrendFilterSP500 (EURUSD,H1) Lambda_max=37394.835512
このスクリプトは、L1TrendFilterLambdaMaxおよびL1TrendFilterメソッドの使用方法を示しており、正則化パラメータλ = 50を固定値として用いる点は、手法の著者による原論文と同様です。
以降の例では、正則化パラメータλの絶対値ではなく、relative = trueフラグを用いて、λmax単位での相対値を使用します。
3.3. λmaxのスケーリング特性
L1フィルタリングにおいてパラメータλmaxは重要な役割を持ちます。これは、解が大域的な線形近似へと退化する正則化の上限を定義するためです。この量の興味深い性質として、時系列長に対するスケーリング依存性があります。
数値実験によれば、λmaxは観測数に対してべき乗則に従って増加します。
![]()
ここでTは時系列の長さ、αはスケーリング指数です。
ランダムウォーク(ブラウン運動)の場合、指数はα ≈ 2.5に近い値になることが示されます。ブラウン運動の振幅は
その結果、これらのスケーリングが組み合わさることで次の関係が得られます。
![]()
これは指数α ≈ 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のべき乗依存性
シミュレーション結果は以下の通りです。
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のべき乗依存性
同様に、他の通貨ペアについても解析することができます。
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のべき乗依存性
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のべき乗依存性
今回扱ったEURUSD、USDJPY、GBPUSDの各系列においても、推定された指数はおおむね2.5に近い値となっています。
複数の時間足および通貨ペアにおけるλmaxの対数スケールでの線形関係は、λmaxが観測数に対してべき乗的に依存することを示しています。
3.3.3. スケーリングの実務的含意
λmaxに対するべき乗依存性の存在は、重要な実務的意味を持ちます。
λmax ∝ T^2.5であるため、λの絶対値は以下に強く依存します。
- データウィンドウの長さ
- 時間足
- 時系列のスケール
したがって、λの絶対値を用いることは実務上あまり適していません。
よりロバストなアプローチは、λ=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インジケータ計算例
比較のため、異なる正則化パラメータを用いた複数のバリエーションを計算することができます。
図8では、CoefLambda = 0.015、CoefLambda = 0.025、およびCoefLambda = 0.055を用いた計算結果を示します。

図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インジケータ計算例
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インジケータ計算例
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インジケータ
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インジケータ
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インジケータ
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インジケータ
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インジケータ
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インジケータ
利便性のために、レジームをカラーで可視化するバージョンも使用することができます。
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インジケータ
図18〜20では、EURGBP、AUDCAD、およびCHFJPYにおける全ボラティリティインジケータの同時表示例を示します。

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

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

図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つの組み合わせをすべて評価します。
- L1FilterOpen = false, L1FilterClose = false(フィルタなしの取引)
- L1FilterOpen = true, L1FilterClose = false(エントリーフィルタ)
- L1FilterOpen = false, L1FilterClose = true(エグジットフィルタ)
- 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トレンド解析を用いた追加フィルタリングが適用されます。
- 直近のL1TotalBars本のデータを用いて平滑化された価格系列を構築します。
- トレンドの成長係数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フィルタの有効性を評価するためには、以下の手順が必要です。
- まず、最大利益を得るためのトレード戦略の最適パラメータセットを見つける必要があります。
その上で、最もパフォーマンスの良い戦略について、売買シグナルの改善を検討することが望まれます。 - L1FilterOpen/L1FilterCloseの設定により、以下の4種類のフィルタ適用パターンを比較します。
- フィルタなしのトレード
- エントリーフィルタありのトレード
- エグジットフィルタありのトレード
- エントリーおよびエグジット両方のフィルタありのトレード
- その後、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の最適化設定
高速最適化のために、「1分OHLC」モードを使用します(本戦略は新しいバーでのみ動作するため、この近似は許容されます)。また、最適化アルゴリズムとして「高速遺伝的アルゴリズム」を選択し、評価基準として「バランス最大」を設定します。

図22:MovingAverageFilteredL1.mq5の最適化パラメータ
この段階の目的はトレード戦略の最適パラメータを見つけることなので、最適化パラメータではすべてのフィルタおよびファイル保存を無効化します。
簡略化のため、「MA Period」パラメータのみを1から800の範囲、ステップ1で最適化します。

図23:MovingAverageFilteredL1.mq5の最適化結果
「MA Period」パラメータの最適化は1分未満で完了し、結果および上位パラメータの一覧が図23に示されます。
より正確なテストのために、テスト設定で[Every tick based on real ticks]を選択します。

図24:MovingAverageFilteredL1.mq5のテスト設定
最良パラメータ「MA Period = 64」で単体テストを実行します。

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

図26:MovingAverageFilteredL1.mq5の最適パラメータを使用したテスト結果
次に、ファイルへのデータ保存を有効にしてテストを実行する必要があります。
そのためにSaveStatistics = trueを設定し、売買シグナルフィルタの4つの組み合わせでテストを実行します。

図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つのフィルタモードにおけるテスト結果ファイル
データ解析は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スクリプト
PlotData.pyを実行すると、以下のチャートが表示されます。
- EURUSDの価格系列(各バーの終値)
- すべてのフィルタモードにおけるバランス曲線
- すべてのフィルタモードにおけるエクイティ曲線
- バランス+エクイティチャート(ドローダウン低減の評価用)
これらのチャートはC:\Data\Charts\にPNG形式で保存されます。

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

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

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

図33:異なるフィルタモードにおけるバランス+エクイティチャート
MetaEditorでPythonスクリプトを実行するためには、Python(例ではバージョン3.14)をインストールし、そのパスを設定で指定する必要があります。

図34:MetaEditorのPython設定
スクリプトはpandasおよびmatplotlibライブラリを使用します。インストールされていない場合:
pip install pandas pip install matplotlib
3.5.1.2. 移動平均戦略にL1フィルタを適用した結果
結果(バランス、エクイティ、およびチャート)を図35〜37に示します
色の定義:
- 青(フィルタなしの戦略)
- 緑(エグジットにL1フィルタ適用)
- 赤(エントリーおよびエグジット両方にL1フィルタ適用)
- オレンジ(エントリーにL1フィルタ適用)

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

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

図37:異なるフィルタモードにおける MovingAverageFilteredL1.mq5のバランス+エクイティチャート
3.5.2. MACDトレード戦略
別の例として、MACD (Moving Average Convergence/Divergence)インジケータに基づく売買シグナルを用いるEAを考えます。
売買シグナル
シグナルはMACDメインラインとシグナルラインのクロスオーバーで生成されます。
- BUY:MACDメインラインがシグナルラインを下から上へクロスした場合
- 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のストラテジーテスター設定

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

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

図41:MACDFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ
ストラテジーテスターで異なるフィルタ設定によるテストを実行すると、testerディレクトリにx_MACD_EURUSD.txtファイルが生成されます。
これらのファイルはC:\Data\にコピーし、その後PlotData.pyスクリプトを実行します。

図42:異なるフィルタモードにおけるMACDFiltered.mq5のテスト結果ファイル
3.5.2.1. MACD戦略にL1フィルタを適用した結果
結果を図43~45に示します。

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

図44:異なるフィルタモードにおける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のテスター設定

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

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

図49:図49:ADXFilteredL1.mq5の結果をファイルに保存するためのテストパラメータ
その後、testerディレクトリにファイルが生成されます。これらはC:\Dataにコピーし、PlotData.pyで処理します。

図50:図50:異なるフィルタモードにおけるADXFiltered.mq5のテスト結果ファイル
3.5.3.1. ADX戦略にL1フィルタを適用した結果
結果を図51~53に示します。

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

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

図53:異なるフィルタモードにおけるADXFilteredL1.mq5のバランス+エクイティチャート
3.5.4. EMAクロスオーバーに基づくトレード戦略
EMAFilteredL1.mq5は、2本の指数移動平均線(EMA)のクロスオーバーに基づくトレンドフォロー戦略を実装します。
この戦略では、2つの移動平均を使用します。
- FastEMA:短期指数移動平均
- 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のテスター設定

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

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

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

図58:異なるトレードシグナルフィルタモードにおけるEMAFilteredL1.mq5のテスト結果ファイル
3.5.4.1. EMA戦略売買シグナルにL1フィルタを適用した結果
結果を図59~61に示します。

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

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

図61:異なるフィルタモードにおけるEMAFilteredL1.mq5のバランス+エクイティチャート
3.5.5. 移動平均、MACD、ADX、EMA戦略におけるL1フィルタ利用のまとめ
検討した例では、EURUSD通貨ペア、H1時間足、2025年を対象としてトレード戦略をテストしました(図62)。

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

図63:異なるフィルタモードにおける移動平均、MACD、ADX、およびEMA戦略のバランス曲線
移動平均、MACD、ADX、およびEMA戦略の分析では、ポジション決済段階でL1フィルタを適用した場合(チャート上では緑色で表示)が最も良い結果を示しました。エグジット時のフィルタ適用は、ノイズによる反転や偽シグナルを効果的に抑制し、安定したトレンド方向へのポジション保持を可能にします。その結果、利益およびプロフィットファクターが向上し、最大ドローダウンも低減されました。
一方、ポジションオープン段階でのL1フィルタ適用(オレンジ色で表示)は、それほど効果的ではありませんでした。追加フィルタによってエントリー数が制限され、取引品質の改善に見合うほどではない形で利益機会の一部を逃す結果となったためです。

図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%とした場合、以下の結果が得られます。
- 移動平均の利益は73.4%から98.43%に増加
- MACDの利益は73.16%から99.69%に増加
- ADXの利益は58.07%から68.5%に増加
- 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
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
ルーチン作業なしのアルゴリズム取引:MetaTrader 5におけるSQLiteを用いた高速取引分析
Python + MetaTrader 5:データ、機能、プロトタイプのための高速研究フレームワーク
OpenCLを用いたMQL5におけるCPUからGPUへの実践的移行パス
決定論的振動型探索(DOS)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
こんな感じだ:
こんな感じだ:
トレンドへの分割は正則化パラメータλに大きく依存し、λが小さいほど、より短いトレンドを捉えることができる。
考慮した例では、λ=0.2*λ_maxの単位でλの固定値を使用した。λ_max単位での計算は、部分的にデータに適応することができます。lambda_max の値自体は、系列の形状(相対スプレッド)、すなわちボラティリティに依存する。
トレンドにはさまざまなフェーズとライフサイクルがあることを念頭に置く必要があります。したがって、現在のトレンドに適応するための何らかのメカニズムが必要である。すなわち、何らかの方法でラムダを管理し、最適なトレンドの分割を見つけることである。
最良の結果は理想的なトレンド市場であるべきで、その例は次のとおりである: EURUSD, 2025, H1 (the best parameters MovingAverage period=61).
L1終値フィルター
ここでは、エグジットフィルターがトレンドエリアでの利益を増加させるのに役立っていることがわかる。
同じストラテジーのバリエーションで、修正時にポジションを追加したもの:
追加なし:
追加あり:
また、平坦な市場区間におけるMovingAverageパラメータの最適値は異なるはずである。すなわち、2つ目の区間における平均の最適な期間は変化しています(しかし、テスターで最適化した場合、見つかったパラメータは最適化の全区間において他のすべてのパラメータの中で最高の利益をもたらします)。
異なるラムダでフラット・インターバルの結果をチェックしてみよう。
フィルターなし
出力フィルターあり lambda=0.2*lambda_max
フィルターあり λ=0.001λ_max(傾向が小さい)。
このように、λ=0.001 lambda_maxの平坦区間では、フィルタなしで結果を改善し、局所的な小さな傾向を考慮することができます。
しかしながら、フィルターλ=0.2*λ_maxを用いたバリエーションは、フィルターなしの戦略よりも低い収益性を示しました。
フラットネス内の局所的なトレンドにポジション(異なるラムダ)を追加したバリエーション
フィルターなし:
Cフィルター lambda=0.2*lambda_maxで補正を加えたもの:
λ=0.001*λ_maxのCフィルターと補正を加えたもの:
λ=0.2*λ_maxのフィルタと補正の追加:λ=0.001*λ_maxのフィルタと補正の追加を加えたバリエーションは、フィルタなしのバリエーションよりも良い結果を示した。
補正の平坦区間内に局所的な小さなトレンド(λ=0.001*λ_max)を追加することで、フィルターなしのオリジナル戦略の利益を増加させることができた(利益という点ではλ=0.2*λ_maxの変種を改善)。
フラットネス内の局所的なトレンドにポジション(異なるラムダ)を加えたバリエーション
フィルターなし
Cフィルター lambda=0.2*lambda_maxで補正を加えたもの:
Cフィルター lambda=0.001*lambda_maxに補正を加える:
λ=0.2*λ_maxのフィルタと補正の加算を用いた変種は、フィルタを用いなかった変種よりも良い結果を示した。
補正の平坦区間内に局所的な小さなトレンド(λ=0.001*λ_max)を加えることで、フィルターなしのオリジナル戦略の利益を増加させることができた(そして、利益という点ではλ=0.2*λ_maxの変種を改善した)。
少なくともデモでは
理解するには、経験を積み、10ヶ月間無駄な作業をした後に、理解できるようになるだろう。