MQL5 Bootstrap (IV): Вспомогательные средства для трейлинг-стопов и переноса стоп-лосса в безубыток
Содержание
- Введение
- Что такое трейлинг-стоп
- Трейлинг-стоп с фиксированными значениями
- Трейлинг-стоп на основе скользящей средней
- Трейлинг-стоп на основе индикатора ATR
- Трейлинг-стоп на основе индикатора Parabolic SAR
- Трейлинг-стоп в денежном выражении
- Периодический трейлинг-стоп
- Что такое перенос стоп-лосса в безубыток
- Перенос стоп-лосса в безубыток по фиксированному числу пунктов
- Перенос стоп-лосса в безубыток с использованием денежных значений
- Заключение
Введение
В предыдущей статье мы добавили в наш проект Bootstrap упрощенные функции для работы с новостями. В этой статье мы создадим типовые вспомогательные средства для трейлинг-стопов и переноса стоп-лосса в безубыток.
При разработке советников или вспомогательных средств управления торговлей разработчики сталкиваются с одними и теми же задачами: как безопасно перемещать стоп-лоссы, соблюдая ограничения брокера на стоп-уровни; как избежать переноса в обратном направлении при изменении значений индикаторов; как правильно преобразовывать целевые денежные величины в ценовые расстояния; как единообразно применять логику к позициям BUY и SELL, разным символам и магическим номерам. Эти повторяющиеся задачи часто приводят к дублированию кода, трудноуловимым ошибкам и потере времени. В этой статье представлен набор переиспользуемых, единообразно спроектированных компонентов для трейлинг-стопов и переноса стоп-лосса в безубыток, которые можно подключить к любому проекту на базе Bootstrap. Эти вспомогательные средства включают типовые защитные механизмы (валидацию уровней, защиту от переноса в обратном направлении, фильтрацию по символу и магическому номеру, корректное преобразование денежных значений в ценовые расстояния). Благодаря этому вы можете внедрить правила трейлинга и переноса стоп-лосса в безубыток в OnTick с минимальными усилиями и доработками.
Трейлинг-стоп и перенос стоп-лосса в безубыток — важнейшие инструменты управления сделками, которые трейдеры используют для защиты открытых позиций и управления рисками. Они позволяют торговым программам автоматически корректировать уровни стоп-лосса по мере того, как позиция выходит в прибыль, снижая вероятность потери накопленной прибыли при развороте рынка.
По определению:
Трейлинг-стоп — модификация обычного ордера стоп-лосс, при которой цена срабатывания стоп-лосса автоматически корректируется по мере движения рыночной цены в прибыльном направлении.
Перенос стоп-лосса в безубыток означает его перемещение точно на цену входа в сделку после того, как рынок начинает двигаться в вашу пользу. Этот прием исключает финансовый риск: при развороте рынка сделка закроется без убытка и без прибыли (без учета комиссий и проскальзывания).

