preview
ARD-регрессия на MQL5: автоматический отбор признаков и случайные признаки Фурье

ARD-регрессия на MQL5: автоматический отбор признаков и случайные признаки Фурье

MetaTrader 5Статистика и анализ |
56 2
Evgeniy Chernish
Evgeniy Chernish


Введение

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

Традиционные методы снижения размерности и отбора признаков имеют заметные ограничения. Например, метод главных компонент (PCA) хорошо сжимает пространство признаков, выбирая направления с максимальной дисперсией. Однако он не учитывает целевую переменную и ориентируется только на признаки. В результате PCA часто сохраняет шум с высокой дисперсией и может отбросить полезные признаки, которые имеют относительно низкую дисперсию, но при этом сильно связаны с целевой переменной. Кроме того, главные компоненты представляют собой линейные комбинации исходных переменных, что затрудняет их интерпретируемость.

Более гибким и элегантным решением является ARD-регрессия (Automatic Relevance Determination — Автоматическое определение релевантности). ARD — это полноценный байесовский подход к линейной регрессии, который автоматически оценивает степень важности (релевантности) каждого входного признака в процессе обучения модели.

В этой статье мы подробно разберём математический аппарат ARD-регрессии и реализуем модель ARDRegression на языке MQL5, после чего сравним точность вычислений и скорость работы нашей реализации с эталонным модулем из библиотеки scikit-learn.

Отдельное внимание будет уделено преодолению главного ограничения линейных моделей — их неспособности улавливать нелинейные зависимости. Для этого мы применим метод случайных признаков Фурье (класс CRBFSampler), который позволяет эффективно аппроксимировать сложные нелинейные функции, оставаясь в рамках линейной модели.

Что такое ARD-регрессия?

Чтобы понять концепцию ARD-регрессии, стоит начать с классической линейной регрессии. В стандартной постановке мы ищем линейную зависимость между вектором признаков x=[x1, x2, …, xD] и целевой переменной y:

regression model

где,

  • D – размерность пространства признаков (количество предикторов),
  • w = [w1, w2, ..., wD] — вектор весов (коэффициентов) модели,
  • w0 — смещение,
  • ϵ ∼ N(0, α^−1) — случайный гауссовский шум измерения с точностью α (alpha) (обратной дисперсией α = 1 / σ^2)


В классическом методе наименьших квадратов (МНК) коэффициенты wj считаются фиксированными детерминированными числами, а их подгонка осуществляется за счёт минимизации суммы квадратов ошибок. Если среди признаков присутствуют неинформативные или сильно коррелированные (мультиколлинеарные) переменные, МНК склонна присваивать им необоснованно большие веса, что приводит к переобучению и нестабильности прогнозов.

Байесовский подход кардинально меняет взгляд на природу параметров. В байесовской регрессии коэффициенты wj перестают быть константами и рассматриваются как случайные величины. До получения данных мы задаем наши априорные представления о параметрах регрессии. В частности, в гребневой байесовской регрессии (Bayesian Ridge) на все веса накладывается одно и то же априорное нормальное распределение с общим параметром регуляризации λ(лямбда):

Ridge prior weights

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

Главная идея ARD-регрессии заключается в том,  что на каждый вес wj накладывается индивидуальное априорное нормальное распределение со своим собственным параметром точности  λj:

ARD prior weights

Каждый гиперпараметр λj выступает в роли индивидуального регулятора силы соответствующего признака:

  • Если признак xj информативен, то в процессе обучения модель уменьшает значение λj → 0. Это расширяет априорное распределение, делая его плоским. Высокая дисперсия разрешает весу wj свободно отклоняться от нуля и принимать значения, необходимые для точного объяснения целевой переменной y.
  • Если признак xj не оказывает влияния на прогноз целевой переменной, тогда модель увеличивает параметр λj → ∞. Дисперсия 1/λj стремится к нулю, и распределение параметра wj схлопывается в точке 0. Это принудительно зануляет вес wj, исключая признак j из финального уравнения регрессии.

В итоге оптимизация вектора λ превращается в автоматический процесс прореживания пространства признаков.

Обучение модели: EM-алгоритм и оценка параметров

Обучение ARD-регрессии строится на EM-подобном алгоритме (Expectation-Maximization), где вектор весов w играет роль непрерывных скрытых переменных. В отличие от классического EM, здесь на M-шаге производится прямая максимизация маргинального логарифма правдоподобия (Log Evidence).

