Разработка индикатора рыночной энтропии: торговая система, основанная на теории информации
Содержание:
Введение
Трейдеры и разработчики алгоритмов постоянно сталкиваются с важным вопросом: имеет ли рынок в данный момент структуру, подходящую для стратегий следования за трендом, или же он находится в переходном либо хаотичном состоянии, где традиционные индикаторы в основном генерируют шум? Хотя большинство инструментов ориентированы на цену и производные от неё показатели, они редко позволяют количественно оценить степень случайности ценового движения. В результате многие системы применяют одну и ту же логику в любых рыночных условиях, что приводит к ложным входам, низкому качеству сигналов и нестабильной эффективности в непредсказуемой среде.
Для решения этой проблемы мы предлагаем методологию, которая формализует рыночную случайность с помощью энтропии Шеннона, применяемой к дискретизированным состояниям цены (рост, падение, движение без существенного изменения цены). Индикатор рыночной энтропии вычисляет нормализованный показатель энтропии в скользящем окне и сопоставляет его с отдельными рыночными режимами — TREND, TRANSITION и CHAOTIC — с использованием чётко определённых пороговых значений. Мы дополняем это быстрым и медленным горизонтами энтропии, импульсом энтропии, дивергенцией, а также обнаружением сжатия/расширения, чтобы информационное состояние было как измеримым, так и реализуемым в MQL5 в виде буферов, флагов режимов, визуальных маркеров и фильтров сигналов на основе правил.
Обзор индикатора и принципы его работы
Индикатор рыночной энтропии основан на теории информации Шеннона и преобразует ценовое движение в измеримое информационное содержание посредством расчёта энтропии. По своей сути индикатор анализирует ценовые движения, классифицируя каждый бар как одно из трёх состояний: восходящее движение, нисходящее движение или движение без существенного изменения цены (флэт), определяемое входным параметром PriceStep. Затем эти состояния обрабатываются в скользящем окне (EntropyPeriod), где индикатор подсчитывает частоту каждого состояния и применяет формулу энтропии:

Этот расчет показывает, какая степень случайности присутствует на рынке в данный момент. Когда преобладает один тип движения (например, преимущественно восходящие движения в условиях сильного тренда), энтропия становится низкой, как правило ниже 0,35, что свидетельствует о структурированных и предсказуемых рыночных условиях. Когда движения равномерно распределяются между направлениями вверх, вниз и состоянием без существенного изменения цены, энтропия поднимается выше 0,65, что указывает на хаотичное и непредсказуемое поведение рынка. Данный индикатор отображает режимы с помощью гистограммы с цветовой кодировкой: зеленый цвет обозначает трендовые условия, желтый — переходные, а красный — хаотические условия. Дополнительные линии отображают быструю энтропию (20), медленную энтропию (100) и дивергенцию между ними.
Переход от простого расчета энтропии к рабочим сигналам на покупку/продажу предполагает анализ взаимодействия энтропии на нескольких периодах и выявление критических переходов в структуре рынка. Сигналы на покупку, как правило, появляются во время перехода от фазы сжатия к фазе расширения. В частности, быстрая энтропия пересекает медленную снизу вверх, энтропия поднимается с уровня ниже 0,30 в диапазон 0,35–0,45, а импульс энтропии становится положительным. Это свидетельствует об организованном направленном движении после периода консолидации.

И наоборот, сигналы на продажу формируются, когда происходит обратное: быстрая энтропия пересекает медленную сверху вниз, энтропия пробивает отметку 0,55 и движется в сторону хаотической зоны, а импульс энтропии становится отрицательным, что указывает на разрушение рыночной структуры. Данный индикатор также выявляет «информационные шоки» (скачки энтропии на 25 % и более) и смены режимов. Все сигналы отображаются на основном графике в виде отдельных стрелок, чтобы избежать их скопления. Этот многоуровневый подход позволяет преобразовать абстрактную теорию информации в конкретные торговые сигналы, выявляя моменты, когда степень случайности рынка снижается (что благоприятно для торговли по тренду) или повышается (что служит предупреждением о флэте или хаотичных условиях).

Начало работы
//+------------------------------------------------------------------+ //| Entropy IT.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #property description "Market Entropy Indicator – Information Theory Visualization" #property description "Multi-period signal generation using entropy relationships" #property description "Features Histogram Compression/Decompression visualization" #property indicator_separate_window #property indicator_minimum -0.2 #property indicator_maximum 1.2 #property indicator_buffers 16 #property indicator_plots 10 //--- Entropy Histogram #property indicator_label1 "Entropy" #property indicator_type1 DRAW_COLOR_HISTOGRAM #property indicator_color1 clrGreen,clrYellow,clrRed #property indicator_width1 2 //--- Smoothed Entropy #property indicator_label2 "Smoothed Entropy" #property indicator_type2 DRAW_LINE #property indicator_color2 clrWhite //--- Entropy Momentum #property indicator_label3 "Entropy Momentum" #property indicator_type3 DRAW_LINE #property indicator_color3 clrCyan //--- Fast Entropy (short period) #property indicator_label4 "Fast Entropy" #property indicator_type4 DRAW_LINE #property indicator_color4 clrLime //--- Slow Entropy (long period) #property indicator_label5 "Slow Entropy" #property indicator_type5 DRAW_LINE #property indicator_color5 clrOrange //--- Entropy Divergence #property indicator_label6 "Divergence" #property indicator_type6 DRAW_LINE #property indicator_color6 clrMagenta //--- Compression/Decompression Indicator #property indicator_label7 "Compress/Decompress" #property indicator_type7 DRAW_COLOR_HISTOGRAM #property indicator_color7 clrBlue,clrRed,clrGray #property indicator_width7 2 //--- Flow Arrows (plot-based) #property indicator_label8 "Entropy Flow" #property indicator_type8 DRAW_COLOR_ARROW #property indicator_color8 clrLime,clrOrange //--- Shock Marker #property indicator_label9 "Information Shock" #property indicator_type9 DRAW_ARROW #property indicator_color9 clrMagenta //--- Regime Change Marker #property indicator_label10 "Regime Shift" #property indicator_type10 DRAW_ARROW #property indicator_color10 clrWhite
Для начала мы настроим индикатор так, чтобы он отображался в отдельном окне с нормализованным диапазоном от -0,2 до 1,2, что позволит нам наглядно визуализировать значения энтропии и их динамику. Мы определяем в общей сложности 16 буферов и 10 построений индикатора для поддержки нескольких уровней визуализации информации. Основой индикатора является гистограмма энтропии, в которой для обозначения различных рыночных режимов используется цветовая кодировка (зеленый, желтый, красный), а сглаженная линия энтропии позволяет более четко видеть базовый информационный тренд. Мы дополнительно расширяем эту концепцию, вводя импульс энтропии, который отражает скорость изменения информации, а также линии быстрой и медленной энтропии, позволяющие сравнивать краткосрочную и долгосрочную неопределенность на рынке.
Опираясь на эту основу, мы расширяем возможности визуализации, чтобы глубже отразить поведение рынка с помощью дивергенции энтропии, выделяющей различия между быстрой и медленной энтропией, а также гистограммы сжатия/расширения, которая наглядно отличает периоды накопления информации от периодов ее расширения. Чтобы сделать этот индикатор более пригодным для принятия решений, мы добавили стрелки потока энтропии, отображающие направление изменения энтропии, всплески случайности и маркеры смены режима, сигнализирующие о переходах между упорядоченными и хаотичными состояниями рынка.
//--- Inputs input int EntropyPeriod = 50; // Base entropy period input int SmoothingPeriod = 10; // Smoothing of entropy line input int MomentumPeriod = 5; // Momentum period input int FastEntropyPeriod = 20; // Fast entropy period for signals input int SlowEntropyPeriod = 100; // Slow entropy period for signals input int PriceStep = 1; // Minimum price change (points) to classify as move input double SignalThreshold = 0.15; // Threshold for signal generation input bool ShowSignals = true; // Draw buy/sell arrows on main chart input int SignalArrowOffset = 15; // Offset in points for arrow placement input bool UseDailyReset = true; // Reset entropy calculation daily input double CompressionZone = 0.30; // Entropy level considered compression zone input double DecompressionZone = 0.50; // Entropy level considered decompression zone input int CompressionBars = 5; // Bars to confirm compression/decompression input int MinSignalGap = 10; // Minimum bars between same signal types //--- Buffers (for subwindow display) double entropyBuffer[]; double entropyColors[]; double compressionIndicator[]; double compressionColors[]; double smoothBuffer[]; double momentumBuffer[]; double fastEntropyBuffer[]; double slowEntropyBuffer[]; double divergenceBuffer[]; double flowBuffer[]; double flowColors[]; double shockBuffer[]; double regimeShiftBuffer[]; //--- Signal buffers double buySignalBuffer[]; double sellSignalBuffer[]; //--- Internal arrays int states[]; // 0=flat, 1=up, 2=down int regime[]; // 0=trend, 1=transition, 2=chaotic int compressionState[]; // -1=compressing, 0=neutral, 1=decompressing //--- Daily tracking datetime currentDay; int dailyUp, dailyDown, dailyFlat, dailyBars; double prevDailyEntropy; //--- Multi-period tracking double prevFastEntropy, prevSlowEntropy; double prevMomentum; //--- Compression tracking double compressionLevel[]; double decompressionLevel[]; //--- Signal tracking to prevent duplicates datetime lastBuySignalTime; datetime lastSellSignalTime; datetime lastCompressionStartTime; datetime lastDecompressionStartTime; int lastBuyBar; int lastSellBar; int lastCompressionBar; int lastDecompressionBar; //--- Chart object prefix string objPrefix;
Здесь мы определяем входные параметры, которые управляют поведением системы, основанной на энтропии. Основные параметры, такие как EntropyPeriod, FastEntropyPeriod и SlowEntropyPeriod, позволяют измерять информацию на нескольких временных горизонтах, а SmoothingPeriod и MomentumPeriod помогают уточнять сигнал за счёт снижения шума и фиксации скорости изменения информации. Кроме того, мы вводим такие пороговые значения, как SignalThreshold, CompressionZone и DecompressionZone, для классификации состояний рынка и запуска значимых событий. Дополнительные параметры, такие как SignalArrowOffset, MinSignalGap и ShowSignals, обеспечивают чёткость визуальных сигналов и не дают им появляться слишком часто, а UseDailyReset позволяет индикатору ежедневно перезапускать расчёты энтропии, благодаря чему он адаптируется к внутридневному поведению рынка.
Для поддержки этой логики мы выделяем несколько буферов и внутренних массивов, которые обеспечивают как визуализацию, так и вычисления. Основные буферы (такие как entropyBuffer, momentumBuffer, fastEntropyBuffer и slowEntropyBuffer) хранят данные разных уровней анализа энтропии, тогда как другие, например compressionIndicator, flowBuffer и shockBuffer, предназначены для визуальных маркеров, выделяющих изменения структуры рынка. Буферы сигналов (buySignalBuffer и sellSignalBuffer) используются для вывода торговых возможностей на график, а внутренние массивы, такие как states, regime и compressionState, классифицируют поведение цены и рыночные условия. Кроме того, мы поддерживаем переменные отслеживания для дневной статистики, сравнений между периодами и времени подачи сигналов, чтобы предотвратить дублирование; это обеспечивает эффективность и логическую согласованность системы при обработке поступающих рыночных данных.
//+------------------------------------------------------------------+ //| OnInit | //+------------------------------------------------------------------+ int OnInit() { //--- Bind buffers SetIndexBuffer(0, entropyBuffer, INDICATOR_DATA); SetIndexBuffer(1, entropyColors, INDICATOR_COLOR_INDEX); SetIndexBuffer(2, smoothBuffer, INDICATOR_DATA); SetIndexBuffer(3, momentumBuffer, INDICATOR_DATA); SetIndexBuffer(4, fastEntropyBuffer, INDICATOR_DATA); SetIndexBuffer(5, slowEntropyBuffer, INDICATOR_DATA); SetIndexBuffer(6, divergenceBuffer, INDICATOR_DATA); SetIndexBuffer(7, compressionIndicator, INDICATOR_DATA); SetIndexBuffer(8, compressionColors, INDICATOR_COLOR_INDEX); SetIndexBuffer(9, flowBuffer, INDICATOR_DATA); SetIndexBuffer(10, flowColors, INDICATOR_COLOR_INDEX); SetIndexBuffer(11, shockBuffer, INDICATOR_DATA); SetIndexBuffer(12, regimeShiftBuffer, INDICATOR_DATA); SetIndexBuffer(13, buySignalBuffer, INDICATOR_DATA); SetIndexBuffer(14, sellSignalBuffer, INDICATOR_DATA); //--- Set empty value for all plots for(int i = 0; i <= 14; i++) PlotIndexSetDouble(i, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Arrow codes for plot-based markers PlotIndexSetInteger(8, PLOT_ARROW, 233); // up/down arrows for flow PlotIndexSetInteger(9, PLOT_ARROW, 167); // lightning PlotIndexSetInteger(10, PLOT_ARROW, 159); // diamond //--- Prefix for chart objects objPrefix = "MktEntropy_" + IntegerToString(ChartID()) + "_"; //--- Initialize tracking variables currentDay = 0; dailyUp = dailyDown = dailyFlat = dailyBars = 0; prevDailyEntropy = EMPTY_VALUE; prevFastEntropy = prevSlowEntropy = prevMomentum = EMPTY_VALUE; //--- Initialize signal tracking lastBuySignalTime = 0; lastSellSignalTime = 0; lastCompressionStartTime = 0; lastDecompressionStartTime = 0; lastBuyBar = -MinSignalGap; lastSellBar = -MinSignalGap; lastCompressionBar = -MinSignalGap; lastDecompressionBar = -MinSignalGap; //--- Initialize compression tracking arrays ArrayResize(compressionState, 1); ArrayResize(compressionLevel, 1); ArrayResize(decompressionLevel, 1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| OnDeinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, objPrefix); }
На этом этапе инициализации мы привязываем все необходимые буферы. Это гарантирует, что энтропия, импульс энтропии, дивергенция, состояния сжатия и торговые сигналы корректно сопоставляются для расчёта и визуализации. Кроме того, мы стандартизируем все графические построения индикатора, задавая для них пустое значение, чтобы предотвратить нежелательную отрисовку, и определяем специальные коды стрелок для визуальных маркеров, таких как направление потока, шоки и смены режима.
Кроме того, мы генерируем уникальный префикс объектов на основе идентификатора графика для безопасного управления объектами на графике, инициализируем все переменные отслеживания для дневных расчётов и сравнений между периодами, а также сбрасываем состояния, связанные с сигналами, чтобы избежать их дублирования. Затем мы подготавливаем динамические массивы для отслеживания сжатия и обеспечиваем надлежащую очистку на этапе деинициализации, удаляя все объекты, связанные с этим индикатором, что позволяет поддерживать окружение графика в чистом и эффективном состоянии.
//+------------------------------------------------------------------+ //| OnCalculate | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total < SlowEntropyPeriod) return 0; //--- Ensure arrays are large enough ArrayResize(states, rates_total); ArrayResize(regime, rates_total); ArrayResize(compressionState, rates_total); ArrayResize(compressionLevel, rates_total); ArrayResize(decompressionLevel, rates_total); int start = prev_calculated > 0 ? prev_calculated - 1 : 0; if(start < 0) start = 0; //--- Process bars for(int i = start; i < rates_total; i++) { UpdateDailyReset(i, time); ClassifyPriceMovement(i, close, time); CalculateAllEntropy(i); CalculateDivergenceAndMomentum(i); DetectCompression(i); if(ShowSignals) { DrawCompressionArrows(i, time, high, low); DrawTradingSignal(i, time, high, low); } UpdateMarkers(i); CalculateSmoothedEntropy(i); } UpdateRegimeLabel(time, rates_total); return rates_total; } //+------------------------------------------------------------------+ //| Update daily reset and manage signal timers | //+------------------------------------------------------------------+ void UpdateDailyReset(int i, const datetime &time[]) { if(!UseDailyReset) return; MqlDateTime dt; TimeToStruct(time[i], dt); datetime barDate = dt.year * 10000 + dt.mon * 100 + dt.day; if(barDate != currentDay) { currentDay = barDate; dailyUp = dailyDown = dailyFlat = dailyBars = 0; prevDailyEntropy = EMPTY_VALUE; if(ShowSignals) { ObjectsDeleteAll(0, objPrefix); ResetSignalTimers(); } } } //+------------------------------------------------------------------+ //| Reset all signal timers | //+------------------------------------------------------------------+ void ResetSignalTimers() { lastBuySignalTime = 0; lastSellSignalTime = 0; lastCompressionStartTime = 0; lastDecompressionStartTime = 0; lastBuyBar = -MinSignalGap; lastSellBar = -MinSignalGap; lastCompressionBar = -MinSignalGap; lastDecompressionBar = -MinSignalGap; }
Мы организуем основной цикл вычислений таким образом, чтобы обеспечить эффективную обработку рыночных данных: сначала мы проверяем, достаточно ли баров для проведения значимого анализа энтропии, а затем динамически изменяем размер всех рабочих массивов в соответствии с поступающими данными. Начиная с рассчитанной отправной точки, мы поочерёдно обрабатываем каждый бар и применяем последовательность модульных функций, которые разбивают алгоритм на чёткие этапы: сброс дневной статистики при необходимости, классификация ценовых движений по состояниям, вычисление энтропии по нескольким периодам и выделение признаков более высокого уровня, таких как импульс и дивергенция. Мы также выявляем фазы сжатия и, при включении данной функции, отображаем визуальные сигналы, такие как стрелки, обозначающие события сжатия и торговые сигналы. Наконец, мы обновляем визуальные маркеры и вычисляем сглаженную линию энтропии, чтобы обеспечить наглядное и понятное отображение информационной структуры рынка.
Наряду с этим мы используем механизм ежедневного сброса, чтобы обеспечить адаптацию индикатора к внутридневным условиям путём очистки накопленных статистических данных и сброса отслеживания энтропии в начале каждого нового торгового дня. Этот процесс также удаляет ранее отрисованные объекты графика и сбрасывает все таймеры сигналов, чтобы устаревшие или дублирующиеся сигналы не сохранялись между сессиями. Специальная функция сброса централизованно очищает все переменные тайминга и отслеживания баров, обеспечивая согласованность и стабильность генерации сигналов.
//+------------------------------------------------------------------+ //| Classify price movement into states (up/down/flat) | //+------------------------------------------------------------------+ void ClassifyPriceMovement(int i, const double &close[], const datetime &time[]) { if(i == 0) { states[i] = 0; return; } double change = close[i] - close[i-1]; double threshold = PriceStep * _Point; if(change > threshold) states[i] = 1; // up else if(change < -threshold) states[i] = 2; // down else states[i] = 0; // flat if(UseDailyReset && time[i] >= time[i-1]) { if(states[i] == 1) dailyUp++; else if(states[i] == 2) dailyDown++; else dailyFlat++; dailyBars++; } } //+------------------------------------------------------------------+ //| Calculate entropy for a given period | //+------------------------------------------------------------------+ double CalculateEntropy(int startIdx, int period, const int &statesArray[]) { int up = 0, down = 0, flat = 0; for(int j = startIdx - period; j < startIdx; j++) { if(statesArray[j] == 1) up++; else if(statesArray[j] == 2) down++; else flat++; } double p_up = (double)up / period; double p_down = (double)down / period; double p_flat = (double)flat / period; double entropy = 0.0; if(p_up > 0) entropy -= p_up * MathLog(p_up) / M_LN2; if(p_down > 0) entropy -= p_down * MathLog(p_down) / M_LN2; if(p_flat > 0) entropy -= p_flat * MathLog(p_flat) / M_LN2; return entropy / (MathLog(3) / M_LN2); // Normalized entropy }
В этом разделе кода мы преобразуем исходные данные о ценах в упрощённое представление на основе состояний, в котором каждый бар классифицируется как восходящее движение, нисходящее движение или движение без существенного изменения / флэт. Для этого текущая цена закрытия сравнивается с предыдущей, а затем применяется пороговое значение, чтобы отфильтровать незначительный шум. Таким образом, мы преобразуем непрерывные колебания цены в дискретные состояния (0, 1, 2), что необходимо для расчёта энтропии. В то же время, когда включён ежедневный сброс, мы отслеживаем, сколько восходящих, нисходящих движений и случаев движения без существенного изменения / флэт происходит в течение дня, что позволяет формировать текущий профиль поведения рынка и вести структурированный учёт ценовой активности с течением времени.
После классификации ценовых движений мы используем эти данные о состояниях для расчёта энтропии за заданный период, подсчитывая, как часто каждое состояние встречается в скользящем окне. Эти количества преобразуются в вероятности, которые затем подставляются в формулу энтропии для оценки уровня неопределённости на рынке. Этот расчёт гарантирует, что в итоговом значении учитываются только допустимые вероятности, а затем нормализует энтропию таким образом, чтобы её значение оставалось в диапазоне от 0 до 1. Это нормированное значение становится эффективной мерой структуры рынка: более низкие значения указывают на более предсказуемое поведение, а более высокие отражают растущую случайность или неупорядоченность.
//+------------------------------------------------------------------+ //| Calculate fast, slow, and base entropy | //+------------------------------------------------------------------+ void CalculateAllEntropy(int i) { //--- Fast entropy if(i >= FastEntropyPeriod) fastEntropyBuffer[i] = CalculateEntropy(i, FastEntropyPeriod, states); else fastEntropyBuffer[i] = EMPTY_VALUE; //--- Slow entropy if(i >= SlowEntropyPeriod) slowEntropyBuffer[i] = CalculateEntropy(i, SlowEntropyPeriod, states); else slowEntropyBuffer[i] = EMPTY_VALUE; //--- Base entropy if(i >= EntropyPeriod) { entropyBuffer[i] = CalculateEntropy(i, EntropyPeriod, states); ClassifyRegime(i); } else { entropyBuffer[i] = EMPTY_VALUE; regime[i] = -1; } } //+------------------------------------------------------------------+ //| Classify market regime based on entropy value | //+------------------------------------------------------------------+ void ClassifyRegime(int i) { if(entropyBuffer[i] < 0.35) { entropyColors[i] = 0; regime[i] = 0; // trend } else if(entropyBuffer[i] < 0.65) { entropyColors[i] = 1; regime[i] = 1; // transition } else { entropyColors[i] = 2; regime[i] = 2; // chaotic } }
Здесь мы рассчитываем энтропию на нескольких временных горизонтах, чтобы охватить как краткосрочное, так и долгосрочное поведение рынка, одновременно формируя ключевой базовый уровень для анализа. Значения быстрой и медленной энтропии вычисляются только при наличии достаточного объёма данных, что обеспечивает точность и предотвращает некорректные вычисления; эти значения сохраняются в соответствующих буферах для последующего сравнения. Затем базовая энтропия, служащая основным показателем рыночной неопределённости, рассчитывается на основе основного периода и сразу же передаётся на этап классификации. Если данных недостаточно, в буферы безопасно записываются пустые значения, а режим помечается как неопределённый, что позволяет сохранить целостность данных на протяжении всего процесса.
Затем мы классифицируем рынок по различным режимам на основе нормализованного значения энтропии, эффективно преобразуя численную неопределённость в значимые состояния рынка. Когда уровень энтропии низкий, рынок помечается как трендовый и ему присваивается соответствующий цвет, что указывает на структурированное и предсказуемое поведение. По мере роста энтропии рынок переходит в смешанное состояние, а в конечном итоге — в хаотический режим, в котором доминирует случайность. Эта классификация не только определяет визуальное представление с помощью цветовой кодировки, но и служит базовым слоем для анализа более высокого уровня, позволяя индикатору ясно и практически применимо интерпретировать и передавать лежащее в основе информационное состояние рынка.
//+------------------------------------------------------------------+ //| Calculate divergence and momentum | //+------------------------------------------------------------------+ void CalculateDivergenceAndMomentum(int i) { //--- Divergence (fast - slow) if(fastEntropyBuffer[i] != EMPTY_VALUE && slowEntropyBuffer[i] != EMPTY_VALUE) divergenceBuffer[i] = fastEntropyBuffer[i] - slowEntropyBuffer[i]; else divergenceBuffer[i] = EMPTY_VALUE; //--- Momentum if(i >= EntropyPeriod + MomentumPeriod && entropyBuffer[i] != EMPTY_VALUE && entropyBuffer[i - MomentumPeriod] != EMPTY_VALUE) momentumBuffer[i] = entropyBuffer[i] - entropyBuffer[i - MomentumPeriod]; else momentumBuffer[i] = EMPTY_VALUE; } //+------------------------------------------------------------------+ //| Detect compression/decompression states | //+------------------------------------------------------------------+ void DetectCompression(int i) { if(entropyBuffer[i] == EMPTY_VALUE) { compressionIndicator[i] = EMPTY_VALUE; return; } //--- Track levels compressionLevel[i] = entropyBuffer[i]; decompressionLevel[i] = entropyBuffer[i]; //--- Find local minima for compression if(i >= CompressionBars) { compressionLevel[i] = entropyBuffer[i]; for(int j = i - CompressionBars; j <= i; j++) { if(entropyBuffer[j] != EMPTY_VALUE && entropyBuffer[j] < compressionLevel[i]) compressionLevel[i] = entropyBuffer[j]; } } //--- Find local maxima for decompression if(i >= CompressionBars) { decompressionLevel[i] = entropyBuffer[i]; for(int j = i - CompressionBars; j <= i; j++) { if(entropyBuffer[j] != EMPTY_VALUE && entropyBuffer[j] > decompressionLevel[i]) decompressionLevel[i] = entropyBuffer[j]; } } //--- Determine state compressionState[i] = 0; // neutral if(i > 0 && entropyBuffer[i-1] != EMPTY_VALUE) { if(entropyBuffer[i] < entropyBuffer[i-1] && entropyBuffer[i] < CompressionZone) compressionState[i] = -1; // compressing else if(entropyBuffer[i] > entropyBuffer[i-1] && entropyBuffer[i] > DecompressionZone) compressionState[i] = 1; // decompressing } //--- Set indicator buffer and colors if(compressionState[i] == -1) { compressionIndicator[i] = entropyBuffer[i]; compressionColors[i] = 0; // Blue } else if(compressionState[i] == 1) { compressionIndicator[i] = entropyBuffer[i]; compressionColors[i] = 1; // Red } else if(compressionState[i] == 0 && entropyBuffer[i] < CompressionZone) { compressionIndicator[i] = entropyBuffer[i]; compressionColors[i] = 2; // Gray } else { compressionIndicator[i] = EMPTY_VALUE; } }
В этом разделе кода мы получаем выводы более высокого уровня из данных энтропии, рассчитывая как дивергенцию энтропии, так и импульс энтропии, что помогает понять, как информация изменяется на разных временных масштабах. Дивергенция рассчитывается как разница между быстрой и медленной энтропией, показывая, отклоняется ли краткосрочное поведение рынка от долгосрочной структуры, тогда как импульс энтропии измеряет скорость изменения энтропии за заданный период и указывает, становится ли рынок более упорядоченным или более хаотичным. Наряду с этим мы реализуем механизм обнаружения сжатия/расширения путём сканирования последних значений энтропии для выявления локальных минимумов и максимумов, эффективно фиксируя зоны, в которых информация либо сжимается (накапливается), либо расширяется.
Затем мы преобразуем эти наблюдения в практически применимые состояния, классифицируя каждый бар как находящийся в состоянии сжатия, расширения или нейтральном состоянии на основе как направления изменения энтропии, так и заранее заданных пороговых зон. Когда энтропия снижается и опускается ниже порога сжатия, считается, что рынок находится в сжатии, сигнализируя о возможном накоплении перед ценовым движением; тогда как рост энтропии выше порога расширения указывает на расширение и усиление беспорядка. Затем эти состояния визуализируются с помощью гистограммы с цветовой кодировкой, где разные цвета обозначают сжатие, расширение или условия низкой активности, что позволяет наглядно увидеть, как плотность информации меняется со временем, и выделить ключевые фазы структурного цикла рынка.
//+------------------------------------------------------------------+ //| Draw compression/decompression circles | //+------------------------------------------------------------------+ void DrawCompressionArrows(int i, const datetime &time[], const double &high[], const double &low[]) { if(!ShowSignals || i == 0 || compressionState[i] == 0) return; //--- Compression start if(compressionState[i] == -1 && compressionState[i-1] != -1) { if(i - lastCompressionBar >= MinSignalGap && (lastCompressionStartTime == 0 || time[i] - lastCompressionStartTime > MinSignalGap * PeriodSeconds())) { double arrowY = low[i] - SignalArrowOffset * _Point * 2; string objName = objPrefix + "compress_" + IntegerToString(i); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 108); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); lastCompressionStartTime = time[i]; lastCompressionBar = i; } } } //--- Decompression start else if(compressionState[i] == 1 && compressionState[i-1] != 1) { if(i - lastDecompressionBar >= MinSignalGap && (lastDecompressionStartTime == 0 || time[i] - lastDecompressionStartTime > MinSignalGap * PeriodSeconds())) { double arrowY = high[i] + SignalArrowOffset * _Point * 2; string objName = objPrefix + "decompress_" + IntegerToString(i); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 108); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrOrange); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); lastDecompressionStartTime = time[i]; lastDecompressionBar = i; } } } } //+------------------------------------------------------------------+ //| Check buy signal conditions | //+------------------------------------------------------------------+ bool IsBuySignal(int i) { bool buyCondition1 = (fastEntropyBuffer[i] > slowEntropyBuffer[i] && fastEntropyBuffer[i-1] <= slowEntropyBuffer[i-1]); bool buyCondition2 = (entropyBuffer[i] < 0.65); bool buyCondition3 = (momentumBuffer[i] > 0 && momentumBuffer[i] > momentumBuffer[i-1]); bool buyCondition4 = (divergenceBuffer[i] > -SignalThreshold); bool buyCondition5 = (regime[i] != 2); bool buyCondition6 = (compressionState[i] == 1 && entropyBuffer[i] > 0.25 && entropyBuffer[i] < 0.45); bool buyCondition7 = (compressionState[i-1] == -1 && compressionState[i] != -1 && momentumBuffer[i] > 0); return ((buyCondition1 && buyCondition2 && buyCondition3 && buyCondition4) || buyCondition6 || buyCondition7); } //+------------------------------------------------------------------+ //| Check sell signal conditions | //+------------------------------------------------------------------+ bool IsSellSignal(int i) { bool sellCondition1 = (fastEntropyBuffer[i] < slowEntropyBuffer[i] && fastEntropyBuffer[i-1] >= slowEntropyBuffer[i-1]); bool sellCondition2 = (entropyBuffer[i] > 0.55); bool sellCondition3 = (momentumBuffer[i] < 0 && momentumBuffer[i] < momentumBuffer[i-1]); bool sellCondition4 = (divergenceBuffer[i] < SignalThreshold); bool sellCondition5 = (regime[i] == 2 && regime[i-1] != 2); bool sellCondition6 = (divergenceBuffer[i] < -SignalThreshold && MathAbs(divergenceBuffer[i]) > MathAbs(divergenceBuffer[i-1])); bool sellCondition7 = (compressionState[i-1] == 1 && compressionState[i] != 1 && momentumBuffer[i] < 0); return ((sellCondition1 && sellCondition2 && sellCondition3) || sellCondition5 || sellCondition6 || sellCondition7); }
В функции DrawCompressionArrows мы визуализируем ключевые структурные события на рынке, рисуя маркеры, которые отмечают начало фаз сжатия и расширения. Когда система фиксирует переход в фазу сжатия (снижение энтропии), она размещает синюю стрелку-маркер под свечой, сигнализируя о том, что рынок вступает в фазу накопления информации. И наоборот, когда начинается расширение (увеличение энтропии), над свечой появляется оранжевый маркер, обозначающий начало расширения или высвобождения информации. Для сохранения наглядности и предотвращения перегруженности мы применяем правила интервалов, используя временные промежутки и разрывы между барами, чтобы сигналы не дублировались слишком часто, а также динамически управляем объектами графика с помощью уникальных имён и контролируемого удаления.
Опираясь на эти визуальные признаки, мы определяем структурированные условия для генерации сигналов на покупку и продажу, основанные на взаимодействии энтропии в нескольких измерениях. Сигналы на покупку срабатывают, когда краткосрочная энтропия становится выше долгосрочной, импульс энтропии усиливается, а рынок не находится в хаотическом режиме; при этом особое внимание уделяется переходам от сжатия к расширению. Сигналы на продажу, напротив, генерируются при возникновении противоположных условий — таких как ослабление импульса, усиление беспорядка или переходы в хаотические состояния, — а также при сильных сдвигах дивергенции и пробоях из фаз расширения.
//+------------------------------------------------------------------+ //| Draw buy/sell arrows | //+------------------------------------------------------------------+ void DrawTradingSignal(int i, const datetime &time[], const double &high[], const double &low[]) { if(!ShowSignals || i < SlowEntropyPeriod || fastEntropyBuffer[i] == EMPTY_VALUE || slowEntropyBuffer[i] == EMPTY_VALUE || entropyBuffer[i] == EMPTY_VALUE || momentumBuffer[i] == EMPTY_VALUE) return; //--- Buy signal if(IsBuySignal(i)) { if(i - lastBuyBar >= MinSignalGap && (lastBuySignalTime == 0 || time[i] - lastBuySignalTime > MinSignalGap * PeriodSeconds())) { double arrowY = low[i] - SignalArrowOffset * _Point; string objName = objPrefix + "buy_" + IntegerToString(i); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 233); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); buySignalBuffer[i] = low[i] - SignalArrowOffset * _Point; lastBuySignalTime = time[i]; lastBuyBar = i; } } } //--- Sell signal if(IsSellSignal(i)) { if(i - lastSellBar >= MinSignalGap && (lastSellSignalTime == 0 || time[i] - lastSellSignalTime > MinSignalGap * PeriodSeconds())) { double arrowY = high[i] + SignalArrowOffset * _Point; string objName = objPrefix + "sell_" + IntegerToString(i); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 234); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); sellSignalBuffer[i] = high[i] + SignalArrowOffset * _Point; lastSellSignalTime = time[i]; lastSellBar = i; } } } } //+------------------------------------------------------------------+ //| Update flow arrows, shock, and regime shift markers | //+------------------------------------------------------------------+ void UpdateMarkers(int i) { //--- Flow arrows if(i > 0 && entropyBuffer[i] != EMPTY_VALUE && entropyBuffer[i-1] != EMPTY_VALUE) { double delta = entropyBuffer[i] - entropyBuffer[i-1]; if(delta > 0.03) { flowBuffer[i] = 1.05; flowColors[i] = 0; } else if(delta < -0.03) { flowBuffer[i] = -0.05; flowColors[i] = 1; } else flowBuffer[i] = EMPTY_VALUE; } //--- Information shock if(i > 0 && entropyBuffer[i] != EMPTY_VALUE && entropyBuffer[i-1] != EMPTY_VALUE) { if(entropyBuffer[i] > entropyBuffer[i-1] * 1.25) shockBuffer[i] = 1.1; else shockBuffer[i] = EMPTY_VALUE; } //--- Regime shifts if(i > 0 && regime[i] != -1 && regime[i-1] != -1 && regime[i] != regime[i-1]) regimeShiftBuffer[i] = 0.9; else regimeShiftBuffer[i] = EMPTY_VALUE; }
Этот код преобразует выходные данные модели энтропии в визуальные сигналы на графике. Функция DrawTradingSignal определяет, выполняются ли условия для открытия позиции на покупку или продажу, используя заранее рассчитанные данные энтропии, импульса энтропии и дивергенции. При подтверждении сигнала на покупку под свечой строится зелёная стрелка вверх, а при сигнале на продажу над свечой строится красная стрелка вниз. Для поддержания качества сигналов и предотвращения избыточного наложения построений функция применяет ограничения по интервалам, используя как разрывы между индексами баров, так и временные задержки, чтобы сигналы не срабатывали повторно слишком близко друг к другу. Кроме того, каждый сигнал сохраняется в соответствующем буфере и отслеживается с помощью временных меток и индексов баров, что обеспечивает согласованное управление состоянием и предотвращает появление дублирующих записей.
Функция UpdateMarkers расширяет возможности уровня визуализации, позволяя глубже понять динамику изменения рыночной информации. Функция отслеживает темп изменения энтропии и отображает стрелки «потока», указывающие, увеличивается или уменьшается неопределённость, что помогает трейдерам понять направленное давление информации. Кроме того, функция выявляет резкие скачки энтропии, обозначая их как «информационные шоки», которые часто предшествуют волатильным движениям рынка. Наконец, функция выявляет смены режимов — переходы между трендовыми, переходными и хаотическими состояниями — и визуально их обозначает, что позволяет трейдерам быстро распознавать структурные изменения в поведении рынка.
//+------------------------------------------------------------------+ //| Calculate smoothed entropy | //+------------------------------------------------------------------+ void CalculateSmoothedEntropy(int i) { if(i < SmoothingPeriod) { smoothBuffer[i] = EMPTY_VALUE; return; } double sum = 0.0; int cnt = 0; for(int j = i - SmoothingPeriod + 1; j <= i; j++) { if(entropyBuffer[j] != EMPTY_VALUE) { sum += entropyBuffer[j]; cnt++; } } smoothBuffer[i] = (cnt > 0) ? sum / cnt : EMPTY_VALUE; } //+------------------------------------------------------------------+ //| Update regime label | //+------------------------------------------------------------------+ void UpdateRegimeLabel(const datetime &time[], int rates_total) { string labelText = ""; color labelColor = clrNONE; double lastEntropy = entropyBuffer[rates_total-1]; double lastFast = fastEntropyBuffer[rates_total-1]; double lastSlow = slowEntropyBuffer[rates_total-1]; double lastDiv = divergenceBuffer[rates_total-1]; int lastCompress = (rates_total-1 >= 0) ? compressionState[rates_total-1] : 0; if(lastEntropy != EMPTY_VALUE) { if(lastEntropy < 0.35) labelText = "TREND MODE (" + DoubleToString(lastEntropy, 2) + ")"; else if(lastEntropy < 0.65) labelText = "TRANSITION MODE (" + DoubleToString(lastEntropy, 2) + ")"; else labelText = "CHAOTIC MODE (" + DoubleToString(lastEntropy, 2) + ")"; if(lastCompress == -1) labelText += " | COMPRESSING"; else if(lastCompress == 1) labelText += " | DECOMPRESSING"; if(lastDiv != EMPTY_VALUE) { labelText += " | Div: " + DoubleToString(lastDiv, 2); if(lastFast != EMPTY_VALUE && lastSlow != EMPTY_VALUE) { if(lastFast > lastSlow) labelText += " (Bull)"; else labelText += " (Bear)"; } } labelColor = (lastEntropy < 0.35) ? clrGreen : (lastEntropy < 0.65) ? clrYellow : clrRed; } else { labelText = "MODE: WAITING FOR DATA"; labelColor = clrGray; } ObjectCreate(0, objPrefix + "regimeLabel", OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_YDISTANCE, 10); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_CORNER, 0); ObjectSetString(0, objPrefix + "regimeLabel", OBJPROP_TEXT, labelText); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_COLOR, labelColor); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_FONTSIZE, 11); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_BACK, false); } //+------------------------------------------------------------------+
В этой части кода основное внимание уделяется обработке исходных показаний энтропии, чтобы сделать их более пригодными для практического использования и удобными для визуальной интерпретации. Функция CalculateSmoothedEntropy применяет к базовым значениям энтропии простое скользящее среднее за заданный период сглаживания (SmoothingPeriod), что позволяет уменьшить шум и краткосрочные колебания. Благодаря усреднению только достоверных значений энтропии за данный период обеспечивается точное отражение сглаженной линией основного тренда рыночной неопределённости. Это помогает трейдерам лучше выявлять устойчивые режимы или сдвиги, а не реагировать на каждое незначительное колебание исходной энтропии.
Функция UpdateRegimeLabel развивает эту идею, обобщая текущее состояние рынка в виде одной легко читаемой метки на графике. Она анализирует последние значения энтропии, быстрой и медленной энтропии, дивергенции и состояния сжатия, чтобы определить, находится ли рынок в трендовом, переходном или хаотическом режиме, а также идет ли сжатие или расширение. Метка на графике также показывает бычьи или медвежьи тенденции на основе сравнения быстрой и медленной энтропии, а также содержит числовые значения для быстрого ориентирования. Благодаря динамическому обновлению текста и цвета метки эта функция дает трейдерам возможность мгновенно оценить рыночную ситуацию, объединяя несколько метрик, основанных на энтропии, в лаконичную визуальную сводку.
Демонстрация индикатора
Чтобы продемонстрировать эффективность данного индикатора, мы использовали настройки по умолчанию и провели тестирование на XAUUSD с таймфреймом H1, что позволило наглядно проследить, как сигналы, основанные на энтропии, ведут себя в условиях структурированного рынка. Тем не менее этот индикатор отличается высокой гибкостью и не ограничивается каким-либо конкретным инструментом или таймфреймом — его можно применять к любому выбранному вами торговому инструменту, при этом он динамически адаптируется к различным рыночным условиям.

С точки зрения практического применения простые правила торговли можно сформулировать следующим образом: торгуйте только тогда, когда рынок находится в режиме тренда или перехода при наличии подтверждающего импульса и дивергенции, и избегайте торговли в хаотичных условиях. Например, сделки на покупку предпочтительны, когда быстрая энтропия пересекает медленную снизу вверх при положительном импульсе энтропии и расширении, тогда как сделки на продажу предпочтительны, когда происходит обратное на фоне растущей неупорядоченности. С другой стороны, не следует открывать позиции при высокой энтропии (в хаотичном режиме), поскольку это помогает исключить входы на ложных пробоях и хаотичное ценовое движение; аналогично зоны сжатия помогают избежать преждевременных входов, снижая риск открытия позиции до того, как произойдет пробой. В частности, режим тренда помогает исключить ложные развороты, режим перехода отфильтровывает слабые сигналы продолжения тренда, а хаотический режим предотвращает открытие сделок в периоды высокой случайности.
Заключение
В данной работе абстрактная идея о том, что «рынки в той или иной степени носят случайный характер», превращается в конкретную реализуемую структуру и практическую реализацию для MQL5. Благодаря дискретизации баров, расчету нормированной энтропии Шеннона и применению четко определенных пороговых значений (например, энтропия < 0,35 → TREND, 0,35–0,65 → TRANSITION, > 0,65 → CHAOTIC) этот индикатор дает детерминированные метки режимов, а не расплывчатые впечатления. Дополнительные слои — быстрая и медленная энтропия, импульс энтропии, дивергенция и локальное сжатие/расширение — помогают выявлять структурное накопление, пробои и внезапные информационные шоки.
На практике, ознакомившись с этим индикатором и научившись им пользоваться, вы сможете:
- обозначать режим рынка (TREND / TRANSITION / CHAOTIC) и использовать его в качестве торгового фильтра;
- распознавать сжатие (низкую энтропию) и расширение (рост энтропии) как признаки подготовки движения и слома структуры;
- генерировать и прореживать сигналы на покупку/продажу с помощью пересечений быстрой и медленной энтропии, а также проверок импульса и дивергенции;
- выявлять информационные шоки и смены режимов для контроля рисков или автоматической приостановки сделок.
Кратко говоря, интеграция энтропии в вашу систему позволяет перейти от реактивной «погони за ценой» к проактивному анализу информации — это сокращает количество ложных входов, улучшает тайминг и обеспечивает проверяемый, готовый к реализации в коде фильтр, который можно встроить в стратегии MQL5 и рабочие процессы управления рисками.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21743
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.