Автоматизация индикатора рыночной энтропии: Торговая система на основе теории информации
Содержание
Введение
Ранее был представлен индикатор рыночной энтропии, использующий энтропию Шеннона для количественной оценки неопределенности движения цены и классификации поведения рынка по трем режимам: тренд, переход и хаотический режим. Хотя индикатор делал видимыми эти режимы и зоны сжатия и расширения, его эффективное использование в торговле требовало постоянного ручного мониторинга и одновременной интерпретации нескольких признаков: пересечений быстрой и медленной энтропии, моментума, режима и состояния сжатия. Такой ручной процесс приводил к двум практическим проблемам: непоследовательному исполнению, когда разные трейдеры по-разному реагируют на один визуальный признак, и упущенным возможностям, когда переходы от сжатия к расширению не удается своевременно отрабатывать.
В этой статье эти проблемы решаются формализацией логики индикатора и созданием воспроизводимого, тестируемого советника на MQL5. Советник предназначен для системных трейдеров и разработчиков MQL5. Он реализует прозрачный и проверяемый конвейер обработки: классификацию состояний, расчет энтропии (быстрой, медленной и базовой), дивергенции и моментума, правила сигналов, совершение сделок и визуализацию. Он работает на каждом тике, совершает сделки по явным параметризованным правилам — LotSize, SL/TP, MagicNumber, MinSignalGap и опциональный разворот при противоположном сигнале — и записывает действия в лог для проверки. Цель — превратить визуальные наблюдения в детерминированную и проверяемую торговую логику, чтобы вы могли скомпилировать, запустить и проверить стратегию в Тестере стратегий и на реальном рынке.
Обзор работы советника
Советник рыночной энтропии обновляет расчеты энтропии на каждом рыночном тике, используя ценовые данные баров. Каждая свеча классифицируется как восходящая, нисходящая или нейтральная, что служит основой для расчета энтропии. На основе этих данных система вычисляет значения быстрой и медленной энтропии, рассчитывает моментум и выявляет зоны сжатия и расширения. Все аналитические процессы выполняются в фоновом режиме, а трейдер взаимодействует только с итоговыми сигналами на графике.
Сигнал на покупку обычно возникает, когда быстрая энтропия пересекает медленную снизу вверх, подтверждается положительным моментумом, а уровни энтропии указывают на условия вне хаотического режима. Кроме того, возможности для покупки могут возникать при пробое после сжатия, когда энтропия переходит из сжатого состояния в расширение при усилении моментума. Для повышения надежности эти сигналы дополнительно подтверждаются пороговыми значениями дивергенции, классификацией режима и переходами состояний сжатия. После подтверждения сигнала советник открывает позицию на покупку и отображает зеленую стрелку вверх под соответствующей свечой.

Сигналы на продажу строятся по обратной логике. Они возникают, когда быстрая энтропия пересекает медленную сверху вниз в условиях повышенной энтропии или ослабления моментума. К дополнительным триггерам относятся переходы в хаотический режим или завершение фаз расширения, сопровождающихся отрицательным моментумом. После подтверждения сигнала советник открывает позицию на продажу и отображает красную стрелку вниз над соответствующей свечой.

Когда включен разворот при противоположном сигнале, советник динамически адаптируется к меняющимся рыночным условиям. Если при открытой позиции обнаруживается противоположный сигнал, система закрывает текущую сделку и немедленно открывает новую позицию в направлении нового сигнала. Если эта функция отключена, позиции удерживаются до достижения стоп-лосса или тейк-профита.
Начало работы
//+------------------------------------------------------------------+ //| Auto Entropy IT.mq5 | //| Git, Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/ru/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "Git, Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ru/users/johnhlomohang/" #property version "1.00" #property description "Based on Market Entropy Indicator" #property description "Market Entropy EA – Information Theory Based Automated Trading" #property description "Retains all indicator visualizations except separate histogram window" #property strict #include <Trade/Trade.mqh> //--- Indicator Inputs (original) 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 //--- EA Trading Inputs input int StopLoss = 100; // Stop Loss (points) input int TakeProfit = 200; // Take Profit (points) input bool ExtOnOppstTrd = true; // Close opposite and reverse on opposite signal input ulong MagicNumber = 202503; // EA Magic Number input double LotSize = 0.1; // Fixed lot size input int MaxRetries = 3; // Max retries for order execution input int RetryDelay = 100; // Delay between retries (ms)
Для начала определим все основные входные параметры, управляющие работой советника. Эти входные параметры относятся к анализу и торговле. Задаются параметры расчета энтропии: базовый, быстрый и медленный периоды, а также чувствительность к моментуму и цене для классификации движения рынка. Также настраиваются генерация и отображение сигналов: пороговые значения, зоны сжатия и расширения, видимость стрелок и интервал между сигналами, чтобы избежать чрезмерной торговли.
Кроме того, предусмотрены параметры логики ежедневного сброса, сохраняющие актуальность расчетов. В торговой части задаются правила управления рисками и исполнения ордеров: уровни стоп-лосса и тейк-профита, размер позиции и магическое число для отслеживания сделок. Также предусмотрены разворот позиции при противоположном сигнале и логика повторных попыток для надежного исполнения ордеров в различных рыночных условиях.
//--- Internal calculation arrays double entropyBuffer[]; double smoothBuffer[]; double momentumBuffer[]; double fastEntropyBuffer[]; double slowEntropyBuffer[]; double divergenceBuffer[]; //--- State 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; //--- 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; //--- Current bar signal tracking int lastTradeBar; bool hasTradedThisBar; double lastTradePrice; //--- Chart object prefix string objPrefix; //--- Trading objects CTrade trade; int m_prevCalculated; int m_barsCount; bool m_firstTick; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Prefix for chart objects objPrefix = "MktEntropyEA_" + IntegerToString(ChartID()) + "_"; //--- Initialize tracking variables currentDay = 0; dailyUp = dailyDown = dailyFlat = dailyBars = 0; //--- Initialize signal tracking lastBuySignalTime = 0; lastSellSignalTime = 0; lastCompressionStartTime = 0; lastDecompressionStartTime = 0; lastBuyBar = -MinSignalGap; lastSellBar = -MinSignalGap; lastCompressionBar = -MinSignalGap; lastDecompressionBar = -MinSignalGap; lastTradeBar = -1; hasTradedThisBar = false; lastTradePrice = 0; //--- Trading initialization trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(20); trade.SetTypeFilling(ORDER_FILLING_FOK); m_prevCalculated = 0; m_barsCount = 0; m_firstTick = true; return INIT_SUCCEEDED; }
Здесь определяются внутренние массивы, в которых хранятся все вычисления, необходимые для работы системы. К ним относятся буферы значений энтропии, сглаженных данных, моментума, а также быстрой и медленной энтропии, используемых для генерации сигналов. Также отслеживается дивергенция между этими показателями, чтобы лучше понимать изменения в поведении рынка.
Кроме того, используются массивы состояний, классифицирующие каждый бар как восходящий, нисходящий или нейтральный, определяющие текущий рыночный режим и устанавливающие, находится ли рынок в сжатии или расширении. Вспомогательные переменные отслеживают ежедневную активность, включая число направленных движений и уровни сжатия для выявления локальных экстремумов. Для чистого формирования сигналов сохраняются временные метки и индексы баров последних сигналов, что предотвращает дубликаты и обеспечивает интервал между сделками.
Эти компоненты инициализируются в OnInit(). Это подготавливает советник к выполнению. Для объектов графика назначается уникальный префикс, чтобы упорядочить графические элементы и избежать конфликтов. Все переменные отслеживания сбрасываются в исходные состояния, что исключает влияние остаточных данных на новые вычисления. Таймеры сигналов и переменные отслеживания баров инициализируются с учетом минимального интервала между сигналами, а торговые флаги предотвращают многократное исполнение на одном баре. Также настраивается торговый объект: задаются магическое число, допустимое проскальзывание и тип исполнения ордера. Наконец, инициализируются переменные управления расчетами, чтобы советник с первого тика работал точно и стабильно, эффективно обрабатывая новые данные по мере их поступления.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, objPrefix); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Update calculations int rates_total = Bars(_Symbol, _Period); if(rates_total < SlowEntropyPeriod) return; //--- Check if we're on a new bar static int lastBarsCount = 0; if(rates_total != lastBarsCount) { lastBarsCount = rates_total; hasTradedThisBar = false; } //--- Resize all calculation arrays ArrayResize(entropyBuffer, rates_total); ArrayResize(smoothBuffer, rates_total); ArrayResize(momentumBuffer, rates_total); ArrayResize(fastEntropyBuffer, rates_total); ArrayResize(slowEntropyBuffer, rates_total); ArrayResize(divergenceBuffer, rates_total); ArrayResize(states, rates_total); ArrayResize(regime, rates_total); ArrayResize(compressionState, rates_total); ArrayResize(compressionLevel, rates_total); ArrayResize(decompressionLevel, rates_total); //--- Determine starting point for calculation int start = m_prevCalculated; if(m_firstTick) { start = 0; m_firstTick = false; } else { if(rates_total > m_prevCalculated) start = MathMax(0, m_prevCalculated - 3); else start = MathMax(0, rates_total - 3); } //--- Copy price data arrays datetime time[]; double open[], high[], low[], close[]; if(CopyTime(_Symbol, _Period, 0, rates_total, time) < rates_total) return; if(CopyOpen(_Symbol, _Period, 0, rates_total, open) < rates_total) return; if(CopyHigh(_Symbol, _Period, 0, rates_total, high) < rates_total) return; if(CopyLow(_Symbol, _Period, 0, rates_total, low) < rates_total) return; if(CopyClose(_Symbol, _Period, 0, rates_total, close) < rates_total) return; //--- Process bars for(int i = start; i < rates_total; i++) { UpdateDailyReset(i, time); ClassifyPriceMovement(i, close, time); CalculateAllEntropy(i); CalculateDivergenceAndMomentum(i); DetectCompression(i); } m_prevCalculated = rates_total; m_barsCount = rates_total; //--- Check current bar for signals int currentBar = rates_total - 1; if(!hasTradedThisBar && currentBar >= SlowEntropyPeriod) { if(fastEntropyBuffer[currentBar] != EMPTY_VALUE && slowEntropyBuffer[currentBar] != EMPTY_VALUE && entropyBuffer[currentBar] != EMPTY_VALUE && momentumBuffer[currentBar] != EMPTY_VALUE) { bool buySig = IsBuySignal(currentBar); bool sellSig = IsSellSignal(currentBar); //--- Draw arrows first if(ShowSignals) { DrawCompressionArrows(currentBar, time, high, low); DrawTradingSignal(currentBar, time, high, low); } //--- Check for trades if(buySig || sellSig) { Print("=== Signal Detected ==="); //--- Check existing position bool hasBuy = false, hasSell = false; ulong ticketBuy = 0, ticketSell = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) { if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { hasBuy = true; ticketBuy = ticket; } else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { hasSell = true; ticketSell = ticket; } } } } Print("Current Position - Has Buy: ", hasBuy, " | Has Sell: ", hasSell); //--- Execute trades if(!hasBuy && !hasSell) { if(buySig && (currentBar - lastBuyBar >= MinSignalGap)) { Print(">>> Executing BUY trade <<<"); ExecuteTrade(ORDER_TYPE_BUY); lastBuyBar = currentBar; hasTradedThisBar = true; } else if(sellSig && (currentBar - lastSellBar >= MinSignalGap)) { Print(">>> Executing SELL trade <<<"); ExecuteTrade(ORDER_TYPE_SELL); lastSellBar = currentBar; hasTradedThisBar = true; } } else if(ExtOnOppstTrd) { if(hasBuy && sellSig) { Print(">>> Reversing: Close BUY, Open SELL <<<"); if(PositionSelectByTicket(ticketBuy)) { if(trade.PositionClose(ticketBuy)) { Sleep(RetryDelay); ExecuteTrade(ORDER_TYPE_SELL); lastSellBar = currentBar; hasTradedThisBar = true; } } } else if(hasSell && buySig) { Print(">>> Reversing: Close SELL, Open BUY <<<"); if(PositionSelectByTicket(ticketSell)) { if(trade.PositionClose(ticketSell)) { Sleep(RetryDelay); ExecuteTrade(ORDER_TYPE_BUY); lastBuyBar = currentBar; hasTradedThisBar = true; } } } } Print("========================"); } } } //--- Update label UpdateRegimeLabel(time, rates_total); }
Функция деинициализации отвечает за очистку, а функция OnTick — за основной цикл выполнения торгового советника. При удалении советника все объекты графика с указанным префиксом удаляются, чтобы сохранить порядок на графике и избежать лишних визуальных элементов. Во время выполнения OnTick сначала проверяет, достаточно ли баров для достоверного расчета энтропии. Затем обновляются рыночные данные. Определяется появление нового бара, чтобы сбросить флаг, запрещающий повторное открытие сделки на том же баре, после чего размер всех внутренних массивов приводится в соответствие с текущим количеством баров. Для повышения эффективности вычисления не запускаются с нуля на каждом тике; вместо этого пересчитываются только последние бары. Ценовые данные копируются в локальные массивы, после чего каждый бар последовательно обрабатывается для ежедневного сброса, классификации движения цены, вычисления значений энтропии, моментума и дивергенции, а также выявления состояний сжатия.
После завершения расчетов последний бар проверяется на наличие подтвержденных торговых сигналов. Если все необходимые данные доступны и на текущем баре еще не совершена сделка, советник проверяет условия для покупки или продажи. Сначала отображаются визуальные сигналы, чтобы сохранить согласованность между отображением на графике и исполнением ордеров. Затем советник просматривает существующие позиции, связанные с его магическим числом и символом, чтобы определить текущую экспозицию. Если позиции нет, советник открывает новую сделку по обнаруженному сигналу с учетом минимального интервала между сигналами. Если позиция уже открыта и включен разворот при противоположном сигнале, советник закрывает существующую сделку. Затем советник открывает новую сделку в противоположном направлении. В ходе этого процесса флаги обновляются, чтобы предотвратить дублирование сделок в пределах одного бара, а для прозрачности выводятся подробные сообщения лога. Наконец, обновляется метка режима на графике, отражающая последнее состояние рынка.
//+------------------------------------------------------------------+ //| Execute Trade | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE tradeType) { double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Calculate stop loss double sl = (tradeType == ORDER_TYPE_BUY) ? price - StopLoss * _Point : price + StopLoss * _Point; //--- Calculate take profit double tp = (tradeType == ORDER_TYPE_BUY) ? price + TakeProfit * _Point : price - TakeProfit * _Point; //--- Normalize prices price = NormalizeDouble(price, _Digits); if(StopLoss > 0) sl = NormalizeDouble(sl, _Digits); if(TakeProfit > 0) tp = NormalizeDouble(tp, _Digits); //--- Use fixed lot size double volume = LotSize; //--- Execute trade with retries string comment = StringFormat("Entropy_%s", (tradeType == ORDER_TYPE_BUY) ? "BUY" : "SELL"); bool success = false; for(int retry = 0; retry < MaxRetries; retry++) { success = trade.PositionOpen(_Symbol, tradeType, volume, price, (StopLoss > 0) ? sl : 0, (TakeProfit > 0) ? tp : 0, comment); if(success) { lastTradePrice = price; Print(StringFormat("Trade Opened: %s | Price: %.5f | SL: %.5f | TP: %.5f | Lots: %.2f", (tradeType == ORDER_TYPE_BUY) ? "BUY" : "SELL", price, (StopLoss > 0) ? sl : 0, (TakeProfit > 0) ? tp : 0, volume)); break; } else { Print(StringFormat("Trade failed (attempt %d/%d): %s", retry+1, MaxRetries, trade.ResultRetcodeDescription())); if(retry < MaxRetries - 1) { Sleep(RetryDelay); // Refresh price for retry price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); price = NormalizeDouble(price, _Digits); if(StopLoss > 0) sl = (tradeType == ORDER_TYPE_BUY) ? price - StopLoss * _Point : price + StopLoss * _Point; if(TakeProfit > 0) tp = (tradeType == ORDER_TYPE_BUY) ? price + TakeProfit * _Point : price - TakeProfit * _Point; } } } }
Эта функция отвечает за совершение сделок. Цена входа выбирается в зависимости от типа ордера: Ask для покупок и Bid для продаж. Затем рассчитываются уровни стоп-лосса и тейк-профита относительно этой цены, а все значения нормализуются в соответствии с точностью символа. Применяется фиксированный размер лота, а сделка совершается с помощью механизма повторных попыток для повышения надежности при временных сбоях. Если ордер исполнен успешно, сохраняется цена входа и выводится подробное сообщение лога с типом сделки, ценой, стоп-лоссом, тейк-профитом и объемом. Если ордер не исполняется, ошибка записывается в лог, затем выполняется короткая пауза, обновляется рыночная цена, пересчитываются уровни и попытка повторяется до достижения максимального числа попыток.
//+------------------------------------------------------------------+ //| Check buy signal conditions | //+------------------------------------------------------------------+ bool IsBuySignal(int i) { if(i < 1) return false; //--- Crossover condition (primary) bool crossUp = (fastEntropyBuffer[i] > slowEntropyBuffer[i] && fastEntropyBuffer[i-1] <= slowEntropyBuffer[i-1]); //--- Entropy not too chaotic bool entropyOk = (entropyBuffer[i] < 0.70); // Relaxed from 0.65 //--- Positive momentum bool momentumOk = (momentumBuffer[i] > 0); //--- Divergence not too negative bool divergenceOk = (divergenceBuffer[i] > -SignalThreshold * 1.5); // Relaxed //--- Compression breakout bool compressionBreakout = (compressionState[i] == 1 && entropyBuffer[i] > 0.20 && entropyBuffer[i] < 0.50); // Relaxed range //--- Decompression end bool decompressionEnd = (compressionState[i-1] == -1 && compressionState[i] != -1 && momentumBuffer[i] > 0); //--- Simplified signal logic bool signal = crossUp || compressionBreakout || decompressionEnd; //--- Additional confirmation (optional) if(crossUp) signal = signal && entropyOk; // Only require entropy check for crossovers return signal; } //+------------------------------------------------------------------+ //| Check sell signal conditions | //+------------------------------------------------------------------+ bool IsSellSignal(int i) { if(i < 1) return false; //--- Crossover condition (primary) bool crossDown = (fastEntropyBuffer[i] < slowEntropyBuffer[i] && fastEntropyBuffer[i-1] >= slowEntropyBuffer[i-1]); //--- Entropy elevated bool entropyOk = (entropyBuffer[i] > 0.50); // Relaxed from 0.55 //--- Negative momentum bool momentumOk = (momentumBuffer[i] < 0); //--- Divergence negative bool divergenceOk = (divergenceBuffer[i] < SignalThreshold); //--- Chaotic regime entry bool chaoticEntry = (regime[i] == 2 && regime[i-1] != 2); //--- Strong negative divergence bool strongDivergence = (divergenceBuffer[i] < -SignalThreshold && MathAbs(divergenceBuffer[i]) > MathAbs(divergenceBuffer[i-1])); //--- Compression end (sell) bool compressionEnd = (compressionState[i-1] == 1 && compressionState[i] != 1 && momentumBuffer[i] < 0); //--- Simplified signal logic bool signal = crossDown || chaoticEntry || strongDivergence || compressionEnd; //--- Additional confirmation (optional) if(crossDown) signal = signal && entropyOk; // Only require entropy check for crossovers return signal; }
Логика сигнала на покупку оценивает несколько условий для выявления зарождающегося бычьего моментума. Она проверяет пересечение быстрой и медленной энтропии, а затем подтверждает его фильтрами энтропии и моментума. Кроме того, логика учитывает пробой после сжатия и сигналы, возникающие при завершении фазы декомпрессии. Таким образом система захватывает как выход из низкоэнтропийного состояния, так и разрешение предшествующей фазы с изменением направленного давления Общий сигнал упрощается: любое из этих ключевых событий может запускать условие на покупку. При этом сигналы пересечения проходят дополнительную фильтрацию по энтропии для повышения их качества.
Логика сигнала на продажу повторяет эту структуру, но ориентируется на медвежьи условия. Фиксируется первичное пересечение, при котором быстрая энтропия опускается ниже медленной, что указывает на возможное движение вниз. Также подтверждается, что уровни энтропии отражают достаточную рыночную активность. Отрицательный моментум и дивергенция дополнительно подтверждают медвежий уклон, а переходы режима в хаотический режим также могут выступать самостоятельными триггерами сигналов на продажу. Дополнительные условия, такие как сильная отрицательная дивергенция и завершение фазы расширения, помогают выявлять разворотные движения или продолжение движения. По аналогии с логикой покупки система объединяет эти условия в упрощенную структуру сигналов. Более строгое подтверждение применяется только тогда, когда сигнал возникает в результате пересечения.
//+------------------------------------------------------------------+ //| 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; 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; lastTradeBar = -1; hasTradedThisBar = false;
Этот раздел отвечает за ежедневное отслеживание состояния и обеспечивает согласованность системы с текущей торговой сессией. Проверяется, включена ли функция ежедневного сброса, и, если да, из временной метки извлекается дата текущего бара. При обнаружении нового дня обновляется сохраненная дата дня и сбрасываются все дневные счетчики. Они отслеживают восходящие, нисходящие и нейтральные движения, а также общее число обработанных баров. Это гарантирует актуальность статистических показателей и исключает перенос устаревшей информации из предыдущих торговых сессий. Если визуализация сигналов включена, все объекты графика удаляются, чтобы избежать визуальной перегруженности, а система подготавливается к отслеживанию сигналов с чистого состояния.
Также сбрасываются все таймеры сигналов, чтобы обеспечить надлежащий интервал между ними и исключить влияние дублирующихся или устаревших сигналов на новые торговые решения. Сюда входят очистка временных меток для сигналов на покупку, продажу, сжатие и расширение, а также сброс счетчиков на основе баров, используемых для соблюдения минимальных интервалов между сделками. Кроме того, сбрасываются флаги отслеживания сделок, чтобы система могла безопасно оценивать новые возможности без влияния предыдущего состояния. Такое сочетание ежедневного сброса и управления таймерами обеспечивает согласованную логику работы советника во всех торговых сессиях.
//+------------------------------------------------------------------+ //| 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(j >= 0) { if(statesArray[j] == 1) up++; else if(statesArray[j] == 2) down++; else flat++; } } int total = up + down + flat; if(total == 0) return 0; double p_up = (double)up / total; double p_down = (double)down / total; double p_flat = (double)flat / total; 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 } //+------------------------------------------------------------------+ //| 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) regime[i] = 0; // trend else if(entropyBuffer[i] < 0.65) regime[i] = 1; // transition else 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) 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 } }
В этом разделе значения энтропии преобразуются в информативную картину рыночной структуры путем отнесения каждого бара к определенному режиму. Текущий уровень энтропии сравнивается с заранее заданными пороговыми значениями, чтобы определить, находится ли рынок в тренде, переходе или хаотическом режиме. Низкая энтропия отражает упорядоченное поведение цены и классифицируется как тренд, умеренная указывает на фазу перехода, а высокая — на хаотический режим. Эта классификация дает общее представление о рыночных условиях и позволяет корректировать ожидания в зависимости от того, упорядочено ценовое движение или случайно.
Затем изменение энтропии измеряется с помощью расчетов дивергенции и моментума. Дивергенция рассчитывается как разница между быстрой и медленной энтропией, что позволяет оценить краткосрочные сдвиги в поведении рынка относительно долгосрочных. Расширение дивергенции указывает на ускорение движения в определенном направлении. Сужение дивергенции, наоборот, может указывать на ослабление рыночных условий. Моментум рассчитывается путем сравнения текущей энтропии с ее значением несколько периодов назад, что помогает определить, растет или снижается неопределенность с течением времени. Эти два компонента вместе дают динамическое представление о скрытых изменениях рынка.
Здесь выявляются фазы сжатия и расширения путем анализа поведения энтропии относительно недавних экстремумов. Отслеживаются локальные минимумы и максимумы за заданное число баров, чтобы выявить периоды сжатия и расширения энтропии. Когда энтропия опускается ниже определенного порогового значения и продолжает снижаться, рынок классифицируется как находящийся в состоянии сжатия, что указывает на консолидацию. Когда энтропия поднимается выше более высокого порогового значения и продолжает расти, рынок отмечается как находящийся в состоянии расширения, что сигнализирует о потенциальном пробое. Если ни одно из этих условий не выполняется, рынок остается в нейтральном состоянии.
//+------------------------------------------------------------------+ //| 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); }
UpdateRegimeLabel обновляет метку на графике, отображающую в реальном времени краткую информацию о текущем рыночном режиме на основе последних значений энтропии и производных показателей. Извлекаются последние значения энтропии, быстрой и медленной энтропии, дивергенции и состояния сжатия. Затем рынок классифицируется как трендовый, переходный или хаотический на основе заранее заданных пороговых значений энтропии. Метка дополняется информацией о том, происходит ли на рынке сжатие или расширение. Кроме того, из соотношения между быстрой и медленной энтропией выводится простой бычий или медвежий уклон. Затем метке присваивается цвет, визуально отображающий силу режима. Наконец, объект графика создается или обновляется так, чтобы эта информация постоянно отображалась в левом верхнем углу графика и могла быть быстро интерпретирована.
//+------------------------------------------------------------------+ //| 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; } } } } //+------------------------------------------------------------------+ //| 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; bool buySig = IsBuySignal(i); bool sellSig = IsSellSignal(i); //--- Buy signal arrow if(buySig) { if(i - lastBuyBar >= MinSignalGap && (lastBuySignalTime == 0 || time[i] - lastBuySignalTime > MinSignalGap * PeriodSeconds())) { double arrowY = low[i] - SignalArrowOffset * _Point; string objName = objPrefix + "buy_" + IntegerToString(i) + "_" + IntegerToString((int)time[i]); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 217); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrLime); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP); lastBuySignalTime = time[i]; } } } //--- Sell signal arrow if(sellSig) { if(i - lastSellBar >= MinSignalGap && (lastSellSignalTime == 0 || time[i] - lastSellSignalTime > MinSignalGap * PeriodSeconds())) { double arrowY = high[i] + SignalArrowOffset * _Point; string objName = objPrefix + "sell_" + IntegerToString(i) + "_" + IntegerToString((int)time[i]); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 218); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3); 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); lastSellSignalTime = time[i]; } } } } //+------------------------------------------------------------------+
Этот раздел отвечает за визуальную отрисовку всех сигналов на графике. Он отображает маркеры сжатия и расширения, а также стрелки покупки и продажи в зависимости от текущего состояния рынка. Для обнаружения сжатия и расширения отслеживаются изменения состояния сжатия. Визуальный маркер срабатывает только при возникновении перехода. Это позволяет фиксировать начало новой фазы, а не повторять сигналы. Также применяются правила интервалов с минимальными промежутками по барам и времени. Это позволяет избежать перегруженности графика. При обнаружении допустимого перехода вычисляется положение выше или ниже свечи. Затем отрисовывается стрелка с меткой и заданным стилем. Сжатие обозначается синим цветом, а расширение — оранжевым. Внутренние переменные отслеживания также обновляются для соблюдения надлежащего интервала между сигналами.
Для торговых сигналов сначала проверяется доступность всех необходимых данных по энтропии и моментуму. Затем с помощью функций логики сигналов проверяется, выполняется ли условие для покупки или продажи. Если обнаружен допустимый сигнал, снова обеспечивается соблюдение правил минимальных интервалов. Это позволяет избежать повторяющихся стрелок на близлежащих барах или таймфреймах. Сигналы на покупку отображаются под свечой в виде зеленой стрелки вверх. Сигналы на продажу отображаются над свечой в виде красной стрелки вниз. Каждому объекту присваивается уникальное имя с использованием индекса бара и временной метки. Это позволяет избежать наложения объектов графика друг на друга. После отрисовки обновляются внутренние переменные отслеживания. Это обеспечивает соблюдение временных интервалов и предотвращает повторную перегруженность графика.
Тестирование на исторических данных
Тестирование на исторических данных проводилось в течение примерно 2 месяцев — с 1 января 2026 года по 28 февраля 2026 года — со следующими настройками:

Ниже приведены кривая эквити и результаты тестирования на исторических данных:


Заключение
Индикатор рыночной энтропии преобразован из инструмента визуального анализа в исполняемую торговую систему с формально определенными правилами и проверяемым поведением. В результате получается компилируемый советник на MQL5, который:
- обновляет быструю, медленную и базовую энтропию, моментум, дивергенцию и состояние сжатия на каждом тике;
- формирует сигналы на покупку и продажу на основе явных, воспроизводимых условий: пересечений, переходов между сжатием и расширением, фильтров дивергенции и моментума;
- открывает сделки и управляет ими с настраиваемыми параметрами LotSize, StopLoss, TakeProfit и MagicNumber;
- при необходимости немедленно разворачивает позиции при появлении противоположных подтвержденных сигналов;
- отображает метки режимов и сигнальные стрелки для быстрой визуальной проверки, сохраняя все логи и состояние для тестирования на исторических данных.
Эта реализация устраняет разрыв между "видением" закономерностей, обусловленных энтропией, и их последовательной реализацией: она поддается аудиту — по логам и объектам графика, тестированию — в Тестере стратегий, и измерению — по MinSignalGap, поведению при развороте, применению SL/TP и успешности совершения сделок через повторные попытки. Рекомендуемые следующие шаги — систематическая оптимизация параметров, тестирование надежности на разных символах и таймфреймах, расширенное управление рисками и капиталом, а также интеграция в инфраструктуру совершения сделок на уровне портфеля. Советник обеспечивает надежную проверяемую основу для перехода от идей теории информации к повторяемым торговым результатам.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21742
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Использование Экономического календаря MQL5 для фильтрации новостей (Часть 4): Точное тестирование на истории со статическими данными
От новичка до эксперта: Автоматизация внутридневных стратегий
Разработка системы оценки портфеля из нескольких советников с учетом корреляции в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования