English
preview
Рыночная микроструктура в MQL5 (Часть 4): Волатильность с эффектом памяти

Рыночная микроструктура в MQL5 (Часть 4): Волатильность с эффектом памяти

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

Введение

В первой части создан защитный базовый слой: математические функции с проверками, проверка ценовых данных и устойчивые статистические функции. Во второй части введена оценка показателя Херста со взвешиванием по достоверности. Она показала, что данные US100 на таймфрейме M1 в Globex находятся вблизи границы случайного блуждания: объединенная оценка H = 0,511, скользящая H ≈ 0,48. В третьей части добавлена функция-оценщик GPH параметра дробного дифференцирования d: объединенная оценка d = -0,006, стандартное отклонение между сессиями — 0,153.

Стандартные показатели волатильности — ATR и стандартное отклонение доходностей — исходят из независимости отдельных баров. При этом остаются без учета четыре эмпирических свойства волатильности: кластеризация — за крупными движениями следуют крупные; персистентность — шоки волатильности затухают медленно; асимметрия — отрицательная доходность повышает будущую волатильность сильнее равной ей по модулю положительной; смешение режимов — ряд может объединять разные законы масштабирования, и усредненные H и d тогда вводят в заблуждение. При работе с внутридневными данными US100 на таймфрейме M1 эти свойства прямо влияют на выбор размера позиции, размещение стоп-лоссов и фильтрацию сигналов. Если не учитывать эти свойства, торговая система будет систематически неверно откалибрована.

В этой статье в MicroStructure_Foundation.mqh добавляются две группы измерительных функций: (1) переработанная реализация мультифрактального спектра на основе MFDFA: она заменяет прежнюю прокси по разбросу τ и возвращает Δα с полем достоверности R²; (2) набор показателей волатильности: RealizedVolatility(), DurationVolatility(), FractionalVolatility(), прокси по мотивам FIGARCH, VolatilityClusteringIndex(), LeverageEffect(), JumpIntensity() и PopulateVolatilityAnalysis().

В этом эмпирическом исследовании используются фьючерсы NQ E-mini Nasdaq 100 с CME Globex, а не CFD-инструмент US100 из предыдущих черновиков. NQ — базовый контракт, торгуемый на бирже; US100 — розничный CFD-инструмент, цена которого формируется на основе индекса, а не реального потока ордеров по фьючерсу. Кластеризация волатильности, асимметрия эффекта левериджа, интенсивность скачков и мультифрактальная структура являются свойствами формирования цены на уровне биржи. Поэтому использование данных самого фьючерсного контракта — более корректный методологический выбор для такого анализа. Двухлетний набор данных NQ на таймфрейме M1 — 514 нью-йоркских сессий с мая 2024 г. по май 2026 г. — охватывает четыре значимых стрессовых эпизода: сворачивание керри-трейда после действий Банка Японии в августе 2024 года; ястребиный сюрприз ФРС в декабре 2024 года; тарифный шок "Дня освобождения" в апреле 2025 года; второй тарифный эпизод в апреле 2026 года.

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

Функция FIGARCHVolatility() не является подогнанной моделью FIGARCH. Для полноценной оценки FIGARCH нужна оптимизация методом максимального правдоподобия по сотням наблюдений; ее вычислительная стоимость не подходит для работы на языке MQL5 в реальном времени. Функция рассчитывает реализованную волатильность со степенными весами, определяемыми по оценке d методом GPH из третьей части. Это инженерная прокси по мотивам FIGARCH. Она учитывает структуру памяти волатильности, не требуя затрат на оценку полноценной модели FIGARCH.

Функция LeverageEffect() использует подогнанную модель GJR-GARCH(1,1,1) через библиотеку волатильности Дьюба (Юн, 2026). Здесь оцениваются реальные параметры модели, а не применяется эвристика. Функция возвращает нормированный показатель асимметрии γ/(α + γ + β): он не зависит от масштаба и позволяет сравнивать сессии напрямую. Флаг, задаваемый при компиляции, выбирает вариант с библиотекой Дьюба либо более простой резервный вариант. Оба описаны в разделе о структуре папок.

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

Результат работы — восемь функций, обновленных или добавленных в MicroStructure_Foundation.mqh; переработанная MFDFA, возвращающая Δα с полем достоверности R²; и полностью заполненная структура VolatilityAnalysis без полей-заглушек. Новый подключаемый файл не создается. Эмпирическую основу серии описывают две сопутствующие рабочие статьи, доступные на SSRN: SSRN 6809080 и SSRN 6847024.


Стилизованные факты о волатильности

Кластеризация волатильности означает, что за крупными изменениями цены обычно следуют другие крупные изменения любого знака, а за небольшими — небольшие. Энгл (1982) описал это явление как "эффект ARCH", за что получил Нобелевскую премию. Автокорреляция квадратов доходностей при лаге, равном 1, непосредственно характеризует кластеризацию. Для дневных данных типичны значения выше 0,2; во внутридневных данных кластеризация часто сильнее. Функция VolatilityClusteringIndex() измеряет кластеризацию напрямую. Это диагностическая прокси, а не подогнанная модель.

Персистентность волатильности означает медленное затухание ее шоков — гиперболическое, а не экспоненциальное, как в GARCH(1,1). Параметр дробного дифференцирования d из третьей части позволяет измерить эту память: если для ряда квадратов доходностей d > 0, у волатильности есть долгая память. Функция FractionalVolatility() непосредственно использует оценку d из третьей части, что намеренно связывает эту статью с частью 3. Ключевое упрощение состоит в том, что d оценивается по ряду доходностей, а затем применяется к квадратам доходностей. При этом для доходностей и волатильности предполагается один и тот же параметр памяти. Для внутридневных данных по акциям это разумное приближение, но оно не гарантировано.

Эффект левериджа (Блэк, 1976) описывает асимметрию: отрицательная доходность повышает будущую волатильность сильнее равной ей по модулю положительной. Стандартная модель эффекта — GJR-GARCH (Глостен, Джаганнатан и Ранкл, 1993). Она добавляет к GARCH(1,1) параметр асимметрии γ: σ²t = ω + α·ε²{t-1} + γ·I_{t-1}·ε²_{t-1} + β·σ²_{t-1}, где I_{t-1} = 1 при отрицательной доходности предыдущего периода. Функция LeverageEffect() оценивает модель через библиотеку Дьюба и возвращает нормированное отношение γ/(α + γ + β).

Скачки волатильности редки, но имеют серьезные последствия. Андерсен, Боллерслев и Диболд (2007) предложили бипауэр-вариацию BPV = (π/2)·mean(|r_t|·|r_{t-1}|) как базовый показатель, устойчивый к скачкам. Функция JumpIntensity() берет BPV за основу порога вместо выборочного стандартного отклонения: скачки, которые требуется обнаружить, сами искажают простой порог по σ. На данных NQ с таймфреймом M1 за 514 сессий средняя интенсивность скачков составляет 1,4%, а 90-й процентиль — 2,1%. Значение выше 5% скорее указывает на артефакты данных — ошибочные тики, смену контракта или аномалии потока котировок, — чем на реальные скачки рынка.

