Применение L1-фильтрации тренда в MetaTrader 5
Основная цель L1-фильтрации тренда (L1 Trend Filtering) заключается в выделении тренда во временном ряде таким образом, чтобы:
- сохранить долгосрочную динамику данных;
- подавить краткосрочные колебания и шум;
- автоматически выявить точки изменения наклона тренда.
В отличие от классических методов сглаживания, этот способ не предполагает гладкость тренда и позволяет получать кусочно-линейную аппроксимацию, что особенно важно при анализе финансовых временных рядов.

Содержание
- Введение
- 1. Постановка задачи фильтрации тренда
1.1. Фильтр Ходрика–Прескотта
1.2. Метод L1-фильтрации тренда
1.3. Роль параметра регуляризации λ
1.4. Геометрическая интерпретация задачи
1.5. Алгоритм расчета λmax - 2. Методы расчета L1-тренда
2.1. L1TrendFilterLambdaMax
2.2. L1TrendFilter - 3. Примеры использования методов расчета L1-тренда
3.1. Расчет L1-тренда на модельных данных (случайные блуждания)
3.2. Расчет L1-тренда для котировок индекса SP500
3.3. Скейлинговые свойства параметра регуляризации λmax
3.3.1. Численный эксперимент для броуновского движения
3.3.2. Скейлинг для финансовых временных рядов
3.3.3. Практическое значение скейлинга
3.4. Индикаторы L1-тренда
3.4.1. Индикатор расчета L1 тренда L1TrendFilter.mq5
3.4.2. Индикатор расчета динамики L1 тренда L1TrendFilterSlope.mq5
3.4.3. Индикатор расчета направления динамики L1-тренда 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. Торговая стратегия на основе индикатора MovingAverage
3.5.1.1. Общая методика анализа эффективности использования L1-тренда для фильтрации торговых сигналов
3.5.1.2. Результаты применения L1-фильтров торговых сигналов для Moving Average
3.5.2. Торговая стратегия на основе индикатора MACD
3.5.2.1. Результаты применения L1-фильтров торговых сигналов стратегии MACD
3.5.3. Торговая стратегия на основе индикатора ADX
3.5.3.1. Результаты применения L1-фильтров торговых сигналов стратегии ADX
3.5.4. Торговая стратегия на основе индикатора EMA
3.5.4.1. Результаты применения L1-фильтров торговых сигналов стратегии ADX
3.5.5. Выводы по результатам использования L1-фильтра в торговых стратегиях MovingAverage, MACD, ADX и EMA - Заключение
Введение
Финансовые временные ряды характеризуются высокой зашумлённостью, частыми выбросами и сменой рыночных режимов. В практических торговых системах это проявляется просто и измеримо: классические «гладкие» фильтры (скользящие средние, HP) запаздывают, размывают моменты смены наклона и часто принимают локальные коррекции за развороты — в результате увеличивается число ложных входов/выходов, снижается Profit Factor и растёт просадка. Кроме того, подбор параметра регуляризации λ обычно сводится к ручной подгонке и плохо переносится между инструментами, таймфреймами и длинами истории.
В статье предлагается практическое решение этих проблем на основе L1‑фильтрации тренда: оптимизация с L1‑регуляризацией вторых разностей автоматически даёт кусочно‑линейную аппроксимацию с явными точками излома. Ключевые преимущества — явная интерпретация изломов как смен режимов, возможность задать масштаб регуляризации через вычисление λmax и перейти к относительному параметру λ = coef⋅λmax, а также линейная вычислительная сложность, пригодная для реализации в MQL5.
Мы показываем не только теорию, но и готовую прикладную дорожную карту: представляем методы расчёта λmax и L1‑тренда, которые есть в языке MQL5, 3 индикатора L1-тренда (тренд, наклон, знак наклона), 7 индикаторов волатильности а также интеграцию в советники и воспроизводимый протокол тестирования (четыре режима фильтрации, выгрузка balance/equity и визуализация).
1. Постановка задачи фильтрации тренда
Рассматривается скалярный временной ряд, который представляется в виде суммы двух компонент:
,
где
- трендовая составляющая, а
- шум или нерегулярная компонента.
Целью является оценка тренда
по наблюдаемым данным
.
Задача формулируется как компромисс между точностью аппроксимации исходных данных и гладкостью оценённого тренда.
1.1. Фильтр Ходрика–Прескотта
Фильтр Ходрика-Прескотта определяет тренд как решение задачи минимизации:
,
где параметр λ регулирует степень сглаживания.
Основные свойства HP-фильтра:
- Линейность по отношению к данным;
- Вычислительная сложность O(n);
- При малых λ тренд приближается к исходным данным;
- При больших λ тренд стремится к наилучшему линейному приближению.
Однако HP-фильтр всегда даёт гладкий тренд и плохо выявляет резкие изменения наклона.
1.2. Метод L1-фильтрации тренда
Основная идея L1-фильтрации заключается в поиске тренда, который близок к исходным данным, но содержит как можно меньше изменений наклона. В отличие от классических методов сглаживания, которые минимизируют квадрат кривизны, L1-подход минимизирует сумму модулей вторых разностей.
Это приводит к принципиально другому результату:
- большинство вторых разностей становится равным нулю,
- тренд автоматически разбивается на линейные сегменты.
Таким образом, L1-фильтр не пытается сделать тренд гладким, а ищет минимальное число структурных изменений, объясняющих наблюдаемые данные. Это делает метод особенно удобным для финансовых временных рядов, где динамика часто состоит из последовательности квазилинейных фаз роста и падения.
В методе L1-фильтрации квадратичный штраф за вторые разности заменяется на L1-норму и тренд определяется как решение выпуклой задачи оптимизации:

В матричной форме:

где:
- y — входной временной ряд;
- x — искомый тренд;
- D — матрица вторых разностей;
- λ>=0 — параметр регуляризации.
Использование L1-нормы приводит к принципиально иному результату: многие вторые разности становятся равными нулю, а значит тренд является кусочно-линейным.
Вторая разность определяется как:

Если
, то точки
лежат на одной прямой.
Следовательно, нулевая вторая разность означает линейный участок тренда, а ненулевая вторая разность соответствует излому. L1-норма способствует разреженности вектора Dx, то есть большинство вторых разностей становятся равными нулю. Это означает, что на соответствующих интервалах тренд является линейным. Точки, где вторые разности ненулевые, интерпретируются как точки излома тренда.
Таким образом, метод L1 Trend Filtering автоматически строит тренд как набор линейных сегментов, соединённых в точках структурных изменений.
Основные свойства L1-фильтрации тренда:
- Тренд представляет собой набор линейных сегментов;
- Точки излома интерпретируются как структурные изменения во временном ряду;
- При λ=0 тренд совпадает с исходными данными;
- При достаточно большом λ тренд точно равен лучшей линейной аппроксимации;
- Вычислительная сложность остаётся линейной по числу наблюдений.
1.3. Роль параметра регуляризации λ
Параметр λ управляет балансом между точностью аппроксимации и сложностью тренда:
| Значение λ | Характер решения |
|---|---|
| λ=0 | x=y, сглаживание отсутствует |
| Малое λ | Слабое сглаживание, много изломов |
| Среднее λ | Кусочно-линейный тренд |
| Большое λ | Почти линейный тренд |
| λ≥λmax | Тренд строго линейный |
Табл.1 Зависимость L1-тренда от параметра регуляризации λ
Таким образом, λ контролирует число и расположение изломов тренда.
1.4. Геометрическая интерпретация задачи
Искомый тренд x можно рассматривать как точку в n-мерном пространстве. Первый член функционала, отвечающий за точность аппроксимации данных, задаёт евклидовый шар с центром в точке наблюдений y: чем ближе x к y, тем меньше ошибка.
Регуляризующий член с L1-нормой вторых разностей задаёт выпуклое многогранное множество (полиэдр). В отличие от гладких эллипсоидов, возникающих при L2-регуляризации, этот полиэдр имеет острые вершины. Эти вершины соответствуют ситуациям, когда некоторые вторые разности тренда равны нулю.
Именно наличие острых углов у L1-нормы приводит к разреженным решениям: оптимальное решение стремится попасть в вершину полиэдра, где активны лишь некоторые ограничения. Это означает, что большая часть вторых разностей обнуляется, а тренд автоматически принимает кусочно-линейную форму.
Оптимальное решение соответствует первой точке касания евклидового шара и L1-полиэдра. В этой точке тренд состоит из линейных сегментов, соединённых в ограниченном числе точек излома.
Параметр λmax соответствует ситуации, когда евклидовый шар касается L1-полиэдра не в вершине, а вдоль подпространства линейных функций. В этом случае все вторые разности равны нулю, и тренд является строго линейным.
При λ≥λmax ни одно из ограничений L1-нормы не становится активным, поэтому дальнейшее увеличение регуляризации не изменяет решение задачи и тренд остаётся линейным.
1.5. Алгоритм расчета λmax
Рассмотрим расчет максимального значения параметра регуляризации λmax для вектора входных данных y длиной N.
1. Построить матрицу второй разности D размером (N-2)xN:

2. Вычислить вектор кривизны Dy.
3. Решить систему линейных уравнений:
![]()
4. Взять максимальное (по модулю) значение вектора v:

Для финансовых временных рядов параметр λmax имеет важное практическое значение:
- Позволяет нормализовать параметр регуляризации;
- Делает выбор λ независимым от масштаба данных;
- Упрощает сравнение различных временных рядов;
- Позволяет интерпретировать λ как долю от максимальной регуляризации.
Использование относительного параметра вида: λ=coef_lambda_max⋅λmax, где множитель coef_lambda_max∈(0,1), значительно упрощает практическое применение метода.
Далее в примерах индикаторов и советников будет использоваться значение λ в единицах λmax, а в параметрах указываться значение множителя coef_lambda_max.
2. Методы расчета L1-тренда
Для практического использования L1-фильтрации тренда в язке MQL5 используются 2 метода, они реализованы для векторов типа double и float.
- L1TrendFilterLambdaMax позволяет получить максимальное значение параметра регуляризации;
- L1TrendFilter реализует расчет L1-тренда с указанным значением параметра регуляризации lambda, который также может быть задан в единицах λmax.
2.1. L1TrendFilterLambdaMax
Метод расчета максимального значения параметра регуляризации λmax для данных вектора.
Вычисления для типа vector<double>:
bool vector::L1TrendFilterLambdaMax( double &lambda_max // максимальное значение параметра регуляризации lambda )
Вычисления для типа vector<float>:
bool vectorf::L1TrendFilterLambdaMax( float &lambda_max // максимальное значение параметра регуляризации lambda );
Параметры
lambda
[out] Максимальное значение параметра регуляризации λmax или -1 в случае ошибки.
Возвращаемое значение
В случае успеха метод возвращает true.
Примечание
Объем потребляемой памяти линейно зависит от размера вектора.
2.2. L1TrendFilter
Метод расчета L1-тренда для данных вектора.
Вычисления для типа vector<double>:
bool vector::L1TrendFilter( double lambda, // значение параметра регуляризации lambda bool relative, // флаг, указывающий на то, что lambda задана в единицах lambda_max vector& result // выходной вектор с результатом L1-фильтрации );
Вычисления для типа vector<float>:
bool vectorf::L1TrendFilter( float lambda, // значение параметра регуляризации lambda bool relative, // флаг, указывающий на то, что lambda задана в единицах lambda_max vectorf& result // выходной вектор с результатом L1-фильтрации );
Параметры
lambda
[in] Значение параметра регуляризации lambda (при relative = true значение задаётся в диапазоне [0, 1] как доля от λmax).
relative
[in] Флаг способа указания параметра регуляризации lambda. Если relative=true, то lambda задается в единицах λmax, иначе будет использовано значение lambda, заданное в абсолютных единицах.
result
[out] Вектор с результатом расчета L1-фильтра.
Возвращаемое значение
В случае успеха метод возвращает true.
Примечание
Объем потребляемой памяти линейно зависит от размера вектора.
Ориентировочные диапазоны множителя при расчете в единицах λmax.
| Значения множителя lambda в режиме relative | Результат |
|---|---|
| 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. Примеры использования методов
В этом разделе мы рассмотрим расчеты L1-тренда на модельных данных броуновского движения, на данных котировок SP500, а также скейлинговых свойств функции λmax для броуновского движения и котировок рынка FOREX.
Также будут рассмотрены 3 варианта индикаторов, которые могут помочь найти наилучшие значения параметров регуляризации lambda (множителей при λmax) для нахождения наилучшего трендового L1-разложения для конкретных символов и периодов.
Кроме того, будут рассмотрены результаты фильтрации торговых сигналов (согласование с построенным L1-трендом) для стратегий MovingAverage, MACD, ADX и EMA.
3.1. Расчет L1-тренда на модельных данных (случайные блуждания)
В качестве примера рассмотрим расчет L1-тренда с различными значениями параметра регуляризации lambda на модельных данных броуновского движения.
Код скрипта:
//+------------------------------------------------------------------+ //| 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
В этом примере видно, что уменьшение параметра регуляризации lambda позволяет произвести более детальное разложение на трендовые участки (рис.1).
Если λ≥λmax, то решением будет прямая линия, соответствующая линейной регрессии (глобальный тренд).

Рис.1. Пример расчета L1-фильтра с различными значениями lambda на данных броуновского движения
Функции расчета 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. Расчет L1-тренда для котировок индекса SP500
Рассмотрим расчет log SP500 из оригинальной статьи l_1 Trend Filtering, S.J. Kim, K. Koh, S. Boyd, and D. Gorinevsky, SIAM Review, problems and techniques section, 51(2):339–360, May 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. Пример расчета L1-тренда для log котировок индекса SP500
Во вкладке "Experts" будет выведено значение λmax для данного временного ряда:
TestL1TrendFilterSP500 (EURUSD,H1) Lambda_max=37394.835512
В этом скрипте показан пример использования методов L1TrendFilterLambdaMax и L1TrendFilter с фиксированным значением lambda=50, как в оригинальной статье авторов метода.
В дальнейших примерах вместо абсолютных значений параметра регуляризации lambda будут использоваться относительные (в единицах λmax) с флагом relative=true.
3.3. Скейлинговые свойства λmax
Параметр λmax играет ключевую роль при использовании L1-фильтрации, поскольку он задаёт верхнюю границу регуляризации, при которой решение вырождается в глобальную линейную аппроксимацию. Интересным свойством этой величины является её масштабная зависимость от длины временного ряда.
Численные эксперименты показывают, что λmax растет по степенному закону от числа наблюдений:
![]()
где: T — длина временного ряда, α — показатель степени.
Для случайного блуждания (броуновского движения) можно показать, что показатель степени должен быть близок к α≈2.5. Амплитуда отклонений броуновского движения растёт как
, оператор второй разности масштабируется как
. При вычислении λmax фактически оценивается максимум величины, связанной с интегралом кривизны ряда.
В результате совокупный масштаб приводит к зависимости:
![]()
что соответствует показателю степени α ≈ 2.5.
Таким образом, при увеличении длины временного ряда значение λmax растет значительно быстрее линейного роста.
3.3.1. Численный эксперимент для броуновского движения
Для проверки масштабного закона был проведён численный эксперимент.
Для различных длин временного ряда T генерировались реализации броуновского движения, после чего вычислялось среднее значение λmax.
Использовалась логарифмическая аппроксимация:
![]()
что позволяет оценить показатель степени α методом линейной регрессии.
Код эксперимента приведён ниже.
//+------------------------------------------------------------------+ //| TestScalingLambdaMaxBrownMovement.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| Generate Brownian motion | //+------------------------------------------------------------------+ void GenerateBrownian(int N,vector<double> &data) { data.Resize(N); data[0] = 0.0; for(int i=1; i<N; i++) data[i] = data[i-1] + (MathRand()/32767.0 - 0.5); } //+------------------------------------------------------------------+ //| LinearRegression | //+------------------------------------------------------------------+ void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b) { double sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0; for(int i = 0; i < n; i++) { sx += x[i]; sy += y[i]; sxx += x[i] * x[i]; sxy += x[i] * y[i]; } double denom = n * sxx - sx * sx; a = (n * sxy - sx * sy) / denom; b = (sy - a * sx) / n; } //+------------------------------------------------------------------+ //| TestScaling with statistics | //+------------------------------------------------------------------+ void TestScalingStatistics() { MathSrand(42); int RUNS = 10; // int MC = 10; // Monte Carlo double alpha_values[]; ArrayResize(alpha_values, RUNS); // --- geometric grid of T int nT = 8; int Tvals[]; ArrayResize(Tvals, nT); int T0 = 64; for(int i = 0; i < nT; i++) Tvals[i] = T0 << i; Print("Scaling test with statistics"); //--- double logT[]; double logLambda[]; vector<double> bm; vector<double> l1_trend; for(int run = 0; run < RUNS; run++) { ArrayResize(logT, nT); ArrayResize(logLambda, nT); //--- for(int i = 0; i < nT; i++) { int T = Tvals[i]; double lambda_sum = 0.0; l1_trend.Resize(T); for(int k = 0; k < MC; k++) { GenerateBrownian(T, bm); double lambda_max=0.0; if (bm.L1TrendFilterLambdaMax(lambda_max)) lambda_sum += lambda_max; bm.L1TrendFilter(0.2,true,l1_trend); } double lambda_avg = lambda_sum / MC; logT[i] = MathLog((double)T); logLambda[i] = MathLog(lambda_avg); } // --- regression double alpha, c; LinearRegression(logT, logLambda, nT, alpha, c); alpha_values[run] = alpha; PrintFormat("run %d -> alpha = %.6f", run+1, alpha); } //--- statistics double mean = 0.0; for(int i=0;i<RUNS;i++) mean += alpha_values[i]; mean /= RUNS; // --- standard deviation double var = 0.0; for(int i=0;i<RUNS;i++) var += (alpha_values[i]-mean)*(alpha_values[i]-mean); var /= (RUNS - 1); double stddev = MathSqrt(var); // --- standard error of mean double sem = stddev / MathSqrt((double)RUNS); // --- theoretical comparison double alpha_theory=2.5; double percent_error=MathAbs(mean-alpha_theory)/alpha_theory*100.0; //--- results PrintFormat("mean alpha = %.6f", mean); PrintFormat("std deviation = %.6f", stddev); PrintFormat("standard error = %.6f", sem); PrintFormat("theory = %.4f", alpha_theory); PrintFormat("percent error from theory = %.4f %%", percent_error); } //+------------------------------------------------------------------+ //| TestScaling | //+------------------------------------------------------------------+ void TestScaling() { MathSrand(1); // --- geometric grid of T int nT = 8; int Tvals[]; ArrayResize(Tvals,nT); //--- int T0 = 64; for(int i=0; i<nT; i++) Tvals[i]=T0<<i; // 64 * 2^i //--- double logT[], logLambda[]; ArrayResize(logT,nT); ArrayResize(logLambda,nT); //--- Print("scaling test for lambda_max"); for(int i=0; i<nT; i++) { int T = Tvals[i]; //--- Monte-Carlo simulations int MC=1000; double lambda_sum = 0.0; for(int k=0; k<MC; k++) { vector<double> bm; GenerateBrownian(T, bm); double lambda_max=0.0; if(bm.L1TrendFilterLambdaMax(lambda_max)) lambda_sum += lambda_max; } double lambda_avg=lambda_sum/MC; logT[i]= MathLog((double)T); logLambda[i]=MathLog(lambda_avg); PrintFormat("T=%5d <lambda_max>=%.6f",T,lambda_avg); } // --- linear regression in log-log double alpha, c; LinearRegression(logT,logLambda,nT,alpha,c); //--- PrintFormat("estimated scaling exponent alpha = %.4f",alpha); double alpha_theory=2.5; PrintFormat("theoretical value = %.4f",alpha_theory); //--- plot scaling law CGraphic g; g.Create(0, "ScalingLaw",0,0,0,1000,600); g.BackgroundMain("Scaling law of lambda_max (Brownian motion)"); g.BackgroundMainSize(16); g.CurveAdd(logT, logLambda, CURVE_POINTS, "Simulation"); //--- double xfit[2], yfit[2]; xfit[0] = logT[0]; xfit[1] = logT[nT-1]; //--- yfit[0] = alpha*xfit[0] + c; yfit[1] = alpha*xfit[1] + c; //---least squares fit g.CurveAdd(xfit, yfit, CURVE_LINES, "LS fit"); g.CurvePlotAll(); g.Update(); DebugBreak(); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- calculate scaling with statistics TestScalingStatistics(); //--- show sample results TestScaling(); } //+------------------------------------------------------------------+
Результат:
TestScalingLambdaMaxBrownMovement (EURUSD,H1) Scaling test with statistics TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 1 -> alpha = 2.480774 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 2 -> alpha = 2.530977 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 3 -> alpha = 2.435511 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 4 -> alpha = 2.461984 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 5 -> alpha = 2.467093 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 6 -> alpha = 2.487965 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 7 -> alpha = 2.532371 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 8 -> alpha = 2.455831 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 9 -> alpha = 2.483485 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 10 -> alpha = 2.420283 TestScalingLambdaMaxBrownMovement (EURUSD,H1) mean alpha = 2.475627 TestScalingLambdaMaxBrownMovement (EURUSD,H1) std deviation = 0.036281 TestScalingLambdaMaxBrownMovement (EURUSD,H1) standard error = 0.011473 TestScalingLambdaMaxBrownMovement (EURUSD,H1) theory = 2.5000 TestScalingLambdaMaxBrownMovement (EURUSD,H1) percent error from theory = 0.9749 % TestScalingLambdaMaxBrownMovement (EURUSD,H1) scaling test for lambda_max TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 64 <lambda_max>=97.302362 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 128 <lambda_max>=566.626861 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 256 <lambda_max>=3162.076116 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 512 <lambda_max>=18271.204936 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 1024 <lambda_max>=100057.796790 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 2048 <lambda_max>=578620.887399 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 4096 <lambda_max>=3192555.936035 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 8192 <lambda_max>=17895314.647170 TestScalingLambdaMaxBrownMovement (EURUSD,H1) estimated scaling exponent alpha = 2.4967 TestScalingLambdaMaxBrownMovement (EURUSD,H1) theoretical value = 2.5000
График (в дважды логарифмическом масштабе) показывает наличие степенной зависимости функции λmax от количества данных для броуновского движения.

Рис.3. Степенная зависимость LambdaMax для броуновского движения
Результаты моделирования показывают:
mean alpha = 2.4756 std deviation = 0.036 theory = 2.5 percent error ≈ 1%
Таким образом, эксперимент подтверждает теоретическую зависимость:
![]()
График в двойном логарифмическом масштабе демонстрирует линейную зависимость между log(λmax) и log(T).
3.3.2. Скейлинг для финансовых временных рядов
Аналогичный эксперимент был проведён для котировок рынка FOREX. Для различных валютных пар и таймфреймов вычислялся показатель степени α.
Результаты показывают, что для реальных финансовых данных значение α также находится в диапазоне α≈2.45–2.60, что очень близко к теоретическому значению для броуновского движения. Это означает, что масштабное поведение λmax практически универсально и сохраняется для различных рынков и таймфреймов.
Скрипт TestScalingLambdaMaxSymbol.mq5 производит расчет показателя степени λmax для заданного символа для стандартных таймфреймов M1-H1.
//+------------------------------------------------------------------+ //| 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. Степенная зависимость LambdaMax для различных таймфреймов EURUSD
Аналогичным образом можно рассмотреть другие валютные пары.
Для 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. Степенная зависимость LambdaMax для различных таймфреймов USDJPY
Для 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. Степенная зависимость LambdaMax для различных таймфреймов GBPUSD
Для рассмотренных котировок EURUSD, USDJPY и GBPUSD полученные значения показателя степени также близки к 2.5.
Линейные зависимости в log-log масштабе для функции λmax для множества таймфреймов и различных валютных пар свидетельствуют о степенной зависимости λmax от количества данных.
3.3.3. Практическое значение скейлинга
Наличие степенной зависимости λmax имеет важное практическое следствие.
Поскольку λmax∝T^2.5, абсолютное значение параметра λ сильно зависит от:
- Длины окна данных;
- Таймфрейма;
- Масштаба временного ряда.
Поэтому использование абсолютного значения λ неудобно для практических задач.
Гораздо более устойчивым является использование относительного параметра λ=c⋅λmax, где c лежит в диапазоне 0<c<1.
Такой подход:
- Делает параметр регуляризации масштабно-инвариантным;
- Упрощает перенос параметров между различными инструментами;
- Позволяет использовать одинаковые настройки для разных таймфреймов.
Именно поэтому в дальнейшем во всех примерах параметр λ будет задаваться в единицах λmax.
3.4. Индикаторы L1-тренда
В этом разделе будут рассмотрены 3 варианта индикаторов:
- Расчет L1-тренда по ценам Close;
- Расчет значений коэффициентов линейного роста L1-тренда по ценам Close;
- Расчет знака коэффициента линейного роста L1-тренда по ценам Close;
Индикаторы могут быть использованы для визуального анализа трендового разложения и могут помочь найти подходящие значения параметра регуляризации lambda для использования в торговых стратегиях.
3.4.1. Индикатор расчета L1-тренда L1TrendFilter.mq5
В данном примере вычисляется L1-фильтр по ценам Close для заданного количества баров (в примере BarsToShow=1000) с коэффициентом lambda, заданным в единицах λmax.
Для расчета используется вызов метода L1TrendFilter(relative=true), где параметр lambda задается в единицах λ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 приведен пример расчета индикатора L1TrendFilter.mq5 с CoefLambda = 0.015.

Рис.7. Пример расчета индикатора L1TrendFilter.mq5 с CoefLambda = 0.015
Для сравнения можно попробовать расчет нескольких вариантов расчета с различными параметрами регуляризации, на рис.8 представлен расчет с параметрами CoefLambda = 0.015, CoefLambda = 0.025 и CoefLambda = 0.055.

Рис.8. Примеры расчета индикатора L1TrendFilter.mq5 с различными значениями CoefLambda
3.4.2. Индикатор расчета динамики L1-тренда L1TrendFilterSlope.mq5
Для отображения коэффициента изменения тренда можно использовать приращение значений индикатора L1TrendFilter.
В качестве примера рассмотрим индикатор L1TrendFilterSlope, отображающий значения в отдельном окне.
Код индикатора L1TrendFilterSlope.mq5:
//+------------------------------------------------------------------+ //| 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. Пример расчета индикаторов L1TrendFilter.mq5 и L1TrendFilterScope с CoefLambda = 0.015
3.4.3. Индикатор расчета направления динамики L1-тренда 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)

Рис.10. Пример расчета индикаторов L1TrendFilter.mq5, L1TrendFilterScope.mq5 и L1TrendFilterScopeSign.mq5 с CoefLambda = 0.015
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
Индикатор рассчитывает остаточную волатильность как разницу между ценой Close и соответствующим значением 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, где используется простое скользящее среднее.
Сглаживание позволяет:
- Уменьшить краткосрочные шумы и выбросы;
- Сделать визуализацию более чистой и наглядной;
- Сосредоточиться на устойчивых изменениях волатильности.
Индикатор полезен для стратегий, где важно оценивать долгосрочные тенденции изменения волатильности, например, при построении адаптивных торговых систем или при фильтрации ложных сигналов в трендовых и флетовых фазах.
Код индикатора 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
Индикатор вычисляет абсолютное значение разницы между ценой Close и 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 (Average True Range) и 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 | Range | Низкая волатильность, флет |
| 1 | Trend | Средняя волатильность, наличие тренда |
| 2 | Expansion | Сильное движение, расширение рынка |
| 3 | Panic | Экстремальная волатильность, резкие движения |
Табл.3. Режимы индикатора L1VolatilityRegime.mq5
Использование индикатора 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-тренда в торговых стратегиях
В этом разделе мы рассмотрим торговые стратегии MovingAverage, MACD,ADX и EMA с различными вариантами использования фильтра торговых сигналов.
Добавление фильтров к торговым сигналам позволяет улучшить характеристики торговых систем. Для анализа эффективности использования фильтров в представленных советниках мы будем сохранять данные по балансу и эквити (на каждом новом баре) в отдельные файлы и использовать Python-скрипт для визуализации результатов работы торговых систем в различных режимах.
Все представленные советники имеют одинаковую архитектуру.
Общий принцип согласования L1-тренда с торговыми сигналами стратегий
- Фильтр открытия сделок (L1FilterOpen = true) позволяет открывать сделки только по направлению доминирующего тренда.
- Фильтр закрытия сделок (L1FilterClose = true) помогает удерживать позиции в сильных трендах и снижает количество преждевременных выходов при локальных коррекциях.
Входные параметры и их значения по умолчанию, общие для всех советников:
//--- 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
Сохранение данных баланса и эквити в файл
Входной параметр SaveStatistics позволяет на каждом новом баре сохранять текущие значения time, close price, balance, equity и т.п. в файл в terminal_data_folder\Tester\Agent-127.0.0.1-3000\MQL5\Files.
Вызов функции сохранения данных на каждом новом баре реализован внутри OnTick() и зависит от значения заданного входного параметра 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.
Имя файла зависит от стратегии и символа и формируется в функции инициализации советников:
//+------------------------------------------------------------------+ //| 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); }
Расчет фильтров торговых сигналов стратегий в различных режимах
Последовательный запуск советника в тестере стратегий со всеми 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.
Файлы с данными balance/equity позволяют сравнивать эффективность фильтрации торговых сигналов при помощи согласования с L1-трендом.
Визуализация данных
Для построения совместных графиков нужно скопировать 4 файла из каталога тестера в отдельную папку и запустить Python-скрипт (в примере "c:\data\").
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("Close 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-тренда и его согласование с торговыми сигналами стратегий
Во всех рассмотренных советниках применяется дополнительная фильтрация на основе анализа рыночной структуры методом L1-фильтрации:
- По последним L1TotalBars строится сглаженный ценовой ряд;
- Рассчитывается коэффициент роста тренда delta — разность между двумя последними значениями сглаженного ряда.
при delta>0 — восходящий L1-тренд, delta<0— нисходящий L1-тренд.
//+------------------------------------------------------------------+ //| 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:
- сигнал BUY игнорируется при отрицательном L1-тренде;
- сигнал SELL игнорируется при положительном L1-тренде.
Сделки открываются только по направлению доминирующего тренда.
//+------------------------------------------------------------------+ //| 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:
- позиция BUY не закрывается по обратному сигналу, пока L1-тренд остаётся восходящим;
- позиция SELL не закрывается, пока L1-тренд остаётся нисходящим.
Это помогает удерживать позиции в сильных трендах и снижает количество преждевременных выходов при локальных коррекциях.
//+------------------------------------------------------------------+ //| 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. Торговая стратегия MovingAverage
В качестве первого примера рассмотрим советник, торгующий по сигналам классической трендовой торговой стратегии Moving Average (скользящая средняя).
Основные торговые сигналы формируются на основе пересечения цены закрытия Close и скользящей средней:
- покупка (BUY) — при пересечении ценой Close значения скользящей средней снизу вверх;
- продажа (SELL) — при пересечении ценой Close значения скользящей средней сверху вниз.
Для анализа используются значения двух последних закрытых баров, что исключает влияние незавершённого текущего бара и снижает количество ложных сигналов.
Дополнительно используются фильтры открытия и закрытия сделок в соответствии с настройками фильтра 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-тренда для фильтрации торговых сигналов
Для анализа эффективности работы фильтра нужно:
- Найти наилучший набор параметров торговой стратегии, при которых получается максимальная прибыль.
Целесообразно улучшать торговые сигналы наилучших стратегий. - Рассмотреть результаты тестирования для 4 вариантов использования фильтров (задавая параметры L1FilterOpen/L1FilterClose):
- Торговля без фильтров;
- Торговля с фильтрацией входа в рынок;
- Торговля с фильтрацией выходов с рынка;
- Торговля с фильтрацией входов и выходов с рынка.
- Запустить Python-скрипт совместного построения картинок.
Рассмотрим эти этапы на примере советника MovingAverageFilteredL1.mq5, торгующего на EURUSD, таймфрейм H1, интервал тестирования 2025 год.
Для работы фильтра мы будем использовать фиксированные значения количества баров для расчета тренда L1TotalBars=1000, параметр регуляризации lambda будем задавать в единицах λ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 minute OHLC" (эта стратегия работает только на новых барах, поэтому такое приближение возможно), алгоритм оптимизации выбираем "Fast genetic based algorithm" с настройкой "Balance max".

Рис.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. Наилучшие параметры оптимизации советника MovingAverageFilteredL1.mq5

Рис.26. Результаты тестирования с наилучшими параметрами оптимизации советника MovingAverageFilteredL1.mq5
Теперь нужно запустить тестирование с сохранением данных в файлы.
Для этого устанавливаем параметр Save statistics=true и запускаем тестирование с 4 комбинациями фильтров торговых сигналов.

Рис.27. Параметры тестирования для сохранения в файлы результатов для советника MovingAverageFilteredL1.mq5
После запуска тестирования со всеми 4 комбинациями фильтров в каталоге тестера появятся файлы 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt и 3_MA_EURUSD.txt.
Они содержат данные по time, close инструмента а также balance, equity для каждого бара интервала тестирования.
Создадим отдельный каталог и скопируем их (в данном примере "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("Close 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()
Для запуска Python-скрипта в MetaEditor нужно нажать кнопку "Compile" (рис.29).

Рис.29. Скрипт PlotData.py в MetaEditor
После запуска скрипта PlotData.py будут выведены графики:
- Котировки EURUSD (цены Close на каждом баре тестирования, позволяют оценить характер движения инструмента и трендовые участки);
- Графики Balance для всех режимов фильтров торговых сигналов;
- Графики Equity для всех режимов фильтров торговых сигналов;
- Графики Balance+Equity для всех режимов фильтров (позволяют оценить уменьшение просадок при использовании каждого из фильтров)
Графики также будут сохранены в каталог "C:\Data\Charts\" в формате PNG.

Рис.30. График EURUSD за тестируемый период

Рис.31. Графики Balance советника MovingAverageFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.32. Графики Equity советника MovingAverageFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.33. Графики Balance и Equity советника MovingAverageFilteredL1.mq5 для различных режимов фильтрации торговых сигналов
Для запуска Python-скриптов в MetaEditor нужно установить Python (в примере версия 3.14) и указать расположение в настройках MetaEditor.

Рис.34. Настройки Python в MetaEditor
Скрипт использует библиотеки pandas и matplotlib, если они еще не установлены, их нужно установить:
pip install pandas pip install matplotlib
3.5.1.2. Результаты применения L1-фильтров торговых сигналов стратегии MovingAverage
Результаты (графики balance, equity и совместный график balance и equity) приведены на рис.35-37.
Цвета:
- Синий (работа стратегии без фильтров);
- Зеленый (L1-фильтр на закрытие позиций);
- Красный (L1-фильтр на открытие и закрытие позиций);
- Оранжевый (L1-фильтр на открытие позиций);

Рис.35. Графики Balance советника MovingAverageFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.36. Графики Equity советника MovingAverageFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.37. Совместные графики Balance+Equity советника MovingAverageFilteredL1.mq5 для различных режимов фильтрации торговых сигналов
3.5.2. Торговая стратегия MACD
В качестве другого примера рассмотрим советник, торгующий по сигналам торговой стратегии на базе индикатора MACD (Moving Average Convergence/Divergence).
Торговые сигналы
Сигналы формируются при пересечении основной линии 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
После запуска в тестере с различными настройками фильтрации торговых сигналов в каталоге тестера стратегий появятся файлы x_MACD_EURUSD.txt, их нужно скопировать в каталог "C:\Data\" и запустить скрипт PlotData.py.

Рис.42. Файлы с результатами тестирования советника MACDFiltered.mq5 в различных режимах фильтрации торговых сигналов
3.5.2.1. Результаты применения L1-фильтров торговых сигналов стратегии MACD
Результаты приведены на рис.43-45.

Рис.43. Графики Balance советника MACDFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.44. Графики Equity советника MACDFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.45. Графики Balance и Equity советника MACDFilteredL1.mq5 для различных режимов фильтрации торговых сигналов
3.5.3. Торговая стратегия ADX
В качестве другого примера рассмотрим советник ADXFilteredL1.mq5, реализующий трендовую торговую стратегию на основе индикатора Average Directional Movement Index (ADX).
Основные торговые сигналы формируются на основе анализа линий +DI и −DI, а также уровня индикатора ADX, который характеризует силу тренда.
Сигналы определяются следующим образом:
- покупка (BUY) — при пересечении линии +DI линии −DI снизу вверх;
- продажа (SELL) — при пересечении линии +DI линии −DI сверху вниз.
Дополнительно учитывается значение индикатора ADX. Если значение ADX меньше заданного порога ADXTrendLevel, считается, что рынок находится в состоянии слабого тренда или флэта, и торговый сигнал игнорируется.
Для анализа используются значения индикатора на двух последних закрытых барах, что позволяет исключить влияние незавершённого текущего бара и уменьшить количество ложных сигналов.
Дополнительно используются фильтры открытия и закрытия сделок в соответствии с настройками фильтра 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); } //+------------------------------------------------------------------+
Настройки, параметры и результаты тестирования советника ADXFiteredL1.mq5 приведены на рис.46-48.

Рис.46. Настройки тестирования советника ADXFilteredL1.mq5

Рис.47. Параметры тестирования советника ADXFilteredL1.mq5

Рис.48. Результаты тестирования советника ADXFilteredL1.mq5
Для тестирования с различными режимами нужно 4 раза запустить советник:

Рис.49. Параметры тестирования для сохранения в файлы результатов для советника ADXFilteredL1.mq5
после этого в каталоге тестера появятся файлы, их нужно скопировать в каталог C:\data и запустить скрипт PlotData.py.

Рис.50. Файлы с результатами тестирования советника ADXFiltered.mq5 в различных режимах фильтрации торговых сигналов
3.5.3.1. Результаты применения L1-фильтров торговых сигналов стратегии ADX
Результаты представлены на рис.51-53.

Рис.51. Графики Balance советника ADXFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.52. Графики Equity советника ADXFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.53. Графики Balance+Equity советника ADXFilteredL1.mq5 для различных режимов фильтрации торговых сигналов
3.5.4. Торговая стратегия на базе пересечения EMA
Рассмотрим советник EMAFilteredL1.mq5, реализующий трендовую торговую стратегию на основе пересечения двух экспоненциальных скользящих средних (EMA).
В стратегии используются две скользящие средние:
- FastEMA — быстрая экспоненциальная средняя;
- SlowEMA — медленная экспоненциальная средняя.
Торговые сигналы формируются следующим образом:
- покупка (BUY) — при пересечении быстрой EMA медленной EMA снизу вверх;
- продажа (SELL) — при пересечении быстрой EMA медленной EMA сверху вниз.
Для анализа используются значения индикаторов на двух последних закрытых барах, что позволяет исключить влияние незавершённого текущего бара и уменьшить количество ложных сигналов.
Дополнительно используются фильтры открытия и закрытия сделок в соответствии с настройками фильтра 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-фильтров нужно последовательно запустить в тестере советник с различными режимами фильтров:

Рис.57. Параметры тестирования для сохранения в файлы результатов для советника EMAFilteredL1.mq5
В результате в папке тестера будут созданы файлы, их нужно скопировать в каталог, заданный в Python-скрипте PlotData.py и запустить его с настройками symbol = "EURUSD"
name_strategy = "EMA".

Рис.58. Файлы с результатами тестирования советника EMAFilteredL1.mq5 в различных режимах фильтрации торговых сигналов
3.5.4.1. Результаты применения L1-фильтров торговых сигналов стратегии EMA
Результаты приведены на рис.59-61.

Рис.59. Графики Balance советника EMAFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.60. Графики Equity советника EMAFilteredL1.mq5 для различных режимов фильтрации торговых сигналов

Рис.61. Графики Balance+Equity советника EMAFilteredL1.mq5 для различных режимов фильтрации торговых сигналов
3.5.5. Выводы по результатам использования L1-фильтра в торговых стратегиях MovingAverage, MACD, ADX и EMA
В рассмотренных примерах тестирование торговых стратегий производилось на валютной паре EURUSD, H1 за 2025 год (рис.62).

Рис.62. График котировок EURUSD за тестируемый период (2025, H1, цены Close)

Рис.63. Графики Balance стратегий Moving Average, MACD, ADX и EMA для различных режимов фильтрации торговых сигналов
Анализ стратегий Moving Average, MACD, ADX и EMA показал, что наилучшие результаты достигаются при использовании L1-фильтрации на этапе закрытия позиций (на графиках выделены зеленым цветом). Применение фильтра на выходе эффективно подавляет шумовые развороты и ложные сигналы, позволяя удерживать сделки в направлении устойчивого тренда. Это приводит к росту прибыли и Profit Factor, а также к снижению максимальной просадки.
Использование L1-фильтрации на этапе открытия позиций (отмечены оранжевым цветом) оказалось менее эффективным, поскольку дополнительная фильтрация ограничивает число входов и приводит к пропуску части прибыльных движений без сопоставимого роста качества сделок.

Рис.64. Графики Balance и Equity стратегий Moving Average, MACD, ADX и EMA для различных режимов фильтрации торговых сигналов
Таким образом, L1-фильтрация торговых сигналов на закрытии позиций повышает стабильность торговой системы, снижает чувствительность к краткосрочным флуктуациям цены и улучшает соотношение прибыль/риск. По сравнению с классическими скользящими средними, L1-фильтр лучше различает временные коррекции и реальные смены тренда, что позволяет более эффективно использовать трендовые стратегии.
Следует обратить внимание на характер кривых Balance и Equity при торговли по сигналам, согласованным с L1-фильтром тренда (рис.64). При торговле по тренду Equity часто находится выше Balance, что значительно улучшает риски и уменьшает просадку. Таким образом, согласование с трендом также улучшает характеристики торговых систем (уменьшает просадку и риски).
Кроме того, при согласовании торговых сигналов с L1-трендом количество сделок уменьшается, а их качество возрастает, что также положительно сказывается на общих статистических харатеристиках торговых стратегий.
| № | Стратегия | Total Net Profit, USD | % Buy and Hold |
|---|---|---|---|
| 1 | Buy and Hold | 1363.8 | 100 % |
| 2 | MovingAverage (без фильтров) | 1001.03 | 73.4 % |
| 3 | MovingAverage (L1-фильтр открытия позиций) | 107.65 | 7.89 % |
| 4 | MovingAverage (L1-фильтр закрытия позиций) | 1342.5 | 98.43 % |
| 5 | MovingAverage (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. Общая прибыль результатам использования L1-фильтра в торговых стратегиях MovingAverage, MACD, ADX и EMA и сравнение со стратегий Buy and Hold
Согласно табл.4, использование L1-фильтра при закрытии позиций позволило всем стратегиям улучшить прибыль.
Если в качестве полного трендового движения (100%) взять результат стратегии Buy and Hold ($1363.8), то получаем результаты:
- Прибыль стратегии Moving Average увеличилась с 73.4% до 98.43%;
- Прибыль стратегии MACD увеличилась с 73.16% до 99.69%;
- Прибыль стратегии ADX увеличилась с 58.07% до 68.5%;
- Прибыль стратегии EMA увеличилась с 70.19% до 92.31%;
В данном примере использование L1-фильтра позволило стратегиям Moving Average, 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 индикаторов волатильности (L1Volatility, L1VolatilitySmoothed, L1VolatilityAbsolute, L1VolatilityNormalized, L1VolatilityNormalizedSmoothed, L1VolatilityRegime, L1VolatilityRegimeColor), а также шаблоны советников и воспроизводимый протокол тестирования (четыре режима: без фильтра, фильтр открытия, фильтр закрытия, оба фильтра; сохранение результатов и скрипт визуализации на Python).
Отметим, что L1-фильтр тренда может быть использован для разметки данных при использовании машинного обучения. В частности, в статье "Разработка трендовых торговых стратегий на основе машинного обучения" для определения тренда используются производные цен, сглаженных фильтром Савицкого–Голея. Аналогичный подход может быть реализован с помощью L1-фильтрации, где тренд аппроксимируется кусочно-линейными функциями, а сила тренда на каждом участке естественным образом связана с наклоном соответствующего сегмента. Режимы тренда, определяемые через индикаторы волатильности, также могут быть использованы в качестве признаков ML-разметки торговых сигналов.
Практические рекомендации:
- Работайте с относительной регуляризацией λ = coeflambdamax ⋅ λmax; для большинства задач брать coef в диапазоне 0.04–0.25, для более тонкой детализации — ≈0.02–0.04, для грубой аппроксимации и выделения режимов — ≈0.12–0.25.
- Для трендовых рынков и стратегий L1‑фильтр эффективен прежде всего при фильтрации закрытий (удержание прибыльных трендов и снижение преждевременных выходов). Применение фильтра на открытие часто сокращает число входов без пропорционального повышения качества.
- Для анализа направления тренда можно использовать простое правило: delta = xfiltered[last] − xfiltered[last−1]; знак delta — направление доминирующего L1‑тренда.
Ограничение: эффект зависит от инструмента, таймфрейма и режима рынка; требуется верификация на исторических данных по выбранным метрикам.
Предложенные MQL5‑модули и тестовый протокол позволяют быстро проверить гипотезу и подобрать рабочие параметры для конкретной торговой системы.
Все коды из статьи также доступны в публичном проекте MQL5\Shared Projects\L1Trend.
Описание примеров
| Тип | Файл | Описание |
|---|---|---|
| Скрипт | MQL5\Scripts\TestL1Trend.mq5 | Тестовый скрипт расчета L1-тренда на модельных данных (случайные блуждания) |
| Скрипт | MQL5\Scripts\TestL1TrendFloatDouble.mq5 | Тестовый скрипт расчета L1-тренда на модельных данных (случайные блуждания) для float и double |
| Скрипт | MQL5\Scripts\TestL1TrendFilterSP500.mq5 | Тестовый скрипт расчета L1-тренда на данных котировок индекса SP500 |
| Файл данных | MQL5\Files\snp500.txt | Файл данных для тестового скрипта (log котировок индекса SP500) |
| Скрипт | 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 | Индикатор расчета остаточной волатильности (разница между ценой Close и значением L1-тренда) |
| Индикатор | MQL5\Indicators\L1VolatilitySmoothed.mq5 | Индикатор расчета сглаженной остаточной волатильности |
| Индикатор | MQL5\Indicators\L1VolatilityAbsolute.mq5 | Индикатор расчета абсолютной волатильности |
| Индикатор | MQL5\Indicators\L1VolatilityNormalized.mq5 | Индикатор расчета нормализованной волатильности с использованием ATR (Average True Range) и L1-тренда |
| Индикатор | MQL5\Indicators\L1VolatilityNormalizedSmoothed.mq5 | Индикатор расчета сглаженной нормализованной волатильности |
| Индикатор | MQL5\Indicators\L1VolatilityRegime.mq5 | Индикатор определения режима рынка |
| Индикатор | MQL5\Indicators\L1VolatilityRegimeColor.mq5 | Цветная версия индикатора определения режима рынка |
| Советник | MQL5\Experts\MovingAverageFilteredL1.mq5 | Советник, торгующий по стратегии MovingAverage с L1-фильтром |
| Советник | MQL5\Experts\MACDFilteredL1.mq5 | Советник, торгующий по стратегии MACD с L1-фильтром |
| Советник | MQL5\Experts\ADXFilteredL1.mq5 | Советник, торгующий по стратегии ADX с L1-фильтром |
| Советник | MQL5\Experts\EMAFilteredL1.mq5 | Советник, торгующий по стратегии пересечения двух EMA с L1-фильтром |
| Скрипт Python | MQL5\Scripts\PlotData.py | Скрипт Python для анализа эффективности применения L1-фильтра к торговым сигналам |
Табл.5. Описание кодов программ в статье
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Особенности написания Пользовательских Индикаторов
Как организовать ИИ-хедж-фонд в MetaTrader 5
Неопределённость как модель (Часть 5): Основы регрессии
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
The article " Using L1 Trend Filtering in MetaTrader 5" has been published:
Author: MetaQuotes
Это превосходно, отличная работа, MetaQuotes. Профессор Стивен Бойд из Стэнфордского университета и его легендарные исследования временных рядов теперь в MQL5.
Это превосходно, отличная работа, MetaQuotes. Профессор Стивен Бойд из Стэнфордского университета и его легендарные исследования временных рядов теперь в MQL5.
Спасибо. Метод не имеет граничных проблем, поэтому его применение может быть полезно в трейдинге.
После того, как статья была готова, выяснилось, что лучше всего добавлять объем на коррекциях тренда, (этот момент в статье не рассматривается), смотрите советники здесь: MA, MACD, ADX, EMA.
Советники с добавлением на коррекциях тренда:
MovingAverageFilteredL1-Add.mq5
MACDFilteredL1-Add.mq5
ADXFilteredL1-Add.mq5
Скрипт показывает фильтрацию тренда L1 для различных значений параметра регуляризации λ (заданных в единицах λmax как lambda_factors = {1.0,0.8,0.5,0.2,0.1,0.01,0.001});