English
preview
Инжиниринг признаков для машинного обучения (Часть 2): Реализация дробного дифференцирования с фиксированным окном в MQL5

Инжиниринг признаков для машинного обучения (Часть 2): Реализация дробного дифференцирования с фиксированным окном в MQL5

MetaTrader 5Статистика и анализ |
38 0
Patrick Murimi Njoroge
Patrick Murimi Njoroge

Оглавление

  1. Введение
  2. Архитектурные ограничения при работе с потоком данных в реальном времени
  3. Класс CFFDEngine
  4. Вычисление весов в MQL5
  5. Вычисление для одного бара
  6. Пользовательский индикатор FFD
  7. Интеграция с экспертными советниками
  8. Проверка на соответствие Python-конвейеру обработки
  9. Вопросы производительности
  10. Заключение
  11. Список литературы
  12. Прикрепленные файлы


Введение

Часть 1 разобрала теорию и реализацию дробного дифференцирования на Python с использованием метода окна фиксированной ширины (FFD) из главы 5 AFML. Три свойства метода FFD делают его идеальным для работы с потоковыми данными в реальном времени: вектор весов предварительно вычисляется один раз, каждое наблюдение зависит от ограниченного окна истории, а вычисление сводится к одному скалярному произведению. В этой статье эти свойства переносятся в готовый к промышленному применению модуль на MQL5 для работы с потоком данных MetaTrader 5 в реальном времени.

Реализация состоит из двух компонентов: CFFDEngine (класс .mqh только в виде заголовочного файла для генерации весов и вычисления скалярного произведения) и FFD.mq5 (пользовательский индикатор, который оборачивает CFFDEngine и отображает дробно-дифференцированный ряд). Индикатор поддерживает оптимизацию MetaTrader prev_calculated и пересчитывает только то, что изменилось с момента предыдущего вызова.

Главная цель проектирования: нулевое выделение памяти при обработке тиков, O(width) на новый бар и амортизированная инициализация O(1) . На типичном инструменте при d = 0.4 и τ = 10−5, ширина окна, определяемая порогом, составляет 1457 баров. При τ = 10−4 она уменьшается до 281 бара. Скалярное произведение по такому окну выполняется на современном оборудовании за микросекунды.


Архитектурные ограничения при работе с потоком данных в реальном времени

MQL5 задает иную вычислительную модель, чем Python. Здесь нет NumPy, Numba и параллельных операций над массивами. Всё выполняется в одном потоке внутри процесса терминала. Эти ограничения определяют каждое проектное решение:

  1. Никакой динамической памяти при обработке тиков. В MQL5 функция ArrayResize относительно дорогая по сравнению с арифметикой. Массивы весов должны выделяться один раз в OnInit() и никогда не изменять размер при работе в реальном времени.
  2. Минимизировать CopyClose вызовы. Каждый CopyClose вызов требует IPC-обмена с сервером истории терминала. Мы запрашиваем ровно width + 1 баров на новый бар, не больше.
  3. Никаких избыточных вычислений логарифма. Для индикаторного варианта можно было бы заранее вычислить буфер логарифмов цен, но это добавило бы второй индикаторный буфер и усложнило реализацию. Поскольку MathLog обычно эффективно реализован на современном оборудовании, его вызов width + 1 раз на бар пренебрежимо дешев.
  4. prev_calculated оптимизация. MetaTrader вызывает OnCalculate на каждом тике, а не только на новых барах. Индикатор должен определять, изменилось ли число баров, и пропускать пересчет, если оно не изменилось.

Архитектура MQL5

Рисунок 1. Архитектура вычисления FFD в MQL5

  • OnInit(): один раз предварительно вычисляет вектор весов FFD и сохраняет его в статическом массиве. После этого шага дополнительных выделений памяти не происходит.
  • OnTick() / OnCalculate(): определяет формирование нового бара и получает минимальное окно истории через CopyClose().
  • Compute(): применяет логарифмическое преобразование (опционально) и вычисляет скалярное произведение вектора весов и ценового окна.
  • Кольцевой буфер: хранит последние width + 1 логарифмов цен для обновления за O(1) на бар при использовании схемы прямого встраивания в EA.
  • На выходе получается значение FFD, которое либо сохраняется в индикаторном буфере для отображения на графике, либо используется напрямую в торговой логике EA.


Класс CFFDEngine

