MQL5: Трейлинг-стоп по Parabolic SAR с динамическим шагом

MQL5: Трейлинг-стоп по Parabolic SAR с динамическим шагом MQL4 / MQL5 (MetaTrader)
Подробная инструкция по созданию функции трейлинг-стопа в MQL5, использующей индикатор Parabolic SAR с динамическим шагом для оптимизации управления позициями и защиты прибыли в эксперте.
Суть: Функция трейлинг-стопа по Parabolic SAR в MQL5 автоматически корректирует Stop Loss открытых позиций, используя значения индикатора iSAR с его встроенным динамическим шагом, обеспечивая защиту прибыли и минимизацию рисков.

Исходный код

Представляем класс CTrailingSAR, который инкапсулирует всю логику работы с индикатором Parabolic SAR и управления Stop Loss для открытых позиций. Этот подход позволяет легко интегрировать трейлинг-стоп в любой эксперт MQL5, обеспечивая чистоту кода и модульность.

Класс инициализирует индикатор iSAR с заданными параметрами и предоставляет метод CheckAndModifyPositions(), который необходимо вызывать в основном цикле советника (например, в OnTick()). Метод перебирает все открытые позиции, фильтрует их по символу и магическому номеру, а затем рассчитывает новый уровень Stop Loss на основе значения Parabolic SAR с учетом небольшого буфера.


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

class CTrailingSAR
{
private:
    int                 _sarHandle;         // Handle для индикатора Parabolic SAR
    double              _sarStep;           // Шаг (acceleration factor) для SAR
    double              _sarMaximum;        // Максимум (maximum acceleration factor) для SAR
    int                 _magicNumber;       // Магический номер для фильтрации позиций
    int                 _slippage;          // Допустимое проскальзывание в пунктах
    double              _bufferPips;        // Буфер в пунктах от SAR для Stop Loss
    ENUM_TIMEFRAMES     _timeframe;         // Таймфрейм для расчета SAR

    //--- Вспомогательные функции
    double GetSARValue(int shift);
    double NormalizePrice(double price);
    double PipsToPoints(double pips);

public:
    //--- Конструктор
    CTrailingSAR(double sarStep, double sarMaximum, int magicNumber, int slippage, double bufferPips, ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT);

    //--- Деструктор
    ~CTrailingSAR();

