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