Класс CFFDEngine предоставляет три метода: Init() (вызывается один раз), Compute() (вызывается на каждом баре в режиме EA) и ComputeBuffer() (вызывается на каждом тике в режиме индикатора). Всё внутреннее состояние (массив весов, ширина окна и параметры конфигурации) задается во время инициализации и затем не изменяется.

//+------------------------------------------------------------------+
//| FFDEngine.mqh — Fixed-Width Fractional Differentiation Engine    |
//| Copyright 2025, Patrick M. Njoroge                               |
//+------------------------------------------------------------------+

#ifndef FFDENGINE_MQH
#define FFDENGINE_MQH

class CFFDEngine
  {
private:
   double            m_weights[];       // reversed weight vector (oldest lag first)
   int               m_width;           // number of weights minus 1
   double            m_d;               // differentiation order
   double            m_threshold;       // weight cutoff τ
   bool              m_use_log;         // apply log transform
   bool              m_initialized;     // initialization flag

   void              BuildWeights(void);

   //--- ln(max(price, 1e-8)): matches Python's clip(lower=1e-8) before np.log
   double            SafeLog(double price) const
     {
      if(price<1e-8)
         price=1e-8;
      return(MathLog(price));
     }

public:
                     CFFDEngine(void) : m_d(0),m_threshold(1e-5),
                     m_use_log(true),m_width(0),
                     m_initialized(false) {}

   bool              Init(double d,double threshold=1e-5,
                          bool use_log=true);
   int               GetWidth(void)   const { return(m_width);       }
   int               GetMinBars(void) const { return(m_width+1);     }
   double            GetD(void)       const { return(m_d);           }
   bool              IsReady(void)    const { return(m_initialized); }

   //--- Single-bar: prices[0]=oldest, prices[count-1]=newest
   double            Compute(const double &prices[],int count);

   //--- Заполнение индикаторного буфера с оптимизацией prev_calculated
   int               ComputeBuffer(const double &prices[],
                                   double &buffer[],
                                   int total,int prev_calculated);
  };

#endif // FFDENGINE_MQH

Класс CFFDEngine полностью реализован в заголовочном файле. Отдельная .mq5 единица компиляции не требуется, поэтому любой EA или индикатор, который подключает FFDEngine.mqh получает полную реализацию, скомпилированную inline. Это устраняет проблемы версионирования библиотек и сводит установку к одному файлу. Используйте #include "FFDEngine.mqh" в файлах-потребителях. Затем компилятор ищет в MQL5\Include, поэтому заголовочный файл должен находиться там.


Вычисление весов в MQL5

Генерация весов в точности повторяет реализацию на Python: итеративное рекуррентное соотношение ωk = −ωk−1 · (d − k + 1) / k, завершается, когда |ωk| становится меньше порога. Результат разворачивается так, что m_weights[0] соответствует самому старому наблюдению, а m_weights[m_width] соответствует самому свежему наблюдению в окне.

void CFFDEngine::BuildWeights(void)
  {
//--- doubling-growth strategy: start at capacity 512, double when full.
//--- this avoids an ArrayResize call on every iteration.
   int    capacity=512;
   double temp[];
   ArrayResize(temp,capacity);
   temp[0]=1.0;
   int n=1;

   for(int k=1; ; k++)
     {
      double w_next=-temp[n-1]*(m_d-(double)k+1.0)/(double)k;
      if(MathAbs(w_next)<m_threshold)
         break;   // mirrors Python: "if abs(weights_) < thres: break"
      if(n>=capacity)
        {
         capacity*=2;
         ArrayResize(temp,capacity);
        }
      temp[n]=w_next;
      n++;
     }

//--- trim to exact size, then reverse into m_weights.
//--- m_weights[0]   = smallest |w|, multiplies oldest price.
//--- m_weights[n-1] = 1.0,          multiplies newest price.
   ArrayResize(temp,n);
   m_width=n-1;
   ArrayResize(m_weights,n);
   for(int i=0; i<n; i++)
      m_weights[i]=temp[n-1-i];
  }

Фиксированного потолка для числа итераций нет. Цикл завершается, когда |ωk| падает ниже порога, ровно как в Python-функции get_weights_ffd(). Фиксированное ограничение опасно: оно может незаметно обрезать вектор весов и привести к значениям FFD, которые больше не совпадают с Python-конвейером обработки. Ограничение в 1000 элементов обрежет веса и исказит результат при d < 0.5 и τ = 10−5 , без ошибки или предупреждения. Буфер с удвоением емкости требует O(log N) вызовов выделения памяти вместо O(N) и перед разворотом обрезается до точного размера.

