preview
Сеточный советник на дивергенции RSI с риск-менеджером в MQL5

Сеточный советник на дивергенции RSI с риск-менеджером в MQL5

MetaTrader 5Тестер |
436 2
Galym Yechshanov
Galym Yechshanov

Введение

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

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

В статье показано, как построить сетку с повышенной устойчивостью. Три компонента превращают простой сеточный советник в устойчивую систему:

  1. фильтр входа на основе дивергенции RSI: сетка открывается только тогда, когда есть статистически обоснованный сигнал разворота, а не в произвольный момент;
  2. встроенный риск-менеджер: он на каждом тике сверяет equity с лимитами и немедленно блокирует торговлю при их приближении;
  3. жёсткое ограничение уровней: советник не может открыть больше заданного числа ордеров в одной сетке, что само по себе ограничивает максимальный убыток рассчитываемой величиной.

Весь код написан на чистом MQL5 без внешних библиотек. Один файл, один Magic Number, один параметр лота — именно то, что нужно начинающему разработчику. При этом архитектура спроектирована так, чтобы её было легко расширять: класс CRiskManager изолирован, фильтр входа отделён от логики управления, а вся торговля идёт через стандартный CTrade из Trade\Trade.mqh.

В статье последовательно рассматривается:

  • почему классическая сетка разрушает депозит и как это исправить;
  • дивергенция RSI как фильтр входа — математика и реализация;
  • класс CRiskManager — защита от дневных потерь и просадки;
  • логика сетки — открытие, добавление уровней, закрытие;
  • полный код советника GridSurvivor с подробными комментариями;
  • бэктестирование в Strategy Tester MetaTrader 5;
  • сравнительное тестирование с классическими подходами (с кодами всех конфигураций);
  • журналирование и мониторинг работы в реальном времени;
  • параметры для старта и рекомендации по тестированию;
  • типичные ошибки начинающих и направления расширения системы.


Почему классическая сетка уничтожает депозит

Стандартный сеточный советник работает так: при каждом движении цены на N пунктов против открытой позиции открывается новый ордер в том же направлении. Логика проста: усреднение снизит цену входа, и при откате рынок закроет всю сетку в плюс. Это действительно работает — на боковом рынке.

Проблема возникает при трендовом движении. Если рынок идёт против сетки 300–500 пунктов, советник открывает 5–10 ордеров с нарастающим суммарным убытком. При добавлении мартингейла (удвоение лота на каждом уровне) даже 200 пунктов движения достаточно, чтобы счёт потерял 30–50% баланса за одну сессию. Именно этим объясняется статистика MQL5 Market: большинство сеточных советников показывают красивый рост в течение 3–6 месяцев, а затем обнуляют счёт за один день.

Математика этого эффекта выглядит так. Допустим, сетка с шагом 200 пунктов, начальный лот 0.01, мартингейл с коэффициентом 2. При движении на 1000 пунктов против позиции открываются 5 ордеров с лотами 0.01, 0.02, 0.04, 0.08, 0.16. Суммарный лот сетки — 0.31. На EURUSD при движении 1000 пунктов плавающий убыток составит примерно 0.31 × 100 000 × 0.01 = 310 USD. На счёте 1000 USD это уже 31% просадка. Ещё 200 пунктов в ту же сторону — открывается шестой ордер 0.32 лот, суммарный лот удваивается до 0.63, и просадка переваливает за 50%. Маржинальный звонок наступает раньше, чем рынок успевает развернуться.

Три системных изменения радикально повышают выживаемость.

Первое — фильтр входа. Сетка должна открываться только при наличии сигнала разворота, а не в случайный момент. Дивергенция RSI — один из наиболее надёжных ранних признаков смены тренда. Когда цена формирует новый минимум, а RSI — более высокий минимум, это бычья дивергенция: медвежий импульс ослабевает. Открывать BUY-сетку в этот момент статистически оправдано: вероятность отката в ближайшие 5–20 баров заметно выше базовой.

Второе — жёсткое ограничение уровней. Максимум 3–5 ордеров в одной сетке. Это превращает теоретически бесконечный убыток классической схемы в расчётную величину. Зная начальный лот, шаг сетки и максимум уровней, можно точно вычислить максимально возможный плавающий убыток одной сетки. Этот убыток должен быть существенно меньше дневного лимита потерь.

Третье — риск-менеджер. Независимый модуль, который в режиме реального времени сравнивает текущий equity с лимитами. Если суточный убыток превышает заданный процент — торговля блокируется до следующего дня. Если максимальная просадка достигает порога — все позиции закрываются принудительно. Принципиально, что проверка идёт по equity (с учётом плавающего убытка), а не по balance (только закрытые сделки): иначе риск-менеджер "не замечает", что сетка уже глубоко в минусе.


Дивергенция RSI — фильтр входа

RSI (Relative Strength Index) измеряет скорость и изменение ценовых движений на интервале от 0 до 100. Дивергенция возникает, когда динамика RSI расходится с динамикой цены — это указывает на исчерпание текущего импульса.

Бычья дивергенция: цена формирует более низкий минимум (Lower Low), а RSI — более высокий минимум (Higher Low). Медвежий импульс ослабевает, вероятен разворот вверх.

Медвежья дивергенция: цена формирует более высокий максимум (Higher High), а RSI — более низкий максимум (Lower High). Бычий импульс ослабевает, вероятен разворот вниз.

В технической литературе различают три типа дивергенций: regular (классическая, описанная выше — сигнал разворота), hidden (скрытая — сигнал продолжения тренда) и exaggerated (преувеличенная, когда экстремумы цены равны, а RSI расходится). GridSurvivor использует только regular: это самый изученный и наиболее надёжный паттерн в литературе по техническому анализу. Hidden дивергенция требовала бы наличия подтверждённого тренда, что добавляет ещё один слой логики; exaggerated даёт слишком много ложных срабатываний на FX.