Алгоритм состоит из двух чередующихся шагов: 

  •  E-шаг (Expectation — Ожидание)

    На E-шаге мы фиксируем текущие значения гиперпараметров шума α и индивидуальных точностей λ. Наша цель — найти апостериорное распределение весов p(w∣y, X, α, λ). Так как априорное распределение весов и функция правдоподобия являются гауссовскими, апостериорное распределение также будет нормальным N(μw, Σ). Благодаря этому вычисление апостериора сводится к вычислению ковариационной матрицы и вектора средних.

    • Ковариационная матрица весов Σ (степень неопределенности коэффициентов):

    Sigma weights

    • Математическое ожидание весов μw (текущие точечные значения коэффициентов):

    mean weights

    Практический нюанс реализации: Если число признаков D больше числа наблюдений N, прямое обращение матрицы ковариации Σ размером D×D становится вычислительно дорогим. В коде класса для этого случая используется тождество Вудбери (Woodbury Matrix Identity), позволяющее сводить операцию к обращению матрицы размером N×N.

    • M-шаг (Maximization — Максимизация)

    На M-шаге найденные апостериорные характеристики из E-шага используются для обновления гиперпараметров λ и α. В классическом EM-алгоритме M-шаг максимизирует математическое ожидание полного логарифма правдоподобия. В случае ARD-регрессии обновление гиперпараметров производится с помощью прямой максимизации маргинального логарифма правдоподобия (evidence maximization). Этот подход эквивалентен M-шагу. Введение коэффициента эффективности признаков γⱼ ускоряет сходимость по сравнению со стандартным EM-алгоритмом. Чтобы предотвратить численную нестабильность и переобучение самих гиперпараметров, над ними задаются слабоинформативные априорные Гамма-распределения с параметрами (α1,α2) и (λ1,λ2).

    • Обновление точностей весов λj:

    precision weights lambda

    где, γj  = 1 − λjΣjj — мера эффективности j-го признака.

    • Обновление точности шума α:

    precision noise

    где, SSE = ||y − Xμw||^2 — сумма квадратов остатков регрессии (Sum of Squared Errors).

    • Отбор признаков (Pruning) и критерий остановки обучения

    После каждого M-шага выполняется отбор признаков. Если гиперпараметр λj превышает заданный порог threshold_lambda, признак признается нерелевантным:

    • Коэффициент wj принудительно обнуляется (wj = 0),
    • Признак j исключается из матрицы X на следующих итерациях  для ускорения расчетов.

    Итерационный процесс продолжается до тех пор, пока изменение вектора весов || w_old  − w_new||  не станет меньше заданного значения tol, либо пока не будет достигнуто максимальное число итераций max_iter.

    Практическая реализация: класс ARDRegression

    Вся математика байесовского вывода — от итерационного EM-алгоритма до динамического отбора признаков  — объединена в класс ARDRegression.

    //+------------------------------------------------------------------+
    //| Класс ARDRegression                                             |
    //+------------------------------------------------------------------+
    class ARDRegression
      {
    private:
       int               m_max_iter;            // Максимальное количество итераций EM-алгоритма
       double            m_tol;                 // Порог сходимости по изменению весов
       double            m_alpha_1,m_alpha_2;   // Параметры Гамма-распределения для точности шума alpha(α)
       double            m_lambda_1,m_lambda_2; // Параметры Гамма-распределения для точностей весов lambda(λ)
       double            m_threshold_lambda;    // Порог отбора (если λ_j > threshold -> вес = 0)
       bool              m_fit_intercept;       // Нужно ли обучать параметр смещения (w0)
       bool              m_compute_score;       // Нужно ли вычислять целевую функцию (Log Evidence/Marginal Log-likelihood)
       bool              m_verbose;             // Вывод сообщений о сходимости алгоритма обучения в журнал
    
       bool              UpdateSigmaAndCoef(const matrix &X_cent, const vector &y_cent, bool &keep_lambda[]);
    
    public:
       vector            coef_;      // Вектор итоговых коэффициентов (w_j)
       double            intercept_; // Итоговое смещение (w_0)
       double            alpha_;     // Итоговая точность шума (1 / sigma^2) (Обратная дисперсия шума σ)
       vector            lambda_;    // Вектор точностей признаков
       matrix            sigma_;     // Ковариационная матрица апостериорного распределения весов
       int               n_iter_;    // Число итераций до сходимости
       double            scores_[];  // История значений Evidence/Marginal Log-likelihood на каждой итерации
       vector            X_offset_;  // Средние значения столбцов матрицы X (для центрирования признаков)
       double            y_offset_;  // Среднее значение целевой переменной y (для центрирования отклика)
    
                         ARDRegression(int      max_iter = 300,
                     double   tol = 1e-3,
                     double   alpha_1 = 1e-6,
                     double   alpha_2 = 1e-6,
                     double   lambda_1 = 1e-6,
                     double   lambda_2 = 1e-6,
                     double   threshold_lambda = 1e4,
                     bool     fit_intercept = true,
                     bool     compute_score = false,
                     bool     verbose = false);
    
       bool              fit(const matrix &X, const vector &y);
       vector            predict(const matrix &X, vector &y_std);
      };
    

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

    • max_iter (по умолчанию 300): Максимальное количество итераций EM-алгоритма,
    • tol (по умолчанию  0.001): Критерий остановки обучения,

    • Гипераприоры Гамма-распределения (alpha_1, alpha_2, lambda_1, lambda_2)

    Эти параметры задают априорные значения точности шума α и точностей весов λj через Гамма-распределения Gamma(α1,α2) и Gamma(λ1,λ2). В 99% практических задач оставляйте эти параметры равными 1e-6 как они заданы по умолчанию. Эти значения задают так называемые неинформативные (плоские) априорные распределения, предоставляя возможность данным говорить самим за себя.

    • threshold_lambda (по умолчанию 10000): параметр для механизма отбора признаков. Уменьшение (до 100 – 1000) делает отбор признаков более агрессивным. Модель будет сильнее прореживать веса, оставляя только самые сильные сигналы,

    • fit_intercept (по умолчанию true): Вычисление смещения. Всегда оставляйте true, если ваши входные данные X не центрированы. Если вы предварительно отцентрировали данные (mean=0), можно поставить false,

    • compute_score (по умолчанию false): Расчет целевой функции (Marginal Log-likelihood / Log Evidence) на каждой итерации. Поставьте true, если вы хотите проанализировать график сходимости модели. Результаты вычислений сохраняются в массив scores_,
    • verbose (по умолчанию false): Вывод детального лога обучения в журнал.

    Основные методы класса

    1. Метод обучения: fit(const matrix &X, const vector &y)

    Главный метод класса, выполняющий обучение модели и динамическое прореживание пространства признаков.

    //+------------------------------------------------------------------+
    //| Метод обучения модели                                            |
    //+------------------------------------------------------------------+
    bool ARDRegression::fit(const matrix &X, const vector &y)
      {
       ulong n_samples  = X.Rows();
       ulong n_features = X.Cols();
    
       matrix X_cent = X;
       vector y_cent = y;
    
    //--- Центрирование данных
       X_offset_.Resize(n_features);
       if(m_fit_intercept)
         {
          y_offset_ = y.Mean();
          y_cent = y - y_offset_;
    
          for(ulong j = 0; j < n_features; j++)
            {
             X_offset_[j] = X.Col(j).Mean();
             X_cent.Col(X_cent.Col(j) - X_offset_[j], j);
            }
         }
       else
         {
          X_offset_.Fill(0.0);
          y_offset_ = 0.0;
         }
    
       coef_ = vector::Zeros(n_features);
    
    //--- Инициализация параметров
       double eps = 2.2204460492503131e-16;
       alpha_ = 1.0 / (y_cent.Var() + eps);
       lambda_ = vector::Ones(n_features);
    
       bool keep_lambda[];
       ArrayResize(keep_lambda, (int)n_features);
       ArrayFill(keep_lambda, 0, (int)n_features, true);
    
       vector coef_old_ = vector::Zeros(n_features);
       ArrayFree(scores_);
    
       for(int iter_ = 0; iter_ < m_max_iter; iter_++)
         {
          //--- 1. Подсчет активных признаков
          ulong keep_count = 0;
          for(ulong j = 0; j < n_features; j++)
             if(keep_lambda[j])
                keep_count++;
    
          if(keep_count == 0)
            {
             coef_.Fill(0.0);
             sigma_.Resize(0, 0);
             n_iter_ = iter_ + 1;
             break;
            }
    
          //--- 2. Обновление sigma_ и coef_  (E-шаг)
          if(!UpdateSigmaAndCoef(X_cent, y_cent, keep_lambda))
            {
             PrintFormat("ARD Error: Aborted at iteration %d due to numerical solver failure.", iter_);
             return false;
            }
    
          //--- 3. Обновление гиперпараметров (M-шаг)
          double sse_ = MathPow(y_cent - X_cent @ coef_, 2).Sum();
    
          vector lambda_keep = SelectByMask(lambda_, keep_lambda);
          vector gamma_ = 1.0 - lambda_keep * sigma_.Diag();
    
          vector coef_select = SelectByMask(coef_, keep_lambda);
          vector lambda_keep_new = (gamma_ + 2.0 * m_lambda_1) / (coef_select * coef_select + 2.0 * m_lambda_2);
          lambda_keep_new.Clip(1e-10, 1e+10); // Ограничиваем сверху и снизу от переполнения
          ScatterByMask(lambda_, keep_lambda, lambda_keep_new);
    
          //--- Числитель и знаменатель
          double num = n_samples - gamma_.Sum() + 2.0 * m_alpha_1;
          double den = sse_ + 2.0 * m_alpha_2;
    
          //--- Защита от деления на 0
          if(den < 1e-12)
             alpha_ = 1e+10;
          else
            {
             alpha_ = num / den;
             // Ограничиваем диапазон [1e-10, 1e+10]
             alpha_ = MathMax(1e-10, MathMin(1e+10, alpha_));
            }
          //  alpha_ = (n_samples - gamma_.Sum() + 2.0 * m_alpha_1) / (sse_ + 2.0 * m_alpha_2);
    
          //--- 4.Отбор признаков (Pruning)
          keep_count = 0;
          for(ulong j = 0; j < n_features; j++)
            {
             keep_lambda[j] = (lambda_[j] < m_threshold_lambda);
             if(!keep_lambda[j])
                coef_[j] = 0.0;
             else
                keep_count++;
            }
    
          //--- 4.1. Расчет целевой функции  (Log Evidence / Objective function)
          if(m_compute_score || m_verbose)
            {
             double s = (m_lambda_1 * MathLog(lambda_) - m_lambda_2 * lambda_).Sum();
             s += m_alpha_1 * MathLog(alpha_) - m_alpha_2 * alpha_;
    
             int sign;
             double logdet_sigma;
             double Signed_Determinant = sigma_.SLogDet(sign,logdet_sigma);
             // Проверяем корректность знака детерминанта (SPD матрица должна иметь sign > 0)
             if(sign <= 0)
               {
                if(m_verbose)
                   PrintFormat("ARD Warning: non-positive determinant for sigma_ at iteration %d (sign=%d). Score calculation skipped.", iter_, sign);
               }
             else
               {
                s += 0.5 * (logdet_sigma + (double)n_samples * MathLog(alpha_) + MathLog(lambda_).Sum());
                s -= 0.5 * (alpha_ * sse_ + (lambda_ * coef_ * coef_).Sum());
                int score_size = ArraySize(scores_);
                ArrayResize(scores_, score_size + 1);
                scores_[score_size] = s;
               }
            }
          //--- 5. Проверка сходимости
          if(iter_ > 0)
            {
             if(MathAbs(coef_old_ - coef_).Sum() < m_tol)
               {
                if(m_verbose)
                   PrintFormat("Converged after %d iterations", iter_);
                n_iter_ = iter_ + 1;
                break;
               }
            }
    
          coef_old_ = coef_;
    
          if(keep_count == 0)
            {
             n_iter_ = iter_ + 1;
             break;
            }
    
          n_iter_ = iter_ + 1;
         }
    
    //--- 6. Финальный пересчет (с самыми последними alpha_ и lambda_)
       if(!UpdateSigmaAndCoef(X_cent, y_cent, keep_lambda))
         {
          Print("ARD Error: Numerical solver failure on final step");
          return false;
         }
    
    //--- Сохранение итогового смещения
       if(m_fit_intercept)
          intercept_ = y_offset_ - X_offset_.Dot(coef_);
       else
          intercept_ = 0.0;
    
       return true;
      }

    //+------------------------------------------------------------------+
    //| Метод для обновления sigma_ и coef_ по текущим alpha_ и lambda_  |
    //+------------------------------------------------------------------+
    bool ARDRegression::UpdateSigmaAndCoef(const matrix &X_cent, const vector &y_cent, bool &keep_lambda[])
      {
       ulong n_samples  = X_cent.Rows();
       ulong n_features = X_cent.Cols();
    
    //--- 1. Формируем X_keep и lambda_keep через функции-маски
       matrix X_keep      = SelectColsByMask(X_cent, keep_lambda);
       vector lambda_keep = SelectByMask(lambda_, keep_lambda);
    
       ulong keep_count = X_keep.Cols();
    
       if(keep_count == 0)
         {
          coef_.Fill(0.0);
          sigma_.Resize(0, 0);
          PrintFormat("ARD Warning: All features pruned (keep_count == 0) at iteration %d. Model cannot be fitted.", n_iter_);
          return true;
         }
    
    //--- 2. Расчет апостериорной ковариационной матрицы весов sigma_ (Direct vs Woodbury)
       if(n_samples >= n_features) // N > D
         {
          matrix gram = X_keep.Transpose().MatMul(X_keep);
          matrix sigma_inv = matrix::Identity(keep_count, keep_count);
          sigma_inv.Diag(lambda_keep);
          sigma_inv = sigma_inv + (gram * alpha_);
          //sigma_ = sigma_inv.PInv();
    
          matrix residuals;
          vector singular_values;
          long rank;
          //--- Решаем A * X = I через GELSD (SVD Divide & Conquer)
          if(!sigma_inv.LeastSquaresSolutionDC(matrix::Eye(keep_count, keep_count), -1.0, sigma_, residuals, singular_values, rank))
            {return false;}
         }
       else // D > N
         {
          //---  Формируем вектор/матрицу inverse lambda
          vector inv_lambda(keep_count);
          for(ulong j = 0; j < keep_count; j++)
             inv_lambda[j] = 1.0 / lambda_keep[j];
    
          //---  X_keep_inv = X_keep * inv_lambda (поэлементное умножение каждого столбца j на inv_lambda[j])
          matrix X_keep_inv(n_samples, keep_count);
          for(ulong i = 0; i < n_samples; i++)
            {
             for(ulong j = 0; j < keep_count; j++)
                X_keep_inv[i][j] = X_keep[i][j] * inv_lambda[j];
            }
    
          //---  Вычисляем инвертируемую матрицу размером (n_samples, n_samples)
          matrix eye_samples = matrix::Identity(n_samples, n_samples);
          matrix inner_mat = (eye_samples / alpha_) + X_keep_inv.MatMul(X_keep.Transpose());
          //matrix inner_inv = inner_mat.PInv();
    
          matrix residuals;
          vector singular_values;
          long rank;
          matrix inner_inv;
          if(!inner_mat.LeastSquaresSolutionDC(matrix::Eye(n_samples, n_samples), -1.0, inner_inv, residuals, singular_values, rank))
            {return false;}
          //--- inner_inv @ X_keep_inv -> размер (n_samples, keep_count)
          matrix temp = inner_inv.MatMul(X_keep_inv);
    
          //--- inv_X_T = inv_lambda_col * X_keep.T -> размер (keep_count, n_samples)
          matrix inv_X_T(keep_count, n_samples);
          matrix X_T = X_keep.Transpose();
          for(ulong i = 0; i < keep_count; i++)
            {
             for(ulong j = 0; j < n_samples; j++)
                inv_X_T[i][j] = X_T[i][j] * inv_lambda[i];
            }
    
          //--- sigma_ = - (inv_X_T @ temp) -> размер (keep_count, keep_count)
          sigma_ = inv_X_T.MatMul(temp) * (-1.0);
    
          //--- Добавление диагонали 1.0 / lambda
          for(ulong j = 0; j < keep_count; j++)
             sigma_[j][j] += inv_lambda[j];
         }
    
    //--- 3. Расчет апостериорного среднего весов coef_
       vector temp_dot = X_keep.Transpose().MatMul(y_cent);
       vector coef_keep = temp_dot.MatMul(sigma_) * alpha_;
    
       coef_.Fill(0.0);
       ScatterByMask(coef_, keep_lambda, coef_keep);
       
       return true;
      }

    2. Метод прогнозирования: predict(const matrix &X, vector &y_std)

    В отличие от стандартных моделей машинного обучения, метод predict возвращает не просто точечные значения, а полноценный вероятностный прогноз: 

    • Точечный прогноз (Математическое ожидание y*):

    predict y

    • Оценка неопределенности (стандартное отклонение ystd). Апостериорная дисперсия прогноза учитывает как шум в данных α^−1, так и неопределенность оценки весов Σ:

    predict y_std

    //+------------------------------------------------------------------+
    //| Метод предсказания                                               |
    //+------------------------------------------------------------------+
    vector ARDRegression::predict(const matrix &X, vector &y_std)
      {
       //--- проверка совместимости размерностей признаков
       if(X.Cols() != coef_.Size())
         {
          PrintFormat("ARD Error [predict]: Dimension mismatch! Model expects %d features, but X has %d cols.",
                      coef_.Size(), X.Cols());
          vector empty(0);
          return empty;
         }
    // --- прогноз условного среднего
       vector y_mean = X.MatMul(coef_);
       if(m_fit_intercept)
          y_mean = y_mean + intercept_;
    
    //--- расчет стандартного отклонения
       ulong n_samples  = X.Rows();
       ulong n_features = X.Cols();
    
    //--- Подсчет активных признаков
       bool keep_lambda[];
       ArrayResize(keep_lambda, (int)n_features);
       ulong keep_count = 0;
    
       for(ulong j = 0; j < n_features; j++)
         {
          keep_lambda[j] = (lambda_[j] < m_threshold_lambda);
          if(keep_lambda[j])
             keep_count++;
         }
    
    //--- если активных признаков нет — возвращаем шум 1 / alpha
       if(keep_count == 0)
         {
          y_std = vector::Ones(n_samples) * MathSqrt(1.0 / alpha_);
          return y_mean;
         }
    
       matrix X_keep = SelectColsByMask(X, keep_lambda);
       matrix X_sigma = X_keep.MatMul(sigma_);
       y_std = MathSqrt((X_sigma * X_keep).Sum(1) + (1.0 / alpha_));
    
       return y_mean;
      }

    Сравнение результатов обучения с моделью Scikit-Learn

    Для проверки математической корректности, точности и численной устойчивости разработанного класса ARDRegression было проведено сравнение с эталонной реализацией из библиотеки scikit-learn (Python).

    Сравнение выполнялось на двух тестовых скриптах:

    • scikit_learn.py — генерирует синтетический датасет, обучает модель sklearn.linear_model.ARDRegression и выводит результаты вычислений в журнал,
    • TestARD.mq5 — MQL5 скрипт, импортирует те же данные (CSV-файлы X_ARD.csv и y_ARD.csv) и обучает нашу реализацию.

    Обе модели обучались на идентичных наборах данных в двух режимах:

    • Датасет 1 (N > D): 200 наблюдений, 50 признаков. Стандартный режим обучения,
    • Датасет 2 (N < D): 40 образцов, 50 признаков. Усеченная выборка для проверки корректности вычислений через тождество Вудбери.

    Истинные параметры модели:

    • смещение (intercept): w0 = 5.0,
    • значимые веса: w1 = 3.5, w2 = −2.0, w3 = 1.2,
    • оставшиеся 47 весов = 0 (незначимые признаки),
    • шум наблюдений: σ = 0.5 (α = 4.0).

    //+------------------------------------------------------------------+
    //|                                                      TestARD.mq5 |
    //|                                                           Eugene |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Eugene"
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #include <ARDRegression\ARD.mqh>
    
    //+------------------------------------------------------------------+
    //| функция для запуска теста и вывода результатов                   |
    //+------------------------------------------------------------------+
    void RunARDTest(const matrix &X, const vector &y, string test_name)
      {
       ulong n_samples  = X.Rows();
       ulong n_features = X.Cols();
    
    //--- Создаем модель
       ARDRegression model(300, 1e-3, 1e-6, 1e-6, 1e-6, 1e-6, 1e4, true, true, false);
    
       uint start_time = GetTickCount();
       model.fit(X, y); // обучение
       uint elapsed_time = GetTickCount() - start_time;
    
    // --- Предсказание и проверка ошибки
       vector y_std;
       vector y_pred = model.predict(X, y_std);
       vector errors = y - y_pred;
       double mse = y_pred.RegressionMetric(y,REGRESSION_MSE);
    
    // --- Вывод результатов в журнал
       PrintFormat("=========================================================");
       PrintFormat(" %s (n_samples=%d, n_features=%d)", test_name, n_samples, n_features);
       PrintFormat("=========================================================");
       PrintFormat("Время выполнения:      %u ms", elapsed_time);
       PrintFormat("Число итераций:        %d", model.n_iter_);
       PrintFormat("Intercept (Истинный 5): %.5f", model.intercept_);
       PrintFormat("Alpha (Точность шума):  %.5f", model.alpha_);
       PrintFormat("MSE предсказания:       %.5f", mse);
    
       int n_scores = ArraySize(model.scores_);
       if(n_scores > 0)
         {
          PrintFormat("Log Evidence (Marginal Likelihood): %.5f", model.scores_[n_scores - 1]);
    
          // --- Динамика Log Evidence
          PrintFormat("\n--- Динамика Log Evidence по итерациям ---");
          if(n_scores <= 10)
            {
             for(int it = 0; it < n_scores; it++)
                PrintFormat("Итерация %02d | Log Evidence: %12.5f", it + 1, model.scores_[it]);
            }
          else
            {
             //--- Показываем первые 5
             for(int it = 0; it < 5; it++)
                PrintFormat("Итерация %02d | Log Evidence: %12.5f", it + 1, model.scores_[it]);
    
             PrintFormat("  ...");
    
             //--- И последние 3
             for(int it = n_scores - 3; it < n_scores; it++)
                PrintFormat("Итерация %02d | Log Evidence: %12.5f", it + 1, model.scores_[it]);
            }
         }
    
       PrintFormat("\n--- Стандартное отклонение предсказаний (y_std, первые 5 сэмплов) ---");
       for(ulong i = 0; i < 5 && i < n_samples; i++)
         {
          PrintFormat("Сэмпл %02d | y_pred: %8.5f | y_std: %8.5f", i, y_pred[i], y_std[i]);
         }
    
       PrintFormat("\n--- Веса coef_ (Истинные: X00=3.5, X01=-2.0, X02=1.2, остальные=0) ---");
       for(ulong i = 0; i < 5 && i < n_features; i++)
         {
          PrintFormat("X%02d | Оценка: %8.5f | Lambda: %10.2f",
                      i, model.coef_[i], model.lambda_[i]);
         }
    
    //---  Pruning
       int active_features = 0;
       for(ulong j = 0; j < n_features; j++)
         {
          if(MathAbs(model.coef_[j]) > 1e-4)
             active_features++;
         }
       PrintFormat("\nОсталось активных признаков с весом > 0: %d из %d\n", active_features, n_features);
      }
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       matrix X_csv;
       CSVtoMatrix("X_ARD.csv", X_csv);
       matrix y_csv;
       CSVtoMatrix("y_ARD.csv", y_csv);
       vector y_full = y_csv.Col(0);
    
    // ТЕСТ 1: Полный датасет (200 x 50) -> Direct Gram Matrix
       RunARDTest(X_csv, y_full, "ТЕСТ 1: FULL DATASET (Direct Gram)");
    
    //--- ТЕСТ 2: Первые 40 элементов  -> Woodbury Identity
       matrix X_sub = X_csv;
       vector y_sub = y_full;
    
    //--- урезаем данные до 40 элементов
       X_sub.Resize(40, 50);
       y_sub.Resize(40);
    
       RunARDTest(X_sub, y_sub, "ТЕСТ 2: SUBSET DATASET (Woodbury Identity)");
      }
    import time
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import ARDRegression
    
    # Функция для вывода результатов работы модели Scikit-Learn
    def RunARDTest(title, model, X, y, elapsed_ms, show_score_history=True):
        y_pred, y_std = model.predict(X, return_std=True)
        mse = np.mean((y - y_pred) ** 2)
        active_count = np.sum(model.coef_ != 0.0)
        n_samples, n_features = X.shape
    
        print("=========================================================")
        print(f" {title} (n_samples={n_samples}, n_features={n_features})")
        print("=========================================================")
        print(f"Время выполнения:       {elapsed_ms} ms")
        print(f"Число итераций:         {model.n_iter_}")
        print(f"Intercept (Истинный 5): {model.intercept_:.5f}")
        print(f"Alpha (Точность шума):  {model.alpha_:.5f}")
        print(f"MSE предсказания:       {mse:.5f}")
        print(f"Log Evidence (Marginal Likelihood): {model.scores_[-1]:.5f}")
    
        # Вывод динамики целевой функции по итерациям
        if show_score_history:
            scores = model.scores_
            n_scores = len(scores)
            print("\n--- Динамика Log Evidence по итерациям ---")
            if n_scores <= 10:
                for it, sc in enumerate(scores):
                    print(f"Итерация {it+1:02d} | Log Evidence: {sc:12.5f}")
            else:
                # Показываем первые 5 и последние 3 итерации
                for it in range(5):
                    print(f"Итерация {it+1:02d} | Log Evidence: {scores[it]:12.5f}")
                print("  ...")
                for it in range(n_scores - 3, n_scores):
                    print(f"Итерация {it+1:02d} | Log Evidence: {scores[it]:12.5f}")
    
        print("\n--- Стандартное отклонение предсказаний (y_std, первые 5 сэмплов) ---")
        for i in range(min(5, n_samples)):
            print(f"Сэмпл {i:02d} | y_pred: {y_pred[i]:8.5f} | y_std: {y_std[i]:8.5f}")
    
        print("\n--- Веса coef_ (Истинные: X00=3.5, X01=-2.0, X02=1.2, остальные=0) ---")
        for j in range(min(5, n_features)):
            print(f"X{j:02d} | Оценка: {model.coef_[j]:8.5f} | Lambda: {model.lambda_[j]:11.2f}")
    
        print(f"\nОсталось активных признаков с весом > 0: {active_count} из {n_features}\n")
    
    # 1. ГЕНЕРАЦИЯ ДАТАСЕТА (200 x 50)
    np.random.seed(42)
    
    n_samples = 200
    n_features = 50
    
    X = np.random.randn(n_samples, n_features)
    
    true_w = np.zeros(n_features)
    true_w[0] = 3.5
    true_w[1] = -2.0
    true_w[2] = 1.2
    
    # Вычисляем истинный сигнал 
    y_true = np.dot(X, true_w) + 5.0
    
    noise = np.random.normal(0, 0.5, size=n_samples)
    y = y_true + noise
    
    # ТЕСТ 1: FULL DATASET (200 x 50) -> N > D
    t0 = time.perf_counter()
    model = ARDRegression(compute_score=True).fit(X, y)
    elapsed = int((time.perf_counter() - t0) * 1000)
    RunARDTest("ТЕСТ 1 [SKLEARN]: FULL DATASET (200x50)", model, X, y, elapsed)
    
    # ТЕСТ 2: SUBSET DATASET (40 x 50) -> N < K (Срез первых 40 строк)
    X_sub = X[:40, :].copy()
    y_sub = y[:40].copy()
    t0 = time.perf_counter()
    model_sub = ARDRegression(compute_score=True).fit(X_sub, y_sub)
    elapsed_sub = int((time.perf_counter() - t0) * 1000)
    RunARDTest("ТЕСТ 2 [SKLEARN]: SUBSET DATASET (40x50)", model_sub, X_sub, y_sub, elapsed_sub)
    
    # FIGURE 1: LOG EVIDENCE LEARNING CURVES
    
    fig1, axes1 = plt.subplots(1, 2, figsize=(10, 5))
    
    # --- Plot 1: Full Dataset (200x50) ---
    iters1 = np.arange(1, len(model.scores_) + 1)
    axes1[0].plot(iters1, model.scores_, 'b-o', linewidth=2, markersize=4, label='Log Evidence')
    axes1[0].set_title('Learning Curve (Test 1: 200x50, N > D)', fontsize=11, fontweight='bold')
    axes1[0].set_xlabel('EM Iteration')
    axes1[0].set_ylabel('Log Evidence')
    axes1[0].grid(True, linestyle=':', alpha=0.6)
    axes1[0].legend()
    
    # --- Plot 2: Subset Dataset (40x50) ---
    iters2 = np.arange(1, len(model_sub.scores_) + 1)
    axes1[1].plot(iters2, model_sub.scores_, 'r-s', linewidth=2, markersize=4, label='Log Evidence')
    axes1[1].set_title('Learning Curve (Test 2: 40x50, N < D)', fontsize=11, fontweight='bold')
    axes1[1].set_xlabel('EM Iteration')
    axes1[1].set_ylabel('Log Evidence')
    axes1[1].grid(True, linestyle=':', alpha=0.6)
    axes1[1].legend()
    plt.tight_layout()
    
    # FIGURE 2: WEIGHT ESTIMATES vs TRUE WEIGHTS
    
    fig2, axes2 = plt.subplots(1, 2, figsize=(10, 5))
    x_features = np.arange(n_features)
    
    # --- Plot 1: Weight Estimates (200x50) ---
    axes2[0].stem(x_features, true_w, linefmt='b-', markerfmt='bo', basefmt='k-', label='True Weights')
    axes2[0].stem(x_features, model.coef_, linefmt='r--', markerfmt='rx', basefmt='k-', label='ARD Estimates')
    axes2[0].set_title('Weight Estimates (Test 1: 200x50, N > D)', fontsize=11, fontweight='bold')
    axes2[0].set_xlabel('Feature Index')
    axes2[0].set_ylabel('Weight Value')
    axes2[0].grid(True, linestyle=':', alpha=0.6)
    axes2[0].legend()
    
    # --- Plot 2: Weight Estimates (40x50) ---
    axes2[1].stem(x_features, true_w, linefmt='b-', markerfmt='bo', basefmt='k-', label='True Weights')
    axes2[1].stem(x_features, model_sub.coef_, linefmt='r--', markerfmt='rx', basefmt='k-', label='ARD Estimates')
    axes2[1].set_title('Weight Estimates (Test 2: 40x50, N < D)', fontsize=11, fontweight='bold')
    axes2[1].set_xlabel('Feature Index')
    axes2[1].set_ylabel('Weight Value')
    axes2[1].grid(True, linestyle=':', alpha=0.6)
    axes2[1].legend()
    plt.tight_layout()
    plt.show()

    Сопоставление показало полное совпадение результатов MQL5-реализации и scikit-learn с точностью до 5-го знака после запятой по всем ключевым метрикам: оценкам весов, апостериорной точности alpha, маргинальному правдоподобию (Log Evidence) и неопределенности предсказаний.

    На рис. 1 представлена динамика логарифма маргинального правдоподобия (Log Evidence) по итерациям EM-алгоритма.

    Learning Curve

    рис. 1 Кривая обучения ARD-регрессии 

    График наглядно иллюстрирует быструю сходимость модели: всего 10–15 итераций. 

    На рис. 2 представлено сравнение восстановленных весов w (красный пунктир) с истинными значениями весов wtrue (синяя линия), заданными при генерации данных.

    weight estimates

    рис. 2 Оценки весов ARD-регрессии

    Модель ARD верно определила 3 информативных признака, достаточно точно оценив коэффициенты как при полноразмерной выборке (200×50), так и в условиях дефицита данных (40×50). 

    Генерация признаков (Feature Engineering) и преодоление проблемы нелинейности

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

    Существует три основных подхода к решению этой задачи.

    • Явное преобразование признаков

    Самый простой способ — сформировать новые признаки вручную с помощью алгебраических и тригонометрических преобразований:

    • полиномиальные степени (x^2, x^3),
    • нелинейные преобразования (∣x∣, ln(x), exp(x)),
    • гармоники (sin(x), cos(x)),
    • парные произведения (xi * xj) и т.д.

    Главный недостаток подхода — проклятие размерности. При росте количества исходных признаков (D) число новых признаков растёт очень быстро. Например, при D=50 полиномиальное расширение второй степени уже даёт более 1300 комбинаций, а третьей степени — десятки тысяч признаков.

    • Ядерный трюк

    В классическом машинном обучении проблему проклятия размерности решают с помощью ядерного трюка (Kernel Trick). В таких моделях, как Kernel Ridge Regression, метод опорных векторов (SVM) или Гауссовские процессы (GP), скалярное произведение признаков в высокоразмерном пространстве заменяется функцией ядра k(x, x′).

    Самым популярным и универсальным является RBF-ядро (Radial Basis Function):

    rbf kernel

    где,

    • x, x′ ∈ RD — два вектора признаков,
    • ∥ x − x′ ∥^2 — квадрат евклидова расстояния между ними,
    • γ > 0 — параметр масштаба (гамма), определяющий ширину ядра и чувствительность к расстоянию между точками.

    Однако у классических ядерных методов есть серьезный недостаток — они требуют вычисления и хранения матрицы Грама NxN и её обращения, что требует O(N^3) операций. Это сильно ограничивает применимость метода на больших выборках.

    • Метод случайных признаков (Random Fourier Features)

    Эффективное решение предложил в 2007 году алгоритм Random Fourier Features (Rahimi & Recht). Вместо использования ядерной модели мы явно генерируем новые признаки, которые хорошо аппроксимируют RBF-ядро, и передаем их в обычную линейную модель.

    Аппроксимация RBF-ядра выполняется следующим образом:

    • Генерация случайных частот — семплируем M случайных векторов из нормального распределения, соответствующего RBF ядру N(0, 2γ),
    • Генерация случайных фаз — семплируем M случайных смещений из равномерного распределения Uniform(0, 2*pi),
    • Преобразование данных — для любой входной точки признаков x размерности D строится трансформированный вектор признаков φ(x) размерности M:

    RFF

    где,

    • x∈RD — вектор исходных признаков,
    • M — количество сгенерированных случайных признаков,
    • wj — веса частот,
    • bj — случайные фазы,
    • Sqrt(2/M) — нормировочный коэффициент.

    Преобразование cos(Wx + b) структурно похоже на формулу нейрона многослойного персептрона. Только вместо обучения весов W и смещений b через обратное распространение ошибки, мы однократно генерируем их из соответствующих распределений и фиксируем. Дальше обучение проходит на этих новых признаках с помощью любой линейной модели.

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

    Реализация генератора случайных признаков: класс CRBFSampler

    //+------------------------------------------------------------------+
    //| Класс генерации случайных признаков (Random Fourier Features)    |
    //| для RBF ядра                                                     |
    //+------------------------------------------------------------------+
    class CRBFSampler
      {
    private:
       double            m_gamma;           // Параметр ширины ядра (gamma)
       ulong             m_n_components;    // Количество генерируемых компонентов (M)
       int               m_seed;            // Seed (-1: случайная генерация, >=0: фиксированная)
    
       matrix            m_random_weights;  // Матрица весов W: (n_features x n_components)
       vector            m_random_offsets;  // Вектор смещений b: (n_components)
       bool              m_is_fitted;       // Флаг успешности генерации весов
    public:
                         CRBFSampler(const double gamma=1.0,
                   const ulong n_components=100,
                   const int seed=-1);
                        ~CRBFSampler(void) {}
    
       bool              Fit(const matrix &X);
       matrix            Transform(const matrix &X);
       matrix            FitTransform(const matrix &X);
       bool              IsFitted(void) const { return m_is_fitted; }
      };
    
    //+------------------------------------------------------------------+
    //| Конструктор                                                      |
    //+------------------------------------------------------------------+
    CRBFSampler::CRBFSampler(const double gamma=1.0,
                             const ulong n_components=100,
                             const int seed=-1)
       : m_gamma(gamma),
         m_n_components(n_components),
         m_seed(seed),
         m_is_fitted(false)
      {
    // --- Инициализация генератора ПСЧ
       if(m_seed != -1)
          MathSrand((uint)m_seed);
       else
          MathSrand(GetTickCount());
      }
    
    //+------------------------------------------------------------------+
    //| Генерация случайных признаков                                    |
    //+------------------------------------------------------------------+
    bool CRBFSampler::Fit(const matrix &X)
      {
       ulong n_features = X.Cols();
    
    //--- Генерация весов W ~ N(0, 2*gamma) с помощью класса CMVN
       vector Mu = vector::Zeros(m_n_components); // Вектор нулевых средних
       matrix Sigma = matrix::Identity(m_n_components, m_n_components);
       Sigma = Sigma * 2.0 * m_gamma;
    
    // Генерируем матрицу нормальных случайных величин размера [n_features x n_components]
       if(!CMVN::rnd(Mu, Sigma, (int)n_features, m_random_weights))
         {
          Print("Ошибка CRBFSampler::Fit: Не удалось сгенерировать веса через CMVN::rnd");
          return false;
         }
    
    //--- Генерация смещений b ~ Uniform(0, 2*PI)
       m_random_offsets = vector::Random(m_n_components, 0.0, 2.0 * M_PI);
    
       m_is_fitted = true;
       return true;
      }
    
    //+------------------------------------------------------------------+
    //| Преобразование матрицы признаков                                 |
    //+------------------------------------------------------------------+
    matrix CRBFSampler::Transform(const matrix &X)
      {
       if(!m_is_fitted)
         {
          Print("Ошибка CRBFSampler::Transform: Модель еще не обучена (вызовите Fit)!");
          matrix empty(0,0);
          return empty;
         }
    
       ulong rows = X.Rows();
    
    //--- 1. Проекция: Projection = X @ W (Размерность: N x M)
       matrix projection = X @ m_random_weights;
    
    //--- 2. Добавление смещения b (векторное сложение по строкам)
       for(ulong i = 0; i < rows; i++)
         {
          projection.Row(MathCos(projection.Row(i) + m_random_offsets), i);
         }
    
    //--- 3. Масштабирование: sqrt(2.0 / n_components)
       double scale = MathSqrt(2.0 / (double)m_n_components);
       projection = projection * scale;
    
       return projection;
      }
    
    //+------------------------------------------------------------------+
    //| Объединенный метод Fit + Transform                               |
    //+------------------------------------------------------------------+
    matrix CRBFSampler::FitTransform(const matrix &X)
      {
       if(!Fit(X))
         {
          matrix empty(0,0);
          return empty;
         }
       return Transform(X);
      }

    Для работы с алгоритмом Random Fourier Features был написан класс CRBFSampler. Он отвечает за генерацию случайных частот и фаз и преобразование исходной матрицы признаков из D-мерного пространства в M-мерное.


    В конструкторе задаются три основных параметра:

    • n_components (M) количество случайных признаков Фурье. Чем больше M, тем точнее аппроксимация RBF-ядра,
    • gamma (γ) — параметр ширины ядра,
    • seed — зерно генератора случайных чисел (для воспроизводимости экспериментов).

    При малом gamma (< 3) модель строит очень плавную, гладкую кривую. Она видит только общую тенденцию и игнорирует мелкие колебания. С увеличением параметра gamma модель строит извилистую, сильно колеблющуюся кривую, пытаясь подстроиться под каждую точку данных.


    Основные методы класса:

    • fit(const matrix &X)  генерирует матрицу случайных частот и вектор фазовых смещений,
    • transform(const matrix &X)  преобразует входную матрицу X размерности N×D в матрицу новых признаков размера N×M,
    • FitTransform(const matrix &X)  объединяет метод генерации весов и смещений и трансформации обучающей выборки X.

    Если перемножить полученную матрицу сгенерированных признаков X_rff на ее транспонированную (X_rff @ X_rff^T), то результатом вычисления скалярных произведений между всеми парами векторов будет приближенная матрица RBF-ядра (матрица сходства).

    Превращаем линейную ARD-регрессию в нелинейную модель

    Проверим на практике связку CRBFSampler и ARDRegression. Для  этого создадим учебный пример - скрипт ARD_RBF.mq5

    Для эксперимента сгенерируем выборку N=300 точек из сложной нелинейной функции: 

    nonlinear function

    В качестве входного признака использовались равномерно распределённые значения в диапазоне [-4, 4]. Исходный одномерный признак X_raw был преобразован в 400-мерное пространство с помощью CRBFSampler(gamma=2.5, n_components=400). Значение gamma=2.5 выбрано в качестве примера — в реальной работе его рекомендуется подбирать через кросс-валидацию.

    После преобразования полученная матрица передается в метод обучения модели ARD-регрессии ard.fit(). Из 400 случайных гармоник ARD отобрал всего 12 действительно значимых признаков, отбросив остальные 388 как бесполезные.

    Помимо точечного прогноза модель рассчитывает дисперсию, что позволяет построить 95%-й доверительный интервал (±2σ).

    На рис.3 показана аппроксимация нелинейной функции на обучающей выборке [-4,4], а также экстраполяция (прогноз) за пределы выборки [- 4.5, -4] & [4, 4.5]. Красной линией отмечено предсказание ARD-регрессии, голубой — истинная функция, серым пунктиром — границы 95%-го доверительного интервала.

    ARD RBF

    рис. 3 Аппроксимация нелинейной функции с помощью ARD-регрессии и RBF ядра


    Время обучения модели на MQL5 составило около 2.3 секунды, тогда как аналогичный код на Python выполняется за 2.8 секунды. Преимущество в скорости достигается за счёт использования функции LeastSquaresSolutionDC. В отличие от метода PInv() эта функция использует алгоритм Divide and Conquer (OpenBLAS/LAPACK). При этом решение системы A⋅X=I через LeastSquaresSolutionDC математически эквивалентно вычислению псевдообратной матрицы Мура — Пенроуза (A+) через SVD-разложение, что гарантирует точность и устойчивость вычислений на плохо обусловленных матрицах.

    После обучения CRBFSampler сохраняет внутри себя сгенерированные случайные веса W и смещения b. Поэтому для корректной работы необходимо:

    • один раз вызвать FitTransform() (или Fit()) на обучающей выборке,

    • для всех новых данных использовать только метод Transform().

    Повторный вызов FitTransform() приведёт к генерации новых случайных признаков, и веса обученной ARD-регрессии станут некорректными.

    Таким образом связка RBFSampler + ARDRegression превращает обычную линейную  регрессию в нелинейный аппроксиматор функций любой сложности. 


    Чтобы запустить скрипт scikit_learn.py, вам понадобится интерпретатор Python и стандартный стек статистических пакетов. О том, как их установить, рассказывается в статье Python + MetaTrader 5: быстрый исследовательский контур для данных, признаков и прототипов.

    После установки пакетов поместите файл scikit_learn.py в папку (например, Scripts/ARD).

    Запускайте скрипт через MetaEditor:

    • в MetaEditor нажмите Файл → Открыть и выберите файл scikit_learn.py из папки ARD,

    • после открытия файла нажмите клавишу F7 (или кнопку "Компилировать" на панели инструментов),

    • результаты вычислений должны появиться во вкладке Журнал.

    Заключение


    В этой статье мы подробно разобрали математический фундамент и практическую реализацию ARD-регрессии (Automatic Relevance Determination) на MQL5, а также расширили её возможности до нелинейного моделирования с помощью случайных признаков Фурье (CRBFSampler).

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

    Сочетание ARD-регрессии и Random Fourier Features позволяет аппроксимировать нелинейность ядерных методов (таких, как гауссовские процессы) при сохранении высокой скорости обучения и стабильности обычной линейной модели. Это делает ARD-регрессию особенно интересной для задач онлайн-обучения (адаптивного переобучения в скользящем окне).

    Кроме того, байесовский подход даёт возможность оценивать неопределённость прогноза и строить доверительные интервалы, что помогает принимать более обоснованные торговые решения.


    Программы, используемые в статье

    # Имя Тип Описание
    1 ARDRegression.mqh Включаемый файл Содержит класс модели ARD регрессии и класс генерации случайных признаков Фурье (CRBFSampler)
    2 scikit_learn.py Скрипт Пример обучения эталонной модели ARDregression из библиотеки scikit-learn
    3 TestARD.mq5 Скрипт Пример обучения MQL5 модели ARDregression
    4 X_ARD.csv CSV Признаки
    5 y_ARD.csv CSV Целевые значения
    6 ARD_RBF.mq5 Скрипт Пример обучения нелинейной модели ARD регрессии с помощью случайного преобразования признаков Фурье
    7 MQL5.zip Архив Все вышеперечисленные файлы можно найти в архиве

    Прикрепленные файлы |
    MQL5.zip (107.36 KB)
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
    Aleksey Nikolayev
    Aleksey Nikolayev | 7 авг. 2026 в 13:52

    Спасибо, интересно. Вероятностный байесовский подход в целом кажется более "родным" для рыночных данных, чем классическая эконометрика.

    Имхо, логичнее было бы сопоставлять с PLS-регрессией, чем с PCA.

    Evgeniy Chernish
    Evgeniy Chernish | 7 авг. 2026 в 17:27
    Aleksey Nikolayev #:

    Спасибо, интересно. Вероятностный байесовский подход в целом кажется более "родным" для рыночных данных, чем классическая эконометрика.

    Имхо, логичнее было бы сопоставлять с PLS-регрессией, чем с PCA.


    Спасибо за проявленный интерес к статье. 

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

    Про Pls регрессию вы правы, все таки это метод обучения с учителем в отличии от pca , просто я не был знаком с этой моделью, сейчас бегло посмотрел. 


    Изучение Стандартной библиотеки MQL5 (Часть 4): Создание библиотеки пользовательских сигналов Изучение Стандартной библиотеки MQL5 (Часть 4): Создание библиотеки пользовательских сигналов
    Сегодня мы используем стандартную библиотеку MQL5 для создания пользовательских классов сигналов и позволяем мастеру MQL5 собрать для нас профессиональный советник. Такой подход упрощает разработку, позволяя даже начинающим программистам создавать надежные советники без глубоких знаний в области программирования, сосредоточившись на настройке параметров и оптимизации стратегии. Давайте шаг за шагом разберем этот процесс.
    Автоматизация торговых стратегий в MQL5 (Часть 44): Обнаружение смены характера рынка (CHoCH) по пробоям свинговых экстремумов Автоматизация торговых стратегий в MQL5 (Часть 44): Обнаружение смены характера рынка (CHoCH) по пробоям свинговых экстремумов
    В этой статье мы разрабатываем на MQL5 систему обнаружения смены характера рынка (Change of Character, CHoCH), которая определяет свинговые максимумы и минимумы на заданном пользователем количестве баров, присваивает максимумам метки HH/LH, а минимумам — LL/HL для определения направления тренда и торгует пробои этих свинговых уровней при изменении структуры рынка, что указывает на возможный разворот.
    Автоматизация торговых стратегий в MQL5 (Часть 45): Инверсный разрыв справедливой стоимости (IFVG) Автоматизация торговых стратегий в MQL5 (Часть 45): Инверсный разрыв справедливой стоимости (IFVG)
    В этой статье мы создадим в MQL5 систему, которая обнаруживает разрывы справедливой стоимости (FVG) и отслеживает их переход в состояние инвертированного FVG (IFVG). Она выявляет бычьи и медвежьи FVG на последних сформированных барах с фильтрацией по минимальному размеру разрыва, отслеживает состояния FVG Normal, Mitigated и Inverted в зависимости от взаимодействия цены с зоной (митигация при пробое дальней границы, возврат цены в зону после митигации и инверсия фиксируется, когда после повторного входа цена закрывается за дальней границей, выходя изнутри зоны), игнорирует новые FVG, ценовые диапазоны которых перекрываются с уже существующими зонами, и ограничивает число отслеживаемых FVG.
    Нейросети в трейдинге: Долговременная память торговых сценариев (RAG-память) Нейросети в трейдинге: Долговременная память торговых сценариев (RAG-память)
    Статья продолжает адаптацию фреймворка VLADriver-RAG к задачам алгоритмической торговли и описывает переход от отдельной сценарной ячейки к ограниченной двухуровневой памяти. Рассматриваются добавление и консолидация завершённого опыта, многоэтапный GPU-поиск глобального Top-K и формирование RAG-токенов с параметрами действия, релевантностью и средним результатом. Память сохраняет опыт вне весов сети и возвращает Актёру компактный исторический контекст при предсказуемом времени чтения.