English
preview
Конструирование признаков для машинного обучения (Часть 6): Признаки микроструктуры рынка в MQL5

Конструирование признаков для машинного обучения (Часть 6): Признаки микроструктуры рынка в MQL5

MetaTrader 5 — Торговые системы |
49 0
Patrick Murimi Njoroge
Patrick Murimi Njoroge

Содержание

  1. Введение
  2. Что на самом деле предоставляет MetaTrader 5: тиковый объем в сравнении с реальными тиковыми данными
  3. Архитектура: CMicrostructureFeatures
  4. Спред Ролла и ценовое воздействие по Роллу
  5. Спред Корвина — Шульца и внутридневная волатильность
  6. Лямбда Кайла
  7. Показатель неликвидности Амихуда (ILLIQ)
  8. Лямбда Хасбрука
  9. Тиковое правило на уровне баров
  10. Интеграция с советником
  11. Визуализация признаков: средство просмотра микроструктуры рынка
  12. Проверка
  13. Согласованность численных результатов Python и MQL5
  14. Заключение:
  15. Прикрепленные файлы
  16. Список литературы


Введение

В части 5 этой серии на Python был реализован полный набор признаков микроструктуры рынка на уровне баров, описанный в главе 19 книги AFML. В ней использовались вычислительные ядра, ускоренные с помощью Numba, и двухслойная архитектура, отделяющая вычисления по данным OHLCV от вычислений по потоку тиков. В этой статье слой расчетов на уровне баров переносится на MQL5 в виде единого подключаемого файла CMicrostructureFeatures.mqh, который любой советник может использовать во время выполнения без зависимости от Python. Кроме того, в комплект входит индикатор в отдельном окне MicrostructureViewer.mq5, который в реальном времени отображает семь признаков для сопоставления с ценой.

Перенос охватывает все пять семейств оценок: спред Ролла и ценовое воздействие по Роллу (1984), спред Корвина — Шульца и внутридневную волатильность по Корвину — Шульцу (2012), лямбду Кайла (1985), показатель неликвидности Амихуда (ILLIQ) (2002) и лямбду Хасбрука (2009). Эти признаки можно рассчитать, используя только данные баров OHLCV. Перенос слоя расчетов на уровне тиков из части 5 отложен. Для расчета лямбд по каждому бару (с t-статистиками) и энтропийных признаков требуются исходные тиковые данные. API тикового объема в MetaTrader 5 не соответствует допущениям реализации на Python, поэтому это несоответствие требует отдельного рассмотрения.

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


Что на самом деле предоставляет MetaTrader 5: тиковый объем в сравнении с реальными тиковыми данными

Реализация на Python из части 5 предполагает доступ к потоку реальных тиковых данных: отдельным сделкам с указанием цены, объема и временной метки. Для оценок лямбды объем со знаком рассчитывается с помощью тикового правила, применяемого к отдельным тикам, а не к ценам закрытия баров. Функция CopyTickVolume() в MQL5 таких данных не предоставляет. Она возвращает количество изменений цены внутри бара, а не количество отдельных сделок. У брокера, который агрегирует поступающие заявки перед публикацией обновлений цен, одна крупная сделка может регистрироваться как один тик, а серия мелких сделок — как множество тиков. Связь между тиковым объемом и фактическим количеством сделок зависит от брокера и не раскрывается.

Для оценок лямбды это имеет вполне конкретное значение. И лямбда Кайла, и лямбда Хасбрука оцениваются с помощью регрессии изменений цены на объем со знаком, который представляет собой произведение направления сделки и ее объема. В реализации на MQL5 на уровне баров направление сделки определяется с помощью тикового правила по ценам закрытия баров (bt = +1, если closet > closet-1; −1, если closet < closet-1; 0 в остальных случаях), а в качестве объема используется тиковый объем. И то, и другое — приблизительные оценки.

На практике это означает, что значения лямбды, полученные в реализации на MQL5, следует рассматривать как сигналы о смене рыночного режима, а не как количественные оценки ценового воздействия. Рост лямбды Кайла во время тренда по-прежнему указывает на увеличение ценового воздействия на единицу потока заявок, даже если абсолютные значения у разных брокеров несопоставимы. Спред Ролла и спред Корвина — Шульца вообще не используют объем; их значения не зависят от брокера и напрямую сопоставимы с результатами, полученными в Python.


Архитектура: CMicrostructureFeatures

CMicrostructureFeatures — поток данных

Рисунок 1. Схема потока данных класса CMicrostructureFeatures из четырех панелей

  • Объединение потоков (синий → бирюзовый): все пять функций получения исторических данных MetaTrader 5 — CopyOpen, CopyHigh, CopyLow, CopyClose и CopyTickVolume — сходятся в методе _CopyBars(), который заполняет массивы исходных данных OHLCV. Если вызов CopyTickVolume завершается с ошибкой, в качестве резервного варианта выполняется попытка вызова CopyRealVolume.
  • Разветвление потоков (бирюзовый → бирюзовый): метод _CopyBars() формирует три общих массива — close, high/low и volume, — которые параллельно считывают все три метода оценки. Стрелки независимы друг от друга; ни один метод оценки не вызывает другой.
  • Соответствие методов оценки и методов доступа (бирюзовый → зеленый): каждый метод оценки записывает результаты только в отведенные ему поля. _ComputeRoll() записывает значения в RollMeasure и RollImpact; _ComputeCS() записывает значения в CSSpread и CSSigma; _ComputeLambdas() записывает значения в KyleLambda, AmihudLambda и HasbrouckLambda. Порядок методов доступа в классе соответствует этим трем группам.
  • Вспомогательные методы (фиолетовый цвет, пунктирные линии): _TickRule() и _OLS() — приватные методы, вызываемые только методом _ComputeLambdas(). Пунктирная стрелка "используется" отличает их от основного пути передачи данных: они получают данные не напрямую от _CopyBars(), а через массивы, которые метод _ComputeLambdas() формирует внутри себя.

Создайте экземпляр класса, указав символ, временной интервал и размер скользящего окна. Вызывайте метод Calculate(start_bar, n_bars) один раз при появлении каждого нового бара. Все вычисления выполняются внутри метода Calculate(); методы доступа предназначены только для чтения.

#include <Features\CMicrostructureFeatures.mqh>

//--- In OnInit():
CMicrostructureFeatures *g_micro = new CMicrostructureFeatures(_Symbol, _Period, 20);

//--- In OnCalculate() or new-bar handler:
if(g_micro.Calculate(1, 100))
  {
   double roll   = g_micro.RollMeasure(0);
   double cs     = g_micro.CSSpread(0);
   double cs_sig = g_micro.CSSigma(0);
   double kyle   = g_micro.KyleLambda(0);
   double amihud = g_micro.AmihudLambda(0);
   double hbck   = g_micro.HasbrouckLambda(0);
   //--- features are valid if > MICRO_EMPTY + 1.0
  }

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


Спред Ролла и ценовое воздействие по Роллу

В модели Ролла (1984) эффективный спред между ценами покупки и продажи выводится из серийной ковариации последовательных изменений цены. Внутри _ComputeRoll() ковариация рассчитывается за один проход с использованием пяти накапливаемых сумм (Σd, Σdlag, Σd², Σdlag², Σd·dlag), что позволяет обойтись без выделения памяти под временные массивы:

//+------------------------------------------------------------------+
//| _ComputeRoll: Roll spread and dollar-normalised roll impact      |
//| Cov(dp_t, dp_{t-1}) accumulated in a single pass via three       |
//| running sums; no temporary arrays are allocated.                 |
//+------------------------------------------------------------------+
void CMicrostructureFeatures::_ComputeRoll()
  {
   int W = m_window;

   //--- outer loop: bar i is the right edge of a W-bar window of price diffs
   for(int i = 0; i <= m_n - W - 1; i++)
     {
      double sum_d   = 0.0;
      double sum_dl  = 0.0;
      double sum_dld = 0.0;
      int    cnt     = 0;

      for(int k = 0; k < W - 1; k++)
        {
         double d  = m_close[i+k]   - m_close[i+k+1];
         double dl = m_close[i+k+1] - m_close[i+k+2];
         sum_d   += d;
         sum_dl  += dl;
         sum_dld += d * dl;
         cnt++;
        }

      if(cnt < 3) continue;
      double cov = (sum_dld - sum_d * sum_dl / cnt) / (cnt - 1);
      m_roll[i] = (-cov > 0.0) ? 2.0 * MathSqrt(-cov) : MICRO_EMPTY;
     }
  }

При порядке индексации таймсерии (индекс 0 соответствует самому новому бару) величина close[i] - close[i+1] представляет собой изменение цены на баре i (более позднее значение минус более раннее). Это соглашение противоположно используемому в Python функцией np.diff(close), которая вычисляет разности в хронологическом порядке. Учет этого различия предотвращает инверсию знака, которая незаметно изменила бы знак всех оценок, основанных на ковариации.