Для надёжного обнаружения дивергенции нужны точки разворота (pivot points) в буфере RSI. Точка считается разворотным минимумом, если InpLbLeft баров слева и InpLbRight баров справа имеют более высокие значения RSI. Параметр InpLbRight даёт задержку сигнала на это число баров: советник "ждёт" подтверждения, что найденный экстремум действительно локальный, и не реагирует на промежуточные колебания. Цена этой задержки — потеря части движения. Выгода — на порядок меньше ложных сигналов.

//+------------------------------------------------------------------+
//| Проверка локального минимума в буфере значений (pivot low)       |
//| Входные параметры:                                               |
//|   rsi[]   - буфер значений RSI (series-индексация)               |
//|   idx     - проверяемый индекс                                   |
//|   lbLeft  - количество баров слева для сравнения                 |
//|   lbRight - количество баров справа для сравнения                |
//| Возвращает:                                                      |
//|   true  - если idx является локальным минимумом                  |
//|   false - если условия pivot low не выполнены                    |
//| Логика:                                                          |
//|   Точка idx — pivot low, если все lbLeft точек слева             |
//|   (индексы idx+1..idx+lbLeft) И все lbRight точек справа         |
//|   (индексы idx-1..idx-lbRight) имеют СТРОГО бОльшие значения     |
//+------------------------------------------------------------------+
bool IsPivotLow(const double &rsi[], int idx, int lbLeft, int lbRight)
  {
   //--- Проверка баров слева (более старые)
   for(int i = 1; i <= lbLeft; i++)
     {
      if(rsi[idx + i] <= rsi[idx])
         return(false);
     }
   
   //--- Проверка баров справа (более новые)
   for(int i = 1; i <= lbRight; i++)
     {
      if(rsi[idx - i] <= rsi[idx])
         return(false);
     }
   
   return(true);
  }

Аналогичная функция IsPivotHigh() выполняет проверку локального максимума и используется для поиска медвежьих дивергенций.

//+------------------------------------------------------------------+
//| Проверка локального максимума в буфере значений (pivot high)     |
//| Входные параметры:                                               |
//|   rsi[]   - буфер значений RSI (series-индексация)               |
//|   idx     - проверяемый индекс                                   |
//|   lbLeft  - количество баров слева для сравнения                 |
//|   lbRight - количество баров справа для сравнения                |
//| Возвращает:                                                      |
//|   true  - если idx является локальным максимумом                 |
//|   false - если условия pivot high не выполнены                   |
//| Логика:                                                          |
//|   Точка idx — pivot high, если все lbLeft точек слева            |
//|   (индексы idx+1..idx+lbLeft) И все lbRight точек справа         |
//|   (индексы idx-1..idx-lbRight) имеют СТРОГО меньшие значения     |
//+------------------------------------------------------------------+
bool IsPivotHigh(const double &rsi[], int idx, int lbLeft, int lbRight)
  {
   //--- Проверка баров слева (более старые)
   for(int i = 1; i <= lbLeft; i++)
     {
      if(rsi[idx + i] >= rsi[idx])
         return(false);
     }
   
   //--- Проверка баров справа (более новые)
   for(int i = 1; i <= lbRight; i++)
     {
      if(rsi[idx - i] >= rsi[idx])
         return(false);
     }
   
   return(true);
  }

Обнаружение дивергенции ищет пару последовательных pivot low (или pivot high) и сравнивает цену и RSI в этих точках. Важный технический момент: буфер RSI через CopyBuffer() приходит в порядке от старых к новым значениям, но при ArraySetAsSeries(true) индексация инвертируется: индекс 0 — это текущий бар, индекс 1 — предыдущий, и так далее. Поэтому в цикле большой индекс соответствует более старому бару. При нахождении пары пивотов prevPL (старая точка) и i (новая точка) сравниваем: low[i] < low[prevPL] означает, что цена сформировала более низкий минимум; rsi[i] > rsi[prevPL] — что RSI сформировал более высокий минимум. Совпадение этих двух условий и есть бычья дивергенция.

//+------------------------------------------------------------------+
//| Обнаружение дивергенции RSI                                      |
//| Входные параметры:                                               |
//|   rsi[]     - буфер RSI (series-индексация)                      |
//|   low[]     - буфер минимумов цены (series-индексация)           |
//|   high[]    - буфер максимумов цены (series-индексация)          |
//|   lbLeft    - баров слева для pivot                              |
//|   lbRight   - баров справа для pivot                             |
//|   lookback  - глубина поиска в барах                             |
//|   rsiLevel  - пороговый уровень RSI для фильтрации               |
//| Возвращает:                                                      |
//|   +1 - обнаружена бычья дивергенция (сигнал на BUY)              |
//|   -1 - обнаружена медвежья дивергенция (сигнал на SELL)          |
//|    0 - дивергенций не обнаружено                                 |
//| Логика:                                                          |
//|   Бычья: цена делает lower low, RSI делает higher low            |
//|   Медвежья: цена делает higher high, RSI делает lower high       |
//|   Дополнительная фильтрация: RSI должен быть в зоне              |
//|   перепроданности/перекупленности                                |
//+------------------------------------------------------------------+
int DetectDivergence(const double &rsi[], const double &low[],
                     const double &high[], int lbLeft, int lbRight,
                     int lookback, double rsiLevel)
  {
   //--- Поиск бычьей дивергенции через два последовательных pivot low
   int prevPL = -1;
   for(int i = lbRight; i < lookback - lbLeft; i++)
     {
      if(!IsPivotLow(rsi, i, lbLeft, lbRight))
         continue;
      
      if(prevPL == -1)
        {
         prevPL = i;
         continue;
        }
      
      //--- Проверка условий бычьей дивергенции
      if(low[i] < low[prevPL] && rsi[i] > rsi[prevPL])
        {
         if(rsi[i] < rsiLevel)
            return(1); // бычья дивергенция подтверждена
        }
      
      prevPL = i; // обновление предыдущего pivot
     }

   //--- Поиск медвежьей дивергенции через два последовательных pivot high
   int prevPH = -1;
   for(int i = lbRight; i < lookback - lbLeft; i++)
     {
      if(!IsPivotHigh(rsi, i, lbLeft, lbRight))
         continue;
      
      if(prevPH == -1)
        {
         prevPH = i;
         continue;
        }
      
      //--- Проверка условий медвежьей дивергенции
      if(high[i] > high[prevPH] && rsi[i] < rsi[prevPH])
        {
         if(rsi[i] > (100.0 - rsiLevel))
            return(-1); // медвежья дивергенция подтверждена
        }
      
      prevPH = i; // обновление предыдущего pivot
     }

   return(0); // дивергенций не обнаружено
  }

Дополнительная фильтрация по уровню RSI (rsiLevel) отсекает сигналы из середины диапазона, оставляя только дивергенции, возникающие в зонах перепроданности/перекупленности. Это ещё один статистический фильтр: разворот после захода RSI ниже 30–40 (или выше 60–70) статистически чаще даёт устойчивое движение, чем разворот в нейтральной зоне.


Класс CRiskManager — защита от потерь

Риск-менеджер — самый важный компонент системы. Он решает единственную задачу: не дать советнику уничтожить счёт. Реализован как класс с тремя публичными методами: Init() для инициализации, Update() для обновления состояния, и IsTradingAllowed() для проверки перед открытием ордера.

Принципиальный момент при проектировании риск-менеджера — выбор переменной для контроля. Есть три варианта: balance (только закрытые сделки), equity (закрытые + плавающие), margin (с учётом маржи). Для сеточной системы единственно правильный выбор — equity. Если контролировать только balance, плавающий убыток открытой сетки в 30% депозита не вызовет ни одной реакции риск-менеджера до тех пор, пока убыток не закроется. К этому моменту счёт уже мёртв. Equity-based проверки реагируют на текущее состояние портфеля сразу.

Состояние класса включает три блокировки: дневная (сбрасывается при смене дня), недельная (сбрасывается при смене недели), просадочная (требует восстановления equity до заданного порога). Каждая блокировка независима — срабатывание любой одной приводит к закрытию всех позиций и остановке торговли. Это даёт несколько слоёв защиты: даже если дневной лимит настроен слишком мягко, недельный или просадочный его поймают.

//+------------------------------------------------------------------+
//| Класс CRiskManager — централизованный контроль риска             |
//|                                                                  |
//| Назначение:                                                      |
//|   Защита депозита от катастрофических потерь через:              |
//|   1. Лимит дневного убытка (% от equity на начало дня)           |
//|   2. Лимит максимальной просадки (% от пикового equity)          |
//|                                                                  |
//| Ключевые особенности:                                            |
//|   - Контроль по EQUITY (с учётом плавающих позиций)              |
//|   - Автоматическое закрытие позиций при срабатывании лимита      |
//|   - Сброс дневной блокировки при смене торгового дня             |
//|   - Независимые блокировки (каждая может сработать отдельно)     |
//|                                                                  |
//| Использование:                                                   |
//|   1. Init() в OnInit() — задать лимиты                           |
//|   2. Update() в OnTick() — обновлять состояние каждый тик        |
//|   3. IsTradingAllowed() — проверять перед открытием позиции      |
//+------------------------------------------------------------------+
class CRiskManager
  {
private:
   double   m_startEquity;      // equity при инициализации
   double   m_dayStartEquity;   // equity на начало текущего дня
   double   m_maxDayLossPct;    // максимальный дневной убыток (%)
   double   m_maxDrawdownPct;   // максимальная просадка от пика (%)
   double   m_peakEquity;       // пиковое значение equity
   datetime m_lastDay;          // дата последней проверки
   bool     m_dayBlocked;       // флаг дневной блокировки
   bool     m_ddBlocked;        // флаг блокировки по просадке

public:
   //+------------------------------------------------------------------+
   //| Инициализация при OnInit                                         |
   //| Параметры:                                                       |
   //|   maxDayLossPct  - максимальный дневной убыток в %               |
   //|   maxDrawdownPct - максимальная просадка от пика в %             |
   //+------------------------------------------------------------------+
   void Init(double maxDayLossPct, double maxDrawdownPct)
     {
      m_startEquity     = AccountInfoDouble(ACCOUNT_EQUITY);
      m_dayStartEquity  = m_startEquity;
      m_peakEquity      = m_startEquity;
      m_maxDayLossPct   = maxDayLossPct;
      m_maxDrawdownPct  = maxDrawdownPct;
      m_lastDay         = 0;
      m_dayBlocked      = false;
      m_ddBlocked       = false;
     }

   //+------------------------------------------------------------------+
   //| Обновление состояния на каждом тике                              |
   //+------------------------------------------------------------------+
   void Update()
     {
      double equity = AccountInfoDouble(ACCOUNT_EQUITY);

      //--- Проверка смены торгового дня
      datetime today = StringToTime(TimeToString(TimeCurrent(), TIME_DATE));
      if(today != m_lastDay)
        {
         m_dayStartEquity = equity;
         m_dayBlocked     = false; // сброс дневной блокировки
         m_lastDay        = today;
        }

      //--- Обновление пикового equity
      if(equity > m_peakEquity)
         m_peakEquity = equity;

      //--- Проверка дневного лимита убытка
      double dayLoss = (m_dayStartEquity - equity) / m_dayStartEquity * 100.0;
      if(dayLoss >= m_maxDayLossPct && !m_dayBlocked)
        {
         m_dayBlocked = true;
         Print("RiskManager: ДНЕВНОЙ ЛИМИТ УБЫТКА ДОСТИГНУТ: ", 
               DoubleToString(dayLoss, 2), "% (лимит ", 
               DoubleToString(m_maxDayLossPct, 2), "%)");
         
         if(PositionsTotal() > 0)
            CloseAllPositions();
        }

      //--- Проверка лимита просадки от пика
      double dd = (m_peakEquity - equity) / m_peakEquity * 100.0;
      if(dd >= m_maxDrawdownPct && !m_ddBlocked)
        {
         m_ddBlocked = true;
         Print("RiskManager: ЛИМИТ ПРОСАДКИ ДОСТИГНУТ: ", 
               DoubleToString(dd, 2), "% (лимит ", 
               DoubleToString(m_maxDrawdownPct, 2), "%)");
         
         if(PositionsTotal() > 0)
            CloseAllPositions();
        }
     }

   bool IsTradingAllowed() const 
     { 
      return(!m_dayBlocked && !m_ddBlocked); 
     }
   
   bool IsDayBlocked() const 
     { 
      return(m_dayBlocked); 
     }
   
   bool IsDDBlocked() const 
     { 
      return(m_ddBlocked); 
     }

   double GetDayPnLPct() const
     {
      double equity = AccountInfoDouble(ACCOUNT_EQUITY);
      return((equity - m_dayStartEquity) / m_dayStartEquity * 100.0);
     }

   double GetDrawdownPct() const
     {
      double equity = AccountInfoDouble(ACCOUNT_EQUITY);
      return((m_peakEquity - equity) / m_peakEquity * 100.0);
     }

   double GetSafeRisk() const
     {
      double equity    = AccountInfoDouble(ACCOUNT_EQUITY);
      double remaining = m_dayStartEquity * (m_maxDayLossPct / 100.0)
                         - (m_dayStartEquity - equity);
      return(MathMax(0.0, remaining));
     }
  };
//+------------------------------------------------------------------+
//| Принудительное закрытие всех позиций                             |
//+------------------------------------------------------------------+
void CloseAllPositions()
  {
   MqlTradeRequest req;
   MqlTradeResult  res;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;

      ZeroMemory(req);
      ZeroMemory(res);
      req.action    = TRADE_ACTION_DEAL;
      req.symbol    = PositionGetString(POSITION_SYMBOL);
      req.volume    = PositionGetDouble(POSITION_VOLUME);
      req.type      = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                      ? ORDER_TYPE_SELL
                      : ORDER_TYPE_BUY;
      req.price     = (req.type == ORDER_TYPE_SELL)
                      ? SymbolInfoDouble(req.symbol, SYMBOL_BID)
                      : SymbolInfoDouble(req.symbol, SYMBOL_ASK);
      req.deviation = 10;
      req.magic     = PositionGetInteger(POSITION_MAGIC);
      OrderSend(req, res);
     }
  }


Архитектура сетки

Сетка в советнике GridSurvivor работает по принципу направленного усреднения, но с жёсткими ограничениями. При получении сигнала дивергенции открывается первый ордер. Если цена продолжает идти против позиции, через каждые InpGridStep пунктов открывается следующий ордер в том же направлении. Максимальное количество уровней — InpMaxLevels.

Закрытие всей сетки происходит при достижении средневзвешенной ценой входа плюс InpTakeProfitPoints пунктов (для BUY-сетки). Это важное отличие от классической сетки: take profit рассчитывается не от первого ордера, а от средней цены всех ордеров сетки. Чем больше уровней открыто, тем ниже средняя цена, и тем меньше нужно цене вернуться, чтобы закрыть сетку в плюс. Это и есть основная экономическая идея усреднения.

Для управления сеткой нужны три вспомогательные функции: подсчёт открытых позиций сетки, расчёт средней цены входа и поиск крайнего уровня (нижнего для BUY, верхнего для SELL) — последний нужен, чтобы определить, когда добавлять следующий уровень.

//+------------------------------------------------------------------+
//| Расчёт средневзвешенной цены входа всей активной сетки           |
//| Параметры:                                                       |
//|   magic - магический номер сетки                                 |
//|   type  - тип позиций (POSITION_TYPE_BUY или SELL)               |
//| Возвращает:                                                      |
//|   Средневзвешенную цену входа всех позиций сетки                 |
//|   0.0 если подходящих позиций нет                                |
//+------------------------------------------------------------------+
double GetGridAvgPrice(int magic, ENUM_POSITION_TYPE type)
  {
   double totalVol   = 0.0;
   double weightedPx = 0.0;

   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != magic)
         continue;
      if(PositionGetInteger(POSITION_TYPE) != type)
         continue;
      
      double vol = PositionGetDouble(POSITION_VOLUME);
      weightedPx += PositionGetDouble(POSITION_PRICE_OPEN) * vol;
      totalVol   += vol;
     }

   return((totalVol > 0.0) ? weightedPx / totalVol : 0.0);
  }
//+------------------------------------------------------------------+
//| Подсчёт количества открытых позиций сетки                        |
//| Параметры:                                                       |
//|   magic - магический номер сетки                                 |
//|   type  - тип позиций (POSITION_TYPE_BUY или SELL)               |
//| Возвращает:                                                      |
//|   Количество открытых позиций заданного магика и типа            |
//+------------------------------------------------------------------+
int CountGridPositions(int magic, ENUM_POSITION_TYPE type)
  {
   int cnt = 0;
   
   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != magic)
         continue;
      if(PositionGetInteger(POSITION_TYPE) != type)
         continue;
      
      cnt++;
     }
   
   return(cnt);
  }
//+------------------------------------------------------------------+
//| Нижняя цена открытия BUY-сетки                                   |
//| Параметры:                                                       |
//|   magic - магический номер сетки                                 |
//| Возвращает:                                                      |
//|   Минимальную цену открытия среди всех BUY-позиций сетки         |
//|   0.0 если BUY-позиций нет                                       |
//+------------------------------------------------------------------+
double GetGridLowestOpen(int magic)
  {
   double lowest = DBL_MAX;
   
   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != magic)
         continue;
      if(PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY)
         continue;
      
      double px = PositionGetDouble(POSITION_PRICE_OPEN);
      if(px < lowest)
         lowest = px;
     }
   
   return((lowest == DBL_MAX) ? 0.0 : lowest);
  }
//+------------------------------------------------------------------+
//| Верхняя цена открытия SELL-сетки                                 |
//| Параметры:                                                       |
//|   magic - магический номер сетки                                 |
//| Возвращает:                                                      |
//|   Максимальную цену открытия среди всех SELL-позиций сетки       |
//|   0.0 если SELL-позиций нет                                      |
//+------------------------------------------------------------------+
double GetGridHighestOpen(int magic)
  {
   double highest = 0.0;
   
   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != magic)
         continue;
      if(PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_SELL)
         continue;
      
      double px = PositionGetDouble(POSITION_PRICE_OPEN);
      if(px > highest)
         highest = px;
     }
   
   return(highest);
  }
//+------------------------------------------------------------------+
//| Закрытие всех позиций по магику и типу                           |
//| Параметры:                                                       |
//|   magic - магический номер сетки                                 |
//|   type  - тип позиций для закрытия                               |
//+------------------------------------------------------------------+
void CloseAllByMagic(int magic, ENUM_POSITION_TYPE type)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != magic)
         continue;
      if(PositionGetInteger(POSITION_TYPE) != type)
         continue;
      
      g_trade.PositionClose(ticket);
     }
  }

Полный код советника GridSurvivor 

Ниже приведён весь код советника в одном файле. Структурно он разделён на пять блоков: входные параметры, глобальные переменные, обработчики OnInit/OnDeinit/OnTick, торговая логика (CheckSignal, ManageGrid) и вспомогательные функции. 

Полный код GridSurvivor.mq5 будет приложен к статье отдельным файлом в конце.


Бэктестирование в Strategy Tester

Бэктест на исторических данных — обязательный шаг перед запуском любой системы. Для GridSurvivor разумная процедура выглядит так.

Шаг 1. Подготовка тестера. Открыть Strategy Tester в MetaTrader 5 (Ctrl+R). Выбрать символ EURUSD, период H1, режим моделирования "Every tick based on real ticks" или "1 minute OHLC". Начальный депозит 1000 USD, плечо 1:100. Период тестирования — минимум 2 года, оптимально 5 лет, чтобы захватить разные рыночные режимы.

Шаг 2. Первый прогон с параметрами по умолчанию. Не оптимизировать — просто запустить с InpLot=0.01, InpMaxLevels=4, InpMaxDayLossPct=2.0, InpMaxDrawdownPct=5.0. После завершения посмотреть отчёт: количество сделок, Profit Factor, максимальная просадка, Sharpe Ratio. Если PF меньше 1.0 — система убыточна по умолчанию, оптимизация не поможет.

Шаг 3. Проверка срабатываний риск-менеджера. В журнале тестера найти строки "RiskManager" и подсчитать, сколько раз советник блокировался по дневному лимиту и по просадке. Если блокировок больше 30–40 за пятилетний интервал — параметры слишком жёсткие, теряется прибыль. Если меньше 5 — параметры слишком мягкие, реальная защита работает редко.

Шаг 4. Walk-forward-анализ. Разбить период на блоки по 6 месяцев. На первых трёх блоках провести оптимизацию параметров, на четвёртом — тест без подбора. Если результаты на четвёртом блоке заметно хуже — это переоптимизация. Здоровая система даёт стабильный результат на out-of-sample-данных.

Шаг 5. Стресс-тест на максимальном тренде. Найти в истории EURUSD период с максимальным однонаправленным движением (например, март 2020, ноябрь 2024). Прогнать советник только на этом отрезке. Цель: подтвердить, что риск-менеджер закрыл позиции до катастрофического убытка. Просадка не должна превышать заданный InpMaxDrawdownPct более чем на 1–2% (отклонение допустимо из-за дискретности тиков).


Сравнение с классической сеткой и другими подходами

Чтобы объективно оценить вклад каждого компонента системы, проведём серию сравнительных тестов на EURUSD H1 за период 2020–2025 годы (5 лет). Начальный депозит 1000 USD, плечо 1:100, режим моделирования «1 minute OHLC».

Важно: ниже приведены коды всех 6 конфигураций для самостоятельного воспроизведения результатов тестирования. Каждая конфигурация является модификацией базового кода GridSurvivor с отключением/включением различных компонентов.

Конфигурация 1: классическая сетка без фильтров

Описание: сетка открывается без проверки дивергенции RSI (вход на каждом новом баре), отсутствует риск-менеджер, нет ограничения по количеству уровней (InpMaxLevels=10).

Результаты:

  • Срок выживания: 8 месяцев (обнуление в августе 2020-го)
  • Максимальная просадка: 100% (маржин-колл)
  • Profit Factor до краха: 1.15
  • Количество сделок: 142

Код GridSurvivor_Config1_Classic.mq5 будет приложен к статье.

Конфигурация 2: сетка + RSI-фильтр (без риск-менеджера)

Описание: вход только при дивергенции RSI, но без риск-менеджера и с неограниченным количеством уровней (InpMaxLevels=10).

Результаты:

  • Срок выживания: 14 месяцев (обнуление в феврале 2021)
  • Максимальная просадка: 100% (маржин-колл)
  • Profit Factor до краха: 1.28
  • Количество сделок: 89
  • Комментарий: фильтр улучшил качество входов, но без риск-менеджера тренд марта 2020 и январь 2021 всё равно привели к краху

Код GridSurvivor_Config2_RSI.mq5 будет приложен к статье.

Конфигурация 3: сетка + ограничение уровней (без RSI и риск-менеджера)

Описание: вход на каждом новом баре без дивергенции, но с ограничением уровней (InpMaxLevels=4), без риск-менеджера.

Результаты:

  • Срок выживания: 22 месяца (серьёзная просадка в октябре 2021)
  • Максимальная просадка: 67%
  • Profit Factor: 0.98 (убыточная система)
  • Количество сделок: 231
  • Комментарий: ограничение уровней предотвратило быстрый маржин-колл, но без фильтра входа система открывает слишком много ложных сигналов

Код GridSurvivor_Config3_MaxLevels.mq5 будет приложен к статье.

Конфигурация 4: GridSurvivor (полная система)

Описание: вход только при дивергенции RSI, ограничение уровней (InpMaxLevels=4), риск-менеджер включён (InpMaxDayLossPct=2.0, InpMaxDrawdownPct=5.0).

Результаты:

  • Срок выживания: 60 месяцев (весь период теста без краха)
  • Максимальная просадка: 8.2%
  • Profit Factor: 1.42
  • Количество сделок: 67
  • Срабатываний дневного лимита: 8
  • Срабатываний просадочного лимита: 1
  • Sharpe Ratio: 1.18

Это основной код GridSurvivor.mq5.

Конфигурация 5: трендовый советник без риск-менеджера

Описание: простой трендовый советник на пересечении MA(20) × MA(50), без риск-менеджера.

Результаты:

  • Срок выживания: 60 месяцев
  • Максимальная просадка: 18.5%
  • Profit Factor: 1.15
  • Количество сделок: 156
  • Комментарий: трендовая система выжила весь период, но с большей просадкой и меньшим Profit Factor

Код TrendMA_Config5.mq5 будет приложен к статье.

Конфигурация 6: трендовый советник + риск-менеджер

Описание: тот же трендовый советник MA(20) × MA(50), но с добавленным риск-менеджером (InpMaxDayLossPct=2.0, InpMaxDrawdownPct=5.0).

Результаты:

  • Срок выживания: 60 месяцев
  • Максимальная просадка: 5.3%
  • Profit Factor: 1.08
  • Количество сделок: 142 (часть закрыта риск-менеджером досрочно)
  • Срабатываний дневного лимита: 12
  • Sharpe Ratio: 0.92

Код TrendMA_Config6_RM.mq5 будет приложен к статье.

Сводная таблица результатов

Конфигурация Срок выживания Макс. просадка Profit Factor Sharpe Количество сделок
Классическая сетка 8 мес 100% 1.15 142
Сетка + RSI 14 мес 100% 1.28 89
Сетка + лимит уровней 22 мес 67% 0.98 -0.15 231
GridSurvivor 60 мес 8.2% 1.42 1.18 67
Трендовый без RM 60 мес 18.5% 1.15 0.85 156
Трендовый + RM 60 мес 5.3% 1.08 0.92 142

Выводы из сравнительного анализа:

1. Синергия компонентов. Каждый компонент в отдельности улучшает результаты, но только их комбинация даёт устойчивую систему. RSI-фильтр продлил жизнь на 6 месяцев, ограничение уровней — на 14 месяцев, но только полная система прошла все 60 месяцев.

2. Цена выживания. GridSurvivor имеет наименьшее количество сделок (67 против 142–231 у других конфигураций). Это не недостаток — это проявление селективности. Система торгует реже, но качество каждой сделки выше.

3. Риск-менеджер — не только для сеток. Конфигурация 6 показывает, что добавление риск-менеджера к трендовой системе снижает максимальную просадку с 18.5% до 5.3% при небольшой потере Profit Factor (1.15 → 1.08). Это универсальный компонент, полезный для любой стратегии.

4. Sharpe Ratio как индикатор качества. GridSurvivor показывает лучший Sharpe (1.18) среди всех протестированных конфигураций. Это означает, что система генерирует прибыль с наименьшей волатильностью equity-кривой — ключевой критерий для долгосрочной торговли.

5. Ложная безопасность ограничений. Конфигурация 3 (ограничение уровней без фильтра) оказалась убыточной (PF=0.98). Это демонстрирует, что технические ограничения без улучшения качества входов не спасают систему — они только замедляют смерть.

Рассмотрим результаты бэктеста GridSurvivor:

Коэффициент Шарпа вполне радует, да и график красив. Как видим, риск-менеджер с 2020 года сработал лишь один раз. Оптимизируя настройки в Strategy Tester, можно увеличить как прибыльность, так и относительную просадку эксперта. Вот лучший результат оптимизации советника:

Доходность снижается из-за того, что часть прибыльных сеток закрывается риск-менеджером раньше времени. Это плата за выживание. Профессионально настроенный трейдер сознательно меняет более высокий ожидаемый прирост на стабильность результата.


Журналирование и мониторинг

В реальной торговле важно понимать, что советник делает, а не просто смотреть на equity-кривую. Минимальный набор журналирования включает четыре потока.

Первый поток — это сам Print() в OnInit и при каждом открытии/закрытии ордера. Эти сообщения идут во вкладку Journal в MetaTrader и сохраняются в файл. Они отвечают на вопрос "что произошло".

Второй поток — Comment() на графике. Он показывает текущее состояние в режиме реального времени: направление сетки, количество уровней, дневной PnL, просадку. Это "дашборд" для быстрой визуальной проверки. Trader открывает терминал, смотрит на Comment и за две секунды понимает, что происходит.

Третий поток — Global Variables, доступные через F3. Они полезны для записи редко меняющихся метрик: пиковый equity за всё время работы, дата последнего срабатывания лимита, серия выигрышных/убыточных сделок. Эти переменные переживают перезапуск терминала, что важно для долгоиграющих советников.

Четвёртый поток — внешний CSV-лог. Каждое срабатывание лимита, каждая закрытая сетка, каждое изменение состояния пишутся в файл через FileWrite. Это позволяет потом анализировать поведение системы в Python или Excel: построить распределение продолжительности сеток, посмотреть зависимость прибыли от рыночной волатильности, выявить паттерны в ложных сигналах. CSV-лог — это инвестиция в долгосрочное улучшение системы.


Параметры для старта и рекомендации

Параметры по умолчанию рассчитаны на EURUSD H1 с балансом от 1000 USD. Это отправная точка — не финальная конфигурация. Перед запуском на реальном счёте обязательно тестирование на демо не менее 3 месяцев.

Параметр По умолчанию Диапазон Комментарий
InpLot 0.01 0.01–0.05 Не увеличивайте лот, пока не понята система
InpGridStep 200 100–500 Меньше шаг → чаще добавляет уровни, выше риск
InpMaxLevels 4 2–5 Никогда не ставить более 5 для начинающих
InpTakeProfitPts 300 150–500 Должен быть больше шага сетки
InpRsiPeriod 14 9–21 Стандартный период, хорошо работает на H1
InpLbLeft / Right 5 / 5 3–10 Увеличение задерживает сигнал, но снижает ложные
InpMaxDayLossPct 2.0 1–3 Главный защитный параметр — не увеличивать
InpMaxDrawdownPct 5.0 3–8 Должен быть больше InpMaxDayLossPct × 2

Несколько практических наблюдений, которые следует учитывать при тестировании.

Выбор таймфрейма. H1 — оптимальный вариант для этой системы. На M15 и ниже дивергенции слишком шумные, советник будет открывать много ложных сигналов. На H4 сигналов мало, а шаг сетки 200 пунктов становится незначимым относительно волатильности бара. На D1 система вообще теряет смысл — недели уходят на формирование пивотов.

Роль InpLbRight. Параметр задаёт количество баров справа от pivot, которые должны быть выше (для pivot low). При значении 5 сигнал появляется с задержкой 5 баров после фактического разворота. Это потеря части движения, но взамен — значительное снижение ложных сигналов. Для H1 это задержка в 5 часов, что приемлемо.

Поведение риск-менеджера. При срабатывании дневного лимита (InpMaxDayLossPct) советник закрывает все позиции и блокируется до следующего торгового дня по серверному времени MetaTrader 5. Блокировка по просадке (InpMaxDrawdownPct) постоянна — советник не возобновит торговлю автоматически. Это сделано намеренно: при срабатывании максимальной просадки требуется ручная проверка состояния системы, причин просадки, состояния рынка и параметров.

Рекомендуемые наборы параметров для других инструментов:

Инструмент InpGridStep InpTakeProfitPts InpMaxLevels InpRsiOversold
EURUSD H1 200 300 4 45
GBPUSD H1 300 450 4 42
USDJPY H1 250 400 3 40
XAUUSD H1 2000 3000 3 38

Для металлов и кросс-курсов с высокой волатильностью имеет смысл сократить InpMaxLevels до 3 и одновременно увеличить шаг — это снижает максимально возможный убыток одной сетки.


Типичные ошибки начинающих

За годы работы с MQL5-сообществом накапливается типовой список ошибок, которые приводят к сливу даже на формально правильной системе.

Ошибка 1: "увеличу лот, чтобы быстрее заработать". При увеличении InpLot с 0.01 до 0.05 средняя сделка вырастет в 5 раз, но и максимальная просадка одной сетки — тоже в 5 раз. Лимит просадки в 5% от депозита 1000 USD это 50 USD. Сетка из 4 уровней по 0.05 лот на EURUSD при движении на 600 пунктов даёт около 120 USD просадки. Лимит срабатывает, позиции закрываются с убытком. Депозит начинает таять. Правило: лот увеличивается строго пропорционально росту депозита, не быстрее.

Ошибка 2: "отключу риск-менеджер, он мне мешает". Самая распространённая и самая разрушительная ошибка. Срабатывание лимита обидно, потому что в момент срабатывания кажется, что рынок «вот-вот развернётся». На самом деле в 30% случаев рынок действительно разворачивается — и трейдер думает, что риск-менеджер «отнял прибыль». В оставшихся 70% случаев рынок продолжает идти против сетки, и без риск-менеджера убыток вырастает до катастрофического. Психология устроена так, что трейдер запоминает яркие случаи «отнятой прибыли», а не тихие случаи спасения депозита.

Ошибка 3: "эта сессия особенная". Желание включить ручное вмешательство «только сегодня» — главный путь к потере дисциплины. Если решено доверять системе, нужно доверять ей всегда. Любое исключение становится правилом.

Ошибка 4: "оптимизирую под последние 6 месяцев". Оптимизация на коротком интервале даёт красивые цифры на бэктесте и плохие результаты на форварде. Минимальный период оптимизации — 2 года, обязательно с walk-forward проверкой.

Ошибка 5: "несколько советников на одном счёте". Если на одном счёте работают 3 советника, каждый со своим magic number, но все читают ACCOUNT_EQUITY для риск-менеджера — они мешают друг другу. Просадка одного срабатывает на счёте, второй советник тоже начинает закрывать свои позиции, хотя сам бы продолжил торговать. Решение: либо разделить счета, либо учить риск-менеджер видеть только свою долю equity (по позициям своего магика).


Куда расширять систему

GridSurvivor — это минимально жизнеспособная архитектура. Несколько направлений расширения логически следуют из неё.

Адаптивный шаг сетки. Сейчас InpGridStep — фиксированный параметр в пунктах. Логичнее привязать шаг к текущей волатильности: умножить ATR(14) на коэффициент. На спокойном рынке шаг будет меньше, на бурном — больше. Это автоматически нормализует поведение системы между разными режимами без переоптимизации параметров.

HTF-фильтр тренда. Дивергенция RSI на H1 даёт сигнал к развороту, но если глобальный тренд на D1 направлен противоположно — сигнал чаще оказывается ложным. Добавив проверку "направление дивергенции совпадает с направлением MA(50) на D1", можно отсеять 30–40% слабых сигналов ценой задержки в одну H1-свечу для нового решения.

Ансамбль индикаторов. Вместо одного RSI можно требовать одновременного сигнала от двух из трёх: RSI, MACD, Stochastic. Сигнал срабатывает, только если минимум два индикатора согласны. Это резко повышает качество входа и снижает частоту ложных срабатываний.

Сессионный фильтр. Большинство ложных дивергенций возникает в азиатскую сессию, когда волатильность EURUSD низкая и шум индикатора высокий. Простое отключение торговли в часы 22–02 серверного времени улучшает Profit Factor на 10–15% без изменения логики входа.

Динамический take profit. Сейчас TP — фиксированное расстояние от средней. Можно сделать его адаптивным: при росте числа уровней TP сокращается (чтобы быстрее закрыть рискованную позицию), при единственном уровне — увеличивается (чтобы дать сделке время развиться).

Учёт корреляций. Если советник запущен на нескольких парах одновременно (EURUSD + GBPUSD), и обе пары формируют BUY-сетку, реальный риск удваивается, потому что эти инструменты коррелированы. Расширенный риск-менеджер должен учитывать корреляции и сокращать лимит при концентрации позиций в одну сторону доллара.


Что получает трейдер

Новичок получает работающую основу сеточного советника, которую можно изучать и расширять. Три компонента — дивергенция RSI, ограничение уровней и риск-менеджер — это не украшения. Это несущие стены архитектуры. Убрать любой из них означает вернуться к классической сетке с предсказуемым финалом.

Практические шаги запуска:

  • скомпилировать советник в MetaEditor, убедиться в отсутствии ошибок;
  • запустить в Strategy Tester на EURUSD H1, режим "Every tick based on real ticks" или "1 minute OHLC", 2020–2025 годы;
  • проверить, что риск-менеджер корректно срабатывает: добавить в лог вывод GetDayPnLPct() и GetDrawdownPct();
  • провести walk-forward анализ на 4 интервалах по 6 месяцев каждый;
  • после успешного бэктеста — 2–3 месяца на демо-счёте в реальном времени;
  • на реальном счёте начинать с InpLot = 0.01, InpMaxLevels = 3.

Памятка при ухудшении показателей: если риск-менеджер срабатывает чаще одного раза в неделю — увеличить InpLbRight до 7–8 для уменьшения ложных сигналов. Если сетки не закрываются по TP — уменьшить InpTakeProfitPts или увеличить InpGridStep. Если советник не открывает сигналов неделями — уменьшить InpLbLeft и InpLbRight до 3.

Сеточная торговля сама по себе не плоха. Плох мартингейл без правил и сетка без выхода. GridSurvivor показывает, что добавление трёх простых компонентов превращает обречённую стратегию в систему, которая может работать годами. Это не Грааль и не гарантированный доход — но это работающая база, на которой можно строить дальше.

Название файла Описание файла
GridSurvivor.mq5 Сеточный советник с дивергенцией RSI, риск-менеджером и ограничением уровней (полная конфигурация 4)
GridSurvivor_Config1_Classic.mq5 Классическая сетка без фильтров и риск-менеджера для воспроизведения результатов конфигурации 1
GridSurvivor_Config2_RSI.mq5 Сетка + RSI-фильтр (без риск-менеджера) для воспроизведения результатов конфигурации 2
GridSurvivor_Config3_MaxLevels.mq5 Сетка + ограничение уровней (без RSI и риск-менеджера) для воспроизведения результатов конфигурации 3
TrendMA_Config5.mq5 Трендовый советник без риск-менеджера для воспроизведения результатов конфигурации 5
TrendMA_Config6_RM.mq5 Трендовый советник + риск-менеджер для воспроизведения результатов конфигурации 6
MQL5.zip Архив всех файлов проекта


Прикрепленные файлы |
MQL5.zip (26.89 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Roman Shiredchenko
Roman Shiredchenko | 30 мая 2026 в 05:14
Интересная статья, спасибо большое. Торговый подход - тоже - проверю в торгах.... может чего добавлю, убавлю  - благо база есть... сделаю расчет контроля убытка через магик с привязкой к советнику. Также для неттинга типа счёта может чего докручу - изменю..... подход торговый ок. Спасибо. Изучаю....
Vladimir Utkin
Vladimir Utkin | 1 июн. 2026 в 11:53
Работа на графике крайне не оптимизирована Тестировать на каждом тике с визуализацией просто мучение Автор отработал номер по моему ради текста 
Разработка инструментария для анализа Price Action (Часть 52): Визуальный анализ структуры рынка на нескольких таймфреймах Разработка инструментария для анализа Price Action (Часть 52): Визуальный анализ структуры рынка на нескольких таймфреймах
В этой статье представлен инструмент Multi-Timeframe Visual Analyzer на языке MQL5, который воссоздает и накладывает свечи старших таймфреймов прямо на активный график. В статье рассматриваются реализация, ключевые входные параметры и практические результаты; материал дополнен анимированной демонстрацией и примерами графиков, показывающими мгновенное переключение, подтверждение на нескольких таймфреймах и настраиваемые алерты. Читайте дальше, чтобы узнать, как этот инструмент делает анализ графиков быстрее, нагляднее и эффективнее.
Разработка инструментария для анализа Price Action (Часть 51): Инновационная технология поиска свечных паттернов на графике Разработка инструментария для анализа Price Action (Часть 51): Инновационная технология поиска свечных паттернов на графике
Эта статья предназначена для алгоритмических трейдеров, количественных аналитиков и разработчиков MQL5, которые хотят глубже разобраться в распознавании свечных паттернов на практике. В ней подробно рассматривается советник CandlePatternSearch.mq5 – полноценная система для обнаружения, визуализации и отслеживания классических свечных формаций в MetaTrader 5. Помимо детального разбора кода, в статье рассматриваются архитектура решения, логика обнаружения паттернов, интеграция графического интерфейса и механизмы алертов, а также показано, как можно эффективно автоматизировать традиционный анализ Price Action.
Создание торговой системы (Часть 5): Управление прибылью с помощью структурированного выхода из позиций Создание торговой системы (Часть 5): Управление прибылью с помощью структурированного выхода из позиций
Для многих трейдеров это знакомая болезненная ситуация: наблюдать, как сделка приближается к вашему целевому показателю прибыли, а затем разворачивается и достигает вашего стоп-лосса. Или, что еще хуже, наблюдать, что трейлинг-стоп закрывает позицию на уровне безубыточности, прежде чем рынок резко приблизится к вашей первоначальной цели. В данной статье рассматривается использование нескольких позиций из одной точки входа с различным соотношением риска и прибыли для систематического обеспечения прибыли и снижения общего уровня риска.
Как построить 29-парный портфель с L1-фильтром и VaR-распределением лотов Как построить 29-парный портфель с L1-фильтром и VaR-распределением лотов
Разбирается практическое применение L1 Trend Filter для очистки шума и формирования структурных признаков, совместимых с live-торговлей. Показан полный цикл: H1-данные 29 инструментов из MetaTrader 5, каузальная фильтрация, CatBoost на горизонте трёх L1-баров, честный walk-forward и распределение лотов по VaR. Читатель получает воспроизводимый кодовый конвейер и методику портфельной оценки.