    //--- Основная функция для проверки и модификации позиций
    void CheckAndModifyPositions();
};

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTrailingSAR::CTrailingSAR(double sarStep, double sarMaximum, int magicNumber, int slippage, double bufferPips, ENUM_TIMEFRAMES timeframe)
{
    _sarStep = sarStep;
    _sarMaximum = sarMaximum;
    _magicNumber = magicNumber;
    _slippage = slippage;
    _bufferPips = bufferPips;
    _timeframe = timeframe;

    // Инициализация индикатора Parabolic SAR
    _sarHandle = iSAR(Symbol(), _timeframe, _sarStep, _sarMaximum);
    if (_sarHandle == INVALID_HANDLE)
    {
        Print("Ошибка при создании индикатора iSAR. Код ошибки: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CTrailingSAR::~CTrailingSAR()
{
    if (_sarHandle != INVALID_HANDLE)
    {
        IndicatorRelease(_sarHandle);
    }
}

//+------------------------------------------------------------------+
//| Получение значения SAR для заданного сдвига                      |
//+------------------------------------------------------------------+
double CTrailingSAR::GetSARValue(int shift)
{
    if (_sarHandle == INVALID_HANDLE)
    {
        return 0.0;
    }

    double sar_values[1];
    if (CopyBuffer(_sarHandle, 0, shift, 1, sar_values) <= 0)
    {
        Print("Ошибка при копировании буфера SAR. Код ошибки: ", GetLastError());
        return 0.0;
    }
    return sar_values[0];
}

//+------------------------------------------------------------------+
//| Нормализация цены в соответствии с символом                      |
//+------------------------------------------------------------------+
double CTrailingSAR::NormalizePrice(double price)
{
    return NormalizeDouble(price, _Digits);
}

//+------------------------------------------------------------------+
//| Перевод пунктов в пункты (для удобства, если Point() отличается)|
//+------------------------------------------------------------------+
double CTrailingSAR::PipsToPoints(double pips)
{
    // Определяем фактический размер пункта для текущего символа.
    // Для 5-значных символов (например, EURUSD), 1 пипс = 10 * _Point (0.0001 против 0.00001).
    // Для 4-значных символов (например, EURUSD), 1 пипс = 1 * _Point (0.0001 против 0.0001).
    // Для 3-значных JPY символов (например, USDJPY), 1 пипс = 10 * _Point (0.01 против 0.001).
    // Для 2-значных JPY символов (например, USDJPY), 1 пипс = 1 * _Point (0.01 против 0.01).
    // Общая проверка: если количество знаков после запятой (Digits) равно 5 или 3, то это "дробные пипсы".
    if (SymbolInfoInteger(Symbol(), SYMBOL_DIGITS) == 5 || SymbolInfoInteger(Symbol(), SYMBOL_DIGITS) == 3)
    {
        return pips * _Point * 10;
    }
    return pips * _Point;
}

//+------------------------------------------------------------------+
//| Основная функция для проверки и модификации позиций              |
//+------------------------------------------------------------------+
void CTrailingSAR::CheckAndModifyPositions()
{
    if (_sarHandle == INVALID_HANDLE)
    {
        return;
    }

    // Получаем текущие значения SAR и цены
    // Используем SAR с предыдущего закрытого бара (shift=1) для стабильности
    double currentSAR = GetSARValue(1);
    double currentPriceBid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
    double currentPriceAsk = SymbolInfoDouble(Symbol(), SYMBOL_ASK);

    if (currentSAR == 0.0) // Если SAR не удалось получить (например, недостаточно баров)
    {
        return;
    }

    // Перебираем все открытые позиции
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong positionTicket = PositionGetTicket(i);
        if (positionTicket == 0)
        {
            continue;
        }

        if (!PositionSelectByTicket(positionTicket))
        {
            Print("Ошибка при выборе позиции ", positionTicket, ". Код ошибки: ", GetLastError());
            continue;
        }

        // Фильтруем по символу и магическому номеру
        if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != _magicNumber)
        {
            continue;
        }

        ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
        double currentSL = PositionGetDouble(POSITION_SL);
        double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
        double newSL = 0.0;
        bool modifyNeeded = false;

        // Определяем новый Stop Loss в зависимости от типа позиции
        if (positionType == POSITION_TYPE_BUY)
        {
            // Для BUY позиций SAR должен быть ниже текущей цены, указывая на восходящий тренд
            if (currentSAR < currentPriceBid)
            {
                // Новый SL = SAR минус буфер (чтобы дать цене немного пространства)
                newSL = NormalizePrice(currentSAR - PipsToPoints(_bufferPips));

                // Условие для модификации: новый SL должен быть в прибыли ИЛИ текущий SL отсутствует
                // И новый SL должен быть выше текущего SL (только подтягиваем, не опускаем)
                if (newSL > openPrice) // Убеждаемся, что новый SL находится в прибыльной зоне
                {
                    if (currentSL == 0.0 || newSL > currentSL)
                    {
                        modifyNeeded = true;
                    }
                }
            }
        }
        else if (positionType == POSITION_TYPE_SELL)
        {
            // Для SELL позиций SAR должен быть выше текущей цены, указывая на нисходящий тренд
            if (currentSAR > currentPriceAsk)
            {
                // Новый SL = SAR плюс буфер
                newSL = NormalizePrice(currentSAR + PipsToPoints(_bufferPips));

                // Условие для модификации: новый SL должен быть в прибыли ИЛИ текущий SL отсутствует
                // И новый SL должен быть ниже текущего SL (только подтягиваем, не поднимаем)
                if (newSL < openPrice) // Убеждаемся, что новый SL находится в прибыльной зоне
                {
                    if (currentSL == 0.0 || newSL < currentSL)
                    {
                        modifyNeeded = true;
                    }
                }
            }
        }

        // Модифицируем позицию, если необходимо
        if (modifyNeeded)
        {
            MqlTradeRequest request;
            MqlTradeResult result;
            ZeroMemory(request);
            ZeroMemory(result);

            request.action = TRADE_ACTION_SLTP;
            request.position = positionTicket;
            request.sl = newSL;
            request.tp = PositionGetDouble(POSITION_TP); // Сохраняем текущий TP
            request.deviation = _slippage;
            request.symbol = PositionGetString(POSITION_SYMBOL);

            if (!OrderSend(request, result))
            {
                Print("Ошибка при модификации позиции ", positionTicket, ": ", GetLastError(), ". Результат: ", result.retcode);
                // Возможно, стоит отправить уведомление, например, через Pushover.
                // См. статью: MT5 и Pushover: Критические PUSH-уведомления через WebRequest
            }
            else
            {
                if (result.retcode == TRADE_RETCODE_DONE)
                {
                    Print("Позиция ", positionTicket, " SL модифицирован на ", newSL);
                }
                else
                {
                    Print("Не удалось модифицировать позицию ", positionTicket, ". Код ошибки: ", result.retcode);
                }
            }
        }
    }
}

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

Класс CTrailingSAR принимает следующие параметры в своем конструкторе, которые определяют поведение индикатора и логику трейлинг-стопа:

  • sarStep: Начальный шаг (acceleration factor) для индикатора Parabolic SAR. Определяет, насколько быстро SAR приближается к цене. Типичные значения: 0.02.
  • sarMaximum: Максимальный шаг (maximum acceleration factor) для индикатора Parabolic SAR. Ограничивает скорость приближения SAR к цене. Типичные значения: 0.2.
  • magicNumber: Уникальный магический номер, используемый для идентификации позиций, открытых вашим советником. Это позволяет трейлинг-стопу управлять только «своими» позициями.
  • slippage: Допустимое проскальзывание в пунктах при модификации Stop Loss. Защищает от слишком больших отклонений цены при исполнении ордера.
  • bufferPips: Буфер в пипсах, который добавляется или вычитается от значения SAR для установки Stop Loss. Это создает небольшое расстояние между SAR и фактическим SL, чтобы избежать преждевременных срабатываний из-за рыночного шума.
  • timeframe: Таймфрейм, на котором будет рассчитываться индикатор Parabolic SAR. По умолчанию используется текущий таймфрейм графика (PERIOD_CURRENT).

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

Для использования функции трейлинг-стопа в вашем эксперте MQL5 выполните следующие шаги:

  1. Создайте файл класса: Сохраните приведенный выше код класса CTrailingSAR в отдельном файле, например, CTrailingSAR.mqh, в папке MQL5/Include вашего терминала MT5.

  2. Подключите класс к эксперту: В вашем файле эксперта (.mq5) добавьте директиву #include в начале файла:

    
    #include 
    
  3. Объявите внешние параметры: Добавьте внешние переменные для настройки параметров SAR и трейлинг-стопа. Это позволит вам легко изменять их через окно настроек советника:

    
    input double InpSARStep = 0.02;         // Шаг SAR
    input double InpSARMaximum = 0.2;       // Максимум SAR
    input int    InpMagicNumber = 12345;    // Магический номер
    input int    InpSlippage = 5;           // Проскальзывание в пунктах
    input double InpBufferPips = 5.0;       // Буфер от SAR в пипсах
    input ENUM_TIMEFRAMES InpSARTimeframe = PERIOD_CURRENT; // Таймфрейм SAR
    
    CTrailingSAR *g_trailingSAR = NULL; // Глобальный указатель на объект класса
    
  4. Инициализация в OnInit(): Создайте экземпляр класса CTrailingSAR в функции OnInit():

    
    int OnInit()
    {
        g_trailingSAR = new CTrailingSAR(InpSARStep, InpSARMaximum, InpMagicNumber, InpSlippage, InpBufferPips, InpSARTimeframe);
        if (g_trailingSAR == NULL)
        {
            Print("Ошибка инициализации CTrailingSAR.");
            return INIT_FAILED;
        }
        return INIT_SUCCEEDED;
    }
    
  5. Деинициализация в OnDeinit(): Освободите память, удалив объект класса в функции OnDeinit():

    
    void OnDeinit(const int reason)
    {
        if (g_trailingSAR != NULL)
        {
            delete g_trailingSAR;
            g_trailingSAR = NULL;
        }
    }
    
  6. Вызов в OnTick(): Вызывайте метод CheckAndModifyPositions() в функции OnTick() для постоянного мониторинга и корректировки Stop Loss:

    
    void OnTick()
    {
        if (g_trailingSAR != NULL)
        {
            g_trailingSAR->CheckAndModifyPositions();
        }
    }
    

Таким образом, ваш эксперт будет автоматически управлять Stop Loss, следуя за индикатором Parabolic SAR. Для более продвинутого управления советником, например, с интерактивными кнопками, вы можете изучить статью MQL5: Панель управления советником с кнопками Buy/Sell и вводом лота. Если вы планируете создавать более сложные системы, требующие взаимодействия между несколькими терминалами, вам может быть полезна статья Связь между терминалами MT5 через Named Pipes на MQL5.

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