Практически каждый опытный программист писал функции и код для автоматического мониторинга позиций и обновления стоп-лоссов, поэтому эти две операции играют ключевую роль во многих программах.
Единственного или наилучшего способа реализации не существует, поэтому не стоит принимать наши слова за окончательный вердикт. В этой статье мы представим вспомогательные функции и классы проекта Bootstrap для трейлинг-стопов и переноса стоп-лосса в безубыток. Благодаря этому вам не придется каждый раз писать их заново, и вы сможете повысить свою продуктивность как разработчика.
Что такое трейлинг-стоп
Чтобы понять, что такое трейлинг-стоп, сначала необходимо разобраться в двух важных понятиях:
01 — Величина трейлинг-стопа
Это расстояние, которое вы хотите поддерживать между текущей рыночной ценой и уровнем стоп-лосса.
Например, предположим, что вы открываете позицию на покупку по EURUSD по цене 1,1000 с трейлинг-стопом в 50 пунктов:
- Когда цена поднимется до 1,1050, стоп-лосс переместится на уровень 1,1000, сохраняя расстояние в 50 пунктов.
- А когда цена поднимется до 1,1075, стоп-лосс переместится на уровень 1,1025, снова оставаясь на 50 пунктов ниже текущей цены.
Величина трейлинг-стопа определяет, на каком расстоянии от рыночной цены будет следовать ваш стоп-лосс.
02 — Величина шага трейлинга
Величина шага трейлинга определяет минимальное изменение цены, необходимое для повторного обновления стоп-лосса.
Например:
- Трейлинг-стоп = 50 пунктов
- Шаг трейлинга = 10 пунктов
По мере изменения цены:
| Текущая цена | Стоп-лосс | Действие |
|---|---|---|
| 1,1050 | 1,1000 | Начальный трейлинг. |
| 1,1055 | 1,1000 | Без изменений (цена изменилась всего на 5 пунктов). |
| 1,1060 | 1,1010 | Стоп-лосс обновлен (цена изменилась на 10 пунктов). |
| 1,1065 | 1,1010 | Без изменений (цена изменилась на 5 пунктов). |
| 1,1070 | 1,1020 | Стоп-лосс обновлен (цена выросла на 10 пунктов (шаг); расстояние до стоп-лосса сохранено — 50 пунктов). |
Проще говоря:
Трейлинг-стоп определяет, на каком расстоянии от текущей цены должен оставаться стоп-лосс.
А шаг трейлинга определяет, насколько должна измениться цена, прежде чем стоп-лосс будет снова перенесен.
Трейлинг-стоп с фиксированными значениями
Наиболее распространенный вариант трейлинг-стопа использует фиксированные расстояния, которые обычно задаются во входных параметрах советника.
Такой подход позволяет трейдерам задать расстояние трейлинга и величину шага, которые программа использует при корректировке стоп-лосса.
input uint TRAILING_STEP = 200; input uint TRAILING_STOP = 20;
Ниже приведена вспомогательная функция для решения этой задачи.
//+------------------------------------------------------------------+ //| Trail open positions using a fixed-point trailing stop. | //| | //| Once a position has moved in profit by at least `trail_points`, | //| the stop loss is automatically adjusted to remain | //| `step_points` behind the current market price. The stop loss is | //| updated only when the new level differs from the current one and | //| satisfies the broker's stop level requirements. | //| | //| Parameters: | //| symbol - Trading symbol whose positions will be managed. | //| trail_points - Minimum profit (in points) required before | //| trailing begins. | //| step_points - Distance (in points) maintained between the | //| current market price and the stop loss. | //| magic - Magic number used to filter positions. Specify | //| -1 to trail all positions on the symbol. | //| | //+------------------------------------------------------------------+ void TrailingFixedPoints::TrailStops(string symbol, double trail_points, double step_points, long magic = -1) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); CPositionInfo pos; CTrade trade; //--- for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!pos.SelectByIndex(i)) continue; if(pos.Symbol() != symbol) continue; long pos_magic = pos.Magic(); if(magic != -1) if(pos_magic != magic) continue; double sl = pos.StopLoss(), tp = pos.TakeProfit(), open_price = pos.PriceOpen(); ulong ticket = pos.Ticket(); ENUM_POSITION_TYPE type = pos.PositionType(); switch(type) { case POSITION_TYPE_BUY: { //--- Ensure the position has moved enough double bid = SymbolInfoDouble(symbol, SYMBOL_BID); if(bid - open_price < trail_points * point) continue; double new_sl = bid - step_points * point; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_BUY, new_sl, tp, symbol)) continue; trade.PositionModify(ticket, new_sl, tp); } break; case POSITION_TYPE_SELL: { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); //--- Ensure the position has moved enough if(open_price - ask < trail_points * point) continue; double new_sl = ask + step_points * point; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_SELL, new_sl, tp, symbol)) continue; trade.PositionModify(ticket, new_sl, tp); } break; default: break; } } }
Перед модификацией позиции функция проверяет, достаточно ли цена изменилась в прибыльном направлении для активации трейлинг-стопа. Это гарантирует, что стоп-лосс будет скорректирован только после достижения заданного расстояния трейлинга.
Для позиции на покупку:
if(bid - open_price < trail_points * point) continue;
Для позиции на продажу:
if(open_price - ask < trail_points * point) continue;
После выполнения условия активации стоп-лосс пересчитывается с использованием значения step_points. Для позиций BUY стоп-лосс устанавливается ниже текущей цены Bid, а для позиций SELL — выше текущей цены Ask.
Значение step_points определяет расстояние между текущей рыночной ценой и новым уровнем стоп-лосса, позволяя ему следовать за рынком с заранее заданным безопасным расстоянием.
Пример использования:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!m_symbol.RefreshRates()) return; //--- double lots = m_symbol.LotsMin(); double ask = m_symbol.Ask(), bid = m_symbol.Bid(); double pts = m_symbol.Point(); //--- if(!PositionExistsByType(POSITION_TYPE_BUY)) m_trade.Buy(lots, sym, ask, ask - STOPLOSS * pts); CTrailingFixedPoints::TrailStops(sym, TRAILING_STOP, TRAILING_STEP, MAGIC_NUMBER); }
Вывод:

Трейлинг-стоп на основе скользящей средней
Один из самых простых и эффективных способов динамической корректировки стоп-лосса — использование индикатора скользящей средней в качестве ориентира для определения новых уровней стоп-лосса.
Скользящая средняя отражает постоянно обновляемый ценовой уровень, который следует за рыночным трендом. Если использовать скользящую среднюю как ориентир для трейлинг-стопа, стоп-лосс может автоматически смещаться в направлении тренда, оставляя позицию открытой, пока рынок остается благоприятным.
//+------------------------------------------------------------------+ //| Trail open positions using a Moving Average. | //| | //| The stop loss is adjusted to the current value of a Moving | //| Average indicator. | //| | //| Parameters: | //| handle - Handle of the Moving Average indicator created using | //| iMA() or IndicatorCreate(). | //| symbol - Trading symbol whose positions will be managed. | //| magic - Magic number used to filter positions. Specify -1 to | //| trail all positions on the specified symbol. | //| | //+------------------------------------------------------------------+ void CTrailingMA::TrailStops(int handle, string symbol, long magic = -1) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); CPositionInfo pos; CTrade trade; //--- double ma[]; if(!CopyBuffer(handle, 0, 0, 1, ma)) return; double ma_value = ma[0]; //--- for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!pos.SelectByIndex(i)) continue; if(pos.Symbol() != symbol) continue; double sl = pos.StopLoss(), open_price = pos.PriceOpen(); ulong ticket = pos.Ticket(); ENUM_POSITION_TYPE type = pos.PositionType(); double tp = pos.TakeProfit(); //--- switch(type) { case POSITION_TYPE_BUY: { if(!isPositionModificationSameLevels(ticket, ma_value, tp)) continue; //--- Modify only when the moving average is above the current sl if(sl != 0 && ma_value <= sl) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_BUY, ma_value, tp, symbol)) continue; trade.PositionModify(ticket, ma_value, tp); } break; case POSITION_TYPE_SELL: { if(!isPositionModificationSameLevels(ticket, ma_value, tp)) continue; //--- Modify only when the moving average is below the current sl if(sl != 0 && ma_value >= sl) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_SELL, ma_value, tp, symbol)) continue; trade.PositionModify(ticket, ma_value, tp); } break; default: break; } } }
Чтобы пользователи могли выбрать подходящий вариант индикатора, указанная выше статическая функция принимает хэндл индикатора скользящей средней.
Поскольку скользящая средняя может изменить направление на противоположное, нужно предотвращать перенос стоп-лосса в направлении, не соответствующем типу позиции. Например, при таких модификациях расстояние до стоп-лосса позиции на покупку увеличивается, поскольку он перемещается вслед за снижающейся скользящей средней.
Для позиции на покупку:
//--- Modify only when the moving average is above the current sl if(sl != 0 && ma_value <= sl) continue;
Для позиции на продажу:
//--- Modify only when the moving average is below the current sl if(sl != 0 && ma_value >= sl) continue;
Приведенные выше операторы if (условия) предотвращают то, что я называю "обратной модификацией".
Есть еще одна перегрузка TrailStops(), которая не принимает параметров. В отличие от статической версии, эта перегрузка использует хэндл, символ и магический номер, хранящиеся в экземпляре класса. Эти значения инициализируются при создании объекта, что позволяет вызывать метод TrailStops() напрямую, не указывая каждый раз одни и те же параметры.
class CTrailingMA { protected: int m_handle; long m_magic; string m_symbol; public: CTrailingMA(const int handle, const string symbol, const long magic=-1); ~CTrailingMA(void); void TrailStops(); static void TrailStops(int handle, string symbol, long magic = -1); };
Пример использования:
#include <Bootstrap\Trailing\TrailingMA.mqh> #include <Bootstrap\positions.mqh> #include <Trade\SymbolInfo.mqh> CTrade m_trade; CSymbolInfo m_symbol; #define sym Symbol() #define MAGIC_NUMBER 112233 int ma_handle; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- m_symbol.Name(sym); m_trade.SetExpertMagicNumber(MAGIC_NUMBER); m_trade.SetTypeFillingBySymbol(sym); m_trade.SetDeviationInPoints(100); //--- ma_handle = iMA(sym, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE); if(ma_handle == INVALID_HANDLE) { printf("Invalid MA handle. Error = %d", GetLastError()); return INIT_FAILED; } ChartIndicatorAdd(0, 0, ma_handle); //Attach the indicator to the chart //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!m_symbol.RefreshRates()) return; //--- double lots = m_symbol.LotsMin(); double ask = m_symbol.Ask(), bid = m_symbol.Bid(); if(!PositionExistsByType(POSITION_TYPE_BUY)) m_trade.Buy(lots, sym, ask); CTrailingMA::TrailStops(ma_handle, sym, MAGIC_NUMBER); //Trailing stop based on the moving average }
Результаты:

Трейлинг-стоп на основе индикатора Average True Range (ATR)
Индикатор ATR широко используется для определения уровней стоп-лосса и тейк-профита. Поскольку этот индикатор отражает текущую волатильность рынка, он подходит для автоматической корректировки стоп-лосса.
В отличие от трейлинг-стопов с фиксированным расстоянием в пунктах, которые сохраняют это расстояние независимо от поведения рынка, трейлинг-стоп на основе ATR автоматически адаптируется к изменению волатильности. При высокой волатильности корректировка стоп-лосса может быть значительной, а при низкой волатильности — небольшой.
//+------------------------------------------------------------------+ //| Trail open positions using the Average True Range (ATR). | //| | //| Trailing is based on the current market volatility as measured | //| by the Average True Range (ATR). //| | //| Parameters: | //| handle - Handle of the ATR indicator created | //| using iATR() or IndicatorCreate(). | //| symbol - Trading symbol whose positions will be | //| managed. | //| stop_atr_multiplier - ATR multiple required before trailing | //| is activated. | //| step_atr_multiplier - ATR multiple maintained between the | //| current market price and the stop loss. | //| magic - Magic number used to filter positions. | //| Specify -1 to trail all positions on | //| the symbol. | //| | //+------------------------------------------------------------------+ void CTrailingATR::TrailStops(int handle, string symbol, double stop_atr_multiplier = 0.5, double step_atr_multiplier = 0.2, long magic = -1) { CPositionInfo pos; CTrade trade; //--- double atr_buff[]; if(!CopyBuffer(handle, 0, 0, 1, atr_buff)) return; double atr_value = atr_buff[0]; //--- for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!pos.SelectByIndex(i)) continue; if(pos.Symbol() != symbol) continue; long pos_magic = pos.Magic(); if(magic != -1) if(pos_magic != magic) continue; double sl = pos.StopLoss(), tp = pos.TakeProfit(), open_price = pos.PriceOpen(); double sl_gap = fabs(open_price - sl); //--- ulong ticket = pos.Ticket(); ENUM_POSITION_TYPE type = pos.PositionType(); switch(type) { case POSITION_TYPE_BUY: { //--- Ensure the position has moved enough double bid = SymbolInfoDouble(symbol, SYMBOL_BID); if(bid - open_price < atr_value * stop_atr_multiplier) continue; double new_sl = bid - atr_value * step_atr_multiplier; if(sl != 0 && new_sl <= sl) continue; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_BUY, new_sl, tp, symbol)) continue; trade.PositionModify(ticket, new_sl, tp); } break; case POSITION_TYPE_SELL: { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); //--- Ensure the positoin has moved enough if(open_price - ask < atr_value * stop_atr_multiplier) continue; double new_sl = ask + atr_value * step_atr_multiplier; if(sl != 0 && new_sl >= sl) continue; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_SELL, new_sl, tp, symbol)) continue; trade.PositionModify(ticket, new_sl, tp); } break; default: break; } } }
Как и рассмотренный ранее трейлинг-стоп на основе скользящей средней, трейлинг-стоп на основе ATR должен предотвращать перенос стоп-лосса в обратном направлении.
Поскольку значение ATR изменяется вместе с волатильностью рынка, рассчитанное расстояние до стоп-лосса также может увеличиваться или уменьшаться. При росте волатильности повторный расчет ATR может дать уровень стоп-лосса, более удаленный от текущей цены. Такая модификация ослабила бы защиту стоп-лоссом и увеличила бы риск по позиции.
Чтобы этого избежать, стоп-лосс корректируется только тогда, когда новый уровень усиливает текущую защиту.
Для позиции на покупку:
if(sl != 0 && new_sl <= sl) continue;
Для позиции на продажу:
if(sl != 0 && new_sl >= sl) continue;
Пример использования:
#include <Bootstrap\Trailing\TrailingATR.mqh> #include <Bootstrap\positions.mqh> #include <Trade\SymbolInfo.mqh> CTrade m_trade; CSymbolInfo m_symbol; #define sym Symbol() #define MAGIC_NUMBER 112233 int atr_handle; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- m_symbol.Name(sym); m_trade.SetExpertMagicNumber(MAGIC_NUMBER); m_trade.SetTypeFillingBySymbol(sym); m_trade.SetDeviationInPoints(100); //--- atr_handle = iATR(sym, PERIOD_CURRENT, 13); if(atr_handle == INVALID_HANDLE) { printf("Invalid ATR handle. Error = %d", GetLastError()); return INIT_FAILED; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!m_symbol.RefreshRates()) return; //--- double lots = m_symbol.LotsMin(); double ask = m_symbol.Ask(), bid = m_symbol.Bid(); double pts = m_symbol.Point(); //--- if(!PositionExistsByType(POSITION_TYPE_BUY)) m_trade.Buy(lots, sym, ask, ask - STOPLOSS * pts); CTrailingATR::TrailStops(atr_handle, sym, 1, 0.4); }
Результаты:

Трейлинг-стоп на основе индикатора Parabolic SAR
Parabolic SAR (Stop and Reverse) — вполне эффективный трендовый индикатор. Его можно использовать для корректировки трейлинг-стопа с тем же подходом, который мы применяли к индикатору скользящей средней.
//+------------------------------------------------------------------+ //| Trail open positions using the Parabolic SAR indicator. | //| | //| The stop loss is adjusted to the latest value of the Parabolic | //| SAR (Stop and Reverse) indicator. | //| | //| Parameters: | //| handle - Handle of the Parabolic SAR indicator created using | //| iSAR() or IndicatorCreate(). | //| symbol - Trading symbol whose positions will be managed. | //| magic - Magic number used to filter positions. Specify -1 to | //| trail all positions on the specified symbol. | //| | //+------------------------------------------------------------------+ void CTrailingSAR::TrailStops(int handle, string symbol, long magic = -1) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); CPositionInfo pos; CTrade trade; //--- double sar_buff[]; if(!CopyBuffer(handle, 0, 0, 1, sar_buff)) return; double sar_value = sar_buff[0]; //--- for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!pos.SelectByIndex(i)) continue; if(pos.Symbol() != symbol) continue; double sl = pos.StopLoss(), tp = pos.TakeProfit(), open_price = pos.PriceOpen(); ulong ticket = pos.Ticket(); ENUM_POSITION_TYPE type = pos.PositionType(); //--- switch(type) { case POSITION_TYPE_BUY: { if(!isPositionModificationSameLevels(ticket, sar_value, tp)) continue; //--- Modify only when the SAR is above the current sl if(sl != 0 && sar_value <= sl) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_BUY, sar_value, tp, symbol)) continue; trade.PositionModify(ticket, sar_value, tp); } break; case POSITION_TYPE_SELL: { if(!isPositionModificationSameLevels(ticket, sar_value, tp)) continue; //--- Modify only when the SAR is below the current sl if(sl != 0 && sar_value >= sl) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_SELL, sar_value, tp, symbol)) continue; trade.PositionModify(ticket, sar_value, tp); } break; default: break; } } }
Для позиции BUY значение Parabolic SAR обычно находится ниже текущей цены. По мере развития восходящего тренда уровень SAR постепенно смещается вверх, задавая динамический уровень стоп-лосса, который следует за трендом и защищает накопленную прибыль.
Для позиции SELL значение SAR остается выше текущей цены и смещается вниз по мере дальнейшего снижения рынка.
Пример использования:
#include <Bootstrap\Trailing\TrailingSAR.mqh> #include <Bootstrap\positions.mqh> #include <Trade\SymbolInfo.mqh> CTrade m_trade; CSymbolInfo m_symbol; #define sym Symbol() #define MAGIC_NUMBER 112233 int sar_handle; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- m_symbol.Name(sym); m_trade.SetExpertMagicNumber(MAGIC_NUMBER); m_trade.SetTypeFillingBySymbol(sym); m_trade.SetDeviationInPoints(100); //--- sar_handle = iSAR(sym, PERIOD_CURRENT, 0.02, 0.2); if(sar_handle == INVALID_HANDLE) { printf("Invalid SAR handle. Error = %d", GetLastError()); return INIT_FAILED; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!m_symbol.RefreshRates()) return; //--- double lots = m_symbol.LotsMin(); double ask = m_symbol.Ask(), bid = m_symbol.Bid(); double pts = m_symbol.Point(); //--- if(!PositionExistsByType(POSITION_TYPE_BUY)) m_trade.Buy(lots, sym, ask, ask - STOPLOSS * pts); CTrailingSAR::TrailStops(sar_handle, sym, MAGIC_NUMBER); }
Результаты:

Трейлинг-стоп в денежном выражении
Хотя этот метод встречается реже, чем рассмотренные ранее, некоторые трейдеры предпочитают управлять трейлинг-стопами, ориентируясь на фактический денежный результат позиции. Таким образом, вместо корректировки стоп-лосса по пипсам, пунктам или значениям индикаторов этот подход использует суммы в валюте счета.
Это позволяет трейдерам настраивать трейлинг-стоп по фактической прибыли или убытку, а не по движению цены, благодаря чему система не зависит от спецификаций символа, размера лота и волатильности рынка.
//+------------------------------------------------------------------+ //| Trail open positions based on monetary profit. | //| | //| This trailing stop method adjusts the stop loss based on the | //| actual monetary value of the position rather than a fixed price | //| distance, indicator value, or number of points. Once a position | //| reaches the specified profit threshold, the stop loss is moved | //| to maintain a defined monetary distance from the current market | //| price. | //| | //| Parameters: | //| symbol - Trading symbol whose positions will be | //| managed. | //| activation_money - Minimum profit in account currency required | //| before the trailing stop is activated. | //| trail_money - Amount of profit in account currency to | //| maintain as the trailing distance. | //| magic - Magic number used to filter positions. | //| Specify -1 to trail all positions on the | //| specified symbol. | //| | //+------------------------------------------------------------------+ void CTrailingMoney::TrailStops(string symbol, double activation_money, double trail_money, long magic = -1) { CPositionInfo pos; CTrade trade; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!pos.SelectByIndex(i)) continue; if(pos.Symbol() != symbol) continue; if(magic != -1 && pos.Magic() != magic) continue; double profit = pos.Profit(); // Wait until the desired profit is reached if(profit < activation_money) continue; double volume = pos.Volume(); double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); if(tick_size <= 0 || tick_value <= 0) continue; // Convert money into a price distance double distance = trail_money * tick_size / (tick_value * volume); double tp = pos.TakeProfit(); double sl = pos.StopLoss(); ulong ticket = pos.Ticket(); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); switch(pos.PositionType()) { case POSITION_TYPE_BUY: { double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double new_sl = NormalizeDouble(bid - distance, digits); if(sl != 0 && new_sl <= sl) continue; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_BUY, new_sl, tp, symbol)) continue; if(!trade.PositionModify(ticket, new_sl, tp)) Print(trade.ResultRetcodeDescription()); } break; case POSITION_TYPE_SELL: { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double new_sl = NormalizeDouble(ask + distance, digits); if(sl != 0 && new_sl >= sl) continue; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_SELL, new_sl, tp, symbol)) continue; if(!trade.PositionModify(ticket, new_sl, tp)) Print(trade.ResultRetcodeDescription()); } break; } } }
Например, зададим порог активации трейлинга 5 USD, а денежный шаг трейлинга — 1 USD:
#include <Bootstrap\Trailing\TrailingMoney.mqh> #include <Bootstrap\positions.mqh> #include <Trade\SymbolInfo.mqh> CTrade m_trade; CSymbolInfo m_symbol; #define sym Symbol() #define MAGIC_NUMBER 112233 //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- m_symbol.Name(sym); m_trade.SetExpertMagicNumber(MAGIC_NUMBER); m_trade.SetTypeFillingBySymbol(sym); m_trade.SetDeviationInPoints(100); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!m_symbol.RefreshRates()) return; //--- double lots = m_symbol.LotsMin(); double ask = m_symbol.Ask(), bid = m_symbol.Bid(); double pts = m_symbol.Point(); //--- if(!PositionExistsByType(POSITION_TYPE_BUY)) m_trade.Buy(lots, sym, ask, ask - STOPLOSS * pts); CTrailingMoney::TrailStops(sym, 5, 1, MAGIC_NUMBER); }
Результаты:

Периодический трейлинг-стоп
В отличие от рассмотренных выше методов, периодический трейлинг-стоп работает на основе времени. Вместо корректировки стоп-лосса по движению рынка этот метод со временем сокращает расстояние до стоп-лосса.
//+------------------------------------------------------------------+ //| Trail open positions at fixed time intervals. | //| | //| This trailing stop method updates the stop loss only after a | //| specified amount of time has elapsed since the last position | //| modification. //| | //| Parameters: | //| interval_seconds - Minimum time in seconds required between | //| consecutive stop loss modifications. | //| trail_step - Number of points by which the stop loss is | //| moved on each update. | //| symbol - Trading symbol whose positions will be | //| managed. | //| magic - Magic number used to filter positions. | //| Specify -1 to trail all positions on the | //| specified symbol. | //| | //+------------------------------------------------------------------+ void CTrailingPeriodic::TrailStops(ulong interval_seconds, double trail_step, string symbol, long magic = -1) { CPositionInfo pos; CTrade trade; double point = SymbolInfoDouble(symbol, SYMBOL_POINT); //--- for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!pos.SelectByIndex(i)) continue; if(pos.Symbol() != symbol) continue; long pos_magic = pos.Magic(); if(magic != -1) if(pos_magic != magic) continue; double sl = pos.StopLoss(), tp = pos.TakeProfit(), open_price = pos.PriceOpen(); double sl_gap = fabs(open_price - sl); //--- ulong ticket = pos.Ticket(); ENUM_POSITION_TYPE type = pos.PositionType(); ulong open_time = (ulong)pos.Time(); ulong update_time = pos.TimeUpdate(); //--- modify after a specified number of seconds has passed ulong time_diff = (long)TimeCurrent() - update_time; if(time_diff < interval_seconds) continue; switch(type) { case POSITION_TYPE_BUY: { double new_sl = sl + trail_step * point; //increase the stoploss by a given number of points if(sl != 0 && new_sl <= sl) continue; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_BUY, new_sl, tp, symbol)) continue; trade.PositionModify(ticket, new_sl, tp); } break; case POSITION_TYPE_SELL: { double new_sl = sl - trail_step * point; //reduce the stoploss by a given number of points if(sl != 0 && new_sl >= sl) continue; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_SELL, new_sl, tp, symbol)) continue; trade.PositionModify(ticket, new_sl, tp); } break; default: break; } } }
Например, каждый час для позиции BUY вы сокращаете расстояние до стоп-лосса на 100 пунктов.
#include <Bootstrap\Trailing\TrailingPeriodic.mqh> #include <Bootstrap\positions.mqh> #include <Trade\SymbolInfo.mqh> CTrade m_trade; CSymbolInfo m_symbol; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- m_symbol.Name(sym); m_trade.SetExpertMagicNumber(MAGIC_NUMBER); m_trade.SetTypeFillingBySymbol(sym); m_trade.SetDeviationInPoints(100); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!m_symbol.RefreshRates()) return; //--- double lots = m_symbol.LotsMin(); double ask = m_symbol.Ask(), bid = m_symbol.Bid(); double pts = m_symbol.Point(); //--- if(!PositionExistsByType(POSITION_TYPE_BUY)) m_trade.Buy(lots, sym, ask, ask - STOPLOSS * pts); CTrailingPeriodic::TrailStops(3600, 100, sym, MAGIC_NUMBER); }
Результаты:

Что такое перенос стоп-лосса в безубыток
В отличие от трейлинг-стопа, который постоянно корректирует стоп-лосс по мере движения рынка в вашу пользу, перенос стоп-лосса в безубыток выполняется однократно. Как только прибыль по позиции достигает заданного уровня, стоп-лосс переносится на цену открытия сделки или немного за нее с использованием необязательного смещения.
Основная цель переноса стоп-лосса в безубыток — исключить возможность превращения прибыльной сделки в убыточную. Если после переноса в безубыток рынок разворачивается, позиция закрывается либо по цене входа (без убытка), либо с небольшой прибылью, когда применяется смещение.
Предположим, вы открываете позицию BUY по цене 1,10000 с начальным стоп-лоссом на уровне 1,09500.
Если прибыль по сделке составит 300 пунктов, ваш советник сможет перенести стоп-лосс на уровень 1,10000, обеспечив закрытие сделки без убытка при любом последующем развороте рынка.
В качестве альтернативы вы можете перенести стоп-лосс на уровень 1,10020, зафиксировав небольшую прибыль и оставив сделке пространство для дальнейшего развития.
В проекте Bootstrap предусмотрены методы, аналогичные методам трейлинг-стопа; рассмотрим их ниже.
Перенос стоп-лосса в безубыток по фиксированному числу пунктов
Метод переноса стоп-лосса в безубыток по фиксированному числу пунктов использует два основных параметра, определяющих, когда и куда следует перенести стоп-лосс.
- activation_points — минимальная прибыль в пунктах, необходимая для переноса стоп-лосса в безубыток. Как только нереализованная прибыль достигает этого порога, стоп-лосс переносится с исходного уровня на уровень безубытка.
- offset_points — количество пунктов, добавляемых к цене открытия или вычитаемых из нее при установке нового стоп-лосса. Это позволяет трейдеру зафиксировать небольшую прибыль вместо того, чтобы переносить стоп-лосс точно на цену входа.
//+------------------------------------------------------------------+ //| Adjusts the stoploss to position's opening price + offset_points | //| | //| Parameters: | //| symbol - Trading symbol whose positions will be managed. | //| activation_points - Minimum profit (in points) required before | //| break-even begins. | //| offset_points - Distance (in points) to add to the new | //| stoploss. | //| magic - Magic number used to filter positions. Specify | //| -1 to trail all positions on the symbol. | //| | //+------------------------------------------------------------------+ void CBreakEvenFixedPoints::BreakEven(double activation_points, double offset_points, string symbol, long magic = -1) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); CPositionInfo pos; CTrade trade; //--- for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!pos.SelectByIndex(i)) continue; if(pos.Symbol() != symbol) continue; long pos_magic = pos.Magic(); if(magic != -1) if(pos_magic != magic) continue; double sl = pos.StopLoss(), tp = pos.TakeProfit(), open_price = pos.PriceOpen(); ulong ticket = pos.Ticket(); ENUM_POSITION_TYPE type = pos.PositionType(); switch(type) { case POSITION_TYPE_BUY: { //--- Ensure the position has moved enough double bid = SymbolInfoDouble(symbol, SYMBOL_BID); if(bid - open_price <= activation_points * point) continue; double new_sl = open_price + offset_points * point; //breakeven sl //--- Prevent unwanted modifications if(new_sl <= open_price) continue; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_BUY, new_sl, tp, symbol)) continue; trade.PositionModify(ticket, new_sl, tp); } break; case POSITION_TYPE_SELL: { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); //--- Ensure the postion has moved enough if(open_price - ask <= activation_points * point) continue; double new_sl = open_price - offset_points * point; //--- Prevent unwanted modifications if(new_sl >= open_price) continue; if(!isPositionModificationSameLevels(ticket, new_sl, tp)) continue; if(!isValidStoploss_Takeprofit(ORDER_TYPE_SELL, new_sl, tp, symbol)) continue; trade.PositionModify(ticket, new_sl, tp); } break; default: break; } } }
Например:
#include <Bootstrap\BreakEven\BreakEvenFixedPoints.mqh> #include <Bootstrap\positions.mqh> #include <Trade\SymbolInfo.mqh> CTrade m_trade; CSymbolInfo m_symbol; #define sym Symbol() #define MAGIC_NUMBER 112233 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ input group "BreakEven"; input uint ACTIVATION_POINTS = 200; input uint OFFSET = 20; input group "Orders"; input uint STOPLOSS = 500; input uint TAKEPROFIT = 500; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- m_symbol.Name(sym); m_trade.SetExpertMagicNumber(MAGIC_NUMBER); m_trade.SetTypeFillingBySymbol(sym); m_trade.SetDeviationInPoints(100); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!m_symbol.RefreshRates()) return; //--- double lots = m_symbol.LotsMin(); double ask = m_symbol.Ask(), bid = m_symbol.Bid(); double pts = m_symbol.Point(); //--- if(!PositionExistsByType(POSITION_TYPE_BUY)) m_trade.Buy(lots, sym, ask, ask - STOPLOSS * pts, ask + TAKEPROFIT * pts); CBreakEvenFixedPoints::BreakEven(ACTIVATION_POINTS, OFFSET, sym, MAGIC_NUMBER); }
Результаты:

Перенос стоп-лосса в безубыток с использованием денежных значений
Перенос стоп-лосса в безубыток по денежным значениям работает аналогично переносу по фиксированным значениям в пунктах. Однако вместо прибыли в пунктах или ценового расстояния используется фактическая сумма прибыли в валюте счета.
Этот подход полезен, когда трейдер хочет управлять риском на основе реальных денежных сумм, а не движения рынка. Перенос в безубыток активируется, как только прибыль по позиции достигает заданной суммы, после чего стоп-лосс перемещается для защиты сделки.
Используются два основных параметра:
- activation_money — минимальная прибыль в валюте счета, необходимая для переноса стоп-лосса в безубыток. Прибыль по позиции должна достичь этого уровня до любой модификации стоп-лосса.
- offset_money — сумма прибыли в валюте счета, которую нужно зафиксировать после переноса стоп-лосса в безубыток. При нулевом значении стоп-лосс переносится точно на цену открытия, а при положительном фиксируется дополнительная прибыль.
Например, при пороге активации 10 долларов и смещении 5 долларов.
Когда нереализованная прибыль по позиции достигает 10 долларов, срабатывает логика переноса стоп-лосса в безубыток. Затем стоп-лосс перемещается на уровень, при котором в случае разворота рынка сделка закроется с прибылью примерно 5 долларов, а не станет убыточной.
#include <Bootstrap\BreakEven\BreakEvenMoney.mqh> #include <Bootstrap\positions.mqh> #include <Trade\SymbolInfo.mqh> CTrade m_trade; CSymbolInfo m_symbol; #define sym Symbol() #define MAGIC_NUMBER 112233 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ input group "Orders"; input uint STOPLOSS = 500; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- m_symbol.Name(sym); m_trade.SetExpertMagicNumber(MAGIC_NUMBER); m_trade.SetTypeFillingBySymbol(sym); m_trade.SetDeviationInPoints(100); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!m_symbol.RefreshRates()) return; //--- double lots = m_symbol.LotsMin(); double ask = m_symbol.Ask(), bid = m_symbol.Bid(); double pts = m_symbol.Point(); //--- if(!PositionExistsByType(POSITION_TYPE_BUY)) m_trade.Buy(lots, sym, ask, ask - STOPLOSS * pts); CBreakEvenMoney::BreakEven(10, 5, sym, MAGIC_NUMBER); }
Результаты:

Заключение
В этой статье мы расширили библиотеку MQL5 Bootstrap, добавив набор переиспользуемых инструментов управления сделками для трейлинг-стопов и переноса стоп-лосса в безубыток. Эти вспомогательные средства избавляют от повторной реализации одной и той же логики модификации позиций в каждом советнике и обеспечивают более чистый и структурированный подход, пригодный для разных торговых систем.
Не существует единственного "лучшего" метода трейлинга или переноса стоп-лосса в безубыток. Для стратегии следования за трендом может быть полезен трейлинг-стоп на основе ATR или Parabolic SAR, а для скальпинговой системы — фиксированные значения в пунктах или периодические корректировки. Аналогичным образом трейдер, управляющий рисками в валюте счета, может счесть перенос стоп-лосса в безубыток по денежным значениям более интуитивно понятным, чем расчеты в пунктах.
Повторим: цель MQL5 Bootstrap — не предложить универсальное торговое решение, а упростить разработку с помощью надежных базовых компонентов, которые разработчики могут комбинировать и расширять. Выделение логики управления сделками в переиспользуемые классы упрощает сопровождение, тестирование и доработку советников.
Обнаружили проблему или хотите улучшить проект? Ознакомьтесь с этим репозиторием GitHub.
Таблица вложений
| Имя файла | Описание и использование |
|---|---|
| Include\Bootstrap\BreakEven\BreakEvenFixedPoints.mqh | Содержит вспомогательные средства для переноса стоп-лосса в безубыток по фиксированным значениям в пунктах. |
| Include\Bootstrap\BreakEven\BreakEvenMoney.mqh | Содержит вспомогательные средства для переноса стоп-лосса в безубыток на основе прибыли в денежном выражении. |
| Include\Bootstrap\Trailing\TrailingATR.mqh | Содержит вспомогательные средства для трейлинг-стопов на основе ATR. |
| Include\Bootstrap\Trailing\TrailingFixedPoints.mqh | Содержит вспомогательные средства для трейлинг-стопов с фиксированным расстоянием в пунктах. |
| Include\Bootstrap\Trailing\TrailingMA.mqh | Содержит вспомогательные средства для трейлинг-стопов на основе скользящей средней. |
| Include\Bootstrap\Trailing\TrailingMoney.mqh | Содержит вспомогательные средства для трейлинг-стопов в денежном выражении. |
| Include\Bootstrap\Trailing\TrailingPeriodic.mqh | Содержит вспомогательные средства для периодических трейлинг-стопов. |
| Include\Bootstrap\Trailing\TrailingSAR.mqh | Содержит вспомогательные средства для трейлинг-стопов на основе индикатора Parabolic SAR. |
| Experts\Bootstrap\BreakEven\BreakEven Test.mq5 | Советник для тестирования рассмотренных выше вспомогательных функций переноса стоп-лосса в безубыток. |
| Experts\Bootstrap\BreakEven\Trailing Stops Test.mq5 | Советник для тестирования рассмотренных выше функций трейлинга. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/23882
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Декомпозиция частичной информации: Когда два индикатора вместе информативнее, чем каждый по отдельности
Рыночная микроструктура в MQL5 (Часть 8): Оценка силы микротренда
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования