MQL4: Алгоритм Частичного Закрытия Сетки для Снижения Просадки (Partial Grid Close)

MQL4: Алгоритм Частичного Закрытия Сетки для Снижения Просадки (Partial Grid Close) MQL4 / MQL5 (MetaTrader)
Подробное руководство по реализации алгоритма частичного закрытия сетки ордеров (Partial Grid Close) в MQL4 для эффективного управления просадкой и рисками. Включает исходный код и пошаговую инструкцию.
Суть: Алгоритм частичного закрытия сетки (Partial Grid Close) в MQL4 позволяет снизить текущую просадку, закрывая определенный процент объема наиболее убыточных или старых ордеров в рамках одной торговой сетки. Это освобождает маржу и уменьшает риск дальнейшего увеличения убытков, стабилизируя торговый счет.

Исходный код

Представленный ниже код является экспертным советником (Expert Advisor) для MQL4, который реализует логику частичного закрытия сетки. Он предназначен для работы в связке с любой сеточной стратегией, использующей уникальный MagicNumber. Советник сканирует открытые ордера, определяет общую просадку по сетке (отдельно для покупок и продаж), и если она превышает заданный порог, приступает к частичному закрытию убыточных ордеров. Вы можете настроить, закрывать ли самые старые или самые убыточные ордера, а также процент закрываемого объема. Этот подход помогает активно управлять риском и снижать потенциальную просадку, не закрывая всю сетку целиком.


#property copyright "FinFluct"
#property link      "https://finfluct.com"
#property version   "1.00"
#property strict

//--- Input parameters
extern int      MagicNumber         = 12345;    // Magic number for EA's orders
extern double   MinLossToClose      = 100.0;    // Minimum total loss (in account currency) for a grid to trigger partial close
extern double   ClosePercentage     = 0.25;     // Percentage of volume to close from each selected order (e.g., 0.25 for 25%)
extern int      MaxOrdersToClose    = 3;        // Maximum number of orders to partially close per processing cycle
extern bool     CloseOnlyOldest     = true;     // If true, closes oldest losing orders first; otherwise, closes most losing orders first
extern int      Slippage            = 3;        // Maximum allowed slippage in points
extern string   CommentPrefix       = "PartialClose"; // Prefix for order comments

//--- Structure to hold order information for sorting
struct OrderInfo {
    int      ticket;
    int      type;
    double   volume;
    double   profit;
    datetime openTime;
};

//--- Global variable to prevent multiple executions on the same tick
datetime lastTickTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
    Print("Partial Grid Close EA initialized. Magic: ", MagicNumber);
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
    Print("Partial Grid Close EA deinitialized.");
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
    // Prevent multiple executions on the same tick
    if (lastTickTime == TimeCurrent()) return;
    lastTickTime = TimeCurrent();

    //--- Collect all relevant orders
    OrderInfo buyOrders[];
    OrderInfo sellOrders[];
    int buyCount = 0;
    int sellCount = 0;

    for (int i = OrdersTotal() - 1; i >= 0; i--) {
        if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;

        // Filter by current symbol and magic number
        if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;

        // Only consider BUY and SELL orders
        if (OrderType() != OP_BUY && OrderType() != OP_SELL) continue;

        OrderInfo currentOrder;
        currentOrder.ticket = OrderTicket();
        currentOrder.type = OrderType();
        currentOrder.volume = OrderLots();
        currentOrder.profit = OrderProfit();
        currentOrder.openTime = OrderOpenTime();

        if (currentOrder.type == OP_BUY) {
            ArrayResize(buyOrders, buyCount + 1);
            buyOrders[buyCount] = currentOrder;
            buyCount++;
        } else if (currentOrder.type == OP_SELL) {
            ArrayResize(sellOrders, sellCount + 1);
            sellOrders[sellCount] = currentOrder;
            sellCount++;
        }
    }

    //--- Process BUY orders grid
    ProcessGrid(buyOrders, buyCount, OP_BUY);

    //--- Process SELL orders grid
    ProcessGrid(sellOrders, sellCount, OP_SELL);
}

//+------------------------------------------------------------------+
//| Helper function to process a grid of orders (either BUY or SELL) |
//+------------------------------------------------------------------+
void ProcessGrid(OrderInfo& orders[], int count, int orderType) {
    if (count == 0) return;

    //--- Calculate total profit/loss for this grid
    double totalGridProfit = 0;
    for (int i = 0; i < count; i++) {
        totalGridProfit += orders[i].profit;
    }

    //--- Check if the grid is in significant loss
    if (totalGridProfit < -MinLossToClose) {
        Print("Grid (", (orderType == OP_BUY ? "BUY" : "SELL"), ") total loss: ", DoubleToString(totalGridProfit, 2), " exceeds threshold: ", DoubleToString(MinLossToClose, 2));

        //--- Sort orders based on configuration
        if (CloseOnlyOldest) {
            // Sort by open time ascending (oldest first) using bubble sort
            for (int i = 0; i < count - 1; i++) {
                for (int j = i + 1; j < count; j++) {
                    if (orders[i].openTime > orders[j].openTime) {
                        OrderInfo temp = orders[i];
                        orders[i] = orders[j];
                        orders[j] = temp;
                    }
                }
            }
        } else {
            // Sort by profit ascending (most losing first) using bubble sort
            for (int i = 0; i < count - 1; i++) {
                for (int j = i + 1; j < count; j++) {
                    if (orders[i].profit > orders[j].profit) { // Swap if i is less losing than j
                        OrderInfo temp = orders[i];
                        orders[i] = orders[j];
                        orders[j] = temp;
                    }
                }
            }
        }

        int closedCount = 0;
        for (int i = 0; i < count && closedCount < MaxOrdersToClose; i++) {
            // Only consider closing losing orders
            if (orders[i].profit < 0) {
                double volumeToClose = orders[i].volume * ClosePercentage;

                // Ensure volume is within allowed limits
                double minLot = MarketInfo(Symbol(), MODE_MINLOT);
                double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

                // Round volume to lot step
                volumeToClose = NormalizeDouble(volumeToClose / lotStep, 0) * lotStep;

                if (volumeToClose < minLot) {
                    Print("Calculated volume ", DoubleToString(volumeToClose, 2), " for order ", orders[i].ticket, " is less than min lot (", DoubleToString(minLot, 2), "). Skipping partial close.");
                    continue;
                }
                if (volumeToClose > orders[i].volume) { // Don't close more than available
                    volumeToClose = orders[i].volume;
                }

                // Select the order again to ensure it's still open and get fresh data
                if (OrderSelect(orders[i].ticket, SELECT_BY_TICKET)) {
                    double currentPrice;
                    if (orderType == OP_BUY) currentPrice = MarketInfo(Symbol(), MODE_BID);
                    else currentPrice = MarketInfo(Symbol(), MODE_ASK);

                    if (OrderClose(orders[i].ticket, volumeToClose, currentPrice, Slippage, CommentPrefix)) {
                        Print("Partially closed order #", orders[i].ticket, " (", (orderType == OP_BUY ? "BUY" : "SELL"), ") volume: ", DoubleToString(volumeToClose, 2), " at price: ", DoubleToString(currentPrice, Digits), ". Remaining volume: ", DoubleToString(OrderLots() - volumeToClose, 2));
                        closedCount++;
                        Sleep(100); // Small delay to avoid flooding the server
                    } else {
                        Print("Failed to partially close order #", orders[i].ticket, ". Error: ", GetLastError(), " (", ErrorDescription(GetLastError()), ")");
                    }
                } else {
                    Print("Failed to select order #", orders[i].ticket, " for partial close. It might have been closed already or ticket is invalid. Error: ", GetLastError(), " (", ErrorDescription(GetLastError()), ")");
                }
            }
        }
    }
}

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

  • MagicNumber: Уникальный магический номер, используемый экспертным советником для идентификации своих ордеров. Убедитесь, что он совпадает с MagicNumber вашей основной сеточной стратегии.
  • MinLossToClose: Минимальный общий убыток (в валюте счета) для группы ордеров (сетки) одного типа (покупки или продажи), при достижении которого активируется алгоритм частичного закрытия.
  • ClosePercentage: Процент от текущего объема каждого выбранного ордера, который будет закрыт. Например, 0.25 означает закрытие 25% объема.
  • MaxOrdersToClose: Максимальное количество ордеров, которые будут частично закрыты за один цикл обработки. Это позволяет контролировать скорость и объем закрытий.
  • CloseOnlyOldest: Логический параметр. Если true, советник будет сначала закрывать самые старые убыточные ордера. Если false, будут закрываться ордера с наибольшим текущим убытком.
  • Slippage: Максимально допустимое проскальзывание в пунктах при исполнении ордера на частичное закрытие.
  • CommentPrefix: Префикс, добавляемый к комментарию ордера при его частичном закрытии. Помогает отслеживать действия советника в истории счета.

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

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

1. Скопируйте предоставленный MQL4 код в MetaEditor (обычно находится в File -> Open Data Folder -> MQL4 -> Experts).

2. Сохраните файл с расширением .mq4 (например, PartialGridClose.mq4).

3. Скомпилируйте код, нажав кнопку ‘Compile’ в MetaEditor. Убедитесь, что нет ошибок.

4. Откройте торговый терминал MetaTrader 4.

5. Перетащите скомпилированный экспертный советник PartialGridClose из окна ‘Навигатор’ на график валютной пары, на которой работает ваша основная сеточная стратегия. Убедитесь, что MagicNumber этого советника совпадает с MagicNumber вашей основной стратегии.

6. В окне настроек советника перейдите на вкладку ‘Входные параметры’ и настройте значения MinLossToClose, ClosePercentage, MaxOrdersToClose и CloseOnlyOldest в соответствии с вашей стратегией управления рисками.

7. Убедитесь, что на вкладке ‘Общие’ установлен флажок ‘Разрешить автоматическую торговлю’ и кнопка ‘Автоторговля’ в терминале активна (зеленая).

8. Настоятельно рекомендуется сначала протестировать советник на демо-счете или в тестере стратегий. Хотя этот алгоритм предназначен для MQL4, принципы тестирования и оптимизации универсальны. Для более глубокого понимания тестирования сложных систем, особенно мультивалютных, вы можете ознакомиться с нашей статьей Мультивалютное тестирование в MT5: обход ограничений тестера, которая хоть и ориентирована на MT5, но подчеркивает важность тщательной проверки.

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

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