Мультифрактальность позволяет определить, описывается ли ряд одним законом масштабирования или сочетанием нескольких. Стандартная мера ширины спектра — Δα = α_max − α_min. Здесь α(q) — показатель Гельдера, получаемый преобразованием Лежандра из τ(q) = q·h(q) − 1. Обновленная функция MultifractalSpectrum() вычисляет эту ширину и записывает среднее R² регрессий масштабирования в поле достоверности. Узкая Δα (< 0,3) означает, что H и d надежно описывают ряд. Широкая Δα (> 0,5) означает, что в ряду есть противоположные режимы памяти, взаимно компенсирующиеся при усреднении.


Реализация 1 — переработанный мультифрактальный спектр (MFDFA)

Структура алгоритма MFDFA осталась неизменной. Изменен только выходной показатель: вместо прежнего разброса τ возвращается Δα, вычисленная преобразованием Лежандра; кроме того, функция принимает выходной параметр достоверности. Фильтр выбросов (|r| ≥ 0,1) сохранен: он исключает случаи смены контракта и ошибочные тики, которые иначе исказили бы моменты при отрицательных q, где в сумме q-х степеней доминируют крупные колебания.

Преобразование Лежандра выполняется следующим образом. Для каждой регрессии при заданном q сохраняется наклон h(q). Показатель Гельдера вычисляется как α(q) = h(q) + q·h′(q), где h′(q) приближенно определяется центральными конечными разностями. Ширина спектра равна Δα = max(α) - min(α). Показатель достоверности равен среднему R² по всем регрессиям при разных q. Если он ниже 0,65, масштабирование ненадежно и результат считается недоступным.

//+------------------------------------------------------------------+
//| MultifractalSpectrum: MFDFA returning Legendre-transform Δα.     |
//| confidence = mean R² of the log(F) ~ log(s) regressions.         |
//| Returns Δα = max(α) − min(α). Returns 0.0 on failure.            |
//|                                                                  |
//| Outlier filter: |r| >= 0.1 removed before profile construction.  |
//| MFDFA is sensitive to extreme values at negative q because large |
//| fluctuations dominate the q-th moment. Rollovers and bad ticks   |
//| must be excluded before the profile is built.                    |
//+------------------------------------------------------------------+
double MultifractalSpectrum(const string symbol, const int tf,
                             const int period, double &confidence,
                             int q_min = -5, int q_max = 5)
  {
   confidence = 0.0;
   if(!ValidateSymbolV2(symbol) || period < 100)
      return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, 0, period + 1, close) < period + 1)
      return 0.0;
   //--- Build log-return array; filter gross outliers
   double returns[];
   ArrayResize(returns, period);
   int valid = 0;
   for(int i = 0; i < period; i++)
     {
      if(close[i] <= 0.0 || close[i + 1] <= 0.0) continue;
      double r = SafeLog(close[i + 1]) - SafeLog(close[i]);
      if(MathAbs(r) < 0.1) returns[valid++] = r;
     }
   if(valid < 100) return 0.0;
   ArrayResize(returns, valid);
   //--- Cumulative sum profile (demeaned)
   double mean_r = 0.0;
   for(int i = 0; i < valid; i++) mean_r += returns[i];
   mean_r /= valid;
   double y[];
   ArrayResize(y, valid);
   double cum = 0.0;
   for(int i = 0; i < valid; i++) { cum += returns[i] - mean_r; y[i] = cum; }
   //--- Geometric scales (powers of two); minimum 4 full segments.
   //--- Minimum scale = 16: below this, linear detrending is unreliable
   //--- on noisy M1 data. Maximum = valid/4 ensures 4 full segments.
   int scales[];
   int max_scale = valid / 4, s = 16, scale_count = 0;
   while(s <= max_scale)
     { ArrayResize(scales, scale_count + 1); scales[scale_count++] = s; s *= 2; }
   if(scale_count < 2) return 0.0;
   //--- q values
   int    q_cnt = q_max - q_min + 1;
   double q_vals[], h_q[];
   ArrayResize(q_vals, q_cnt);
   ArrayResize(h_q,   q_cnt);
   ArrayInitialize(h_q, 0.0);
   for(int qi = 0; qi < q_cnt; qi++) q_vals[qi] = q_min + qi;
   double r2_sum = 0.0;
   int    r2_cnt = 0;
   for(int qi = 0; qi < q_cnt; qi++)
     {
      double q = q_vals[qi];
      double logF[], logS[];
      int points = 0;
      for(int sidx = 0; sidx < scale_count; sidx++)
        {
         int seg_s = scales[sidx], n_seg = valid / seg_s;
         if(n_seg < 2) continue;
         double F_sum = 0.0;
         int    seg_cnt = 0;
         for(int v = 0; v < n_seg; v++)
           {
            int    start = v * seg_s;
            double sx = 0.0, sy = 0.0, sxy = 0.0, sxx = 0.0;
            for(int k = 0; k < seg_s; k++)
              { sx += k; sy += y[start+k]; sxy += k*y[start+k]; sxx += k*k; }
            double dn = seg_s * sxx - sx * sx;
            if(MathAbs(dn) < DBL_MIN_POSITIVE) continue;
            double sl = (seg_s*sxy - sx*sy)/dn, ic = (sy - sl*sx)/seg_s;
            double var = 0.0;
            for(int k = 0; k < seg_s; k++)
              { double res = y[start+k] - (ic + sl*k); var += res*res; }
            var /= seg_s;
            if(q == 0.0) F_sum += MathLog(MathMax(var, DBL_MIN_POSITIVE));
            else         F_sum += MathPow(MathMax(var, DBL_MIN_POSITIVE), q/2.0);
            seg_cnt++;
           }
         if(seg_cnt == 0) continue;
         double Fq = (q == 0.0) ? MathExp(F_sum/seg_cnt)
                                 : MathPow(F_sum/seg_cnt, 1.0/q);
         if(Fq <= DBL_MIN_POSITIVE) continue;
         ArrayResize(logF, points+1); ArrayResize(logS, points+1);
         logF[points] = MathLog(Fq); logS[points] = MathLog((double)seg_s);
         points++;
        }
      if(points < 2) continue;
      double lsx=0,lsy=0,lsxy=0,lsxx=0;
      for(int p=0;p<points;p++){lsx+=logS[p];lsy+=logF[p];lsxy+=logS[p]*logF[p];lsxx+=logS[p]*logS[p];}
      double den = points*lsxx - lsx*lsx;
      if(MathAbs(den) < DBL_MIN_POSITIVE) continue;
      h_q[qi] = (points*lsxy - lsx*lsy) / den;
      //--- R² for this q regression
      double ym=lsy/points, ss_tot=0, ss_res=0;
      double pi_int = (lsy - h_q[qi]*lsx)/points;
      for(int p=0;p<points;p++)
        {
         double pr = pi_int + h_q[qi]*logS[p];
         ss_res += (logF[p]-pr)*(logF[p]-pr);
         ss_tot += (logF[p]-ym)*(logF[p]-ym);
        }
      if(ss_tot > DBL_MIN_POSITIVE) { r2_sum += 1.0 - ss_res/ss_tot; r2_cnt++; }
     }
   if(r2_cnt > 0) confidence = r2_sum / r2_cnt;
   //--- Legendre transform: α(q) = h(q) + q·h'(q)
   //--- h'(q) by central finite differences
   double alpha[];
   ArrayResize(alpha, q_cnt);
   for(int qi = 0; qi < q_cnt; qi++)
     {
      double dh;
      if(qi == 0)          dh = h_q[1] - h_q[0];
      else if(qi == q_cnt-1) dh = h_q[q_cnt-1] - h_q[q_cnt-2];
      else                  dh = (h_q[qi+1] - h_q[qi-1]) / 2.0;
      alpha[qi] = h_q[qi] + q_vals[qi] * dh;
     }
   double amax = alpha[0], amin = alpha[0];
   for(int qi = 1; qi < q_cnt; qi++)
     { if(alpha[qi] > amax) amax=alpha[qi]; if(alpha[qi] < amin) amin=alpha[qi]; }
   double delta_alpha = amax - amin;
   if(!MathIsValidNumber(delta_alpha) || delta_alpha > 10.0) return 0.0;
   return MathMax(0.0, delta_alpha);
  }

