MT4 Max Orders Bypass: Скрипт для обхода ограничения через пулы заявок

MT4 Max Orders Bypass: Скрипт для обхода ограничения через пулы заявок MQL4 / MQL5 (MetaTrader)
Подробная инструкция по созданию MQL4 скрипта для обхода ограничения на количество открытых ордеров (Max Orders) в MT4, используя механизм пулов заявок. Увеличьте свои торговые возможности.
Суть: Скрипт обходит ограничение брокера на количество открытых ордеров (Max Orders) в MT4, агрегируя множество ‘виртуальных’ заявок в одну ‘реальную’ позицию. Вместо отправки каждого ордера на сервер, он управляет внутренним пулом заявок и открывает/закрывает/модифицирует одну реальную позицию, отражающую суммарный объем и направление всех виртуальных ордеров.

Исходный код

Представленный MQL4 скрипт реализует механизм пула заявок. Он позволяет вашему советнику или другому скрипту ‘открывать’ и ‘закрывать’ виртуальные ордера, не отправляя их на сервер брокера. Вместо этого, скрипт постоянно отслеживает суммарный объем и направление всех виртуальных ордеров и поддерживает одну единственную реальную позицию, которая отражает этот агрегированный объем. Таким образом, вы можете иметь сотни ‘виртуальных’ ордеров, но на сервере брокера будет виден только один.

Для взаимодействия с этим скриптом, ваш основной советник должен будет вызывать функции AddVirtualOrder() и RemoveVirtualOrder() вместо стандартных OrderSend() и OrderClose(). Это позволяет эффективно обойти ограничение Max Orders, установленное брокером.


//+------------------------------------------------------------------+
//|                                       MaxOrdersBypass_Pooling.mq4|
//|                                                  FinFluct.com    |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "FinFluct.com"
#property link      "https://finfluct.com"
#property version   "1.00"
#property strict

//--- Входные параметры
extern int      MagicNumber         = 12345;    // Уникальный Magic Number для отслеживания реального ордера
extern double   SlippagePips        = 3;        // Допустимое проскальзывание при открытии/закрытии ордеров
extern int      MaxVirtualOrders    = 1000;     // Максимальное количество виртуальных ордеров

//--- Структура для хранения виртуального ордера
struct VirtualOrder
  {
   int      id;         // Уникальный ID виртуального ордера
   double   lot;        // Объем виртуального ордера
   int      type;       // Тип ордера (OP_BUY или OP_SELL)
   string   comment;    // Комментарий к виртуальному ордеру
  };

//--- Глобальные переменные
VirtualOrder virtualOrders[];      // Массив для хранения всех виртуальных ордеров
int          virtualOrdersCount = 0; // Текущее количество виртуальных ордеров
int          nextVirtualOrderID = 1; // Следующий доступный ID для виртуального ордера

//--- Переменные для отслеживания текущей реальной позиции
int          currentRealTicket  = 0;
double       currentRealLot     = 0.0;
int          currentRealType    = -1; // -1: нет позиции, OP_BUY: покупка, OP_SELL: продажа

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ArrayResize(virtualOrders, MaxVirtualOrders);
   Print("MaxOrdersBypass_Pooling: Инициализация завершена. Magic: ", MagicNumber);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Закрываем реальный ордер при деинициализации, если он есть
   if (currentRealTicket > 0)
     {
      if (OrderSelect(currentRealTicket, SELECT_BY_TICKET))
        {
         OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), SlippagePips);
         Print("MaxOrdersBypass_Pooling: Закрыт реальный ордер #", currentRealTicket, " при деинициализации.");
        }
     }
   Print("MaxOrdersBypass_Pooling: Деинициализация завершена.");
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   ManagePooledOrders();
  }

//+------------------------------------------------------------------+
//| Добавляет новый виртуальный ордер в пул                          |
//+------------------------------------------------------------------+
int AddVirtualOrder(double lot, int type, string comment = "")
  {
   if (virtualOrdersCount >= MaxVirtualOrders)
     {
      Print("MaxOrdersBypass_Pooling: Достигнут лимит виртуальных ордеров (", MaxVirtualOrders, "). Невозможно добавить новый.");
      return(0); // Возвращаем 0 в случае ошибки
     }

   if (lot <= 0 || (type != OP_BUY && type != OP_SELL))
     {
      Print("MaxOrdersBypass_Pooling: Некорректные параметры для AddVirtualOrder. Lot: ", lot, ", Type: ", type);
      return(0);
     }

   virtualOrders[virtualOrdersCount].id      = nextVirtualOrderID;
   virtualOrders[virtualOrdersCount].lot     = lot;
   virtualOrders[virtualOrdersCount].type    = type;
   virtualOrders[virtualOrdersCount].comment = comment;
   virtualOrdersCount++;
   
   Print("MaxOrdersBypass_Pooling: Добавлен виртуальный ордер ID: ", nextVirtualOrderID, ", Lot: ", lot, ", Type: ", (type == OP_BUY ? "BUY" : "SELL"), ", Comment: ", comment);
   
   nextVirtualOrderID++;
   ManagePooledOrders(); // Обновляем реальную позицию сразу после добавления
   return(nextVirtualOrderID - 1); // Возвращаем ID добавленного ордера
  }

//+------------------------------------------------------------------+
//| Удаляет виртуальный ордер из пула по ID                          |
//+------------------------------------------------------------------+
bool RemoveVirtualOrder(int id)
  {
   for (int i = 0; i < virtualOrdersCount; i++)
     {
      if (virtualOrders[i].id == id)
        {
         // Сдвигаем элементы массива, чтобы удалить текущий
         for (int j = i; j < virtualOrdersCount - 1; j++)
           {
            virtualOrders[j] = virtualOrders[j+1];
           }
         virtualOrdersCount--;
         Print("MaxOrdersBypass_Pooling: Удален виртуальный ордер ID: ", id);
         ManagePooledOrders(); // Обновляем реальную позицию сразу после удаления
         return(true);
        }
     }
   Print("MaxOrdersBypass_Pooling: Виртуальный ордер ID: ", id, " не найден.");
   return(false);
  }

//+------------------------------------------------------------------+
//| Управляет реальной позицией на основе пула виртуальных ордеров  |
//+------------------------------------------------------------------+
void ManagePooledOrders()
  {
   double netBuyLots  = 0.0;
   double netSellLots = 0.0;

   // Подсчитываем суммарный объем по направлениям
   for (int i = 0; i < virtualOrdersCount; i++)
     {
      if (virtualOrders[i].type == OP_BUY)
        {
         netBuyLots += virtualOrders[i].lot;
        }
      else if (virtualOrders[i].type == OP_SELL)
        {
         netSellLots += virtualOrders[i].lot;
        }
     }

   double targetNetLot  = 0.0;
   int    targetNetType = -1;

   if (netBuyLots > netSellLots)
     {
      targetNetLot  = netBuyLots - netSellLots;
      targetNetType = OP_BUY;
     }
   else if (netSellLots > netBuyLots)
     {
      targetNetLot  = netSellLots - netBuyLots;
      targetNetType = OP_SELL;
     }
   // Если netBuyLots == netSellLots, targetNetLot останется 0.0, targetNetType -1

   // Нормализуем лот до минимально допустимого
   double minLot = MarketInfo(Symbol(), MODE_MINLOT);
   double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
   
   if (targetNetLot < minLot) targetNetLot = 0.0; // Если объем меньше минимального, считаем его нулевым
   targetNetLot = NormalizeDouble(targetNetLot, 2); // Округляем до 2 знаков после запятой для точности
   targetNetLot = MathFloor(targetNetLot / lotStep) * lotStep; // Приводим к шагу лота

   // --- Проверяем текущую реальную позицию --- 
   bool realOrderFound = false;
   currentRealTicket = 0;
   currentRealLot = 0.0;
   currentRealType = -1;

   for (int i = 0; i < OrdersTotal(); i++)
     {
      if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
         if (OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
           {
            currentRealTicket = OrderTicket();
            currentRealLot    = OrderLots();
            currentRealType   = OrderType();
            realOrderFound    = true;
            break;
           }
        }
     }

   // --- Логика управления реальной позицией --- 
   if (targetNetLot > 0)
     {
      // Должна быть открыта позиция
      if (realOrderFound)
        {
         // Позиция уже открыта
         if (currentRealType == targetNetType && currentRealLot == targetNetLot)
           {
            // Все совпадает, ничего не делаем
            // Print("MaxOrdersBypass_Pooling: Реальная позиция соответствует целевой. Ticket: ", currentRealTicket);
           }
         else
           {
            // Позиция открыта, но не соответствует целевой (объем или направление)
            Print("MaxOrdersBypass_Pooling: Закрываем текущую реальную позицию #", currentRealTicket, " (Lot: ", currentRealLot, ", Type: ", (currentRealType == OP_BUY ? "BUY" : "SELL"), ") для обновления.");
            OrderClose(currentRealTicket, currentRealLot, OrderClosePrice(), SlippagePips);
            
            // Открываем новую позицию
            int ticket = OrderSend(Symbol(), targetNetType, targetNetLot, (targetNetType == OP_BUY ? Ask : Bid), SlippagePips, 0, 0, "Pooled Order", MagicNumber, 0, (targetNetType == OP_BUY ? clrBlue : clrRed));
            if (ticket > 0)
              {
               Print("MaxOrdersBypass_Pooling: Открыта новая реальная позиция #", ticket, " (Lot: ", targetNetLot, ", Type: ", (targetNetType == OP_BUY ? "BUY" : "SELL"), ").");
               currentRealTicket = ticket;
               currentRealLot    = targetNetLot;
               currentRealType   = targetNetType;
              }
            else
              {
               Print("MaxOrdersBypass_Pooling: Ошибка открытия новой реальной позиции. Error: ", GetLastError());
              }
           }
        }
      else
        {
         // Нет открытой позиции, нужно открыть новую
         int ticket = OrderSend(Symbol(), targetNetType, targetNetLot, (targetNetType == OP_BUY ? Ask : Bid), SlippagePips, 0, 0, "Pooled Order", MagicNumber, 0, (targetNetType == OP_BUY ? clrBlue : clrRed));
         if (ticket > 0)
           {
            Print("MaxOrdersBypass_Pooling: Открыта новая реальная позиция #", ticket, " (Lot: ", targetNetLot, ", Type: ", (targetNetType == OP_BUY ? "BUY" : "SELL"), ").");
            currentRealTicket = ticket;
            currentRealLot    = targetNetLot;
            currentRealType   = targetNetType;
           }
         else
           {
            Print("MaxOrdersBypass_Pooling: Ошибка открытия новой реальной позиции. Error: ", GetLastError());
           }
        }
     }
   else
     {
      // Целевой объем равен 0, позиция должна быть закрыта
      if (realOrderFound)
        {
         Print("MaxOrdersBypass_Pooling: Закрываем реальную позицию #", currentRealTicket, " (Lot: ", currentRealLot, ", Type: ", (currentRealType == OP_BUY ? "BUY" : "SELL"), ") так как целевой объем равен 0.");
         OrderClose(currentRealTicket, currentRealLot, OrderClosePrice(), SlippagePips);
         currentRealTicket = 0;
         currentRealLot    = 0.0;
         currentRealType   = -1;
        }
     }
  }

//+------------------------------------------------------------------+
//| Вспомогательная функция для вывода списка виртуальных ордеров    |
//+------------------------------------------------------------------+
void PrintVirtualOrders()
  {
   Print("--- Текущие виртуальные ордера (", virtualOrdersCount, ") ---");
   for (int i = 0; i < virtualOrdersCount; i++)
     {
      Print("ID: ", virtualOrders[i].id, ", Lot: ", virtualOrders[i].lot, ", Type: ", (virtualOrders[i].type == OP_BUY ? "BUY" : "SELL"), ", Comment: ", virtualOrders[i].comment);
     }
   Print("--------------------------------------------------");
  }

//+------------------------------------------------------------------+
//| Пример использования (можно удалить или закомментировать)        |
//+------------------------------------------------------------------+
// int testOrderID1 = 0;
// int testOrderID2 = 0;
// int testOrderID3 = 0;

// void OnStart()
//   {
//    // Пример добавления виртуальных ордеров
//    testOrderID1 = AddVirtualOrder(0.1, OP_BUY, "Test Buy 1");
//    Sleep(1000);
//    testOrderID2 = AddVirtualOrder(0.2, OP_BUY, "Test Buy 2");
//    Sleep(1000);
//    testOrderID3 = AddVirtualOrder(0.3, OP_SELL, "Test Sell 1");
//    Sleep(1000);
//    PrintVirtualOrders();

//    // Пример удаления виртуальных ордеров
//    RemoveVirtualOrder(testOrderID1);
//    Sleep(1000);
//    PrintVirtualOrders();

//    // Добавление ордера, который изменит направление
//    AddVirtualOrder(0.5, OP_SELL, "Test Sell 2");
//    Sleep(1000);
//    PrintVirtualOrders();

//    // Удаление всех оставшихся
//    RemoveVirtualOrder(testOrderID2);
//    Sleep(1000);
//    RemoveVirtualOrder(testOrderID3);
//    Sleep(1000);
//    PrintVirtualOrders();
//   }

Разбор параметров

  • MagicNumber: Уникальный идентификатор, который скрипт использует для отслеживания своей единственной реальной позиции среди всех ордеров на счете. Убедитесь, что этот номер не используется другими советниками или скриптами на том же символе.
  • SlippagePips: Максимально допустимое проскальзывание в пунктах при открытии или закрытии реальной позиции. Важно для контроля исполнения ордеров, особенно во время высокой волатильности. Для более продвинутой защиты от расширения спреда, рекомендуем ознакомиться с нашей статьей MQL4 Spread Filter: Защита от расширения спреда во время новостей.
  • MaxVirtualOrders: Максимальное количество виртуальных ордеров, которое может одновременно храниться в пуле. Это ограничение накладывается на сам скрипт, а не на брокера.

Как запустить

Для использования скрипта выполните следующие шаги:

  1. Сохраните код: Скопируйте предоставленный MQL4 код и сохраните его как новый файл в MetaEditor (например, MaxOrdersBypass_Pooling.mq4) в директории MQL4/Experts.
  2. Компилируйте: Откройте файл в MetaEditor и нажмите кнопку 'Компилировать' (или F7). Убедитесь, что нет ошибок.
  3. Прикрепите к графику: Перетащите скомпилированный советник MaxOrdersBypass_Pooling из окна 'Навигатор' MT4 на график торгового инструмента, для которого вы хотите обойти ограничение. Убедитесь, что 'Разрешить автоматическую торговлю' (AutoTrading) включено в настройках советника и на панели MT4.
  4. Интеграция с вашим советником: Ваш основной советник или скрипт, который обычно отправляет ордера, теперь должен вызывать функции AddVirtualOrder(lot, type, comment) и RemoveVirtualOrder(id), предоставляемые этим скриптом. Для этого вам нужно будет включить этот скрипт как инклюд-файл или просто скопировать функции в ваш основной советник. Пример: #include (если вы используете его как инклюд).
  5. Мониторинг: Отслеживайте логи в MT4 (вкладка 'Эксперты') для проверки работы скрипта и виртуальных ордеров. Для более продвинутого логирования и мониторинга сделок, особенно при работе на VPS, может быть полезной статья MQL5 Discord WebRequest: Логирование Сделок Советника на VPS.
  6. Тестирование: Перед использованием на реальном счете обязательно тщательно протестируйте работу скрипта на демо-счете. Убедитесь, что логика агрегации и управления позицией работает корректно. Если ваш советник использует сложные алгоритмы для генерации сигналов, например, как описано в K-Means в MQL5: Фильтрация ложных сигналов MACD | FinFluct, убедитесь, что взаимодействие с пулом заявок не нарушает его логику.

Оцените статью
FinFluct