Метод RollImpact() нормирует спред на денежный объем того же бара, close[i] × tick_volume[i], получая безразмерную меру спреда на единицу ликвидности. Если тиковый объем или цена закрытия равны нулю, метод RollImpact возвращает MICRO_EMPTY, не допуская ошибки деления на ноль.


Спред Корвина — Шульца и внутридневная волатильность

Оценка Корвина — Шульца (2012) использует пары максимумов и минимумов соседних баров и не требует данных об объеме или тиковых данных. Для использования в MetaTrader 5 это наиболее "чистый" метод оценки в наборе, поскольку все необходимые ему данные — максимум и минимум каждого бара — в равной мере доступны у всех брокеров.

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

//+------------------------------------------------------------------+
//| _ComputeCS: Corwin-Schultz spread and Parkinson-Beckers sigma    |
//| denom = 3 - 2√2 ≈ 0.1716  (from the C-S spread derivation)       |
//| k1    = 4 ln 2            (Parkinson volatility normalization)   |
//| Both constants are pre-computed once before the loop.            |
//+------------------------------------------------------------------+
void CMicrostructureFeatures::_ComputeCS()
  {
   const double denom = 3.0 - 2.0 * MathSqrt(2.0);   // 3 - 2√2 ≈ 0.1716
   const double k1    = 4.0 * MathLog(2.0);          // Parkinson normalization

   for(int i = 0; i < m_n - 1; i++)
     {
      double hi0 = m_high[i],   lo0 = m_low[i];
      double hi1 = m_high[i+1], lo1 = m_low[i+1];
      if(lo0 <= 0.0 || lo1 <= 0.0 || hi0 <= 0.0 || hi1 <= 0.0) continue;

      double beta  = MathPow(MathLog(hi0/lo0), 2) + MathPow(MathLog(hi1/lo1), 2);
      double h2    = MathMax(hi0, hi1);
      double l2    = MathMin(lo0, lo1);
      double gamma = MathPow(MathLog(h2/l2), 2);
      double alpha = (MathSqrt(2*beta) - MathSqrt(beta)) / denom
                  - MathSqrt(gamma / denom);

      if(alpha < 0.0) { m_cs_spread[i] = MICRO_EMPTY; }
      else
        {
         double ea      = MathExp(alpha);
         m_cs_spread[i] = 2.0 * (ea - 1.0) / (1.0 + ea);
        }
      m_cs_sigma[i] = MathSqrt(beta / (2.0 * k1));   // Parkinson-Beckers σ
     }
  }

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


Лямбда Кайла

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

Расчет методом наименьших квадратов (МНК) выполняет закрытый метод _OLS(), который работает с заранее сформированными массивами x и y на заданном срезе [start, start+len). Все пять сумм накапливаются за один проход; память под временную матрицу не выделяется. Метод возвращает false, если знаменатель меньше порогового значения 1e-20; в этом случае вызывающий код оставляет m_kyle[i] равным MICRO_EMPTY:

//--- signed-volume array is built once, then sliced by the rolling window
double b     = _TickRule(i);
double vol_i = (double)m_volume[i];
x_kyle[i]    = b * vol_i;
dp[i]        = m_close[i] - m_close[i+1];   // price change (newer − older)
y[i]         = dp[i];

//--- rolling OLS for Kyle's Lambda
double beta_k = 0.0, t_k = 0.0;
if(_OLS(x_kyle, y, i, W, beta_k, t_k))
   m_kyle[i] = beta_k;

t-статистика вычисляется методом _OLS() одновременно с коэффициентом, но в настоящее время не сохраняется в отдельном массиве. Если вам нужны t-статистики для матрицы признаков машинного обучения, обеспечьте доступ к ним, добавив в класс массив double m_kyle_t[] и заполняя его в методе _ComputeLambdas(). Реализация на Python по умолчанию сохраняет и коэффициенты, и t-статистики; версия на MQL5 не сохраняет t-статистики, чтобы не удваивать объем используемой памяти в советниках, которым нужен только коэффициент.


Показатель неликвидности Амихуда (ILLIQ)

Показатель неликвидности Амихуда (ILLIQ) не требует классификатора направления. Для его расчета абсолютную логарифмическую доходность делят на денежный объем и усредняют результат по окну:

//--- Amihud: rolling mean of |dp| / (close * tick_volume)
x_amihud[i] = (dv > 0.0) ? MathAbs(dp[i]) / dv : MICRO_EMPTY;

//--- accumulate valid observations in the window
double sum_a = 0.0;
int    cnt_a = 0;
for(int k = i; k < i + W; k++)
  {
   if(x_amihud[k] > MICRO_EMPTY + 1.0)
     { sum_a += x_amihud[k]; cnt_a++; }
  }
if(cnt_a > 0) m_amihud[i] = sum_a / (double)cnt_a;

Проверка условия x_amihud[k] > MICRO_EMPTY + 1.0 исключает бары, в которых тиковый объем был равен нулю или отсутствовала цена закрытия. Этот подход надежнее отдельных проверок на ноль или NaN, поскольку MICRO_EMPTY = −1e38 намного меньше любого содержательно осмысленного значения ILLIQ, поэтому такая проверка никогда не отбрасывает корректное наблюдение с низким значением ILLIQ.


Лямбда Хасбрука

При расчете лямбды Хасбрука вместо линейного объема со знаком, используемого в оценке Кайла, применяется квадратный корень из денежного объема, умноженный на знак направления. Это уменьшает влияние выбросов и лучше согласуется с теоретической кривой ценового воздействия, полученной из моделей управления запасами:

//--- Hasbrouck: signed square-root of dollar volume
//      b is the bar-close tick rule direction already computed above;
//      MathAbs(dv) guards against a negative sentinel in dv.          
x_hbck[i] = b * MathSqrt(MathAbs(dv));

double beta_h = 0.0, t_h = 0.0;
if(_OLS(x_hbck, y, i, W, beta_h, t_h))
   m_hasbrouck[i] = beta_h;

Вызов MathAbs(dv) предотвращает ошибку области определения функции MathSqrt, если переменной dv было присвоено отрицательное значение-маркер. На практике dv = close × tick_volume всегда неотрицательно; эта проверка служит мерой предосторожности на случай будущих изменений кода.


Тиковое правило на уровне баров

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

Классификация по тиковому правилу на основе цен закрытия баров MetaTrader 5

Рисунок 2. Иллюстрация из трех панелей, показывающая работу тикового правила по ценам закрытия баров на синтетических данных

  • Панель (a): синтетический ряд цен закрытия с искусственно добавленным восходящим трендом на барах 20–35.
  • Панель (b): направление bt ∈ {−1, +1}, определяемое тиковым правилом по ценам закрытия баров. Во время тренда направление преимущественно равно +1, однако на отдельных барах оно меняется на противоположное. В пояснении отмечено основное ограничение: тиковое правило по ценам закрытия баров дает приближенную оценку, подверженную шуму. Определение фактического направления каждого тика по данным CopyTicks() позволило бы учесть все сделки внутри бара, а не только цену последней сделки.
  • Панель (c): тиковый объем со знаком bt × TickVolume — регрессор x для оценки лямбды Кайла и лямбды Хасбрука. Более высокие столбцы во время тренда отражают всплеск объема, искусственно заданный в генераторе синтетических данных и создающий преобладание покупок, которое и призваны выявлять оценки лямбды.

Тиковое правило по ценам закрытия баров дает смещенную оценку. Оно определяет направление только по последней сделке в баре и игнорирует промежуточные сделки. Бар, который открылся по цене на середине спреда, в течение сессии торговался с повышением, но закрылся немного ниже цены закрытия предыдущего бара, будет классифицирован как продажа (-1), хотя большинство сделок были покупками. На коротких внутридневных барах это смещение невелико, а на дневных барах оно может быть значительным. Это смещение никак не влияет на спред Ролла и оценки Корвина — Шульца, что служит еще одной причиной отдавать им предпочтение в конвейерах обработки данных дневных баров.


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

Стандартная схема интеграции предусматривает размещение единственного объекта CMicrostructureFeatures в глобальной области видимости, его инициализацию в OnInit() и вызов один раз при появлении каждого нового бара. Затем вычисленные признаки передаются тому компоненту дальнейшей обработки, которому они нужны: в вектор признаков для модели ONNX, фильтр сигналов или множитель для расчета размера позиции:

#include <Features\CMicrostructureFeatures.mqh>

input int                inp_window    = 20;
input int                inp_lookback  = 100;

CMicrostructureFeatures *g_micro = NULL;
int g_last_bar = -1;

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_micro = new CMicrostructureFeatures(_Symbol, _Period, inp_window);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_micro != NULL) { delete g_micro; g_micro = NULL; }
  }

