English
preview
Рыночная микроструктура в MQL5 (Часть 5): Микроструктурный шум

Рыночная микроструктура в MQL5 (Часть 5): Микроструктурный шум

MetaTrader 5 — Индикаторы |
41 0
Max Brown
Max Brown

Введение

В первой части мы создали защитный базовый слой: математические функции с проверками, проверку ценовых данных и надежные статистические примитивы. Во второй части мы добавили взвешенную по достоверности оценку показателя Херста и установили, что данные NQ на таймфрейме M1 в Globex близки к границе случайного блуждания. В третьей части мы добавили функцию-оценщик GPH для параметра дробного дифференцирования d. В четвертой части мы добавили полный набор показателей волатильности — реализованную и фракционную волатильность, прокси по мотивам FIGARCH, эффект левериджа GJR-GARCH, выявление скачков на основе бипауэр-вариации и ширину мультифрактального спектра.

В частях 1-4 мы определили поведение волатильности и наличие у нее памяти. Но происхождение этой волатильности пока не объяснили. На минутном масштабе у изменений цены два разных источника. Первый — новая информация: информированные трейдеры используют сведения, еще не отраженные в цене, и приближают ее к справедливой стоимости. Второй — шум, возникающий из механики торговли: колебания цены между Bid и Ask, издержки немедленного исполнения и дисперсия, порождаемая механизмом обработки ордеров, а не формированием рыночной цены.

Если система не разделяет эти компоненты, ее калибровка систематически неверна. Высокий коэффициент шума означает, что большая часть изменений цены временна и затем обращается вспять. На таких данных индекс кластеризации из четвертой части завышает эффект ARCH. Высокое значение прокси для котируемого спреда указывает на повышенные издержки исполнения независимо от уровня волатильности. Если в сессии преобладает влияние спреда Bid–Ask, оценка фракционной волатильности отражает транзакционные издержки, а не поток информации.

В этой статье мы добавляем в MicroStructure_Foundation.mqh две группы измерений. Во-первых, добавляем структуру MicrostructureAnalysis и пять функций для расчета прокси для котируемого спреда, спреда по модели Ролла, коэффициента шума на основе OHLC, дисбаланса ордеров и неблагоприятного отбора. Во-вторых, добавляем функцию PopulateMicrostructureAnalysis(), связывающую результаты с показателями волатильности из четвертой части и заполняющую поле microstructure_noise, предусмотренное еще в первой части.

В исследовании используется тот же набор данных фьючерса NQ E-mini Nasdaq 100, что и в четвертой части, но расширенный до 602 нью-йоркских сессий с января 2024 года по июнь 2026 года. Расширенная выборка включает весь период тарифного шока апреля 2025 года и все пять стрессовых режимов, рассматриваемых в серии. Эмпирическая основа исследования описана в сопутствующей работе, доступной на SSRN под номером 6847024.

Прежде чем перейти к реализации, нужно явно оговорить один момент. На данных NQ на таймфрейме M1 оценка спреда по модели Ролла близка к нулю. Это эмпирически верный результат, а не ошибка программы. На минутном масштабе для фьючерса с сотнями сделок в баре и минимальным шагом цены 0,25 пункта ковариация последовательных доходностей от закрытия к закрытию определяется преимущественно формированием цены внутри бара, а не колебаниями между Bid и Ask. Функция остается в инструментарии для инструментов и таймфреймов, где колебания между Bid и Ask различимы, а также как теоретическая основа для анализа баров длительностью менее минуты. В эмпирическом разделе это прямо указано.

Результат работы — пять функций, добавленных в MicroStructure_Foundation.mqh, новая структура MicrostructureAnalysis и полностью заполненное поле microstructure_noise структуры RobustFractalAnalysis. Новый подключаемый файл не создается.


Теория микроструктурного шума

Два источника изменения цены

В литературе по рыночной микроструктуре наблюдаемые изменения цены разделяют на два компонента: инновацию эффективной цены, связанную с новой информацией, и шумовой член, связанный с временными торговыми издержками. Ролл (1984) показал, что даже на эффективном рынке совершение сделок по ценам Bid и Ask порождает отрицательную ковариацию последовательных доходностей. Такая отрицательная ковариация — признак шума. При отрицательной автокорреляции доходностей на лаге, равном 1, ряд показывает возврат к среднему на уровне баров. Причина не в том, что информированные участники предвидят развороты, а в механическом чередовании цен Bid и Ask, которое сначала раздвигает соседние цены, затем вновь сближает их.

Модель Ролла

В модели Ролла (1984) подразумеваемый спред определяется по ковариации последовательных изменений цены. Если c — половина спреда, а сделки случайным образом чередуются между ценами Bid и Ask, подразумеваемый спред равен:

Спред Ролла = 2 × √(max(0, −Cov(Δp_t, Δp_{t−1})))

При отрицательной ковариации последовательных изменений цены спред Ролла положителен, а колебания между Bid и Ask различимы. При положительной ковариации, когда преобладает трендовое движение, оценщик возвращает ноль. В модели Ролла три допущения: эффективная цена следует случайному блужданию, вероятности покупки и продажи равны, спред постоянен. На NQ с таймфреймом M1 ни одно допущение не выполняется в точности, но оценщик остается стандартным приближением первого порядка для определения спреда только по ценовым данным. Описанная в эмпирическом разделе неспособность оценщика обнаружить спред на M1 сама по себе информативна: при минутной агрегации формирование цены внутри бара полностью поглощает сигнал колебаний между Bid и Ask.

Неблагоприятный отбор

Компонент неблагоприятного отбора — разность котируемого спреда и спреда по модели Ролла. Если котируемый спред значительно превышает спред Ролла, маркет-мейкеры учитывают риск торговли с более информированными контрагентами — так в наблюдаемых данных проявляется механизм Кайла (1985). Большая доля неблагоприятного отбора в котируемом спреде — предупреждение: в потоке ордеров присутствуют информированные участники, и простые направленные стратегии могут систематически выбирать неверное направление. На NQ с таймфреймом M1 спред Ролла близок к нулю, поэтому компонент неблагоприятного отбора фактически сводится к прокси для котируемого спреда. В эмпирическом разделе это прямо отражено.

Разложение шума на основе OHLC

Дисперсия отклонения цены закрытия от середины диапазона бара (H+L)/2 характеризует величину этого отклонения. При чисто шумовом процессе цены закрытия равномерно распределены вокруг середины диапазона. При направленном движении цены закрытия устойчиво располагаются возле одной из границ диапазона. Базовый коэффициент шума — отношение дисперсии отклонения цены закрытия от середины диапазона к полной дисперсии бара.

В усовершенствованной версии объединяются три компонента. Первый показатель сопоставляет две дисперсии: для разности между ценой закрытия и серединой диапазона и для самого диапазона бара. Это основной показатель шума. Второй — инвертированное отношение тела бара к диапазону (1 - |C-O|/(H-L)): при движении цены от открытия к закрытию в одном направлении тело занимает почти весь диапазон бара и шум мал; при разнонаправленных колебаниях тело невелико относительно диапазона и шум высок. Третий — доля дисперсии доходности от открытия к закрытию в общей дисперсии диапазона. Компоненты объединяются с весами 40%, 40% и 20% соответственно в один коэффициент шума в диапазоне [0, 1]. Чем выше значение, тем больше шума и слабее направленный сигнал.

Прокси для котируемого спреда

Для баров M1 без текущих данных Bid и Ask средний диапазон бара используется как прокси для спреда. Обоснование дано в работах Паркинсона (1980), а также Гармана и Класса (1980): если цена внутри бара следует случайному блужданию, диапазон между ценой максимума и ценой минимума пропорционален стандартному отклонению процесса. Когда при стрессе маркет-мейкеры расширяют спред котировок, последовательные сделки совершаются по ценам с большим разбросом, а диапазон бара пропорционально расширяется. Эта прокси не измеряет спред Bid-Ask напрямую: она смешивает расширение спреда с направленным движением внутри бара и представляет собой относительный показатель, последовательно отражающий издержки исполнения по сессиям. Это прямо указано в заголовке функции и в разделе "Ограничения".

Дисбаланс ордеров

Дисбаланс ордеров измеряет направленное давление внутри бара. На данных OHLCV без тиковой детализации взвешенная по объему прокси использует положение цены закрытия в диапазоне бара: (Close - Low)/(High - Low) дает значение от 0 до 1 и равно 0,5 при закрытии в середине диапазона. Значения выше 0,5 указывают на давление со стороны покупателей; значения ниже 0,5 — на давление со стороны продавцов. Взвешенное по объему среднее за окно после центрирования относительно нуля дает дисбаланс ордеров в диапазоне [-1, +1]. Бары с High = Low исключаются — их направленный сигнал не определен.

Связь с частями 2-4

Есть три прямые связи с предыдущими частями. Во-первых, при высоком microstructure_noise оценка показателя Херста из второй части смещается к 0,5. Шум подавляет сигнал долгой памяти, поэтому H указывает на большую близость к случайному блужданию, чем у истинного процесса эффективной цены. Во-вторых, ковариация в модели Ролла рассчитывается с помощью функции двухпроходного вычисления дисперсии из первой части, обеспечивающей численную устойчивость при близких к нулю дисперсиях. В-третьих, шум и волатильность различаются: по данным 602 сессий корреляция усовершенствованного коэффициента шума с реализованной волатильностью из четвертой части составляет лишь 0,159. Показатели не дублируют друг друга, и это эмпирически обосновывает расчет обеих групп.


Реализация

Новая структура — MicrostructureAnalysis
//+------------------------------------------------------------------+
//| MicrostructureAnalysis: noise and spread decomposition results.  |
//| Populated by PopulateMicrostructureAnalysis().                   |
//| adverse_selection = quoted_spread - roll_spread.                 |
//| roll_confidence = |correlation| of adjacent returns [0,1].       |
//| On NQ M1 data, roll_spread approaches zero (see empirical study) |
//| because M1 aggregation absorbs the bid-ask bounce signal.        |
//+------------------------------------------------------------------+
struct MicrostructureAnalysis
  {
   double quoted_spread;      // Mean bar range as spread proxy (points)
   double roll_spread;        // Roll-implied spread (points)
   double noise_ratio;        // Enhanced OHLC noise ratio [0,1]
   double order_imbalance;    // Volume-weighted directional pressure [-1,+1]
   double adverse_selection;  // quoted_spread minus roll_spread (points)
   double roll_confidence;    // |correlation| of adjacent returns [0,1]
  };
BidAskSpread()

При вызове на текущем графике получает котируемый спред через функцию SymbolInfoDouble, а для исторических данных использует средний диапазон бара. Возвращает спред в пунктах. Результат проходит проверку корректности с порогом 10 000 пунктов, унаследованную от функции ValidateSymbolV2(). На исторических барах M1 без текущих данных Bid-Ask диапазон бара служит прокси с той же оговоркой, что приведена в заголовке структуры.

//+------------------------------------------------------------------+
//| BidAskSpread: quoted spread in points.                           |
//| Live: SymbolInfoDouble ASK - BID, divided by point size.         |
//| Historical proxy: mean bar range over the window (points).       |
//| The historical proxy conflates spread widening with directional  |
//| intrabar movement but is consistent as a relative indicator.     |
//+------------------------------------------------------------------+
double BidAskSpread(const string symbol, const int tf,
                     const int window)
  {
   if(!ValidateSymbolV2(symbol) || window < 2) return 0.0;
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   if(point <= DBL_MIN_POSITIVE) return 0.0;
   //--- Attempt live bid-ask first (works on current bar)
   double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
   if(ask > bid && bid > 0)
     {
      double spread = (ask - bid) / point;
      if(spread < 10000.0) return spread;
     }
   //--- Fallback: mean bar range as historical proxy
   double high[], low[];
   ArraySetAsSeries(high, true); ArraySetAsSeries(low, true);
   if(CopyHigh(symbol, (ENUM_TIMEFRAMES)tf, 0, window, high) < window) return 0.0;
   if(CopyLow (symbol, (ENUM_TIMEFRAMES)tf, 0, window, low)  < window) return 0.0;
   double sum_range = 0.0;
   int    valid     = 0;
   for(int i = 0; i < window; i++)
     {
      double r = (high[i] - low[i]) / point;
      if(r > 0 && r < 10000.0) { sum_range += r; valid++; }
     }
   if(valid == 0) return 0.0;
   return sum_range / valid;
  }
MicrostructureNoise()

Вычисляет отношение дисперсии разности между ценой закрытия и серединой диапазона к общей дисперсии бара — базовый коэффициент шума. Коэффициент безразмерен и сопоставим между разными инструментами и уровнями цен. Защита знаменателя обеспечивается константой DBL_MIN_POSITIVE.

//+------------------------------------------------------------------+
//| MicrostructureNoise: close-to-midpoint variance fraction.        |
//| Returns noise_basic in [0,1]. Higher = more noise.               |
//| Midpoint = (High + Low) / 2. Measures how far Close deviates     |
//| from midpoint relative to total bar range variance.              |
//+------------------------------------------------------------------+
double MicrostructureNoise(const string symbol, const int tf,
                            const int window)
  {
   if(!ValidateSymbolV2(symbol) || window < 3) return 0.0;
   double high[], low[], close[];
   ArraySetAsSeries(high, true); ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   if(CopyHigh (symbol,(ENUM_TIMEFRAMES)tf,0,window,high)  < window) return 0.0;
   if(CopyLow  (symbol,(ENUM_TIMEFRAMES)tf,0,window,low)   < window) return 0.0;
   if(SafeCopyClose(symbol,tf,0,window,close)              < window) return 0.0;
   double sum_diff = 0.0, sum_rng = 0.0;
   double diff_vals[], rng_vals[];
   ArrayResize(diff_vals, window); ArrayResize(rng_vals, window);
   int valid = 0;
   for(int i = 0; i < window; i++)
     {
      double mid = (high[i] + low[i]) / 2.0;
      if(mid <= DBL_MIN_POSITIVE) continue;
      diff_vals[valid] = (close[i] - mid) / mid;
      rng_vals [valid] = (high[i]  - low[i]) / mid;
      valid++;
     }
   if(valid < 3) return 0.0;
   //--- Two-pass variance for numerical stability
   double mean_d = 0.0, mean_r = 0.0;
   for(int i=0;i<valid;i++){mean_d+=diff_vals[i];mean_r+=rng_vals[i];}
   mean_d/=valid; mean_r/=valid;
   double var_d = 0.0, var_r = 0.0;
   for(int i=0;i<valid;i++)
     { var_d+=(diff_vals[i]-mean_d)*(diff_vals[i]-mean_d);
       var_r+=(rng_vals[i] -mean_r)*(rng_vals[i] -mean_r); }
   var_d/=valid; var_r/=valid;
   if(var_r <= DBL_MIN_POSITIVE) return 0.0;
   return MathMin(1.0, var_d / (var_r + DBL_MIN_POSITIVE));
  }
EnhancedMicrostructureNoise()

Трехкомпонентный коэффициент шума на основе OHLC: дисперсия разности цены закрытия и середины диапазона (40%), единица минус отношение тела бара к диапазону (40%) и доля дисперсии изменений от открытия до закрытия (20%). Перед объединением каждый из трех компонентов проходит отдельную защитную проверку. Записывает результат в RobustFractalAnalysis.microstructure_noise.

//+------------------------------------------------------------------+
//| EnhancedMicrostructureNoise: three-component OHLC noise ratio.   |
//| Component 1 (40%): close-to-midpoint variance fraction.          |
//| Component 2 (40%): 1 - body_to_range (Garman-Klass efficiency).  |
//| Component 3 (20%): OC variance as fraction of range variance.    |
//| Returns noise in [0,1]. Writes to rfa.microstructure_noise.      |
//+------------------------------------------------------------------+
double EnhancedMicrostructureNoise(const string symbol, const int tf,
                                    const int window,
                                    RobustFractalAnalysis &rfa)
  {
   if(!ValidateSymbolV2(symbol) || window < 3) return 0.0;
   double open[], high[], low[], close[];
   ArraySetAsSeries(open,true); ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);  ArraySetAsSeries(close,true);
   if(CopyOpen (symbol,(ENUM_TIMEFRAMES)tf,0,window,open)  < window) return 0.0;
   if(CopyHigh (symbol,(ENUM_TIMEFRAMES)tf,0,window,high)  < window) return 0.0;
   if(CopyLow  (symbol,(ENUM_TIMEFRAMES)tf,0,window,low)   < window) return 0.0;
   if(SafeCopyClose(symbol,tf,0,window,close)              < window) return 0.0;
   double diff_v[],rng_v[],btr_v[],oc_v[];
   ArrayResize(diff_v,window); ArrayResize(rng_v,window);
   ArrayResize(btr_v,window);  ArrayResize(oc_v,window);
   int valid = 0;
   for(int i = 0; i < window; i++)
     {
      double br  = high[i] - low[i];
      double mid = (high[i] + low[i]) / 2.0;
      if(mid <= DBL_MIN_POSITIVE || br <= DBL_MIN_POSITIVE) continue;
      diff_v[valid] = (close[i] - mid) / mid;
      rng_v [valid] = br / mid;
      btr_v [valid] = MathAbs(close[i] - open[i]) / br;
      oc_v  [valid] = (close[i] - open[i]) / mid;
      valid++;
     }
   if(valid < 3) return 0.0;
   //--- Two-pass variance for each component
   double mean_d=0,mean_r=0,mean_oc=0,mean_btr=0;
   for(int i=0;i<valid;i++){mean_d+=diff_v[i];mean_r+=rng_v[i];
                             mean_oc+=oc_v[i]; mean_btr+=btr_v[i];}
   mean_d/=valid; mean_r/=valid; mean_oc/=valid; mean_btr/=valid;
   double var_d=0,var_r=0,var_oc=0;
   for(int i=0;i<valid;i++)
     { var_d +=(diff_v[i]-mean_d) *(diff_v[i]-mean_d);
       var_r +=(rng_v[i] -mean_r) *(rng_v[i] -mean_r);
       var_oc+=(oc_v[i]  -mean_oc)*(oc_v[i]  -mean_oc); }
   var_d/=valid; var_r/=valid; var_oc/=valid;
   //--- Component 1: close-to-midpoint fraction
   double c1 = (var_r > DBL_MIN_POSITIVE)
               ? MathMin(1.0, var_d/(var_r+DBL_MIN_POSITIVE)) : 0.0;
   //--- Component 2: inverted body-to-range (Garman-Klass efficiency)
   double c2 = MathMax(0.0, 1.0 - mean_btr);
   //--- Component 3: OC variance as fraction of range variance
   double c3 = (var_r > DBL_MIN_POSITIVE)
               ? MathMin(1.0, var_oc/(var_r+DBL_MIN_POSITIVE)) : 0.0;
   double noise = MathMax(0.0, MathMin(1.0,
                          0.40*c1 + 0.40*c2 + 0.20*c3));
   //--- Write to shared struct field reserved in Part 1
   rfa.microstructure_noise = noise;
   if(!MathIsValidNumber(rfa.microstructure_noise))
      rfa.microstructure_noise = 0.0;
   return noise;
  }
BidAskBounceEffect() — спред Ролла

Вычисляет спред по модели Ролла на основе ковариации соседних логарифмических доходностей между ценами закрытия. Использует двухпроходный расчет ковариации, согласованный с реализацией первой части. Нижняя граница на уровне нуля предотвращает комплексные результаты при положительной ковариации. Возвращает расчетный спред в пунктах. Также возвращает показатель достоверности, равный модулю корреляции двух рядов соседних доходностей. Значения ниже 0,0083 — 10-го процентиля в исследовании NQ — указывают на ненадежность оценки.

//+------------------------------------------------------------------+
//| BidAskBounceEffect: Roll-implied spread.                         |
//| spread = 2 * sqrt(max(0, -Cov(Δp_t, Δp_{t-1})))                  |
//| confidence = |correlation| of adjacent returns [0,1].            |
//|                                                                  |
//| On NQ M1, this returns values near zero because intrabar price   |
//| discovery absorbs the bid-ask bounce signal at 1-min aggregation.|
//| The function is retained for sub-minute timeframes and for       |
//| instruments where the bounce is detectable.                      |
//+------------------------------------------------------------------+
double BidAskBounceEffect(const string symbol, const int tf,
                           const int window, double &confidence)
  {
   confidence = 0.0;
   if(!ValidateSymbolV2(symbol) || window < 5) return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, 0, window + 1, close) < window + 1) return 0.0;
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   if(point <= DBL_MIN_POSITIVE) return 0.0;
   //--- Build log-return array; filter gross outliers
   int    n   = ArraySize(close) - 1;
   double ret[];
   ArrayResize(ret, n);
   int    valid = 0;
   for(int i = 0; i < n; i++)
     {
      if(close[i] <= 0 || close[i+1] <= 0) continue;
      double r = SafeLog(close[i+1]) - SafeLog(close[i]);
      if(MathAbs(r) < 0.1) ret[valid++] = r;
     }
   if(valid < 4) return 0.0;
   ArrayResize(ret, valid);
   //--- Two-pass covariance of adjacent returns
   double mean1 = 0.0, mean2 = 0.0;
   int    pairs = valid - 1;
   for(int i = 0; i < pairs; i++) { mean1 += ret[i]; mean2 += ret[i+1]; }
   mean1 /= pairs; mean2 /= pairs;
   double cov = 0.0, var1 = 0.0, var2 = 0.0;
   for(int i = 0; i < pairs; i++)
     {
      cov  += (ret[i]-mean1) * (ret[i+1]-mean2);
      var1 += (ret[i]-mean1) * (ret[i]  -mean1);
      var2 += (ret[i+1]-mean2) * (ret[i+1]-mean2);
     }
   cov /= pairs; var1 /= pairs; var2 /= pairs;
   //--- Confidence: absolute correlation of the two series
   double denom_conf = MathSqrt(MathMax(0,var1) * MathMax(0,var2));
   if(denom_conf > DBL_MIN_POSITIVE)
      confidence = MathMin(1.0, MathAbs(cov / denom_conf));
   //--- Roll spread: floor at zero when covariance is positive
   if(cov >= 0) return 0.0;
   double spread_price = 2.0 * MathSqrt(-cov);
   return MathMax(0.0, spread_price / point);
  }
OrderImbalance()

Взвешенная по объему прокси для направленного давления. Для каждого бара (Close - Low)/(High - Low) показывает относительное положение цены закрытия внутри диапазона. Взвешенное по объему среднее, центрированное относительно нуля, дает дисбаланс ордеров в диапазоне [-1, +1]. Бары с нулевым диапазоном исключаются.

//+------------------------------------------------------------------+
//| OrderImbalance: volume-weighted directional pressure.            |
//| Returns (Close-Low)/(High-Low) centered at zero, volume-weighted.|
//| +1 = all closes at high (buyer dominated).                       |
//| -1 = all closes at low (seller dominated).                       |
//| Bars with High == Low excluded (zero range = undefined signal).  |
//+------------------------------------------------------------------+
double OrderImbalance(const string symbol, const int tf,
                       const int window)
  {
   if(!ValidateSymbolV2(symbol) || window < 3) return 0.0;
   double high[], low[], close[];
   long   vol[];
   ArraySetAsSeries(high,true); ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true); ArraySetAsSeries(vol,true);
   if(CopyHigh  (symbol,(ENUM_TIMEFRAMES)tf,0,window,high)  < window) return 0.0;
   if(CopyLow   (symbol,(ENUM_TIMEFRAMES)tf,0,window,low)   < window) return 0.0;
   if(SafeCopyClose(symbol,tf,0,window,close)               < window) return 0.0;
   if(CopyTickVolume(symbol,(ENUM_TIMEFRAMES)tf,0,window,vol) < window) return 0.0;
   double weighted_sum = 0.0;
   double total_vol    = 0.0;
   for(int i = 0; i < window; i++)
     {
      double br = high[i] - low[i];
      if(br <= DBL_MIN_POSITIVE) continue;  // exclude zero-range bars
      double frac = (close[i] - low[i]) / br; // [0,1]
      double imb  = 2.0 * frac - 1.0;        // center at zero [-1,+1]
      double v    = (vol[i] > 0) ? (double)vol[i] : 1.0;
      weighted_sum += imb * v;
      total_vol    += v;
     }
   if(total_vol <= DBL_MIN_POSITIVE) return 0.0;
   return MathMax(-1.0, MathMin(1.0, weighted_sum / total_vol));
  }
PopulateMicrostructureAnalysis()

Функция-обертка вызывает все пять функций, заполняет структуру MicrostructureAnalysis, вычисляет adverse_selection = quoted_spread - roll_spread и записывает microstructure_noise в RobustFractalAnalysis. Применяет ту же схему защиты от NaN, что и функция PopulateVolatilityAnalysis() из четвертой части.

//+------------------------------------------------------------------+
//| PopulateMicrostructureAnalysis: fills MicrostructureAnalysis and |
//| writes microstructure_noise into RobustFractalAnalysis.          |
//| adverse_selection = quoted_spread - roll_spread.                 |
//| On NQ M1, roll_spread ≈ 0 so adverse_selection ≈ quoted_spread.  |
//+------------------------------------------------------------------+
void PopulateMicrostructureAnalysis(const string symbol, const int tf,
                                     const int window,
                                     MicrostructureAnalysis &msa,
                                     RobustFractalAnalysis  &rfa)
  {
   msa.quoted_spread     = BidAskSpread(symbol, tf, window);
   msa.noise_ratio       = EnhancedMicrostructureNoise(symbol, tf, window, rfa);
   msa.roll_spread       = BidAskBounceEffect(symbol, tf, window,
                                               msa.roll_confidence);
   msa.order_imbalance   = OrderImbalance(symbol, tf, window);
   msa.adverse_selection = MathMax(0.0, msa.quoted_spread - msa.roll_spread);
   //--- NaN guards
   if(!MathIsValidNumber(msa.quoted_spread))     msa.quoted_spread     = 0.0;
   if(!MathIsValidNumber(msa.roll_spread))       msa.roll_spread       = 0.0;
   if(!MathIsValidNumber(msa.noise_ratio))       msa.noise_ratio       = 0.0;
   if(!MathIsValidNumber(msa.order_imbalance))   msa.order_imbalance   = 0.0;
   if(!MathIsValidNumber(msa.adverse_selection)) msa.adverse_selection = 0.0;
   if(!MathIsValidNumber(msa.roll_confidence))  msa.roll_confidence  = 0.0;
  }


Эмпирическое исследование — микроструктурный шум NQ на таймфрейме M1

Все оценщики применены к данным 602 нью-йоркских сессий фьючерса NQ E-mini Nasdaq 100 на CME Globex с января 2024 года по июнь 2026 года. Фильтр нью-йоркской сессии сохраняет бары со временем открытия в интервале от 14:30 до 21:00 UTC. Сессии с менее чем 300 барами на таймфрейме M1 исключаются. По сравнению с исследованием четвертой части выборка расширена на 88 сессий. Она охватывает более длительный обычный период и весь период тарифного шока апреля 2025 года. Все показатели заново рассчитываются по исходным барам OHLCV с помощью Python-реализации приведенных выше функций.

Figure 1 — NQ noise ratio and quoted spread proxy time series with regime shading

Рис. 1. Улучшенный коэффициент шума (вверху) и прокси для котируемого спреда (внизу) по 602 сессиям NQ, январь 2024 г. - июнь 2026 г. Фоновая закраска показывает четыре выделенных стрессовых эпизода. Во время тарифного шока апреля 2025 года котируемый спред увеличивается сильнее всего — в 2,6 раза относительно обычного уровня. В момент шока по Банку Японии коэффициент шума достигает максимума.

Таблица 1. Сводная статистика (602 нью-йоркские сессии, фьючерсы NQ, таймфрейм M1)

Показатель Среднее значение Стандартное отклонение Мин. 25% Медиана 75% Макс.
Прокси для котируемого спреда (пункты) 13,82 6,47 5,34 9,51 12,41 16,34 74,18
Расчетный спред Ролла (пункты) 0,0001 0,0001 0,0000 0,0000 0,0000 0,0002 0,0013
Достоверность оценки Ролла [0,1] 0,060 0,046 0,000 0,025 0,050 0,086 0,248
Коэффициент шума (базовый) 0,466 0,132 0,202 0,372 0,458 0,541 1,000
Отношение тела бара к диапазону 0,453 0,019 0,396 0,440 0,452 0,466 0,513
Коэффициент шума (улучшенный) 0,604 0,053 0,438 0,570 0,602 0,632 0,815
Дисбаланс ордеров [-1, +1] 0,021 0,056 -0,185 -0,016 0,024 0,061 0,158
Неблагоприятный отбор (пункты) 13,82 6,47 5,34 9,51 12,41 16,34 74,18
Реализованная волатильность (на бар) 0,00042 0,00018 0,00013 0,00029 0,00038 0,00050 0,00310

