English
preview
Автоматизация индикатора рыночной энтропии: Торговая система на основе теории информации

Автоматизация индикатора рыночной энтропии: Торговая система на основе теории информации

MetaTrader 5Примеры |
23 0
Hlomohang John Borotho
Hlomohang John Borotho

Содержание

  1. Введение
  2. Обзор работы советника
  3. Начало работы
  4. Тестирование на исторических данных
  5. Заключение


Введение

Ранее был представлен индикатор рыночной энтропии, использующий энтропию Шеннона для количественной оценки неопределенности движения цены и классификации поведения рынка по трем режимам: тренд, переход и хаотический режим. Хотя индикатор делал видимыми эти режимы и зоны сжатия и расширения, его эффективное использование в торговле требовало постоянного ручного мониторинга и одновременной интерпретации нескольких признаков: пересечений быстрой и медленной энтропии, моментума, режима и состояния сжатия. Такой ручной процесс приводил к двум практическим проблемам: непоследовательному исполнению, когда разные трейдеры по-разному реагируют на один визуальный признак, и упущенным возможностям, когда переходы от сжатия к расширению не удается своевременно отрабатывать.

В этой статье эти проблемы решаются формализацией логики индикатора и созданием воспроизводимого, тестируемого советника на 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

Прикрепленные файлы |
Использование Экономического календаря MQL5 для фильтрации новостей (Часть 4): Точное тестирование на истории со статическими данными Использование Экономического календаря MQL5 для фильтрации новостей (Часть 4): Точное тестирование на истории со статическими данными
В этой статье реализован статический источник новостей на основе CSV для Тестера стратегий, позволяющий предварительно загружать и запрашивать исторические экономические события при тестировании на исторических данных. Он заменяет вызовы календаря в реальном времени в режиме тестера быстрым поиском в памяти, при этом логика работы в реальной торговле остается прежней и обеспечивает детерминированные, воспроизводимые результаты с явным контролем над тем, какие события включаются. Это позволяет надежно проверять новостные фильтры, временное удаление стопов и правила блокировки новых сделок.
От новичка до эксперта: Автоматизация внутридневных стратегий От новичка до эксперта: Автоматизация внутридневных стратегий
Мы переводим идею ретеста EMA(50) в советник для внутридневной торговли, основанный на поведении цены. В исследовании формализуются трендовый уклон, взаимодействие с EMA (проколоть/пробить и закрыться), подтверждение реакции и дополнительные фильтры, после чего они реализуются в MQL5 с использованием модульных функций и дескрипторов с безопасным управлением ресурсами. Визуальное тестирование в Тестере стратегий позволяет проверить корректность сигналов. В результате получается понятный шаблон для кодирования дискреционных отскоков.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Разработка системы оценки портфеля из нескольких советников с учетом корреляции в MQL5 Разработка системы оценки портфеля из нескольких советников с учетом корреляции в MQL5
Большинство алгоритмических трейдеров оптимизируют советники по отдельности, но никогда не оценивают, как они работают вместе на одном счете. Коррелированные стратегии усиливают просадки, а не снижают их, пробелы в покрытии создают в портфеле слепые зоны на целые торговые сессии. В этой статье мы создадим полноценный оценщик портфеля на MQL5. Он считывает дневные P&L из CSV-файлов бэктестов, вычисляет полную матрицу корреляции Пирсона, строит карту торговой активности по часам и дням недели, оценивает диверсификацию по классам активов и выдает сводную оценку от A+ до F. Весь исходный код прилагается, внешние библиотеки не требуются.