English
preview
Изучение стандартной библиотеки MQL5 (Часть 10): Канал полиномиальной регрессии

Изучение стандартной библиотеки MQL5 (Часть 10): Канал полиномиальной регрессии

MetaTrader 5Примеры |
44 0
Clemence Benjamin
Clemence Benjamin

Финансовые рынки часто демонстрируют ускоряющиеся движения, закругленные развороты и S-образные тренды, которые линейные инструменты (каналы линейной регрессии, простые скользящие средние и т. д.) не могут адекватно описать. Для трейдера или разработчика торговых алгоритмов это проявляется в виде ощутимых трудностей: границы канала отстают или перестают адекватно работать, шум в сигналах после появления кривизны и ручных обходных решений (смена таймфреймов, оценка криволинейных трендов на глаз), которые являются субъективными, сложными для воспроизведения и плохо масштабируются на множество инструментов и практически не поддаются переносу в автоматизированные системы.

В этой статье эти трудности переформулируются в конкретную инженерную задачу: реализовать формальную, воспроизводимую методику моделирования ценовой кривизны и дополнить её адаптивными полосами волатильности, подходящими как для визуального анализа, так и для автоматизированного использования. Конкретно, критерии успеха следующие: компилируемый индикатор MQL5, который (1) строит полиномиальный тренд по настраиваемому окну ретроспективного анализа, (2) создает верхнюю/нижнюю полосы на основе стандартного отклонения остатков, (3) предоставляет степень, период и множитель отклонения как параметры, задаваемые во время выполнения, (4) проверяет входные данные и обрабатывает граничные случаи (недостаток данных, сбой аппроксимации) и (5) эффективно работает в реальном времени с минимальной нагрузкой на ЦП.

Для достижения этих целей мы используем ALGLIB для численно устойчивой аппроксимации полинома методом наименьших квадратов, реализуем несмещенный оценщик остаточной волатильности и оптимизируем перерасчет таким образом, чтобы на каждом тике обновлялся только самый последний бар. В результате получается переиспользуемый компонент Polynomial Regression Channel, который формализует выявление кривизны тренда, стандартизирует построение полос и готов к интеграции в торговые процессы или советники.

Содержание

  1. Введение
  2. Понимание концепции канала полиномиальной регрессии
  3. Реализация кода
  4. Тестирование
  5. Заключение


Введение

Большинство инструментов следования за трендом предполагают, что цена движется по прямой линии. Это предположение глубоко укоренилось — оно заложено в каналах линейной регрессии, скользящих средних и бесчисленном множестве других индикаторов, на которые трейдеры полагаются каждый день. Проблема в том, что реальные рынки не читают учебники. Ценовое движение ускоряется, замедляется и формирует криволинейные структуры, для отображения которых эти линейные модели просто не предназначены.

Последствия этого несоответствия поначалу незаметны, но в совокупности они накапливаются. Линейные инструменты отстают, когда тренды набирают обороты. Они становятся ненадежными, когда цена начинает изгибаться или менять направление. То, что в один момент выглядит как стабильный канал, в следующий момент превращается либо в чрезмерно широкий диапазон, либо в структуру, которую цена неоднократно нарушает. Это не особенность рынка — это фундаментальное ограничение математики, лежащей в основе индикатора. Модель не описывает реальность.

В этой статье мы рассмотрим иной подход: канал полиномиальной регрессии, полностью реализованный на MQL5 с использованием численной библиотеки ALGLIB. Вместо того чтобы пытаться построить прямую линию через криволинейное движение цены, мы аппроксимируем полиномы более высокого порядка, которые действительно могут следовать за изгибами. В результате получается канал, который естественным образом адаптируется к кривизне рынка и реагирует на меняющиеся тренды логичным образом.

Мы разберем математические концепции полиномиальной регрессии простым языком — достаточно, чтобы понять, что вы видите на графике, но не настолько, чтобы запутаться в теории — и рассмотрим полную реализацию на MQL5, объясняя каждый компонент в контексте, чтобы вы могли модифицировать, расширять или просто учиться на примере кода. Конечный продукт — это полностью функциональный индикатор, готовый к компиляции, тестированию и использованию.

К концу этой статьи у вас будет не просто новый инструмент. У вас будет практическое понимание того, как передовые численные методы — обычно скрытые в академических статьях — могут быть применены в MQL5 для решения реальных проблем, с которыми трейдеры сталкиваются каждый день. Вы поймете, почему работает полиномиальная регрессия, где ее целесообразно использовать и как эффективно ее внедрить, не изобретая велосипед заново.


Понимание концепции канала полиномиальной регрессии

Полиномиальная регрессия — это естественное расширение линейной регрессии. Вместо моделирования зависимости цены от времени в виде прямой линии, она использует полином — кривую, которая может изгибаться и менять форму, более точно следуя данным. В технической терминологии зависимой переменной является цена P, а независимой переменной — время, обычно обозначаемое индексом t.

Модель линейной регрессии предполагает простейшую возможную зависимость:

Где P(t) — цена в момент времени t, a — свободный член (точка пересечения с вертикальной осью), а b — наклон (насколько изменяется цена за единицу времени). Это хорошо работает, когда цена действительно движется по прямой линии. Но когда она изгибается — а это обычно происходит — линия пересекает данные, упуская истинную структуру.

Полиномиальная регрессия обеспечивает большую гибкость за счет включения членов более высокого порядка:

Где:

  • d — степень полинома
  • a₀, a₁, ..., ad — коэффициенты, определяемые методом наименьших квадратов

Эта дополнительная гибкость важна, поскольку рынки редко соответствуют простым моделям. Полином второй степени (квадратичный) может описывать параболическое ускорение — вспомните ралли криптовалют, которое начинается медленно, а затем резко ускоряется. Полином третьей степени (кубический) может моделировать точки перегиба и S-образные структуры, где тренд постепенно меняет направление. Эти паттерны вовсе не редкость; они постоянно встречаются на всех рынках и во всех таймфреймах.

Канал полиномиальной регрессии состоит из трех компонентов, работающих вместе. Центральная линия — это подобранная полиномиальная кривая, обозначаемая как P̂(t). Она представляет собой оцененный тренд — кривую траекторию, которая наилучшим образом соответствует недавнему движению цены согласно критерию наименьших квадратов. Представьте это как динамическую, криволинейную версию скользящей средней.

Остаточные значения измеряют отклонение фактической цены от этой подобранной кривой:

Каждое остаточное значение показывает, насколько цена отклонилась от тренда в определенной точке. Положительные остаточные значения означают, что цена была выше кривой; отрицательные — ниже. Сами по себе остаточные значения — это просто числа. Но когда мы рассматриваем их совокупно, они дают нам полезную информацию о волатильности.

Стандартное отклонение этих остаточных значений дает меру того, насколько широко цена обычно колеблется вокруг криволинейного тренда:

Где N — количество наблюдений (период ретроспективного анализа).

Это стандартное отклонение концептуально похоже на то, что используется в полосах Боллинджера, но с важным отличием: точкой отсчета является криволинейная линия регрессии, а не простая скользящая средняя. Поэтому полосы адаптируются не только к изменениям волатильности, но и к изменениям формы самого тренда.

Используя эту меру, границы каналов определяются следующим образом:

Где k — определяемый пользователем множитель отклонения. Эта конструкция создает огибающую вокруг изогнутой линии тренда, ширина которой определяется недавней волатильностью цен вокруг этой кривой.

Три основных параметра контролируют поведение канала, и понимание их является ключом к эффективному использованию индикатора.

Степень полинома d определяет, насколько сильной может быть кривизна модели. Степень 1 дает прямую линию — идентичную линейной регрессии. Это удобно в качестве базового показателя или когда вам нужен именно линейный канал. Степень 2 позволяет описывать ускоряющиеся или замедляющиеся тренды. Степень 3 допускает S-образную форму, в пределах которой находятся как ускорение, так и замедление. Более высокие степени обеспечивают еще большую гибкость, но имеют свою цену: риск переобучения, особенно в условиях нестабильного рынка. Слишком гибкий полином начнет подстраиваться под шум, а не под сигнал, создавая нестабильные кривые, которые не отражают истинный тренд.

Период ретроспективного анализа N определяет, сколько баров входит в каждое вычисление регрессии. Это классический компромисс в техническом анализе. Большие значения сглаживают шум и создают более стабильный канал, который изменяется медленно. Меньшие значения делают канал более чувствительным к недавнему движению цены, но также более чувствительным к случайным колебаниям. Универсальной "лучшей" настройки не существует — она зависит от инструмента, которым вы торгуете, таймфрейма, за которым вы наблюдаете, и вашего личного стиля торговли.

Множитель отклонения k контролирует ширину канала. Большие значения создают более широкие полосы, которые чаще удерживают цену внутри канала. Это означает меньше касаний полос и меньше сигналов, но сигналы, которые возникают, как правило, более значимы. Меньшие значения приводят к более узким полосам, которые цена касается чаще, что может быть полезно для стратегий возврата к среднему, но также генерирует больше ложных сигналов. На практике трейдеры часто корректируют этот параметр в зависимости от волатильности инструмента и своей толерантности к риску.

После применения к графику канал полиномиальной регрессии предоставляет несколько уровней информации. Наклон центральной линии указывает направление тренда — восходящий наклон для восходящих трендов, нисходящий для нисходящих трендов. Кривизна этой линии показывает, ускоряется ли тренд (становится круче) или замедляется (выравнивается). Верхняя полоса действует как динамическое сопротивление, нижняя — как динамическая поддержка. Изменения ширины полосы отражают изменение условий волатильности: расширяющиеся полосы указывают на увеличение волатильности, а сужающиеся — на консолидацию или снижение волатильности. Закрытие за пределами полос может сигнализировать о пробое, особенно если за ним следует период узких, сужающихся полос.

Интегрируя эти наблюдения — наклон, кривизну, ширину полосы и касания полосы — трейдер может принимать более обоснованные решения о входе, выходе и размещении стоп-лоссов. Канал не дает механических сигналов на покупку и продажу, но предоставляет богатый контекст для интерпретации ценового движения в рамках криволинейного тренда.

После того, как математическая основа заложена, следующим шагом является перевод этой модели в работающий код MQL5. Мы будем использовать численную библиотеку ALGLIB для выполнения основной работы, что позволит нам сосредоточиться на структуре и логике индикатора, а не на реализации полиномиальной регрессии с нуля.


Реализация кода

Мы будем создавать этот индикатор систематически, по частям. Каждый блок кода сопровождается объяснением того, что он делает и почему он имеет именно такую структуру. Цель состоит не просто в том, чтобы представить работающий код, а в том, чтобы помочь вам глубоко понять индикатор, чтобы вы могли изменять его, расширять или отлаживать, когда что-то работает не так, как ожидалось.

Первая часть любого индикатора MQL5 определяет его визуальное оформление и поведение на графике. Здесь мы объявляем три графических построения: центральную линию, верхнюю и нижнюю полосы. Каждому присваивается свой цвет и стиль линии — синий для центральной, красный для верхней и салатовый для нижней. Буферы, содержащие фактические значения линий, объявляются как глобальные массивы. Это стандартная практика MQL5, которая указывает терминалу, сколько буферов нужно выделить и как их отобразить.

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

//--- plot Middle
#property indicator_label1  "Middle"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- plot Upper
#property indicator_label2  "Upper"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

//--- plot Lower
#property indicator_label3  "Lower"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrLime
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1

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

//--- input parameters
input int      InpDegree     = 2;           // Polynomial degree
input int      InpPeriod     = 20;          // Period (number of bars)
input double   InpDeviation  = 2.0;         // Channel deviation multiplier
input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; // Applied price

//--- indicator buffers
double         MiddleBuffer[];
double         UpperBuffer[];
double         LowerBuffer[];

Особого внимания заслуживает включение библиотеки ALGLIB. Добавив #include <Math\Alglib\alglib.mqh>, мы получаем доступ к профессиональной численной библиотеке, которая тщательно тестировалась и оптимизировалась в течение многих лет. Это не какой-то игрушечный код — ALGLIB используется в научных вычислениях и количественных финансах по всему миру. Класс CAlglib предоставляет статические методы для полиномиальной аппроксимации, интерполяции, линейной алгебры, оптимизации и многого другого. Для этого индикатора мы будем использовать две функции: PolynomialFit для выполнения регрессии методом наименьших квадратов и BarycentricCalc для вычисления подобранного полинома в любой точке. Обе функции реализованы численно устойчивым способом, что крайне важно при работе с полиномами более высоких степеней или большими наборами данных. Без ALGLIB нам пришлось бы реализовывать собственные операции обращения матриц и процедуры решения — что привело бы к скрытым ошибкам и численной нестабильности.

Входные параметры объявлены с разумными значениями по умолчанию, которые хорошо подходят для первоначального тестирования. Степень полинома по умолчанию равна 2, что является хорошей отправной точкой для большинства рынков. Это позволяет сделать один изгиб — достаточный для захвата параболических движений — без риска переобучения, связанного с более высокими степенями. Период по умолчанию равен 20, распространенной длине периода ретроспективного анализа, используемой во многих индикаторах. Множитель отклонения по умолчанию равен 2,0, что соответствует типичной настройке для полос Боллинджера. Тип цены по умолчанию — цена закрытия, но пользователи могут выбрать любую из стандартных ценовых констант через окно свойств индикатора. Эти входные параметры отображаются в пользовательском интерфейсе, что позволяет трейдерам адаптировать индикатор к различным рыночным условиям без перекомпиляции.

Три буфера индикатора объявлены как массивы типа double. Позже мы свяжем их с графическими построениями с помощью метода SetIndexBuffer. В этих буферах хранятся вычисленные значения для каждого бара; терминал считывает данные из этих буферов для построения линий на графике. Хранение их в виде глобальных массивов гарантирует сохранение данных между вызовами функций и их доступность на протяжении всего жизненного цикла индикатора.

Теперь перейдем к функции инициализации, OnInit. Эта функция вызывается ровно один раз при загрузке индикатора на график. Это идеальное место для проверки входных параметров, настройки буферов и выполнения любых разовых настроек, которые не нужно повторять при каждом тике.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- check parameters
   if(InpDegree < 1)
     {
      Print("Degree must be at least 1");
      return(INIT_PARAMETERS_INCORRECT);
     }
   if(InpPeriod < InpDegree+1)
     {
      Print("Period must be larger than degree");
      return(INIT_PARAMETERS_INCORRECT);
     }
   if(InpDeviation < 0)
     {
      Print("Deviation multiplier cannot be negative");
      return(INIT_PARAMETERS_INCORRECT);
     }

//--- set indicator buffers
   SetIndexBuffer(0, MiddleBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, UpperBuffer,  INDICATOR_DATA);
   SetIndexBuffer(2, LowerBuffer,  INDICATOR_DATA);

//--- set accuracy
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

//--- set plot labels
   PlotIndexSetString(0, PLOT_LABEL, "Middle ("+IntegerToString(InpDegree)+")");
   PlotIndexSetString(1, PLOT_LABEL, "Upper");
   PlotIndexSetString(2, PLOT_LABEL, "Lower");

//--- done
   return(INIT_SUCCEEDED);
  }

Проверка параметров может показаться шаблоном, но на самом деле она крайне важна для надёжного индикатора. Полиномиальная степень меньше единицы не имеет математического смысла — нам нужна как минимум линейная аппроксимация. Период должен быть не меньше степени + 1, иначе у нас будет меньше точек данных, чем параметров, что сделает аппроксимацию некорректной, а полученный полином — неоднозначным. Множитель отклонения не может быть отрицательным, поскольку отрицательная ширина инвертирует канал, поместив верхнюю полосу ниже нижней полосы. Если какое-либо из этих условий не выполняется, мы выводим конкретное сообщение об ошибке и возвращаем INIT_PARAMETERS_INCORRECT. Это предотвращает запуск индикатора с недопустимыми настройками и дает пользователю четкую обратную связь о том, что нужно исправить.

После проверки мы связываем буферы с графическими построениями с помощью SetIndexBuffer. Второй параметр, INDICATOR_DATA, указывает терминалу, что это обычные буферы индикаторов (в отличие от цветовых буферов или других специальных типов). Далее устанавливаем количество отображаемых цифр в соответствии с настройкой количества цифр на графике (_Digits) — небольшая деталь, которая гарантирует отображение значений с необходимой точностью. Наконец, присваиваем каждой линии пользовательскую метку. Метка центральной линии включает выбранную степень полинома, что позволяет легко определить, какая версия индикатора используется, особенно если у вас запущено несколько экземпляров с разными настройками.

Функция деинициализации, OnDeinit, пуста, поскольку мы не выделили каких-либо динамических ресурсов, требующих ручной очистки. Она включена для полноты картины, но фактически ничего не делает.

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }

Теперь переходим к сути индикатора: функции OnCalculate. Она вызывается при каждом новом тике и отвечает за заполнение буферов индикатора правильными значениями для каждого бара. Мы рассмотрим ее подробно, потому что именно здесь происходит вся основная работа.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- check for minimum bars
   if(rates_total < InpPeriod)
      return(0);

//--- working arrays for ALGLIB
   double x[];
   double y[];
   ArrayResize(x, InpPeriod);
   ArrayResize(y, InpPeriod);

//--- determine start bar
   int start = prev_calculated > 0 ? prev_calculated - 1 : 0;
   if(start < InpPeriod-1)
      start = InpPeriod-1;

Первая проверка гарантирует, что на графике достаточно баров для формирования полного окна. В противном случае мы возвращаем 0 и ничего не отображается. Это мера безопасности, предотвращающая ошибки при первой загрузке индикатора на график с недостаточным количеством исторических данных.

Затем мы выделяем два локальных массива, x и y, каждый размером с период ретроспективного анализа InpPeriod. В них будут храниться независимая переменная (индекс бара в текущем окне) и соответствующие значения цен. Использование локальных массивов делает функцию самодостаточной и позволяет избежать загрязнения глобального пространства имен временными переменными.

Определение начального бара — это оптимизация производительности, которую стоит рассмотреть. Параметр prev_calculated показывает, сколько баров уже обработано в предыдущем вызове OnCalculate. Если он больше нуля, нам нужно обновить только самый последний бар — тот, который находится по индексу prev_calculated - 1. В противном случае мы начинаем с нуля и должны обработать все бары, для которых доступно полное окно, то есть начиная с индекса InpPeriod - 1. Такая логика гарантирует, что мы не будем тратить время ЦП на пересчет всей истории для каждого тика, что было бы особенно неэффективно на графиках с высокой частотой обновления.