Строку расчетного спреда Ролла нужно сразу пояснить. Близкие к нулю значения — в среднем 0,0001 пункта при минимальном шаге цены 0,25 пункта — подтверждают, что колебания цены между Bid и Ask не выявляются при минутной агрегации данных фьючерсов NQ. Средний показатель достоверности оценки Ролла составляет 0,060 — значительно ниже порога 0,65, использованного для MFDFA в четвертой части. Это ожидаемый результат для рынка, где каждый бар объединяет сотни сделок: ковариация соседних доходностей между ценами закрытия определяется прежде всего формированием рыночной цены внутри бара, а не чередованием сделок по Bid и Ask. Функция сохранена для полноты инструментария и для работы с данными короче минуты либо инструментами с более широкими спредами. Значения неблагоприятного отбора численно совпадают с прокси для котируемого спреда, поскольку спред Ролла ≈ 0; для прозрачности показаны обе строки.

Среднее значение улучшенного коэффициента шума — 0,604, а стандартное отклонение заметно мало — 0,053. По разложению OHLC примерно 60% вариации цены в типичной сессии NQ на таймфрейме M1 приходится на шум. Малый разброс показывает, что шум — устойчивая структурная особенность инструмента на этом таймфрейме, а не отдельное эпизодическое явление.

Figure 2 — Scatter of noise ratio vs realized volatility and intraday noise profile

Рис. 2. Слева — улучшенный коэффициент шума в сопоставлении с реализованной волатильностью на бар по 602 сессиям (r = 0,159, p < 0,001); режимы обозначены цветом. Шум и волатильность — разные показатели. Справа — внутридневной профиль шума (синий, левая ось) и диапазон бара (красный, правая ось) по минутам после открытия нью-йоркской сессии. В течение сессии диапазон бара резко сокращается, а шум немного увеличивается.

Перед таблицей режимов выделим два вывода из анализа корреляций показателей и сопоставления режимов.

Во-первых, шум и волатильность — разные величины. Корреляция улучшенного коэффициента шума с реализованной волатильностью по 602 сессиям составляет лишь 0,159 (p < 0,001). Эти два показателя дают разную информацию, поэтому их стоит рассчитывать отдельно. Корреляция прокси для котируемого спреда с реализованной волатильностью равна 0,959. Причина в том, что на данных M1 сама прокси фактически измеряет волатильность, а не в тождестве понятий спреда и волатильности. Как и ожидалось, истинный спред, то есть оценка по модели Ролла, почти не коррелирует с волатильностью.

Во-вторых, внутридневной шум немного растет от открытия к закрытию — с 0,532 до 0,551, — тогда как диапазон бара резко сокращается с 26,1 до 11,5 пункта. Это противоречит прогнозу простой гипотезы об U-образном профиле ликвидности. Это означает, что к концу сессии диапазон баров сужается, но направленность движения не усиливается пропорционально: в абсолютном выражении колебания меньше, однако сигнал не становится "чище". Это может согласовываться с закрытием позиций к концу сессии, вызывающим небольшие движения с возвратом к среднему, хотя объема выборки недостаточно для установления причинной связи.

Таблица 2. Сравнение режимов (602 сессии)

Режим Сессии Коэффициент шума Относительно нормального режима Прокси для спреда (пункты) Относительно нормального режима Дисбаланс ордеров Реализованная волатильность
Нормальный 556 0,603 — 13,14 — 0,021 0,00040
Шок Банка Японии (август 2024 г.) 8 0,677 +12% 23,16 1,76× 0,010 0,00082
Решение ФРС (декабрь 2024 г.) 6 0,593 -2% 15,55 1,18× 0,028 0,00054
Тарифный шок, апрель 2025 г. 14 0,645 +7% 34,12 2,60× 0,008 0,00119
Тарифный шок, апрель 2026 г. 18 0,564 -6% 14,23 1,08× 0,031 0,00040

Сравнение режимов дает три вывода, согласующиеся с анализом волатильности в четвертой части. Сворачивание керри-трейда на фоне шока Банка Японии в августе 2024 года сопровождалось максимальным шумом — 0,677, на 12% выше обычного уровня. Это указывает на сильные кратковременные колебания цены, а не на чистое направленное движение при экстремальной волатильности. Во время тарифного шока апреля 2025 года прокси для котируемого спреда максимальна — 34,1 пункта, в 2,6 раза выше обычного уровня. Шум повышен, но не экстремален — 0,645. В обоих стрессовых эпизодах дисбаланс ордеров приближается к нулю — 0,010 и 0,008 соответственно. Это согласуется с выводом четвертой части: при остром стрессе поток двусторонний, без явного преобладания одного направления. Во время тарифного эпизода апреля 2026 года шум и спред ниже обычного уровня или близки к нему. Это вновь согласуется с выводом, полученным при сопоставлении работ: к 2026 году рынки, возможно, в значительной степени адаптировались к тарифному режиму.

Figure 3 — Order imbalance distribution and noise ratio by regime

Рис. 3. Слева — распределение среднего за сессию дисбаланса ордеров по 602 сессиям. Распределение центрировано чуть выше нуля — среднее 0,021. Это указывает на слабое систематическое преобладание покупателей по NQ за период выборки. Справа — улучшенный коэффициент шума по режимам. Шок Банка Японии — явный выброс; тарифный эпизод апреля 2026 года — самый спокойный.


Ограничения

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

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

Исследование данных NQ непосредственно это подтверждает. Расчетный спред Ролла близок к нулю во всех 602 сессиях: среднее 0,0001 пункта, максимум 0,0013 пункта при минимальном шаге цены 0,25 пункта. При минутной агрегации это корректный результат: формирование рыночной цены внутри бара, объединяющего сотни сделок, полностью поглощает ковариацию соседних доходностей, необходимую модели Ролла для выявления колебаний цены между Bid и Ask. Функция дает содержательный сигнал на таймфреймах короче минуты и инструментах с более широкими эффективными спредами, но не на NQ M1. При применении функции BidAskBounceEffect() к данным NQ M1 считайте результат приблизительно нулевым независимо от вычисленного значения. Функция включена для полноты инструментария и согласованности между таймфреймами. Она предназначена для баров короче минуты или инструментов, где можно обнаружить колебания цены между Bid и Ask. На NQ M1 не используйте ее для торговых решений, если roll_confidence не превышает содержательного порога для выбранного инструмента.

Приближения на основе OHLCV также вносят определенное смещение в оценку дисбаланса ордеров. Взвешенная по объему прокси на основе (Close - Low)/(High - Low) корректно выявляет бары с выраженным направлением при закрытии, но ошибается, если цена в течение минуты двигалась в одном направлении, затем развернулась и закрылась около середины диапазона, несмотря на преобладание покупателей или продавцов за сессию.

Теоретически корректный подход — до расчета показателей объединять тиковые данные в бакеты по объему или денежной стоимости сделок. Эта методология лежит в основе VPIN (Изли и соавт., 2012) и исследований Лопеса де Прадо (2018) по построению баров: каждый бакет содержит фиксированный объем рыночной активности, а не охватывает фиксированный промежуток времени. Ньороге (2026) описывает в статьях сообщества MQL5 практическую реализацию на языке MQL5 баров активности и дисбаланса — прямую альтернативу используемым здесь приближениям на временных барах. Агрегация по бакетам полностью устраняет смещение OHLCV: каждая сделка участвует в оценках спреда и дисбаланса, а полученные показатели стационарны по рыночной активности, а не по времени на часах.

Реализации на минутных OHLCV-барах подходят для индикаторов на языке MQL5 без доступа к истории тиков, работающих в реальном времени, и для инструментов, по которым тиковые данные недоступны или слишком объемны. Они дают корректные по направлению сигналы на уровне сессии даже тогда, когда разложение Ролла не срабатывает. Это согласуется с остальной серией, во всех частях которой используются данные M1. В шестой части появится функция VPIN_OHLC() — наиболее близкое из доступных приближений к тиковому VPIN без данных по отдельным сделкам. На нее распространяется то же ограничение; для ясности мы повторим его и там.


Практические пороги

Все приведенные ниже пороги получены из эмпирического распределения по 602 сессиям NQ на таймфрейме M1 с января 2024 года по июнь 2026 года. Это эвристические рабочие пороги по эмпирическим квантилям, а не формальные критические значения статистических тестов. Они относятся именно к NQ на таймфрейме M1 и этому периоду выборки. Рассматривайте их как отправную точку и заново определяйте для своего инструмента, таймфрейма и периода данных.

Показатель Пороговое значение Основание Интерпретация Действие
Коэффициент шума (улучшенный) > 0,63 75-й процентиль Повышенный шум; оценки волатильности менее достоверны Увеличьте расстояние до стоп-лосса; снизьте вес индекса кластеризации из четвертой части
Коэффициент шума (улучшенный) < 0,57 25-й процентиль Формирование рыночной цены с меньшим шумом, чем обычно Оценки волатильности и показателя Херста более достоверны
Прокси для котируемого спреда > 16,3 пункта 75-й процентиль Повышенные издержки исполнения Увеличьте расстояние до стоп-лосса; уменьшите размер позиции
Прокси для котируемого спреда > 20,8 пункта 90-й процентиль Расширение спреда до стрессового уровня Существенно уменьшите размер позиции; проверьте корректность потока котировок
Достоверность оценки Ролла < 0,008 10-й процентиль Оценка Ролла недостоверна (типично для NQ на таймфрейме M1) Считайте значение roll_spread недоступным
Дисбаланс ордеров > 0,09 90-й процентиль Сильное давление покупателей Рассмотрите возможность торговли в направлении дисбаланса
Дисбаланс ордеров < -0,06 10-й процентиль Сильное давление продавцов Рассмотрите возможность торговли в направлении дисбаланса
//--- Example: Part 5 metrics gating the Part 4 volatility signal
RobustFractalAnalysis  rfa;
MicrostructureAnalysis msa;
VolatilityAnalysis     va;

PopulateHurstAnalysis(Symbol(), PERIOD_M1, 90, rfa);
PopulateVolatilityAnalysis(Symbol(), PERIOD_M1, 90, va, rfa);
PopulateMicrostructureAnalysis(Symbol(), PERIOD_M1, 90, msa, rfa);

//--- Select volatility estimate; discount when noise is elevated
double current_vol = va.realized_vol;
if(rfa.multifractal_width > 0.99 && rfa.dimension_confidence > 0.65)
   current_vol = va.figarch_vol;
else if(va.clustering_index > 0.20)
   current_vol = va.fractional_vol;

//--- Gate clustering signal on noise quality
bool noise_clean = (msa.noise_ratio < 0.63);
double target_vol = 0.0004;   // calibrated to NQ M1 mean realized vol
double base_pos   = (current_vol > DBL_MIN_POSITIVE)
                    ? target_vol / current_vol : 1.0;

//--- Scale down for spread elevation
if(msa.quoted_spread > 20.8) base_pos *= 0.5;
else if(msa.quoted_spread > 16.3) base_pos *= 0.75;

//--- Scale down for jump activity (Part 4)
if(va.jump_intensity > 0.021)
   base_pos *= MathMax(0.3, 1.0 - va.jump_intensity * 10.0);

//--- Align with order imbalance if strong and noise is clean
double oi_signal = 0.0;
if(noise_clean && MathAbs(msa.order_imbalance) > 0.09)
   oi_signal = MathSign(msa.order_imbalance);


Структура папок и путь к подключаемому файлу

Все функции пятой части добавлены в существующий заголовочный файл базового слоя. Новые файлы не создаются. Структура папок не изменилась по сравнению с частями 1-4:

MQL5\
└── Indicators\
    └── HurstProfile\
        ├── HurstProfile.mq5
        └── Includes\
            └── MicroStructure_Foundation.mqh
#include "Includes\MicroStructure_Foundation.mqh"


Заключение

В этой статье мы добавили в файл MicroStructure_Foundation.mqh пять функций анализа микроструктурного шума и ввели структуру MicrostructureAnalysis. Функция BidAskSpread() возвращает прокси для котируемого спреда. Функция BidAskBounceEffect() вычисляет расчетный спред Ролла. Исследование показывает, что на NQ M1 она возвращает значения около нуля, подтверждая необходимость тиковых данных для выявления колебаний цены между Bid и Ask на этом инструменте и таймфрейме. Функция EnhancedMicrostructureNoise() возвращает трехкомпонентный коэффициент шума на основе OHLC, информативный и устойчивый между сессиями. Функция OrderImbalance() измеряет направленное давление на уровне бара. Функция PopulateMicrostructureAnalysis() связывает все пять показателей с оценками волатильности из четвертой части и заполняет поле microstructure_noise, предусмотренное еще в первой части.

Исследование 602 сессий NQ на таймфрейме M1 показывает: по разложению OHLC примерно 60% вариации цены приходится на шум, причем разброс между сессиями мал (SD = 0,053). При стрессовых эпизодах шум повышается: на фоне шока Банка Японии его коэффициент достигает 0,677 — на 12% выше обычного уровня. Однако шум остается отдельной от волатильности характеристикой (r = 0,159). В среднем дисбаланс ордеров близок к нулю и еще больше снижается при остром стрессе, что согласуется с выводом четвертой части о двустороннем потоке. Во время тарифного эпизода апреля 2026 года шум и спред находятся на обычном уровне или ниже него. Это вновь согласуется с полученными в разных работах свидетельствами адаптации рынка.

Как отмечено в разделе "Ограничения", разложение Ролла частично не работает на данных фьючерсов NQ с таймфреймом M1. Это корректный эмпирический результат, а не недостаток реализации. Коэффициент шума, прокси для котируемого спреда и дисбаланс ордеров остаются информативными и пригодными для использования. В шестой части все пять показателей станут входными данными для сигнала потока ордеров вместе с оценками волатильности из четвертой части.

Как получить исходный код через MQL5 Algo Forge

Algo Forge предоставляет облачный контроль версий на базе Git, поэтому вам будет доступна актуальная версия кода, в том числе обновления и исправления после публикации статьи. Полный репозиторий доступен в MQL5 Algo Forge.


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

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

Прикрепленные файлы |
HurstProfile.mq5 (12.98 KB)
За пределами GARCH (Часть VI): Фракционное броуновское движение и мультипликативный каскад в MQL5 За пределами GARCH (Часть VI): Фракционное броуновское движение и мультипликативный каскад в MQL5
В этой статье реализуется модуль моделирования MMAR: по параметрам, полученным при подгонке (H, распределение, коэффициенты, выборочная волатильность), он строит синтетические ценовые траектории. Модуль строит мультифрактальное торговое время с помощью мультипликативного каскада, генерирует фракционное броуновское движение методом Дэвиса — Харта или разложением Холецкого, масштабирует его до целевой волатильности и строит составной процесс посредством деформации времени. Читатели получат класс на языке MQL5, пригодный для многократного использования, рекомендации по выбору метода в зависимости от длины траектории и этапы валидации для сценарного тестирования и применения метода Монте-Карло в следующей части.
Нейронная сеть на практике: Когда использовать искусственный нейрон и как он работает в MQL5 Нейронная сеть на практике: Когда использовать искусственный нейрон и как он работает в MQL5
Мы реализовали в MQL5 нейрон, обученный методом стохастического градиентного спуска, и сравнили его функцию потерь с линейной регрессией. С помощью кода и графиков мы показываем, как нормализация, выбор скорости обучения и структура задачи влияют на сходимость. Статья предлагает руководство по отладке обучения, интерпретации поведения ошибки и выбору подходящей архитектуры или функции активации.
Создание индикатора профиля объёма "Спектр ликвидности" (Liquidity Spectrum) на языке MQL5 Создание индикатора профиля объёма "Спектр ликвидности" (Liquidity Spectrum) на языке MQL5
Создайте в MQL5 профиль объёма «Спектр ликвидности», который распределяет объём по равным ценовым интервалам за выбранный период анализа с использованием цен закрытия свечей. В данном руководстве рассматриваются получение данных с помощью функций копирования, разбиение по ценовым интервалам и нормализация, а также отрисовка прямоугольников объёма и линий POC с использованием объектов графика и временных смещений для выявления на графике зон ликвидности с высокой активностью.
За пределами GARCH (Часть V): Подгонка мультифрактального спектра в MQL5 За пределами GARCH (Часть V): Подгонка мультифрактального спектра в MQL5
В этой статье мы реализуем модуль подгонки спектра: из tau(q) вычисляем f(alpha) дискретным преобразованием Лежандра, затем с помощью BLEIC подгоняем нормальный, биномиальный, пуассоновский и гамма-спектры с покомпонентными ограничениями. Выбирается модель с минимальной SSE; ее параметры — например, alpha_min, alpha_max или alpha_0, gamma — становятся входными параметрами каскада для мультифрактального моделирования.