//+------------------------------------------------------------------+
//| PopulateMultifractalAnalysis: writes multifractal_width (Δα) and |
//| dimension_confidence (mean R²) into RobustFractalAnalysis.       |
//| Sessions with confidence < 0.65 have unreliable Δα estimates.    |
//+------------------------------------------------------------------+
void PopulateMultifractalAnalysis(const string symbol, const int tf,
                                   const int period, RobustFractalAnalysis &result)
  {
   result.multifractal_width    = 0.0;
   result.dimension_confidence  = 0.0;
   if(!ValidateSymbolV2(symbol) || period < 100) return;
   double conf = 0.0;
   double width = MultifractalSpectrum(symbol, tf, period, conf);
   if(MathIsValidNumber(width))       result.multifractal_width   = width;
   if(MathIsValidNumber(conf))        result.dimension_confidence = conf;
  }


Реализация 2 — реализованная волатильность и волатильность с поправкой на длительность

Функция RealizedVolatility() возвращает квадратный корень из среднего квадратов логарифмических доходностей. Это базовый оценщик: при расчете не предполагается определенная структура памяти или рыночных режимов. Функция DurationVolatility() переводит RV в годовое выражение с учетом длительности бара. В исходной реализации знаменателем было 252 × 86 400 секунд — расчет исходил из круглосуточной торговли. Нью-йоркская сессия длится с 09:30 до 16:00 ET — 23 400 секунд в день. После исправления функция использует 23 400 секунд для данных на таймфрейме M1, а для дневных и старших таймфреймов — длительность полных суток.

//+------------------------------------------------------------------+
//| RealizedVolatility: sqrt(mean(squared log-returns))              |
//+------------------------------------------------------------------+
double RealizedVolatility(const string symbol, const int tf,
                           const int window, int shift = 0)
  {
   if(!ValidateSymbolV2(symbol) || window < 2) return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, shift, window + 1, close) < window + 1) return 0.0;
   int    n_returns = ArraySize(close) - 1;
   double sum_sq = 0.0;
   int    valid  = 0;
   for(int i = 0; i < n_returns; i++)
     {
      if(close[i] <= 0.0 || close[i+1] <= 0.0) continue;
      double r = SafeLog(close[i+1]) - SafeLog(close[i]);
      sum_sq += r * r; valid++;
     }
   if(valid == 0) return 0.0;
   return MathSqrt(sum_sq / valid);
  }

//+------------------------------------------------------------------+
//| DurationVolatility: session-aware annualized realized volatility.|
//| Uses NY session seconds (23,400) for M1; full-day (86,400) for   |
//| H1 and above. The previous version used 86,400 for all frames,   |
//| which overstated annualized vol for session-filtered M1 data.    |
//+------------------------------------------------------------------+
double DurationVolatility(const string symbol, const int tf,
                           const int bars, int shift = 0)
  {
   if(!ValidateSymbolV2(symbol) || bars < 2) return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, shift, bars + 1, close) < bars + 1) return 0.0;
   int    n_returns = ArraySize(close) - 1;
   double sum_sq = 0.0;
   int    valid  = 0;
   for(int i = 0; i < n_returns; i++)
     {
      if(close[i] <= 0.0 || close[i+1] <= 0.0) continue;
      double r = SafeLog(close[i+1]) - SafeLog(close[i]);
      sum_sq += r * r; valid++;
     }
   if(valid == 0) return 0.0;
   double realized = MathSqrt(sum_sq / valid);
   int    tf_sec   = PeriodSeconds(tf);
   if(tf_sec <= 0) return 0.0;
   //--- NY session = 23,400 s for M1; full day for longer frames
   double day_sec    = (tf == PERIOD_M1) ? 23400.0 : 86400.0;
   double ann_factor = MathSqrt(252.0 * day_sec / tf_sec);
   return realized * ann_factor;
  }


Реализация 3 — фракционная волатильность

Функция FractionalVolatility() применяет ядро памяти ARFIMA к квадратам доходностей. Веса затухают пропорционально kd−1, где k — лаг, а d — оценка методом GPH из третьей части. При d = 0 веса одинаковы, а результат равен стандартной реализованной волатильности. При d > 0 более ранние наблюдения получают относительно больший вес; оценка волатильности учитывает дальнюю зависимость. При d < 0 веса убывают быстрее, чем при равномерном усреднении, отражая антиперсистентность.

//+------------------------------------------------------------------+
//| FractionalVolatility: ARFIMA memory-kernel weighted volatility.  |
//| Weights: w_k = k^(d-1). When d = 0, equals realized volatility.  |
//| Simplification: d from returns applied to squared returns.       |
//| This assumes return memory and volatility memory share the same  |
//| parameter — reasonable for intraday equity, not guaranteed.      |
//+------------------------------------------------------------------+
double FractionalVolatility(const string symbol, const int tf,
                             const int window, double d = -1.0)
  {
   if(!ValidateSymbolV2(symbol) || window < 5) return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, 0, window + 1, close) < window + 1) return 0.0;
   int n_returns = ArraySize(close) - 1;
   if(n_returns < 4) return 0.0;
   double returns[];
   ArrayResize(returns, n_returns);
   int valid_ret = 0;
   for(int i = 0; i < n_returns; i++)
     {
      if(close[i] <= 0.0 || close[i+1] <= 0.0) continue;
      double r = SafeLog(close[i+1]) - SafeLog(close[i]);
      if(MathAbs(r) < 0.1) returns[valid_ret++] = r;
     }
   if(valid_ret < 5) return 0.0;
   ArrayResize(returns, valid_ret);
   if(d <= -0.49 || d >= 0.49)
     { double conf_dummy; d = GPHEstimator(returns, valid_ret, conf_dummy); }
   d = MathMax(-0.49, MathMin(0.49, d));
   double sum_w = 0.0, sum_wrr = 0.0;
   for(int k = 1; k <= valid_ret; k++)
     {
      double w = MathPow((double)k, d - 1.0);
      if(!MathIsValidNumber(w) || w > 1e10) w = 1.0;
      sum_w   += w;
      sum_wrr += w * returns[valid_ret - k] * returns[valid_ret - k];
     }
   if(sum_w <= DBL_MIN_POSITIVE) return 0.0;
   return MathSqrt(MathMax(0.0, sum_wrr / sum_w));
  }


Реализация 4 — прокси по мотивам FIGARCH

Функция FIGARCHVolatility() не является подогнанной моделью FIGARCH. Полноценная FIGARCH требует оценки параметров методом максимального правдоподобия; для обработки в реальном времени 390-барных сессий на таймфрейме M1 это слишком затратно. Функция служит прокси для условной дисперсии со степенным взвешиванием. Рекурсия весов w_j = w_j-1 · (j-1-d)/j представляет собой усеченное ядро фракционного дифференцирования (Бэйли и соавт., 1996), непосредственно применяемое к квадратам доходностей. Для базовой дисперсии используется бипауэр-вариация, а не выборочное среднее: так нижняя граница остается устойчивой к отдельным скачкам доходности. Жестко заданные omega = 1e-6 и beta = 0.85 удалены. Теперь функция использует оценку d методом GPH из третьей части и базовую бипауэр-вариацию.

//+------------------------------------------------------------------+
//| FIGARCHVolatility: FIGARCH-inspired power-law conditional proxy. |
//|                                                                  |
//| THIS IS NOT A FITTED FIGARCH MODEL. It is a power-law weighted   |
//| realized volatility proxy using the fractional differencing      |
//| kernel of Baillie et al. (1996). The memory exponent d comes     |
//| from the Part 3 GPH estimate. The baseline is bipower variation, |
//| which is robust to isolated jump returns.                        |
//+------------------------------------------------------------------+
double FIGARCHVolatility(const string symbol, const int tf,
                          const int window, double d = -1.0)
  {
   if(!ValidateSymbolV2(symbol) || window < 20) return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, 0, window + 2, close) < window + 2) return 0.0;
   int n_returns = ArraySize(close) - 1;
   if(n_returns < 20) return 0.0;
   double returns[];
   ArrayResize(returns, n_returns);
   int valid_ret = 0;
   for(int i = 0; i < n_returns; i++)
     {
      if(close[i] <= 0.0 || close[i+1] <= 0.0) continue;
      double r = SafeLog(close[i+1]) - SafeLog(close[i]);
      if(MathAbs(r) < 0.1) returns[valid_ret++] = r;
     }
   if(valid_ret < 20) return 0.0;
   ArrayResize(returns, valid_ret);
   if(d <= -0.49 || d >= 0.49)
     { double conf_dummy; d = GPHEstimator(returns, valid_ret, conf_dummy); }
   d = MathMax(-0.49, MathMin(0.49, d));
   //--- Bipower variation baseline: BPV = (π/2)·mean(|r_t|·|r_{t-1}|)
   //--- Robust to isolated jumps: a single large return contaminates
   //--- at most two adjacent products, leaving the mean stable.
   double bpv_sum = 0.0;
   int    bpv_cnt = 0;
   for(int i = 1; i < valid_ret; i++)
     { bpv_sum += MathAbs(returns[i]) * MathAbs(returns[i-1]); bpv_cnt++; }
   double baseline_var = (bpv_cnt > 0)
                         ? (M_PI/2.0) * bpv_sum / bpv_cnt : 0.0;
   //--- Fractional differencing kernel: w_0 = 1; w_j = w_{j-1}*(j-1-d)/j
   int    L = MathMin(100, valid_ret - 1);
   double weights[];
   ArrayResize(weights, L);
   weights[0] = 1.0;
   for(int j = 1; j < L; j++)
     {
      weights[j] = weights[j-1] * (j - 1.0 - d) / (double)j;
      if(!MathIsValidNumber(weights[j]) || MathAbs(weights[j]) > 1e6)
         weights[j] = 0.0;
     }
   double cond_var = 0.0, sum_w = 0.0;
   for(int j = 0; j < L; j++)
     {
      int idx = valid_ret - 1 - j;
      if(idx < 0) break;
      cond_var += weights[j] * returns[idx] * returns[idx];
      sum_w    += weights[j];
     }
   if(sum_w > DBL_MIN_POSITIVE) cond_var /= sum_w;
   else                          cond_var  = baseline_var;
   cond_var = MathMax(cond_var, baseline_var);
   if(cond_var <= 0.0) return 0.0;
   return MathSqrt(cond_var);
  }


Реализация 5 — индекс кластеризации, эффект левериджа GJR и интенсивность скачков на основе бипауэр-вариации

Функция VolatilityClusteringIndex() не изменилась: она возвращает автокорреляцию квадратов доходностей при лаге, равном 1, — прямой диагностический показатель эффекта ARCH.

Функция LeverageEffect() была существенно переработана. Исходная реализация сравнивала дисперсию следующего бара после отрицательных и положительных доходностей. Это была простая эвристика, а не модель эффекта левериджа. В переработанной версии через библиотеку волатильности Дьюба подгоняется модель GJR-GARCH(1,1,1), а функция возвращает нормализованное отношение асимметрии γ/(α + γ + β). Это отношение не зависит от масштаба и сопоставимо между сессиями. Учитывается условие стационарности: функция возвращает 0.0, если α + γ/2 + β ≥ 1. Вариант выполнения выбирается флагом времени компиляции USE_GJR_LEVERAGE. Если библиотека Дьюба не установлена, функция компилируется и работает с исходной резервной оценкой дисперсии с разделением по знаку доходности.

Функция JumpIntensity() теперь берет бипауэр-вариацию в качестве базового уровня вместо выборочного стандартного отклонения. Доходность считается скачком, если ее квадрат превышает σ_mult ² × BPV. Такой подход устойчивее: скачкообразные доходности сами завышают выборочную σ, с которой их затем сравнивают.

//+------------------------------------------------------------------+
//| VolatilityClusteringIndex: lag-1 autocorrelation of squared rets |
//| Diagnostic for the ARCH effect. Values above 0.1 confirm         |
//| clustering; above 0.2 indicate strong, predictable volatility.   |
//+------------------------------------------------------------------+
double VolatilityClusteringIndex(const string symbol, const int tf,
                                   const int window)
  {
   if(!ValidateSymbolV2(symbol) || window < 3) return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, 0, window + 1, close) < window + 1) return 0.0;
   int    n_returns = ArraySize(close) - 1;
   if(n_returns < 3) return 0.0;
   double sq_ret[];
   ArrayResize(sq_ret, n_returns);
   int    valid = 0;
   for(int i = 0; i < n_returns; i++)
     {
      if(close[i] <= 0.0 || close[i+1] <= 0.0) continue;
      double r = SafeLog(close[i+1]) - SafeLog(close[i]);
      sq_ret[valid++] = r * r;
     }
   if(valid < 3) return 0.0;
   ArrayResize(sq_ret, valid);
   double mean = 0.0;
   for(int i = 0; i < valid; i++) mean += sq_ret[i];
   mean /= valid;
   double num = 0.0, den = 0.0;
   for(int i = 0; i < valid - 1; i++)
     {
      num += (sq_ret[i] - mean) * (sq_ret[i+1] - mean);
      den += (sq_ret[i] - mean) * (sq_ret[i]   - mean);
     }
   if(den <= DBL_MIN_POSITIVE) return 0.0;
   return MathMax(-1.0, MathMin(1.0, num / den));
  }

//+------------------------------------------------------------------+
//| LeverageEffect: GJR-GARCH(1,1,1) asymmetry parameter.            |
//| Returns γ/(α + γ + β) in [0, 1] — normalized, scale-free.        |
//|                                                                  |
//| Requires Dube volatility library. Add before your #include:      |
//|   #define USE_GJR_LEVERAGE                                       |
//| Without this define, falls back to next-bar variance asymmetry.  |
//| Returns 0.0 if model fails to converge or stationarity           |
//| condition α + γ/2 + β >= 1 is violated.                          |
//+------------------------------------------------------------------+
double LeverageEffect(const string symbol, const int tf,
                        const int window)
  {
   if(!ValidateSymbolV2(symbol) || window < 30) return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, 0, window + 1, close) < window + 1) return 0.0;
   int    n_returns = ArraySize(close) - 1;
   if(n_returns < 30) return 0.0;
   double SCALE = 100.0;
   double returns[];
   ArrayResize(returns, n_returns);
   int    valid = 0;
   for(int i = 0; i < n_returns; i++)
     {
      if(close[i] <= 0.0 || close[i+1] <= 0.0) continue;
      double r = SafeLog(close[i+1]) - SafeLog(close[i]);
      if(MathAbs(r) < 0.1) returns[valid++] = r * SCALE;
     }
   if(valid < 30) return 0.0;
   ArrayResize(returns, valid);

#ifdef USE_GJR_LEVERAGE
   //--- GJR-GARCH path (Dube library required)
   //--- Model: σ²_t = ω + α·ε²_{t-1} + γ·I_{t-1}·ε²_{t-1} + β·σ²_{t-1}
   //--- params order after fit(): [mu, omega, alpha, gamma, beta]
   vector obs;
   obs.Init(valid);
   for(int i = 0; i < valid; i++) obs[i] = returns[i];
   ArchParameters spec;
   spec.observations     = obs;
   spec.include_constant = true;
   spec.vol_model_type   = VOL_GJR_GARCH;
   spec.garch_p = 1;
   spec.garch_o = 1;
   spec.garch_q = 1;
   ConstantMean gjr_model;
   if(!gjr_model.initialize(spec)) return 0.0;
   ArchModelResult res = gjr_model.fit(SCALE);
   if(!res.params.Size()) return 0.0;
   double alpha = res.params[2];
   double gamma = res.params[3];
   double beta  = res.params[4];
   if((alpha + gamma/2.0 + beta) >= 1.0) return 0.0;  // stationarity
   if(alpha < 0.0 || gamma < 0.0 || beta < 0.0)  return 0.0;
   double denom = alpha + gamma + beta;
   if(denom <= DBL_MIN_POSITIVE) return 0.0;
   return MathMax(0.0, MathMin(1.0, gamma / denom));
#else
   //--- Fallback: next-bar variance asymmetry proxy
   //--- Measures sign-conditioned next-bar squared return ratio.
   //--- Less rigorous than GJR-GARCH; requires no external library.
   double sum_sq_neg = 0.0, sum_sq_pos = 0.0;
   int    cnt_neg = 0, cnt_pos = 0;
   for(int i = 0; i < valid - 1; i++)
     {
      double v = returns[i+1] * returns[i+1];
      if(returns[i] < 0.0) { sum_sq_neg += v; cnt_neg++; }
      else                  { sum_sq_pos += v; cnt_pos++; }
     }
   if(cnt_neg == 0 || cnt_pos == 0) return 0.0;
   double vn = sum_sq_neg/cnt_neg, vp = sum_sq_pos/cnt_pos;
   double dn = vn + vp;
   if(dn <= DBL_MIN_POSITIVE) return 0.0;
   return MathMax(-1.0, MathMin(1.0, (vn - vp) / dn));
#endif
  }

//+------------------------------------------------------------------+
//| JumpIntensity: proportion of returns classified as jumps.        |
//| Baseline: bipower variation BPV = (π/2)·mean(|r_t|·|r_{t-1}|).   |
//| A return is a jump when r²_t > sigma_mult² × BPV.                |
//| BPV is robust to isolated jumps; sample σ is not.                |
//| Values above 2% indicate elevated activity; above 5% indicates   |
//| data artifacts (bad ticks, rollovers). Reject if > 5%.           |
//+------------------------------------------------------------------+
double JumpIntensity(const string symbol, const int tf,
                      const int window, double sigma_mult = 3.0)
  {
   if(!ValidateSymbolV2(symbol) || window < 5 || sigma_mult <= 0.0) return 0.0;
   double close[];
   if(SafeCopyClose(symbol, tf, 0, window + 1, close) < window + 1) return 0.0;
   int    n_returns = ArraySize(close) - 1;
   if(n_returns < 5) return 0.0;
   double returns[];
   ArrayResize(returns, n_returns);
   int    valid = 0;
   for(int i = 0; i < n_returns; i++)
     {
      if(close[i] <= 0.0 || close[i+1] <= 0.0) continue;
      double r = SafeLog(close[i+1]) - SafeLog(close[i]);
      if(MathIsValidNumber(r)) returns[valid++] = r;
     }
   if(valid < 5) return 0.0;
   ArrayResize(returns, valid);
   //--- Bipower variation baseline
   double bpv_sum = 0.0;
   for(int i = 1; i < valid; i++)
      bpv_sum += MathAbs(returns[i]) * MathAbs(returns[i-1]);
   double bpv = (valid > 1) ? (M_PI/2.0) * bpv_sum / (valid - 1) : 0.0;
   if(bpv <= DBL_MIN_POSITIVE) return 0.0;
   double threshold_sq = sigma_mult * sigma_mult * bpv;
   int    jumps = 0;
   for(int i = 0; i < valid; i++)
      if(returns[i] * returns[i] > threshold_sq) jumps++;
   return (double)jumps / valid;
  }