Метод Init() проверяет параметры, вызывает BuildWeights() и выводит конфигурацию в журнал:

bool CFFDEngine::Init(double d,double threshold=1e-5,
                      bool use_log=true)
  {
   if(d<0.0 || d>2.0)
     {
      PrintFormat("CFFDEngine::Init — d must be in [0, 2], got %.4f",d);
      return(false);
     }
   if(threshold<=0.0)
     {
      Print("CFFDEngine::Init — threshold must be positive");
      return(false);
     }

   m_d=d;
   m_threshold=threshold;
   m_use_log=use_log;

   BuildWeights();
   m_initialized=(m_width>0);

   if(m_initialized)
      PrintFormat("CFFDEngine: d=%.4f  threshold=%.2e  width=%d  min_bars=%d  use_log=%s",
                  m_d,m_threshold,m_width,GetMinBars(),
                  m_use_log ? "true" : "false");
   else
      Print("CFFDEngine::Init — no weights generated (d too small?)");

   return(m_initialized);
  }


Вычисление для одного бара

Метод Compute() — сердце движка. Он принимает массив цен и возвращает значение FFD для самого последнего бара. Реализация — прямое скалярное произведение: без ветвлений, без выделения памяти и без вызовов функций, кроме SafeLog:

double CFFDEngine::Compute(const double &prices[],int count)
  {
   if(!m_initialized || count<m_width+1)
      return(EMPTY_VALUE);

//--- use the last (m_width + 1) prices.
   int start=count-m_width-1;

   double result=0.0;
   for(int i=0; i<=m_width; i++)
     {
      double val=prices[start+i];
      if(m_use_log)
         val=SafeLog(val);   // ln(max(price, 1e-8)) — matches Python clip
      result+=m_weights[i]*val;
     }

   return(result);
  }

Цикл выполняется ровно m_width + 1 раз. Для d = 0.4 при τ = 10−5 ширина окна равна 1457, поэтому для каждого нового бара требуется 1458 операций умножения-сложения, что значительно меньше одной миллисекунды. SafeLog применяет нижнюю границу 10−8 перед вызовом MathLog, что точно соответствует Python-варианту clip(lower=1e-8). Для реальных ценовых данных нижняя граница никогда не достигается; она нужна только для обеспечения численно идентичных результатов перекрестной проверки относительно Python-конвейера обработки.

Буферный вариант для индикаторов следует той же логике, но добавляет оптимизацию prev_calculated:

int CFFDEngine::ComputeBuffer(const double &prices[],
                              double &buffer[],
                              int total,int prev_calculated)
  {
   if(!m_initialized)
      return(0);

//--- determine starting bar
   int start;
   if(prev_calculated>m_width)
      start=prev_calculated-1;  // recompute only current bar
   else
     {
      //--- full computation: mark lookback bars as empty
      for(int i=0; i<m_width && i<total; i++)
         buffer[i]=EMPTY_VALUE;
      start=m_width;
     }

//--- main computation loop
   for(int i=start; i<total; i++)
     {
      double result=0.0;

      for(int k=0; k<=m_width; k++)
        {
         double val=prices[i-m_width+k];
         if(m_use_log)
            val=SafeLog(val);   // ln(max(price, 1e-8))
         result+=m_weights[k]*val;
        }

      buffer[i]=result;
     }

   return(total);
  }

Оптимизация на основе prev_calculated — ключевой механизм эффективности. При первом вызове (prev_calculated == 0) функция вычисляет значения FFD для каждого бара, начиная с m_width — это единоразовая стоимость для всей истории. При последующих вызовах prev_calculated отражает число обработанных баров. На практике цикл обрабатывает не более двух баров: последний закрытый бар и вновь сформированный бар. Это означает, что индикатор обрабатывает каждый входящий тик за O(width) времени, а не за O(total × width).


Пользовательский индикатор FFD

Индикатор оборачивает CFFDEngine и отображает ряд FFD в отдельном подокне. Он использует вторую форму OnCalculate, которая принимает один массив price[], выбранный входным параметром ENUM_APPLIED_PRICE. Это позволяет индикатору обрабатывать любую применяемую цену (close, open, high, low, median, typical или weighted) без изменений кода.