//--- main loop
   for(int i = start; i < rates_total; i++)
     {
      //--- fill x and y arrays (x = 0 .. period-1, y = price values)
      for(int j = 0; j < InpPeriod; j++)
        {
         x[j] = j;  // use integer indices as regressor
         y[j] = GetPrice(InpPrice, open, high, low, close, i - InpPeriod + 1 + j);
        }

Внутри основного цикла для каждого индекса бара i необходимо заполнить массивы x и y данными для окна регрессии, заканчивающегося на этом баре. Независимая переменная x — это просто индекс j от 0 до InpPeriod-1. Вы можете задаться вопросом, почему мы используем простые индексы, а не фактические временные метки или номера баров. Ответ заключается в том, что полиномиальная регрессия инвариантна относительно преобразования и масштабирования независимой переменной — сдвиг всех значений x на константу или умножение их на скаляр не меняет форму аппроксимирующей кривой, только значения коэффициентов. Использование 0, 1, 2,… позволяет уменьшить количество чисел и избежать численных проблем, которые могут возникнуть при использовании больших временных меток UNIX. Это также означает, что коэффициенты полинома не зависят от фактического времени баров, что является желательным свойством для индикатора графика.

Соответствующие значения y получаются путем вызова вспомогательной функции GetPrice с соответствующими массивами цен и индексом i - InpPeriod + 1 + j. Это выражение может показаться сложным, но на самом деле оно просто вычисляет индекс бара в истории графика, соответствующий позиции j в текущем окне. Поскольку j изменяется от 0 до InpPeriod-1, этот индекс изменяется от i - InpPeriod + 1 до i — ровно до последних баров InpPeriod, заканчивающихся на текущем баре i. Каждому бару присваивается собственное уникальное окно, которое сдвигается вперед по мере выполнения цикла.

      //--- create ALGLIB objects for polynomial fit
      CBarycentricInterpolantShell p;
      CPolynomialFitReportShell rep;
      int info;

      //--- call polynomial fitting (degree = InpDegree => m = InpDegree+1)
      CAlglib::PolynomialFit(x, y, InpPeriod, InpDegree+1, info, p, rep);

Теперь мы готовимся вызвать ALGLIB. Мы инстанцируем три объекта: p типа CBarycentricInterpolantShell, который хранит полином в форме, позволяющей эффективно вычислять его значения; rep типа CPolynomialFitReportShell, который может содержать дополнительную информацию об аппроксимации (хотя мы ее здесь не используем); и целочисленный объект info, который будет получать код состояния от процедуры аппроксимации.

Вызов функции CAlglib::PolynomialFit принимает массивы x и y, количество точек (InpPeriod), количество базисных функций (которое равно InpDegree+1 для многочлена степени InpDegree), а также ссылки на info, p и rep. Функция выполняет аппроксимацию данных полинома указанной степени методом наименьших квадратов и возвращает код состояния в переменной info. Положительное значение указывает на успешное выполнение; нулевое или отрицательное значение указывает на различные ошибки — недостаточно данных, сингулярная матрица, численные проблемы и т. д.

      //--- if fitting succeeded, compute channel
      if(info > 0)
        {
         //--- compute fitted values for all points in the window
         double fitted[];
         ArrayResize(fitted, InpPeriod);
         double sum_sq = 0.0;

         for(int j = 0; j < InpPeriod; j++)
           {
            fitted[j] = CAlglib::BarycentricCalc(p, x[j]);
            double resid = y[j] - fitted[j];
            sum_sq += resid * resid;
           }

Если аппроксимация выполнена успешно (об этом свидетельствует info > 0), мы выделяем временный массив fitted для хранения подобранных значений для каждой точки в окне. Затем мы перебираем в цикле все точки в окне, вызывая CAlglib::BarycentricCalc(p, x[j]) для вычисления подобранного полинома в каждой точке x[j]. Это дает нам подобранную цену в этой точке. Остаток — это разница между фактической ценой ( y[j] ) и подобранным значением. Мы возводим каждый остаток в квадрат и накапливаем сумму квадратов в sum_sq.

Вы можете задаться вопросом, почему нам нужны подобранные значения для всех точек в окне, а не только для текущей точки. Причина в том, что нам необходимо стандартное отклонение остатков по всему периоду для вычисления ширины канала. Стандартное отклонение — это мера типичного отклонения цены от подобранной кривой, и его вычисление по всем точкам в окне дает нам надежную оценку текущей волатильности вокруг тренда.

         //--- standard deviation of residuals
         double stddev = MathSqrt(sum_sq / (InpPeriod - InpDegree - 1));  // unbiased

Расчет стандартного отклонения заслуживает особого внимания. Мы вычисляем его как квадратный корень из дисперсии, где дисперсия равна sum_sq / (n - d - 1). Здесь n — период, а d — степень. Это несмещенная оценка, которая использует n - d - 1 степеней свободы, поскольку мы оценили d+1 параметров (коэффициенты полинома) на основе данных. Знаменатель меньше n, что немного увеличивает оценочную дисперсию — эта корректировка важна при малом размере выборки, поскольку дает более точную оценку истинной дисперсии генеральной совокупности. Без этой поправки канал был бы немного уже, чем следует, особенно для более коротких периодов ретроспективного анализа.

         //--- value at the current point (x = InpPeriod-1)
         double middle = CAlglib::BarycentricCalc(p, InpPeriod-1);
         MiddleBuffer[i] = middle;
         UpperBuffer[i]  = middle + InpDeviation * stddev;
         LowerBuffer[i]  = middle - InpDeviation * stddev;
        }

Значение центральной линии на текущем баре — это просто полином, вычисленный в последней точке окна, где x = InpPeriod-1. Сохраняем это значение в MiddleBuffer[i]. Верхняя и нижняя полосы затем вычисляются как middle ± InpDeviation * stddev. Эти значения записываются в соответствующие буферы, а терминал будет использовать их для построения линий на графике.

      else
        {
         //--- fitting failed, copy previous values or set empty
         if(i > 0)
           {
            MiddleBuffer[i] = MiddleBuffer[i-1];
            UpperBuffer[i]  = UpperBuffer[i-1];
            LowerBuffer[i]  = LowerBuffer[i-1];
           }
         else
           {
            MiddleBuffer[i] = 0.0;
            UpperBuffer[i]  = 0.0;
            LowerBuffer[i]  = 0.0;
           }
        }
     }

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

Если аппроксимация не сработала — что случается редко, но возможно при работе с вырожденными данными, такими как постоянные цены на протяжении всего временного интервала, — нам необходимо корректно её обработать. Для баров после первого мы просто копируем значения предыдущего бара вперед. Это гарантирует непрерывность канала и отсутствие отвлекающих разрывов. Для самого первого бара (что возможно только если доступно полное окно данных, но аппроксимация всё равно не удалась) мы устанавливаем значения равными нулю. На практике такие сбои настолько редки, что такой простой резервный механизм вполне достаточен.