Реализация 6 — функция-обертка PopulateVolatilityAnalysis

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

//+------------------------------------------------------------------+
//| PopulateVolatilityAnalysis: fills all VolatilityAnalysis fields. |
//| asymmetry = standardized skewness of log-returns.                |
//| This is a distributional diagnostic. The volatility asymmetry    |
//| is captured separately by leverage_effect (GJR γ ratio).         |
//+------------------------------------------------------------------+
void PopulateVolatilityAnalysis(const string symbol, const int tf,
                                  const int period, VolatilityAnalysis &result,
                                  RobustFractalAnalysis &rfa)
  {
   result.realized_vol     = RealizedVolatility(symbol, tf, period, 0);
   result.duration_vol     = DurationVolatility(symbol, tf, period, 0);
   result.fractional_vol   = FractionalVolatility(symbol, tf, period, -1.0);
   result.figarch_vol      = FIGARCHVolatility(symbol, tf, period, -1.0);
   result.clustering_index = VolatilityClusteringIndex(symbol, tf, period);
   result.leverage_effect  = LeverageEffect(symbol, tf, period);
   result.jump_intensity   = JumpIntensity(symbol, tf, period, 3.0);
   //--- Standardized skewness of log-returns
   double close[];
   result.asymmetry = 0.0;
   if(SafeCopyClose(symbol, tf, 0, period + 1, close) >= period + 1)
     {
      int    nret = ArraySize(close) - 1, vld = 0;
      double rets[];
      ArrayResize(rets, nret);
      for(int i = 0; i < nret; i++)
        {
         if(close[i] <= 0.0 || close[i+1] <= 0.0) continue;
         double r = SafeLog(close[i+1]) - SafeLog(close[i]);
         if(MathAbs(r) < 0.1) rets[vld++] = r;
        }
      if(vld > 3)
        {
         double mu = 0.0, var = 0.0, sk = 0.0;
         for(int i=0;i<vld;i++) mu += rets[i]; mu /= vld;
         for(int i=0;i<vld;i++) var += (rets[i]-mu)*(rets[i]-mu); var /= vld;
         double sd = MathSqrt(var);
         if(sd > DBL_MIN_POSITIVE)
           {
            for(int i=0;i<vld;i++) { double z=(rets[i]-mu)/sd; sk += z*z*z; }
            result.asymmetry = sk / vld;
           }
        }
     }
   //--- NaN/Inf guards
   if(!MathIsValidNumber(result.realized_vol))     result.realized_vol     = 0.0;
   if(!MathIsValidNumber(result.duration_vol))     result.duration_vol     = 0.0;
   if(!MathIsValidNumber(result.fractional_vol))   result.fractional_vol   = 0.0;
   if(!MathIsValidNumber(result.figarch_vol))      result.figarch_vol      = 0.0;
   if(!MathIsValidNumber(result.clustering_index)) result.clustering_index = 0.0;
   if(!MathIsValidNumber(result.leverage_effect))  result.leverage_effect  = 0.0;
   if(!MathIsValidNumber(result.jump_intensity))   result.jump_intensity   = 0.0;
   if(!MathIsValidNumber(result.asymmetry))        result.asymmetry        = 0.0;
   //--- Volatility confidence proxy
   rfa.volatility_confidence = result.clustering_index;
   if(!MathIsValidNumber(rfa.volatility_confidence)) rfa.volatility_confidence = 0.0;
  }


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

Все оценщики применялись к данным 514 нью-йоркских сессий фьючерса NQ E-mini Nasdaq 100 на CME Globex за период с мая 2024 года по май 2026 года. Фильтр нью-йоркской сессии сохраняет бары со временем открытия в интервале от 14:30 до 21:00 UTC. Сессии с менее чем 300 барами на таймфрейме M1 исключаются. Данные получены у поставщика фьючерсных котировок для розничных клиентов; биржа-источник — CME Group. Значения поля асимметрии винзоризированы по первому и 99-му процентилям (-1,39 и 1,85). Это ограничивает влияние трех сессий с экстремальной асимметрией — максимум 7,81, — указывающей на артефакты внутридневных данных, которые не отсеял фильтр |r| < 0,1. Во всех остальных полях используются исходные значения. Все 514 сессий проходят минимальный порог R² для MFDFA — 0,65; наименьшее наблюдаемое R² равно 0,851. Показанные здесь значения левериджа получены из прокси для дисперсии с разделением доходностей по знаку, рассчитанной скриптом анализа на Python. В MQL5 функция LeverageEffect() использует GJR-GARCH(1,1,1) через библиотеку Дьюба, если определен флаг USE_GJR_LEVERAGE. В этом режиме она возвращает значения только из интервала [0, 1]. Прокси возвращает значения в диапазоне [-1, +1], поэтому возможен отрицательный леверидж — он наблюдался во время тарифного эпизода апреля 2025 года.

Figure 1 — NQ realized volatility time series with regime shading

Рис. 1. Реализованная волатильность сессий NQ в годовом выражении (%) за 514 сессий, май 2024 г. - май 2026 г. Заштрихованы выявленные стрессовые эпизоды. Тарифный шок апреля 2025 года (темно-красный) выделяется сильнее всего: средняя годовая волатильность составила 38,2% — в 3,1 раза выше обычного уровня.

Таблица 1. Сводная статистика по 514 нью-йоркским сессиям фьючерса NQ на таймфрейме M1
Показатель Среднее значение Стандартное отклонение Мин. 25% Медиана 75% Макс.
Реализованная волатильность (на бар) 0,00042 0,00018 0,00013 0,00029 0,00038 0,00050 0,00310
Волатильность с поправкой на длительность (в годовом выражении, %) 13,39% 7,45% 4,38% 9,10% 11,88% 15,72% 96,53%
Фракционная волатильность (на бар) 0,00036 0,00015 0,00010 0,00024 0,00033 0,00044 0,00220
Прокси FIGARCH (на бар) 0,00041 0,00017 0,00012 0,00028 0,00037 0,00049 0,00290
Индекс кластеризации 0,146 0,096 -0,031 0,076 0,138 0,203 0,575
Эффект левериджа (прокси) 0,046 0,132 -0,596 -0,036 0,055 0,127 0,445
Интенсивность скачков (бипауэр-вариация, 3σ) 1,40% 0,57% 0,00% 1,03% 1,29% 1,80% 3,60%
Асимметрия (винзоризированный коэффициент асимметрии) -0,071 0,555 -1,386 -0,367 -0,139 0,169 1,850
d (оценка методом GPH) -0,010 0,077 -0,246 -0,061 -0,011 0,044 0,202

Figure 2 — Box plots of six NQ metrics by regime

Рис. 2. Распределение шести показателей по рыночным режимам. На графике волатильности с поправкой на длительность (вверху слева) тарифный шок апреля 2025 года выделяется как явный выброс. Индекс кластеризации (вверху по центру) заметно ниже при остром рыночном стрессе: во время кризисов волатильность становится менее предсказуемой от бара к бару. Δα (внизу по центру) остается стабильно высокой во всех режимах.

Три показателя волатильности — реализованная, фракционная и прокси FIGARCH — изменяются сходным образом: парные корреляции выше 0,97. Фракционная волатильность в среднем на 14% ниже реализованной. Это согласуется со слегка антиперсистентным d = -0,010 из третьей части: слабая антиперсистентность уменьшает вес последних наблюдений. Прокси FIGARCH занимает промежуточное положение между двумя оценками.

Два результата сравнения режимов требуют отдельного пояснения. Во-первых, при остром стрессе индекс кластеризации ниже: 0,107 во время тарифного шока против 0,147 в обычных сессиях. При остром стрессе волатильность становится более хаотичной и менее предсказуемой от бара к бару, а не сильнее кластеризуется. Эффект ARCH ослабевает именно тогда, когда он был бы наиболее полезен для прогнозирования. Во-вторых, во время тарифного шока апреля 2025 года прокси эффекта левериджа была отрицательной (-0,011): движения вверх усиливали волатильность сильнее движений вниз. Это согласуется с шорт-сквизами и резкими восстановительными ралли во время ценовых дислокаций, вызванных тарифами. Однако одного эпизода на одном инструменте недостаточно, чтобы установить причинно-следственную связь. Реализация GJR-GARCH на языке MQL5 учитывает асимметричную реакцию дисперсии через параметр γ. Однако приведенные здесь значения получены из прокси, а не из подогнанной модели GJR.

Во время тарифного эпизода апреля 2026 года волатильность в годовом выражении составила 11,5% против обычного уровня 12,5%. Это согласуется с предположением Брауна (2026e), что к 2026 году тарифный режим уже в значительной мере был учтен в ценах. Согласованность результатов двух работ служит косвенным подтверждением, хотя выборка охватывает один инструмент, а интерпретация остается предварительной.

Figure 3 — NQ scatter plots: clustering vs leverage and Δα vs vol

Рис. 3. Слева — зависимость индекса кластеризации от прокси эффекта левериджа по 514 сессиям (r = 0,127, p = 0,004). Эти два показателя, по-видимому, в значительной степени независимы и дополняют друг друга. Стрессовые режимы, отмеченные цветными точками, группируются в разных квадрантах. Особенно выделяется отрицательная прокси эффекта левериджа во время тарифного шока апреля 2025 года. Справа — Δα в сопоставлении с реализованной волатильностью (r = 0,022, p = 0,616). Мультифрактальность не коррелирует с уровнем волатильности, что согласуется с ее ролью структурной характеристики, не зависящей от величины волатильности.



Ширина мультифрактального спектра (Δα — преобразование Лежандра)

Для всех 514 сессий получены допустимые оценки MFDFA: средний показатель достоверности R² составляет 0,972, минимальный — 0,851. В каждой сессии показатель достоверности выше порога 0,65, поэтому ни одну оценку не требуется помечать как ненадежную по качеству. Это более сильный результат, чем в пилотном исследовании US100, где семь из 72 сессий не достигли порога. Он согласуется с более регулярным масштабированием реальной фьючерсной микроструктуры по сравнению с CFD-прокси, хотя различие может объясняться и другими факторами.

Показатель Δα Достоверность по R²
Сессии с допустимыми оценками 514 514
Среднее значение 0,889 0,972
Стандартное отклонение 0,176 0,024
Мин. 0,460 0,851
25% 0,764 0,962
Медиана 0,875 0,979
75% 0,985 0,990
Макс. 1,659 1,000

Значения Δα для фьючерсов NQ существенно выше, чем в пилотном исследовании CFD US100: медиана 0,875 против 0,498. Это ожидаемо: биржевые фьючерсы проявляют более сильную мультифрактальность, чем CFD-прокси, поскольку отражают реальную неоднородность потока ордеров. Взаимодействие информированных трейдеров, маркет-мейкеров, HFT-алгоритмов и институционального потока создает более сложную структуру масштабирования, чем брокерский продукт, отслеживающий индекс. Это согласуется с главным утверждением статьи: близкие к нулю H и d из второй и третьей частей — результат усреднения резко противоположных режимов памяти, а не свидетельство отсутствия памяти у ряда.

Таблица 2. Сводка по режимам (514 сессий)
Режим Сессии Средняя волатильность (%) Относительно нормального режима Кластеризация Эффект левериджа Среднее значение Δα
Нормальный 468 12,52 — 0,147 0,048 0,891
Шок Банка Японии (август 2024 г.) 8 23,77 1,9× 0,072 0,008 0,812
Решение ФРС (декабрь 2024 г.) 6 15,42 1,2× 0,207 0,047 0,774
Тарифы, апрель 2025 г. 14 38,22 3,1× 0,107 -0,011 0,885
Тарифы, апрель 2026 г. 18 11,46 0,9× 0,164 0,055 0,929

Распределение режимов по Δα:

  • Δα < 0,76 (умеренное смешение режимов) — 125 сессий (24,3%);
  • Δα 0,76-0,99 (сильное смешение режимов — типичный случай) — 265 сессий (51,6%);
  • Δα > 0,99 (экстремальное смешение режимов) — 124 сессии (24,1%).

В двухлетней выборке NQ нет почти монофрактальных сессий (Δα < 0,30). Минимальное наблюдаемое Δα составляет 0,460. Значит, нижний порог из пилотного исследования US100 (Δα < 0,30 — почти монофрактальный режим) к фьючерсам NQ неприменим. Во всех сессиях мультифрактальность как минимум умеренная; различается лишь степень смешения режимов.


Практическая интерпретация и торговые пороги

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

Показатель Пороговое значение Основание Интерпретация Действие
Индекс кластеризации > 0,20 75-й процентиль Выраженный эффект ARCH; волатильность предсказуема от бара к бару Выбирайте размер позиции с учетом целевой волатильности; отдавайте предпочтение прокси FIGARCH
Индекс кластеризации < 0,08 25-й процентиль Слабая кластеризация; доходности близки к независимым одинаково распределенным величинам (i.i.d.) Достаточно более простой оценки реализованной волатильности
Эффект левериджа (GJR) > 0,13 75-й процентиль Выраженная асимметрия; отрицательные шоки сильнее повышают волатильность Увеличьте расстояние до стоп-лосса для коротких позиций; уменьшите размер длинных позиций при входе
Эффект левериджа (GJR) < -0,10 — Обратная асимметрия (режим резкого отскока после стрессового эпизода) Не применяйте стандартную логику эффекта левериджа; уменьшите размер позиции
Интенсивность скачков > 2,1% 90-й процентиль Повышенная частота экстремальных доходностей Уменьшите размер позиции; проверьте поток котировок
Интенсивность скачков > 5% — Вероятный артефакт данных Отклоните данные сессии; не торгуйте
Δα (мультифрактальный показатель) < 0,76 25-й процентиль Умеренное смешение режимов; оценки H и d частично надежны Предпочтительна фракционная волатильность; используйте H и d с осторожностью
Δα (мультифрактальный показатель) > 0,99 75-й процентиль Экстремальное смешение режимов; средние оценки H и d ненадежны Отдавайте предпочтение прокси по мотивам FIGARCH; считайте H и d менее информативными
Достоверность по R² < 0,65 — Оценка MFDFA ненадежна (редкий случай для NQ) Считайте значение Δα недоступным
//--- Example: adaptive position sizing using volatility and multifractal width
RobustFractalAnalysis rfa;
VolatilityAnalysis va;
PopulateMultifractalAnalysis(Symbol(), PERIOD_M1, 390, rfa);
PopulateVolatilityAnalysis(Symbol(), PERIOD_M1, 90, va, rfa);

//--- Select volatility estimate based on regime
double current_vol;
if(rfa.multifractal_width > 0.99 && rfa.dimension_confidence > 0.65)
   current_vol = va.figarch_vol;       // strong mixing: use memory proxy
else if(va.clustering_index > 0.20)
   current_vol = va.fractional_vol;    // moderate clustering: fractional
else
   current_vol = va.realized_vol;      // normal: simple realized vol

//--- Scale to target volatility (0.0004 calibrated to NQ M1 mean realized vol)
double target_vol    = 0.0004;
double base_position = 1.0;
if(current_vol > DBL_MIN_POSITIVE)
   base_position = target_vol / current_vol;

//--- Reduce when jump activity is elevated
if(va.jump_intensity > 0.02)
   base_position *= MathMax(0.3, 1.0 - va.jump_intensity * 10.0);

//--- Widen stop asymmetrically when GJR leverage is significant
double stop_multiplier = 1.0;
if(va.leverage_effect > 0.10)
   stop_multiplier = 1.0 + va.leverage_effect;


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

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

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

Для использования функции LeverageEffect() с GJR-GARCH дополнительно установите библиотеку волатильности Дьюба, описанную и приложенную в статье 22258 сообщества MQL5 (Юн, К. Т., 2026).. После установки добавьте директиву define перед директивой include в индикаторе или советнике:

#define USE_GJR_LEVERAGE
#include "Includes\MicroStructure_Foundation.mqh"

Без директивы define файл компилируется и работает с резервной прокси для дисперсии с разделением по знаку доходности. Поле leverage_effect заполняется всегда; его интерпретация зависит от ветви выполнения, как указано в заголовке функции.


Заключение

В этой статье мы переработали и расширили функции волатильности в файле MicroStructure_Foundation.mqh, устранив методологические недостатки, выявленные модератором.

Этот вариант отличается от исходного тремя основными изменениями. Во-первых, функция LeverageEffect() теперь подгоняет модель GJR-GARCH(1,1,1) через библиотеку Дьюба и возвращает нормализованное отношение асимметрии γ/(α + γ + β), а не эвристическую оценку дисперсии следующего бара. Во-вторых, функция MultifractalSpectrum() теперь возвращает Δα, полученную преобразованием Лежандра, и показатель достоверности R² вместо прокси на основе разброса τ. В-третьих, функция JumpIntensity() использует бипауэр-вариацию как базовый уровень: порог остается устойчивым к влиянию тех самых скачков, которые он должен выявлять.

Эмпирическое исследование 514 сессий фьючерса NQ на таймфрейме M1 за май 2024 г. - май 2026 г. подтверждает: парные корреляции трех оценок волатильности выше 0,97; средняя интенсивность скачков на основе бипауэр-вариации составляет 1,4%, и ни в одной сессии она не превышает 5%; медиана Δα, полученной преобразованием Лежандра, равна 0,875 при среднем R², равном 0,972; все 514 сессий проходят порог достоверности R² 0,65.

Практический результат — полностью заполненная структура VolatilityAnalysis: realized_vol и fractional_vol характеризуют уровень волатильности и память; clustering_index — предсказуемость от бара к бару; leverage_effect (GJR) — асимметричную реакцию; jump_intensity отмечает сессии с артефактами; asymmetry описывает распределение доходностей; прокси FIGARCH дает условную дисперсию с поправкой на память, если Δα указывает на сильное смешение режимов. В пятой части эти показатели будут использоваться как обусловливающие входные признаки для оценки микроструктурного шума и анализа потока ордеров.


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

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

Прикрепленные файлы |
HurstProfile.mq5 (12.98 KB)
За пределами GARCH (Часть V): Подгонка мультифрактального спектра в MQL5 За пределами GARCH (Часть V): Подгонка мультифрактального спектра в MQL5
В этой статье мы реализуем модуль подгонки спектра: из tau(q) вычисляем f(alpha) дискретным преобразованием Лежандра, затем с помощью BLEIC подгоняем нормальный, биномиальный, пуассоновский и гамма-спектры с покомпонентными ограничениями. Выбирается модель с минимальной SSE; ее параметры — например, alpha_min, alpha_max или alpha_0, gamma — становятся входными параметрами каскада для мультифрактального моделирования.
Оптимизация на основе кривых Безье — Bezier Curve-based Optimization (BCO) Оптимизация на основе кривых Безье — Bezier Curve-based Optimization (BCO)
Разбираем Bezier Curve-based Optimization (BCO) — один из самых современных алгоритмов, в котором новые решения строятся как точки на кривых Безье. Переносим его в MQL5 и проверяем на стандартном стенде, композитном тесте и стенде со сдвигом области поиска. Попутно выясняем, что на самом деле обеспечивает его эффективность.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
За пределами GARCH (Часть IV): Реализация анализа разбиений в MQL5 За пределами GARCH (Часть IV): Реализация анализа разбиений в MQL5
В этой статье мы переходим от исследований на Python к нативной инженерной реализации на MQL5. Мы создаем первый модуль библиотеки MMAR: общий заголовочный файл с константами, класс OLS-регрессии на основе SVD, оценщик обобщенного показателя Херста и механизм анализа разбиений. Последний вычисляет функцию разбиения, определяет tau(q), оценивает H интерполяцией точки пересечения с нулем и оценивает мультифрактальность по результатам трех диагностических тестов. При проверке на 500 000 барах EURUSD на таймфрейме M10 механизм менее чем за четыре секунды правильно определил данные как мультифрактальные. Четвертая часть серии из восьми статей. В пятой части кривая tau(q) будет подгоняться к четырем распределениям-кандидатам с использованием преобразования Лежандра.