//+------------------------------------------------------------------+
//| FFD.mq5 — Fixed-Width Fractional Differentiation Indicator       |
//| Copyright 2025, Patrick M. Njoroge                               |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_label1  "FFD"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

//--- inputs
input double             InpD         = 0.4;         // Differentiation order d
input double             InpThreshold = 1e-5;        // Weight cutoff τ
input bool               InpUseLog    = true;        // Log-transform prices
input ENUM_APPLIED_PRICE InpPrice     = PRICE_CLOSE; // Applied price

#include "FFDEngine.mqh"

double     FFDBuffer[];
CFFDEngine g_engine;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   SetIndexBuffer(0,FFDBuffer,INDICATOR_DATA);
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);

   if(!g_engine.Init(InpD,InpThreshold,InpUseLog))
     {
      Print("FFD indicator: engine initialization failed");
      return(INIT_FAILED);
     }

//--- hide bars before the lookback window is filled
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,g_engine.GetWidth());

//--- display name in chart window
   string short_name=StringFormat("FFD(%.2f, τ=%.0e)",
                                  InpD,InpThreshold);
   IndicatorSetString(INDICATOR_SHORTNAME,short_name);
   IndicatorSetInteger(INDICATOR_DIGITS,6);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   return(g_engine.ComputeBuffer(price,FFDBuffer,
                                 rates_total,prev_calculated));
  }

Вся логика индикатора — это одна строка в OnCalculate, а всё остальное — конфигурация. Это результат инкапсуляции вычисления в CFFDEngine: индикатор представляет собой тонкий слой визуализации, а тот же модуль можно повторно использовать в EA, скриптах и многоиндикаторных фреймворках без повторения кода.

Использование индикатора из EA

EA, которому нужно значение FFD для конкретного символа и таймфрейма, может вызвать индикатор через iCustom:

int ffd_handle;