Наконец, возвращаем rates_total. Это значение становится новым prev_calculated при следующем вызове OnCalculate, что позволяет корректно работать логике оптимизации, которую мы настроили ранее — перерасчет потребуется только для самого последнего бара на последующих тиках.

Вспомогательная функция GetPrice проста, но ее стоит включить, чтобы основной цикл оставался чистым. Она принимает перечисление типов цен и массивы цен и возвращает соответствующее значение для заданного индекса. Это позволяет легко добавлять новые типы цен в будущем, не загромождая основную логику вычислений.

//+------------------------------------------------------------------+
//| Helper function to get price based on ENUM_APPLIED_PRICE         |
//+------------------------------------------------------------------+
double GetPrice(ENUM_APPLIED_PRICE price_type,
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                int index)
  {
   switch(price_type)
     {
      case PRICE_CLOSE:   return close[index];
      case PRICE_OPEN:    return open[index];
      case PRICE_HIGH:    return high[index];
      case PRICE_LOW:     return low[index];
      case PRICE_MEDIAN:  return (high[index] + low[index]) / 2.0;
      case PRICE_TYPICAL: return (high[index] + low[index] + close[index]) / 3.0;
      case PRICE_WEIGHTED:return (high[index] + low[index] + 2*close[index]) / 4.0;
      default:              return close[index];
     }
  }
//+------------------------------------------------------------------+

Код завершен. Он эффективен, надежен и четко структурирован. Каждый компонент объяснен, от общей концепции до деталей реализации на низком уровне. С этой основой вы можете скомпилировать индикатор и начать тестировать его на реальных графиках.


Тестирование

После компиляции (F7 в MetaEditor) перетащите индикатор на любой график. Вы увидите три линии: синюю центральную, красную верхнюю полосу и зеленую нижнюю полосу.

Начните с EURUSD H1, настройки по умолчанию (степень 2, период 20, отклонение 2,0, цена закрытия). Прокрутите график назад и посмотрите, как изгибается центральная линия: в отличие от прямолинейного канала линейной регрессии, она подстраивается под движение цены.

Тестирование Polynomial Regression Channel в тестере стратегий

Теперь поэкспериментируем с параметрами. Уменьшим период до 10 — канал станет более чувствительным, но и более шумным. Увеличим период до 50 — канал станет более сглаженным, но будет медленнее реагировать на цену. Измените степень на 1 для базовой линии линейной регрессии, затем на 2 или 3 при параболическом движении. Убедитесь, как кривая лучше следует за ускорением (но следите за переобучением при степени 3).

Отрегулируйте множитель отклонения: значение 1.5 сужает полосы для сигналов возврата к среднему; значение 2,5 расширяет их, делая пробои более значимыми. Попробуйте использовать разные типы цен: использование максимумов (high) смещает канал вверх, а минимумов (low) — вниз; соответственно, такие варианты могут выступать как динамическое сопротивление и поддержка.

Протестируйте на разных инструментах. Полосы автоматически расширяются во время волатильных новостных событий и сужаются в периоды затишья — это заложено в математических расчетах.

Интерпретация требует практики. Цена у верхней полосы: хватает ли силы на пробой или возможен разворот? Ищите подтверждение в виде свечей. Наклон и кривизна центральной линии добавляют контекста — если линия становится круче, это указывает на усиление импульса, а сглаживание предупреждает об истощении.

Используйте ее как любой канал: визуальный анализ, размещение стоп-лоссов или часть более крупной системы. Экспериментируйте, чтобы понять её сильные стороны.


Заключение

Мы начали с практической задачи — линейные каналы не могут отражать криволинейную структуру рынка — и предложили конкретное, проверяемое решение: полиномиальный регрессионный канал, реализованный на MQL5, который вы можете скомпилировать и прикрепить к любому графику. Использование ALGLIB позволило нам опираться на надежные численные методы (аппроксимацию полиномами и барицентрическую интерполяцию) вместо того, чтобы заново реализовывать хрупкий код линейной алгебры. Индикатор включает проверку входных данных (степень ≥ 1, период > степени, неотрицательное отклонение), несмещенную оценку стандартного отклонения, учитывающую оцениваемые параметры, и корректную обработку ошибок при неудачной аппроксимации.

С практической точки зрения реализация минимизирует накладные вычислительные расходы во время выполнения за счет пересчета только последнего бара (когда это возможно), использует небольшие преобразованные x-значения (0.. N-1) для предотвращения проблем с масштабированием чисел, а также сохраняет результаты в стандартных буферах индикатора для мгновенного отображения на графике или программного использования. Рекомендации по параметрам по умолчанию (степень = 2, период ≈ 20, отклонение ≈ 2,0) обеспечивают разумную начальную точку, а явные предупреждения о переобучении разъясняют, когда более высокие степени неприемлемы для зашумленных данных.

В результате вы получаете готовый к применению индикатор (PolynomialRegressionChannel.mq5) и четкий паттерн расширения: добавление оповещений о касании полос, раскрашивание полос по наклону или кривизне, предоставление метрик кривизны для правил сигналов или использование центра и полос в качестве базовых элементов внутри советника. Используйте канал для визуального контекста, размещения стоп-лоссов или в качестве воспроизводимого фильтра в автоматизированных стратегиях — всегда помните о компромиссах между параметрами (чувствительность против стабильности) и риске подгонки модели под шум при увеличении степени полинома.

Вложения

Название файла Тип Версия Описание
PolynomialRegressionChannel.mq5 Индикатор 1.00 Полный исходный код MQL5 для индикатора Polynomial Regression Channel. Он использует библиотеку ALGLIB для выполнения полиномиальной аппроксимации методом наименьших квадратов и строит адаптивный трендовый канал с центральной, верхней и нижней линиями. Код включает в себя полную проверку входных данных, эффективный расчет скользящего окна, который пересчитывается только при необходимости, и вспомогательную функцию для выбора различных типов цен. Несмещенная оценка стандартного отклонения обеспечивает точную ширину канала даже при более коротких периодах ретроспективного анализа.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21501

Прикрепленные файлы |
Создание торговых систем с искусственным интеллектом на MQL5 (Часть 1): Реализация обработки JSON для API ИИ Создание торговых систем с искусственным интеллектом на MQL5 (Часть 1): Реализация обработки JSON для API ИИ
В этой статье разрабатывается модуль разбора JSON на языке MQL5 для обмена данными при интеграции с API ИИ. Основное внимание уделяется классу JsonValue для обработки JSON-структур. Реализуются методы сериализации и десериализации JSON-данных с поддержкой различных типов данных, таких как строки, числа и объекты. Это необходимо для взаимодействия с ИИ-сервисами, такими как ChatGPT, и служит основой для будущих торговых систем на базе ИИ, обеспечивая точную обработку данных.
Возможности Мастера MQL5, которые вам нужно знать (Часть 91): Использование списков с пропусками и сети Хопфилда в пользовательском классе трейлинга Возможности Мастера MQL5, которые вам нужно знать (Часть 91): Использование списков с пропусками и сети Хопфилда в пользовательском классе трейлинга
В следующем исследовании идей, тестируемых с помощью Мастера MQL5, рассмотрим, могут ли списки с пропусками и сеть Хопфилда сформировать стратегию трейлинга, направленную на защиту прибыли. Как уже отмечалось, управлению трейлинг-стопом в торговых системах часто уделяется меньше внимания, чем сигналам входа или управлению капиталом. Трейлинг-стопы могут сыграть решающую роль, например на трендовых рынках, поэтому данный подход тестируется на GBPJPY.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Изучение стандартной библиотеки MQL5 (Часть 9): Использование ALGLIB для фильтрации избыточного числа сигналов пересечения скользящих средних Изучение стандартной библиотеки MQL5 (Часть 9): Использование ALGLIB для фильтрации избыточного числа сигналов пересечения скользящих средних
Во время боковых движений цены трейдеры сталкиваются с избыточными сигналами от множественных пересечений скользящих средних. Сегодня мы обсудим, как ALGLIB предварительно обрабатывает исходные данные о ценах для формирования отфильтрованных сигнальных серий пересечений, которые также могут генерировать оповещения при их возникновении. Давайте разберёмся, как можно использовать математическую библиотеку в программах на MQL5.