//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- New-bar guard ---
   datetime bar_time = iTime(_Symbol, _Period, 0);
   if(bar_time == g_last_bar) return;
   g_last_bar = bar_time;

   if(!g_micro.Calculate(1, inp_lookback)) return;

   double roll   = g_micro.RollMeasure(0);
   double cs     = g_micro.CSSpread(0);
   double kyle   = g_micro.KyleLambda(0);
   double amihud = g_micro.AmihudLambda(0);
   double hbck   = g_micro.HasbrouckLambda(0);

   //--- Feature validity check — MICRO_EMPTY if window not yet filled
   if(roll  <= MICRO_EMPTY + 1.0) return;
   if(kyle  <= MICRO_EMPTY + 1.0) return;

   //--- Use features for signal logic...
  }

Проверка появления нового бара сравнивает iTime(_Symbol, _Period, 0) со временем последнего обработанного бара. Это требует меньше ресурсов, чем ведение отдельной переменной-счетчика баров, и корректно работает при нерегулярном добавлении баров. Calculate() всегда начинает расчет с бара со смещением 1 (последнего завершенного бара, а не текущего формирующегося), чтобы избежать ошибки заглядывания в будущее.


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

В советнике достаточно методов доступа к числовым значениям, но в ходе исследования полезно видеть, как признаки меняются на фоне динамики цены. MicrostructureViewer.mq5 — это индикатор в отдельном окне, который использует тот же класс CMicrostructureFeatures и отображает все семь признаков отдельными линиями. Дополнительная панель управления CMicroPanel.mqh позволяет включать и выключать отображение каждого ряда, переключаться между исходным и нормализованным масштабами и сразу видеть последнее исходное значение каждого признака. Поскольку индикатор использует тот же класс, что и советник, кривые на графике отображают именно те числовые значения, с которыми работает ваша стратегия: нет второй, отличающейся реализации, которую пришлось бы поддерживать в согласованном состоянии.

Индикатор признаков микроструктуры рынка с панелью

Рисунок 4. Подокно MicrostructureViewer с панелью управления CMicroPanel (темная тема)

Конструкция индикатора определялась двумя проблемами:

  • Различие масштабов. Значения семи признаков различаются на много порядков: спред Корвина — Шульца составляет около 10-3 в долях цены, лямбда Кайла — это коэффициент наклона регрессии, который может быть исчезающе малым, а показатель Ролла выражается в исходных единицах цены. При отображении исходных значений на одной оси график становится нечитаемым. На панели доступен переключатель "Нормализация (z-оценка)", который выполняет стандартизацию по z-оценке независимо для каждого видимого ряда, позволяя сравнивать их формы на общей оси, тогда как панель по-прежнему показывает последнее исходное значение каждого признака.
  • Правила индексации. Класс расчета признаков использует порядок индексации таймсерии (смещение 0 соответствует самому новому бару), тогда как буферы индикаторов MQL5 имеют хронологический порядок (индекс 0 соответствует самому старому бару). Средство просмотра преобразует один порядок индексации в другой по формуле bufidx = rates_total − 1 − offset, поэтому самое новое вычисленное значение всегда попадает в крайнюю правую ячейку, выровненную по текущему ценовому бару на графике выше.

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

#property indicator_separate_window
#property indicator_buffers 7
#property indicator_plots   7

input int              InpWindow     = 20;          // Rolling window
input int              InpMaxBars    = 1500;        // Max bars to compute (newest)
input bool             InpNormalize  = true;        // Start with z-score normalize
input ENUM_PANEL_THEME InpPanelTheme = PANEL_DARK;  // Panel color theme

//--- Heavy step: recompute every feature over the newest window
int n = MathMin(rates_total, InpMaxBars);
if(n < InpWindow + 2)             return(false);
if(!g_feat.Calculate(0, n))     return(false);   // offset 0 = newest bar

for(int f = 0; f < MICRO_NFEAT; f++)
   for(int o = 0; o < n; o++)
      g_raw[o][f] = FeatureAt(f, o);             // cache raw values for cheap remaps

На этом малозатратном этапе за один проход вычисляются среднее значение и стандартное отклонение для каждого признака по допустимым наблюдениям (исключая значения-маркеры), после чего в буфер записывается либо исходное значение, либо его z-оценка. Один и тот же код обслуживает оба режима, переключение между которыми осуществляется только с помощью флага g_normalize:

//--- Cheap step: map raw -> buffers, optional per-feature z-score
double mean = sum / cnt;
double var  = (sum2 - sum * sum / cnt) / (cnt - 1);
double sd   = (var > 0.0) ? MathSqrt(var) : 0.0;

for(int o = 0; o < n; o++)
  {
   double v = g_raw[o][f];
   if(!IsValidMicro(v)) continue;
   int bufidx = rates_total - 1 - o;             // newest -> rightmost slot
   double out = (g_normalize && sd > 0.0) ? (v - mean) / sd : v;
   SetBuf(f, bufidx, out);
  }

Панель управления не содержит никакой логики. Для каждого признака панель отображает кнопку-переключатель, цвет которой совпадает с цветом графика этого признака, а рядом с ней — последнее исходное значение с выравниванием по правому краю; кроме того, на панели есть переключатель Normalize („Нормализовать“) и кнопка Recompute („Пересчитать“). Основной индикатор считывает имена кнопок в OnChartEvent(), изменяет собственное состояние и выполняет перерисовку. При включении или выключении отображения признака меняется лишь тип отрисовки его графика; переключение нормализации запускает только описанное выше малозатратное преобразование значений:

//--- feature visibility toggle: flip the plot's draw type, no recompute
if(sparam == g_panel.FeatureBtnName(f))
  {
   g_vis[f] = !g_vis[f];
   PlotIndexSetInteger(f, PLOT_DRAW_TYPE, g_vis[f] ? DRAW_LINE : DRAW_NONE);
   RefreshPanel();
   ChartRedraw();
  }

//--- normalize toggle: cheap remap of cached raw values, no recompute
if(sparam == g_panel.NormalizeBtnName())
  {
   g_normalize = !g_normalize;
   FillBuffers(g_last_rt);
   RefreshPanel();
   ChartRedraw();
  }

Поскольку на панели всегда отображается исходная величина — например, спред Корвина — Шульца может составлять 0.000850, — сохраняется возможность количественной интерпретации каждого признака, даже когда линии на графике стандартизированы для сравнения их формы. Визуализатор выполняет расчеты для символа и таймфрейма графика, на котором он установлен, поэтому при добавлении на график EURUSD H1 он рассчитывает именно те признаки, которые использовал бы советник на том же графике. Поместите файл CMicroPanel.mqh в папку MQL5\Include\Microstructure\ и скомпилируйте файл MicrostructureViewer.mq5 из папки MQL5\Indicators\; после этого индикатор появится в "Навигаторе" в разделе "Индикаторы".


Проверка

Сопутствующий скрипт MicrostructureValidation.mq5 проверяет четыре свойства для текущего символа и периода. Все четыре соответствуют математическим инвариантам, которые должны выполняться независимо от данных:

Проверка Свойство Обоснование
1 f.Count() == n_bars Размер выходных массивов должен соответствовать заданной длине.
2 Спред Ролла ≥ 0 там, где он определен Roll = 2√(max(−Cov, 0)); оператор max гарантирует, что значение будет неотрицательным.
3 Спред Корвина — Шульца ∈ (0; 0.05] там, где он определен Спред свыше 5 % экономически неправдоподобен для любого ликвидного инструмента и указывает на ошибку в данных.
4 Показатель неликвидности Амихуда (ILLIQ) ≥ 0 там, где он определен ILLIQ = |доходность| / денежный объем; и числитель, и знаменатель неотрицательны.

Запустите скрипт на панели "Скрипты" в MetaEditor для любого символа с полными данными OHLCV (например, EURUSD на графике H1 с историей не менее 120 баров). В журнале "Эксперты" должны появиться четыре строки с пометкой PASS, а затем — примеры значений для бара 0. Если проверка 3 завершилась неудачно, убедитесь, что история цен по данному символу полная и что для всех баров в окне выполняется условие: максимальная цена ≥ цена закрытия ≥ минимальная цена.


Численное совпадение результатов Python и MQL5

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

Сравнение Python и MQL5: значения признаков микроструктуры рынка (синтетические данные)

Рисунок 5. Иллюстрация из четырех графиков: численное совпадение результатов Python и MQL5 на 120 синтетических барах

  • Графики (a) и (c): спред Ролла и спред Корвина — Шульца в динамике. Линии Python (сплошная) и MQL5 (пунктирная) визуально неотличимы. Сильные колебания спреда Корвина — Шульца на графике (c) обусловлены побарным оцениванием: для расчета каждого значения используются только две пары "максимум — минимум" соседних баров.
  • Графики (b) и (d): диаграммы рассеяния результатов Python и MQL5. На обеих диаграммах r² = 1.000000, что подтверждает совпадение результатов двух реализаций в пределах точности вычислений с плавающей запятой. Любое реальное алгоритмическое отличие привело бы к появлению точек, лежащих вне диагонали y = x.

Перед использованием в рабочей среде запустите обе реализации на одном и том же наборе исторических данных OHLCV, экспортированных из MetaTrader 5, и убедитесь, что коэффициент R² на диаграмме рассеяния равен 1.000000. Если это не так, наиболее вероятная причина — различие в правилах индексации: в реализации на Python используется хронологический порядок (от самых старых записей к новым), а в реализации на MQL5 — порядок индексации таймсерии (от самых новых записей к старым, причем индекс 0 соответствует самой новой записи). Ошибка знака при вычислении изменения цены привела бы к уменьшению ковариации Ролла вдвое и вызвала бы систематическое смещение, видимое на панели (b).


Заключение:

В этой статье слой расчета признаков микроструктуры рынка на уровне баров из части 5 был перенесен на MQL5 в виде единого включаемого файла CMicrostructureFeatures.mqh, размещенного в папке MQL5\Include\Features\. Класс рассчитывает семь признаков для каждого бара — спред Ролла, ценовое воздействие по Роллу, спред Корвина — Шульца, сигму Корвина — Шульца, лямбду Кайла, показатель неликвидности Амихуда (ILLIQ) и лямбду Хасбрука — исключительно по данным OHLCV, без зависимости от Python. Сопутствующий индикатор в отдельном окне MicrostructureViewer.mq5 с панелью управления CMicroPanel отображает те же семь признаков в реальном времени и позволяет включать и отключать их отображение, нормализовать их и считывать значения непосредственно на графике.

Основное ограничение, описанное в этой статье, связано с API тикового объема MetaTrader 5. Функция CopyTickVolume() возвращает число изменений цены, а не фактическое число сделок. Для оценок спреда (Ролла и Корвина — Шульца) это не имеет значения: в них используются только цены закрытия, максимума и минимума. При оценке лямбда-коэффициентов тиковый объем и тиковое правило по ценам закрытия баров служат приближениями, которые позволяют выявлять рыночные режимы на порядковом уровне, но не получать количественные оценки ценового воздействия. Слой обработки тиковых данных на Python, использующий поток реальных тиковых данных для расчета лямбда-коэффициентов и их t-статистик для каждого бара, остается более точным вариантом при наличии тиковых данных.

В следующей статье этой серии будут рассмотрены энтропийные признаки из главы 18 книги AFML и их реализация на Python с использованием оценок Шеннона, Лемпеля — Зива, подстановочной оценки (plug-in) и оценки Контойянниса из модуля afml.features.entropy.


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

  Файл Местоположение Описание
1. CMicrostructureFeatures.mqh MQL5\Include\Features\ Класс для расчета признаков микроструктуры рынка на уровне баров. Рассчитывает спред Ролла, ценовое воздействие по Роллу, спред Корвина — Шульца, сигму Корвина — Шульца, лямбду Кайла, показатель неликвидности Амихуда (ILLIQ) и лямбду Хасбрука исключительно по данным OHLCV.
2. MicrostructureValidation.mq5 MQL5\Scripts\ Скрипт проверки. Выполняет четыре проверки математических инвариантов (длина массива, неотрицательность спреда Ролла, диапазон значений спреда Корвина — Шульца, неотрицательность показателя неликвидности Амихуда (ILLIQ)) для текущего символа и периода.
3. MicrostructureViewer.mq5 MQL5\Indicators\ Индикатор просмотра в отдельном окне. Отображает все семь признаков микроструктуры рынка, рассчитанных для символа и таймфрейма графика, на котором установлен индикатор, в виде линий с возможностью включать и отключать их отображение и при необходимости применять стандартизацию по z-оценке отдельно для каждого признака.
4. CMicroPanel.mqh MQL5\Include\Microstructure\ Панель управления и отображения значений для индикатора просмотра. Отображает кнопки включения и отключения каждого признака, последнее исходное значение каждого признака, а также элементы управления "Нормализовать" и "Пересчитать" в светлой или темной теме оформления.


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

  1. Лопес де Прадо, М. (2018). Достижения в области финансового машинного обучения. Wiley. Глава 19.
  2. Ролл, Р. (1984). Простая неявная оценка эффективного спреда между ценами покупки и продажи на эффективном рынке. Journal of Finance, 39(4), 1127–1139.
  3. Корвин, С. А., и Шульц, П. (2012). Простой способ оценки спредов между ценами покупки и продажи на основе дневных максимумов и минимумов. Journal of Finance, 67(2), 719–760.
  4. Паркинсон, М. (1980). Метод экстремальных значений для оценки дисперсии доходности. Journal of Business, 53, 61–65.
  5. Кайл, А. С. (1985). Непрерывные аукционы и инсайдерская торговля. Econometrica, 53(6), 1315–1335.
  6. Амихуд, Й. (2002). Неликвидность и доходность акций: эффекты в поперечном сечении и временных рядах. Journal of Financial Markets, 5(1), 31–56.
  7. Хасбрук, Дж. (2009). Торговые издержки и доходность американских акций: оценка эффективных издержек на основе дневных данных. Journal of Finance, 64(3), 1445–1477.
  8. Хасбрук, Дж. (2007). Эмпирическая микроструктура рынка. Издательство Оксфордского университета.
  9. О’Хара, М. (1995). Теория микроструктуры рынка. Блэквелл.

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

Прикрепленные файлы |
CMicroPanel.mqh (14.25 KB)
Разработка динамического мультивалютного советника (Часть 9): Фильтрация шума исполнения с учетом микроструктуры рынка Разработка динамического мультивалютного советника (Часть 9): Фильтрация шума исполнения с учетом микроструктуры рынка
В статье представлен фильтр исполнения для нескольких торговых инструментов, который оценивает качество рынка в реальном времени, прежде чем разрешить сделку. Он измеряет динамику спреда, скорость тиков, разрывы в котировках и микроволатильность, оценивает проскальзывание, а затем классифицирует состояние рынка, чтобы блокировать торговлю при неблагоприятных условиях. После затухания шума модель продолжения движения после свипа ликвидности оценивает изменения структуры рынка, чтобы входы в рынок происходили только при стабильных условиях исполнения.
Адаптивное сглаживание цен в MQL5: фильтр Калмана с оценкой шума без внешних библиотек Адаптивное сглаживание цен в MQL5: фильтр Калмана с оценкой шума без внешних библиотек
Скользящие средние с фиксированным весовым коэффициентом вносят запаздывание, которое не учитывает смену рыночных режимов. В данной работе представлен индикатор на основе адаптивного скалярного фильтра Калмана, реализованный штатными средствами MQL5. Он оценивает дисперсию шума процесса Q по дисперсии приращений цены в скользящем окне, а дисперсию шума измерений R — по дисперсии цены в скользящем окне, применяет ограничение снизу для обеспечения численной устойчивости и пересчитывает коэффициент усиления Калмана на каждом баре. Линия индикатора, отображаемая поверх ценового графика, сравнивается с 20-периодной EMA по показателям MAE, RMSE, запаздывания и гладкости для количественной оценки качества отслеживания цены и подавления шума.
Метаразметка классических стратегий (Часть 2): Фильтрация сделок по ADX и определение размера позиций Метаразметка классических стратегий (Часть 2): Фильтрация сделок по ADX и определение размера позиций
Пересечение линий DI часто дает сигналы на боковом рынке, где +DI и -DI колеблются без устойчивого преобладания одной из линий. Мы создаем двухслойную гибридную модель: алгоритм выбора гиперпараметров TPE в Optuna оптимизирует фильтр рыночного режима по порогу ADXR, периоду расчета DI и минимальному расстоянию между линиями DI, чтобы максимизировать точность (precision) сигналов на отложенном валидационном интервале; затем случайный лес использует одиннадцать признаков, рассчитанных на основе ADX, для допуска входов в рынок или определения размера позиций с помощью afml.bet_sizing. Полученная модель отфильтровывает всплески сигналов на боковом рынке и калибрует размер позиции при торговле EURUSD на H1.
Форма ценового ряда: Введение в топологический анализ данных и вложение Такенса в MQL5 Форма ценового ряда: Введение в топологический анализ данных и вложение Такенса в MQL5
В статье представлены практические основы анализа формы ценовых рядов в MQL5. Реализовано вложение Такенса с временной задержкой для построения облака точек в фазовом пространстве, а также вычисление полной матрицы попарных расстояний с возможностью выбора нормы. Классы CTDAPointCloud и CTDADistance сопровождаются демонстрационным скриптом, который выполняет вложение данных графика и выводит результаты, подготавливая входные данные для дальнейшей обработки топологическими инструментами.