int OnInit(void)
  {
   ffd_handle=iCustom(_Symbol,_Period,"FFD",
                      0.4,          // InpD
                      1e-5,         // InpThreshold
                      true,         // InpUseLog
                      PRICE_CLOSE); // InpPrice

   if(ffd_handle==INVALID_HANDLE)
     {
      Print("Failed to create FFD indicator");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

void OnTick(void)
  {
   double ffd_val[];
   if(CopyBuffer(ffd_handle,0,0,1,ffd_val)<1)
      return;

//--- ffd_val[0] is the FFD value of the current (forming) bar
   if(ffd_val[0]==EMPTY_VALUE)
      return;

//--- use ffd_val[0] in trading logic...
  }

Этот подход делегирует все вычисления индикаторному процессу, который выполняется только один раз независимо от того, сколько EA запрашивают тот же буфер. Если несколько EA торгуют один и тот же символ на одном таймфрейме с одинаковым d, они совместно используют один экземпляр индикатора: MetaTrader автоматически переиспользует экземпляр индикатора.


Интеграция с экспертными советниками

Для EA, которым нужен прямой контроль или которые работают с символами и таймфреймами, не совпадающими с графиком, модуль можно встроить напрямую. EA вызывает CopyClose на каждом новом баре и передает результат в Compute():

#include "FFDEngine.mqh"

CFFDEngine g_ffd;
datetime   g_last_bar=0;

input double InpD         = 0.4;
input double InpThreshold = 1e-5;

int OnInit(void)
  {
   if(!g_ffd.Init(InpD,InpThreshold,true))
      return(INIT_FAILED);

   return(INIT_SUCCEEDED);
  }

void OnTick(void)
  {
//--- new bar detection
   datetime current_bar=iTime(_Symbol,_Period,0);
   if(current_bar==g_last_bar)
      return;
   g_last_bar=current_bar;

//--- retrieve prices
   int bars_needed=g_ffd.GetMinBars();
   double prices[];
   if(CopyClose(_Symbol,_Period,1,bars_needed,prices)<bars_needed)
      return;

//--- compute FFD value for the last closed bar
   double ffd_value=g_ffd.Compute(prices,bars_needed);
   if(ffd_value==EMPTY_VALUE)
      return;

//--- trading logic uses ffd_value as a feature
   ProcessSignal(ffd_value);
  }

На две детали реализации стоит обратить внимание. Во-первых, CopyClose(_Symbol, _Period, 1, bars_needed, prices) начинает копирование с индекса бара 1 (самого последнего закрытого бара), а не с индекса бара 0 (текущего формирующегося бара). Это исключает вычисление FFD на незавершенном баре с изменяющейся ценой закрытия. Иначе может возникнуть смещение из-за заглядывания в будущее (look-ahead bias) при принятии решений на закрытии бара.

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

FFD для нескольких признаков

В полноценном ML-конвейере EA может потребоваться дробно дифференцировать несколько признаков (цену close, VWAP, volume-weighted close и т. д.), каждый из которых потенциально имеет собственное оптимальное d*. Движок поддерживает это за счет создания нескольких экземпляров CFFDEngine объектов:

CFFDEngine g_ffd_close;     // d* = 0.40 for close price
CFFDEngine g_ffd_volume;    // d* = 0.25 for cumulative volume

int OnInit(void)
  {
   if(!g_ffd_close.Init(0.40,1e-5,true))
      return(INIT_FAILED);
   if(!g_ffd_volume.Init(0.25,1e-5,false))
      return(INIT_FAILED);

   return(INIT_SUCCEEDED);
  }

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


Проверка на соответствие Python-конвейеру обработки

Перед использованием модуль на MQL5 должен давать численно идентичные результаты с Python-конвейером обработки на одних и тех же входных данных. Процедура валидации такова:

  1. Экспортировать ценовой ряд из MetaTrader в CSV с помощью скрипта.
  2. Запустить Python-функцию frac_diff_ffd на ценах из CSV с теми же настройками d, τ и use_log.
  3. Запустить MQL5-класс CFFDEngine на тех же ценах (либо через скрипт, записывающий результаты в CSV, либо сравнив значения индикаторного буфера с выводом Python).
  4. Вычислить максимальное абсолютное расхождение по всем барам. Максимальное абсолютное расхождение должно быть ниже 10−12.

Следующий MQL5-скрипт автоматизирует экспорт и вычисление для валидации:

//+------------------------------------------------------------------+
//| FFDValidation.mq5 — Export FFD values for cross-validation       |
//+------------------------------------------------------------------+
#include "FFDEngine.mqh"

input double InpD         = 0.4;
input double InpThreshold = 1e-5;
input int    InpBars      = 5000;

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart(void)
  {
   CFFDEngine engine;
   if(!engine.Init(InpD,InpThreshold,true))
      return;

   double close[];
   int copied=CopyClose(_Symbol,_Period,0,InpBars,close);
   if(copied<engine.GetMinBars())
     {
      PrintFormat("Not enough bars: got %d, need at least %d",
                  copied,engine.GetMinBars());
      return;
     }

//--- ensure chronological order (oldest first)
   ArraySetAsSeries(close,false);

//--- compute FFD for all bars
   double ffd_buffer[];
   ArrayResize(ffd_buffer,copied);
   engine.ComputeBuffer(close,ffd_buffer,copied,0);

//--- write to CSV
   int file=FileOpen("ffd_validation.csv",FILE_WRITE|FILE_CSV,",");
   if(file==INVALID_HANDLE)
     {
      Print("Cannot open file");
      return;
     }

   FileWrite(file,"bar_index","close","ffd");
   for(int i=0; i<copied; i++)
     {
      if(ffd_buffer[i]!=EMPTY_VALUE)
         FileWrite(file,i,DoubleToString(close[i],8),
                   DoubleToString(ffd_buffer[i],12));
     }

   FileClose(file);
   PrintFormat("Validation file written: %d bars",copied);
  }

Соответствующая проверка на Python читает оба CSV-файла и сообщает расхождение:

import pandas as pd
import numpy as np
from afml.features.fracdiff import frac_diff_ffd

# Load MQL5 output
mql5 = pd.read_csv("ffd_validation.csv")

# Reconstruct the same computation in Python
prices = pd.Series(mql5["close"].values, name="close")
py_ffd = frac_diff_ffd(prices, d=0.4, thres=1e-5, use_log=True)

# Align indices and compare
mql5_ffd = mql5["ffd"].values
py_ffd_vals = py_ffd.values

max_diff = np.max(np.abs(mql5_ffd[:len(py_ffd_vals)] - py_ffd_vals))
print(f"Max absolute difference: {max_diff:.2e}")
assert max_diff < 1e-12, "VALIDATION FAILED"
print("VALIDATION PASSED")

Если максимальное расхождение превышает 10−12, две самые частые причины — обрезанный вектор весов и ошибка порядка массива. CopyClose записывает данные в соответствии с флагом ArraySetAsSeries целевого массива. Если другая часть EA изменит этот флаг, порядок массива может незаметно развернуться. Всегда вызывайте ArraySetAsSeries(close, false) в качестве меры предосторожности перед передачей массива в ComputeBuffer.


Вопросы производительности

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

Когда вычислять

Самая важная оптимизация не вычислительная, а временная: вычислять FFD только при формировании нового бара, а не на каждом тике. График M1 на активной валютной паре получает 5–50 тиков в секунду. Вычисление FFD на каждом тике расходует CPU и дает нестабильные значения (close текущего бара меняется с каждым тиком). Показанная выше схема определения нового бара полностью устраняет эту проблему.

Для индикатора MetaTrader обрабатывает это автоматически через prev_calculated. На последующих тиках внутри того же бара prev_calculated равен rates_total, а тело цикла ComputeBuffer выполняется ровно один раз (чтобы обновить значение формирующегося бара). Это приемлемо для визуального отображения, где пользователь ожидает видеть обновление индикатора в реальном времени.

Размещение в памяти

Массивы MQL5 непрерывны в памяти. Внутренний цикл скалярного произведения обращается к prices[] и m_weights[] последовательно, что оптимально для кэш-линий CPU. Особые соображения по размещению не нужны: естественный паттерн доступа уже дружественен к кэшу.

Ветвление в горячем цикле

Метод if(m_use_log) внутри цикла скалярного произведения добавляет ветвление на каждой итерации. Для максимальной пропускной способности можно разделить Compute() на версии с логарифмированием и без него и выбрать функцию в Init(). На практике предсказатель ветвлений быстро сойдется, потому что условие неизменно внутри вызова, поэтому накладные расходы фактически равны нулю. Ясность кода одной функции перевешивает теоретическое наносекундное улучшение.

Численная точность

MQL5 использует арифметику двойной точности IEEE 754 (64 бита), такую же, как Python float64. Скалярное произведение накапливает width + 1 слагаемых. Для ширины от сотен до нескольких тысяч накопленная ошибка с плавающей точкой имеет порядок 10−13 до 10−14, что намного ниже любой практической значимости. Компенсированное суммирование (Кэхэна) не требуется.

Параметр Типичное значение Влияние
d 0.3 – 0.5 Управляет компромиссом между стационарностью и памятью
τ (порог) 10−5 – 10−4 Основной фактор ширины окна; уменьшение τ вдвое примерно удваивает ширину
Ширина (бары) 200 – 4000 Задается d и τ. При τ = 10−5: от 926 (d = 0.5) до 4075 (d = 0.1). При τ = 10−4: от 199 до 650.
Время вычисления на бар < 1 ms Масштабируется линейно с шириной. При width = 1457 скалярное произведение выполняется значительно меньше чем за миллисекунду, что пренебрежимо мало относительно задержки CopyClose.
Память на один экземпляр модуля < 40 KB Массив весов (8 байт × width) + переменные состояния. При width = 4075: ~33 КБ.

Таблица 1. Характеристики производительности CFFDEngine для типичных значений параметров


Заключение

Реализация на MQL5 сохраняет математические свойства, установленные в сопутствующей статье, и при этом адаптируется к ограничениям торговой среды реального времени. Вектор весов вычисляется один раз в цикле, управляемом порогом, без фиксированного ограничения; вычисление на бар — это ограниченное скалярное произведение; индикатор интегрируется с оптимизацией prev_calculated в MetaTrader.

Поддерживаются три схемы использования. Пользовательский индикатор (FFD.mq5) обеспечивает визуальную обратную связь и служит общим источником данных для нескольких EA через дескрипторы iCustom. Шаблон прямого встраивания использует CFFDEngine внутри EA для полного контроля над временем вычислений и выбором цены. Шаблон нескольких признаков создает отдельные движки для каждого признака, которому требуется дробное дифференцирование, при этом каждый экземпляр модуля хранит собственный d* и вектор весов.

Скрипт валидации подтверждает численную эквивалентность Python-конвейеру обработки. После проверки значения d*, определенные с помощью fracdiff_optimal в Python, можно жестко задать как входные параметры EA, замыкая цикл между офлайн-исследованием и работой с потоковыми данными в реальном времени. По мере развития конвейера обработки будущие статьи интегрируют модуль FFD в полную цепочку построения признаков и вывода модели внутри фреймворка Expert Advisor.


Список литературы

  1. Лопес де Прадо, М. (2018). Advances in Financial Machine Learning. Wiley. Глава 5: Дробно дифференцированные признаки.
  2. Хоскинг, J. R. M. (1981). «Fractional differencing». Biometrika, 68(1), 165–176.
  3. Справочник MQL5: CopyBuffer, CopyClose, OnCalculate.


Прикрепленные файлы

Файл Поместить в Описание
FFDEngine.mqh MQL5\Include Класс CFFDEngine: генерация весов, вычисление для одного бара, заполнение индикаторного буфера.
FFD.mq5 MQL5\Indicators Пользовательский индикатор, отображающий ряд FFD в отдельном окне
FFDValidation.mq5 MQL5\Scripts Скрипт валидации, экспортирующий значения FFD для сверки с Python
ffd_cross_validate.py Каталог проекта Читает CSV, экспортированный FFDValidation.mq5, повторно вычисляет значения FFD в Python через frac_diff_ffd с теми же параметрами d, threshold и log, а также сообщает максимальное абсолютное расхождение по барам. Требуются NumPy и Pandas. Определяет d из имени файла.

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

Прикрепленные файлы |
FFDEngine.mqh (10.63 KB)
FFD.mq5 (3.38 KB)
FFDValidation.mq5 (4.87 KB)
Нейросети в трейдинге: от рыночного шума к устойчивому торговому плану (MomAD) Нейросети в трейдинге: от рыночного шума к устойчивому торговому плану (MomAD)
В статье рассматривается адаптация идей MomAD к задачам нейросетевого трейдинга. Основное внимание уделено проблеме нестабильности торговых решений, когда модель слишком часто меняет сценарий и разрушает прибыльный план. Описаны теоретические основы Momentum-Aware Planning, расстояния Хаусдорфа и их перенос в латентное пространство рыночных состояний. В практической части реализован базовый OpenCL-механизм оценки расхождения между сценариями.
Моделирование рынка: Первые шаги на SQL в MQL5 (V) Моделирование рынка: Первые шаги на SQL в MQL5 (V)
В предыдущей статье я показал, как следовало действовать для добавления механизма запросов. Это было нужно для того, чтобы внутри кода MQL5 вы могли полноценно использовать SQL и получать результаты при выполнении команды SQL SELECT FROM. Но осталось рассказать последнюю функцию, которую нам необходимо реализовать. Это функция DatabaseReadBind. И, поскольку для правильного понимания требуется чуть более развернутое объяснение, было решено сделать это не в той предыдущей статье, а в сегодняшней. Итак, поскольку тема будет довольно объемной, перейдём сразу к следующему разделу.
Торговые инструменты MQL5 (Часть 24): Улучшение восприятия глубины с помощью 3D-кривых, режима панорамирования и навигации через виджет ViewCube Торговые инструменты MQL5 (Часть 24): Улучшение восприятия глубины с помощью 3D-кривых, режима панорамирования и навигации через виджет ViewCube
В этой статье мы улучшим инструмент построения 3D-графиков биномиального распределения в MQL5, добавим сегментированную 3D-кривую для улучшения восприятия глубины функции массы вероятности. Также интегрируем режим панорамирования для смещения целевой точки камеры и реализуем интерактивный куб обзора с зонами наведения курсора и анимацией для обеспечения быстрой смены ориентации. Мы добавим кликабельные подзоны на кубе обзора для граней, ребер и углов, чтобы анимировать переходы камеры к стандартным видам, сохраняя при этом переключаемые 2D/3D режимы, обновления в реальном времени и настраиваемые параметры для иммерсивного вероятностного анализа в торговле.
Торговые инструменты MQL5 (Часть 23): Трёхмерные графики с управляемой камерой и поддержкой DirectX для анализа распределений Торговые инструменты MQL5 (Часть 23): Трёхмерные графики с управляемой камерой и поддержкой DirectX для анализа распределений
В этой статье мы усовершенствовали инструмент построения графиков биномиального распределения в MQL5, интегрировав DirectX для 3D-визуализации, что позволило переключаться между 2D и 3D режимами с управляемым камерой поворотом, масштабированием и автоматическим подбором положения камеры для иммерсивного анализа. Мы визуализируем столбцы гистограммы в 3D, опорные плоскости и оси наряду с кривой функции вероятностной массы, сохраняя при этом 2D-элементы, такие как панели статистики, легенда и настраиваемые темы, градиенты и метки.