
Разработка стратегии Zone Recovery Martingale на MQL5
Введение
В этой статье мы пошагово реализуем советник на основе торговой стратегии Zone Recovery Martingale (мартингейл зоны восстановления) на языке MetaQuotes Language 5 (MQL5) для MetaTrader 5. Это популярная стратегия, направленная на противодействие убыточным позициям путем открытия противоположных позиций с немного большим объемом. По сути, это стратегия следования за трендом, для которой не имеет значения направление рынка. Она исходит из того, что в какой-то момент рынок пойдет либо вниз, либо вверх, и в конечном итоге будут достигнуты определенные цели. Мы автоматизируем эту систему с помощью MQL5.
В статье рассмотрены следующие темы:
- Определение стратегии Zone Recovery
- Описание стратегии Zone Recovery
- Реализация средствами MQL5
- Результаты тестирования на истории
- Заключение
Определение стратегии Zone Recovery
Стратегия Zone Recovery в основном используется в торговле на рынке Форекс для контроля и сокращения потерь. Основная идея стратегии — определение точных диапазонов цен, в которых, по ожиданиям трейдера, будет колебаться рынок. Трейдер запускает серию встречных сделок, чтобы построить зону восстановления, когда сделка движется неблагоприятно и достигает заранее определенной зоны убытков. В идеале эти сделки следует размещать таким образом, чтобы возвращение в зону восстановления позволяло закрыть всю позицию в целом с безубыточностью или даже с прибылью.
Стратегия в первую очередь ориентирована на хеджирование и усреднение. Когда рынок идет против первой сделки, трейдер создает противоположную позицию такого же размера для хеджирования. Если рынок продолжает снижаться, через определенные промежутки времени открываются новые сделки, чтобы усреднить цену входа. Сделки открываются таким образом, чтобы совокупная прибыль от восстановительных сделок могла сравняться с убытками от первоначальной сделки, когда рынок вернется к своему среднему значению. Такой подход требует четкой стратегии управления рисками, поскольку накопление позиций может привести к высоким требованиям к марже и подверженности влиянию волатильности.
Способность стратегии восстановления зоны превращать убыточную сделку в прибыльную без необходимости точного прогнозирования направления рынка является одним из ее главных преимуществ. Стратегия позволяет трейдерам извлекать прибыль из рыночной волатильности и разворотов, превращая неблагоприятные тренды в шансы на восстановление.
Метод торговли на основе зоны восстановления наиболее подходит опытным трейдерам с глубоким пониманием стратегий управления рисками и динамики рынка. Это мощный инструмент в арсенале трейдера, который особенно полезен на нестабильных рынках с частыми колебаниями цен. Хотя стратегия способна восстанавливаться после неудачных сделок и приносить прибыль, ее сложность требует продуманного планирования и расчетливого исполнения.
Описание стратегии Zone Recovery
Торговля начинается с открытия начальной позиции. Предположим, трейдер открывает позицию на покупку, потому что верит, что рынок вырастет. Первая цель — заработать на растущем тренде. Если цена поднимается до заранее определенного уровня прибыли, трейдер закрывает позицию и фиксирует прибыль. Используя эту простую стратегию, трейдер может извлечь прибыль из благоприятных рыночных движений, не прибегая к более сложным стратегиям.
Если рынок движется против первоначальной длинной позиции и достигает заранее определенной точки убытка, активируется механизм смягчения убытков. В этот момент трейдер открывает позицию на продажу с большим размером лота, обычно вдвое превышающим размер предыдущей длинной позиции. Таким образом осуществляется попытка компенсировать убытки от первой сделки возможными прибылями от новой позиции. Используя присущие рынку колебания, стратегия предполагает разворот или, по крайней мере, стабилизацию в заданном диапазоне.
Трейдер следит за новой позицией на продажу по мере движения рынка. Совокупное воздействие первоначальной покупки и более крупных позиций на продажу в идеале приводит к ситуации безубыточности или прибыли, если рынок продолжает падать и достигает другой заранее определенной точки. После этого трейдер может закрыть обе позиции, используя прибыль от более крупной последующей сделки для компенсации убытков от первой. Чтобы гарантировать, что вся позиция будет закрыта с прибылью в зоне восстановления, эта стратегия требует точных расчетов и правильного выбора времени.
Реализация средствами MQL5
Чтобы создать советник на языке MQL5, ориентированный на визуализацию уровней зоны восстановления (обычно их четыре), нам сначала необходимо определить эти уровни. Сделать это нужно как можно раньше, поскольку они будут иметь решающее значение для визуализации торговой системы. Используем ключевое слово #define, которое является встроенной директивой в MQL5, с помощью которой можно назначать мнемонические имена константам. Ниже показано, как это делается:
#define ZONE_H "ZH" // Define a constant for the high zone line name #define ZONE_L "ZL" // Define a constant for the low zone line name #define ZONE_T_H "ZTH" // Define a constant for the target high zone line name #define ZONE_T_L "ZTL" // Define a constant for the target low zone line name
Здесь мы определяем константы границ нашей зоны: ZONE_H обозначается 'ZH' (Zone High), ZONE_L - 'ZL' (Zone Low), ZONE_T_H - 'ZTH' (Zone Target High) и ZONE_T_L - 'ZTL' (Zone Target Low). Эти константы представляют соответствующие уровни в нашей системе.
После наших определений нам необходимо будет открыть торговые позиции. Необходимо добавить файл для открытия позиций. Мы используем директиву include для включения торговой библиотеки, содержащей функции для торговых операций.
#include <Trade/Trade.mqh>
CTrade obj_trade;
Сначала мы используем угловые скобки, чтобы обозначить, что файл, который мы хотим включить, содержится в папке include, и указываем папку Trade, затем идет обычная или обратная косая черта и имя целевого файла, в данном случае - Trade.MQH. CTrade — это класс обработки торговых операций, а obj_trade — экземпляр этого класса, обычно объект-указатель, созданный из класса CTrade для предоставления доступа к переменным-членам класса.
После этого нам понадобится некая логика управления для генерации сигналов на открытие позиций. В нашем случае мы используем RSI (индикатор относительной силы), но вы можете использовать любой другой, который посчитаете нужным.
int rsi_handle; // Handle for the RSI indicator double rsiData[]; // Array to store RSI data int totalBars = 0; // Variable to keep track of the total number of bars
В rsi_handle хранится ссылка на индикатор RSI, который инициализируется в функции OnInit, что позволяет советнику извлекать значения RSI. Массив rsiData хранит значения RSI, извлеченные с помощью CopyBuffer, и используется для определения торговых сигналов на основе пороговых значений RSI. Переменная totalBars отслеживает общее количество баров на графике, гарантируя, что торговая логика выполняется только один раз для каждого нового бара, предотвращая многократное выполнение в пределах одного бара. В совокупности эти переменные позволяют советнику генерировать торговые сигналы на основе значений RSI, сохраняя при этом правильное время исполнения.
Наконец, после определения значений индикатора, мы определяем уровни генерации сигнала индикатора и уровни зон.
double overBoughtLevel = 70.0; // Overbought level for RSI double overSoldLevel = 30.0; // Oversold level for RSI double zoneHigh = 0; // Variable to store the high zone price double zoneLow = 0; // Variable to store the low zone price double zoneTargetHigh = 0; // Variable to store the target high zone price double zoneTargetLow = 0; // Variable to store the target low zone price
Снова определим две double-переменные, overBoughtLevel и overSoldLevel, и присвоим им значения 70 и 30 соответственно. Они служат нам крайними уровнями для срабатывания сигнала. Кроме того, определим четыре дополнительные переменные типа double - zoneHigh, zoneLow, zoneTradegetHigh и zoneTargetLow - и инициализируем их нулем. Они будут содержать наши уровни настройки восстановления ниже в коде.
Мы определили все глобальные переменные, имеющие решающее значение для системы. Перейдем к обработчику событий OnInit, который вызывается всякий раз при инициализации советника. Именно в этом случае нам необходимо инициализировать дескриптор индикатора, из которого мы позже будем копировать данные для дальнейшего анализа. Для инициализации индикатора мы используем встроенную функцию, возвращающую его дескриптор, предоставляя правильные параметры.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the RSI indicator rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Return initialization result return(INIT_SUCCEEDED); }
В обработчике событий деинициализации советника нам необходимо освободить данные индикатора, а также освободить сохраненные данные. Обычно мы делаем это для того, чтобы удалить индикатор из памяти компьютера и сэкономить ресурсы, поскольку он больше не будет использоваться, а если и будет, то дескрипторы индикатора и данные будут созданы при инициализации советника.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove RSI indicator from memory IndicatorRelease(rsi_handle); ArrayFree(rsiData); // Free the RSI data array }
Переходим к обработчику событий OnTick, представляющему собой функцию, которая вызывается при каждом тике, то есть изменении цены котировки. Это опять же одна из основных наших функций, поскольку она содержит все важнейшие фрагменты кода для успешной реализации торговой стратегии. Поскольку мы будем открывать позиции, нам необходимо определить наши цены bid и ask, чтобы мы могли использовать их самые последние значения для анализа.
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
Определение цен bid и ask с использованием данных символа в качестве данных типа double помогает нам получать последние котировки цен для дальнейшего анализа. Функция, которую мы используем, представляет собой перегружающую функцию, содержащую два варианта, но мы используем первый из них, который принимает всего два параметра. Первый параметр — это символ. Мы используем _Symbol для автоматического извлечения символа на текущем графике и SYMBOL_ASK для получения перечисления типа double.
Таким образом мы получили последние котировки цен, технически сгенерированные на каждом тике. Мы продолжаем определять диапазон и цель зоны для советника. Мы определяем их как переменные типа данных double и умножаем на переменную _Point, которая содержит размер пункта текущего символа в валюте котировки.
double zoneRange = 200*_Point; double zoneTarget = 400*_Point;
Наконец, нам нужны служебные переменные, которые обеспечат правильное открытие позиций восстановления. В данном случае мы определяем четыре такие переменные. LastDirection будет хранить тип последней открытой позиции, чтобы гарантировать чередование ордеров на покупку и продажу. Продажа будет равна -1, а покупка — +1. Это произвольные значения, которые можно изменить. Затем используется и инициализируется нулевым значением лот восстановления (recovery lot), функция которого будет заключаться в хранении наших вычисленных точек восстановления, чтобы мы не потеряли отслеживание объемов следующих сделок, при условии что стратегия все еще работает. Также у нас есть две логические переменные, isBuyDone и isSellDone, которые обычно используются для хранения флагов, помогающих нам не открывать несколько позиций одновременно. Обратите внимание, что все наши переменные являются статическими, чтобы гарантировать, что они не будут обновляться, если только мы сами не обновим их по отдельности. Это связано с тем, что локальные переменные, объявленные с помощью ключевого слова static, сохраняют свои значения на протяжении всего времени существования функции. Таким образом, при каждом следующем вызове функции OnTick наши локальные переменные будут содержать те значения, которые они имели во время предыдущего вызова.
static int lastDirection = 0; //-1 = sell, 1 = buy static double recovery_lot = 0.0; static bool isBuyDone = false, isSellDone = false;
После определения всех служебных переменных переходим к открытым позициям, из которых впоследствии реализуем логику зоны восстановления. Мы намерены добиться этого с помощью индикатора RSI, который полностью настраивается в соответствии с предпочтениями пользователя, так что вы можете просто использовать свой метод входа. Теперь, чтобы сэкономить ресурсы, мы хотим получать данные от индикатора на каждой свече, а не на каждом тике. Это достигается так:
int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) return; totalBars = bars;
Здесь мы определяем целочисленные переменные bars и инициализируем их числом текущих баров на графике, что достигается с помощью встроенной функции iBars, которая принимает два аргумента: символ и период. Затем мы проверяем, равно ли количество ранее определенных баров текущему количеству баров, и если это так, то это означает, что мы все еще находимся на текущем баре, и, таким образом, мы возвращаемся, то есть прерываем операцию и возвращаем управление вызывающей программе. Если же две переменные не совпадают, это означает, что мы перешли на новую свечу и можем продолжать. Поэтому мы обновляем totalBars до текущих баров, чтобы на следующем тике у нас было обновленное значение переменной totalBars.
Чтобы зона восстановления вступила в силу и была открыта только одна позиция, нам необходимо открыть одну позицию на экземпляр. Таким образом, если количество позиций больше одной, нам не нужно добавлять никаких других позиций. Мы просто используем return раньше.
Если мы не возвращаемся к этой точке, это значит, что у нас пока нет позиции, и мы можем приступить к ее открытию. Таким образом, мы копируем данные из хэндла индикатора и сохраняем их в массиве данных индикатора для дальнейшего анализа. Это достигается с помощью функции буфера копирования.
if (PositionsTotal() > 0) return;
if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return;
Функция копирования буфера представляет собой перегружающую функцию, которая возвращает целое число. В целях безопасности мы используем оператор if, чтобы проверить, возвращает ли она запрошенные данные. Если нет, то у нас недостаточно данных для возврата, поскольку дальнейший анализ невозможен. Однако давайте посмотрим, что делает эта функция. Она содержит пять аргументов. Первый — это хэндл индикатора, из которого копируются данные, второй — номер буфера индикатора. В данном случае это 0, но значение может меняться в зависимости от используемого вами индикатора. Третий аргумент — начальная позиция или индекс строки, из которой копируются данные. Здесь мы используем 1, чтобы обозначить, что мы начинаем со столбца, предшествующего текущему столбцу на графике. Четвертый — это количество хранимых данных. Нам здесь достаточно двух, поскольку мы не проводим подробный анализ. Наконец, мы предоставляем целевой массив хранилища извлеченных данных.
После извлечения данных из хэндла индикатора мы приступаем к использованию данных в торговых целях, а точнее, к генерации сигналов. Сначала мы ищем сигналы на покупку. Мы делаем это, используя оператор if и открывая позицию на покупку.
if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel){ obj_trade.Buy(0.01);
Если данные с индексом 1 в сохраненном массиве меньше определенного уровня перепроданности, а данные с индексом 0 больше уровня перепроданности, это означает, что произошло пересечение линии RSI и уровня перепроданности, что означает наличие сигнала на покупку. Затем мы используем объект trade и оператор-точку, чтобы получить доступ к методу покупки, содержащемуся в классе CTrade. В этом случае мы открываем позицию на покупку объемом 0,01 и игнорируем остальные параметры, такие как стоп-лосс и тейк-профит. Мы не будем их использовать, так как они заставят нашу систему работать не так, как задумано, поскольку мы реализуем стратегию зоны восстановления, которая не требует закрытия позиций на уровнях стоп-лосса.
Однако для установки уровней зоны восстановления нам понадобится тикет позиции, чтобы мы могли получить доступ к ее свойствам. Для получения тикета мы используем результат ордера ранее открытой позиции. После получения тикета, мы проверяем, что он больше 0, что означает успешное открытие позиции, а затем выбираем позицию по тикету. Если мы можем выбрать ее по тикету, то мы можем получить свойства позиции, но нас интересует только цена открытия. От цены открытия мы устанавливаем позиции следующим образом: цена открытия позиции на покупку — это zone high (максимум зоны), чтобы получить zone low (минимум зоны), мы просто вычитаем диапазон зоны из zone high. Для верхних и нижних целевых значений зоны мы просто прибавляем zone target (целевое значение зоны) к zone high и вычитаем zone target из zone low соответственно. Нам осталось нормализовать значения в соответствии с количеством знаков после запятой у символа для точности.
ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0){ if (PositionSelectByTicket(pos_ticket)){ double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice,_Digits); zoneLow = NormalizeDouble(zoneHigh - zoneRange,_Digits); zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget,_Digits); zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget,_Digits);
Мы уже можем устанавливать уровни, но они не видны на графике. Создадим функцию для визуализации четырех определенных уровней на графике.
drawZoneLevel(ZONE_H,zoneHigh,clrGreen,2); drawZoneLevel(ZONE_L,zoneLow,clrRed,2); drawZoneLevel(ZONE_T_H,zoneTargetHigh,clrBlue,3); drawZoneLevel(ZONE_T_L,zoneTargetLow,clrBlue,3);
Мы создаем простую функцию void, которая принимает четыре входных параметра, а именно: levelName, price, CLR и width. Мы используем встроенную функцию ObjectCreate для создания горизонтальной линии, которая охватывает всю длину графика, прикрепляя ее к указанному времени и цене. Наконец, мы используем ObjectSetInteger, чтобы задать цвет объекта для уникальности и ширину для более легкой регулировки видимости.
void drawZoneLevel(string levelName, double price, color clr, int width) { ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width }
Наконец, установим последнее направление на значение 1, чтобы показать, что мы открыли позицию на покупку, устанавливаем следующий объем восстановления как начальный, умноженный на константу-множитель, в данном случае 2, то есть мы удваиваем объем, и, наконец, устанавливаем флаг isBuyDone на true, а isSellDone - на false.
lastDirection = 1; recovery_lot = 0.01*2; isBuyDone = true; isSellDone = false;
Ниже приведен полный код для открытия позиции и настройки уровней зоны восстановления.
//--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } }
Для открытия позиции на продажу и настройки уровней зоны восстановления логика управления остается, но с обратными условиями, как показано ниже.
else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
Здесь мы проверяем, выполняются ли условия сигнала на продажу, и если да, то мгновенно открываем позицию на продажу. Затем мы получаем ее тикет и используем его для получения цены открытия позиции для установки уровней зоны восстановления. Поскольку это позиция на продажу, ее цена становится zone low. Для получения zone high мы просто прибавляем диапазон зоны к zone high. Аналогично мы добавляем zone target к zone high, чтобы получить zone target high (максимальный целевой показатель зоны), и вычитаем zone target из zone low, чтобы получить zone target low (минимальный целевой показатель зоны). Для визуализации мы снова рисуем четыре уровня с помощью функций. Наконец, мы просто настраиваем наши служебные переменные.
Нам удалось открыть позиции на основе поданного сигнала и настроить систему зоны восстановления. Ниже приведен полный код всего функционала.
void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) return; totalBars = bars; if (PositionsTotal() > 0) return; if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return; //--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } } //--- Check for overbought condition and open a sell position else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } }
Мы можем настроить систему зоны восстановления, но нам нужно следить за ней и гарантировать, что мы выйдем из нее, как только достигнем целевых уровней, или откроем соответствующие позиции, как только достигнем заранее определенных уровней. Нам придется делать это на каждом тике, и, таким образом, логика должна быть реализована до ограничения цикла свечей. Сначала давайте проверим условие, при котором цены достигают целевых уровней, а затем проверим условие, при котором цены достигают уровней зоны восстановления.
if (zoneTargetHigh > 0 && zoneTargetLow > 0){ if (bid > zoneTargetHigh || bid < zoneTargetLow){ obj_trade.PositionClose(_Symbol); deleteZoneLevels(); ... } }
Здесь мы проверяем, что система зоны восстановления настроена, исходя из логики, что целевые уровни выше нуля, а это значит, что у нас уже есть работающая система. Если это так, мы проверяем, находится ли цена bid выше целевого максимума или ниже целевого минимума, что является признаком того, что мы можем спокойно удалить зону восстановления, поскольку ее цель достигнута. Уровни зоны удаляются с помощью функции deleteZoneLevels. Используемая функция имеет тип void, поскольку нам не нужно ничего возвращать, а встроенная функция ObjectDelete реализована для удаления уровней, принимая два аргумента: индекс графика и имя объекта.
void deleteZoneLevels(){ ObjectDelete(0,ZONE_H); ObjectDelete(0,ZONE_L); ObjectDelete(0,ZONE_T_H); ObjectDelete(0,ZONE_T_L); }
Чтобы закрыть позиции (поскольку на данный момент их может быть несколько), мы используем цикл, который рассматривает их все, а затем удаляет по отдельности. Это достигается с помощью приведенного ниже кода.
for (int i = PositionsTotal()-1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if (ticket > 0){ if (PositionSelectByTicket(ticket)){ obj_trade.PositionClose(ticket); } } }
После закрытия всех позиций и удаления уровней мы сбрасываем систему на настройки по умолчанию.
//closed all, reset all zoneHigh=0;zoneLow=0;zoneTargetHigh=0;zoneTargetLow=0; lastDirection=0; recovery_lot = 0;
Уровни и цели зоны устанавливаются на ноль, за исключением последнего направления и лота восстановления. Это статические переменные, поэтому нам нужно сбрасывать их вручную. Для динамических переменных в этом нет необходимости, поскольку они чаще всего обновляются автоматически.
Полный код, отвечающий за удаление системы после достижения ее целей, выглядит так:
//--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > 0 && zoneTargetLow > 0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); // Close the current position deleteZoneLevels(); // Delete all drawn zone levels for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); // Close positions by ticket } } } //--- Reset all zone and direction variables zoneHigh = 0; zoneLow = 0; zoneTargetHigh = 0; zoneTargetLow = 0; lastDirection = 0; recovery_lot = 0; } }
Переходим к открытиям позиций восстановления. Нам нужно проверить, работает ли система, как показано на примере, в котором уровни зон выше нуля. Если это так, устанавливаем ее, объявляя переменную lots_rec, которую мы используем для хранения наших лотов восстановления. Затем мы нормализуем его до трех знаков после запятой для точности, поскольку торговый счет, который мы используем, является микролотовым. Это значение может меняться в зависимости от типа используемой вами учетной записи. Например, если вы используете стандартный счет, его минимальный лот равен 1, и, таким образом, ваше значение будет равно 0, чтобы избавиться от десятичных знаков. Большинство из них имеют 2 десятичных знака, но у вас может быть тип счета 0,001, и тогда ваше значение будет равно 3, чтобы округлить лоты до ближайших 3 десятичных знаков.
if (zoneHigh > 0 && zoneLow > 0){ double lots_Rec = 0; lots_Rec = NormalizeDouble(recovery_lot,2); ... }
Затем мы проверяем, находится ли цена bid выше zone high. Если предыдущий флаг isBuyDone имеет значение false или последнее значение направления меньше нуля, мы открываем восстановительную позицию на покупку. После открытия позиции мы устанавливаем lastDirection на 1, что означает, что ранее открытая позиция является позицией покупки, вычисляем лоты восстановления и сохраняем их в переменной recovery_lot для использования при следующем вызове позиции восстановления, а затем устанавливаем флаг isBuyDone в true, а isSellDone - в false, что указывает на то, что позиция покупки уже открыта.
if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } }
В противном случае, если цена bid ниже zone low, мы открываем восстановительную позицию на продажу, как показано на рисунке.
else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
Полный код, отвечающий за открытие позиций восстановления, теперь выглядит следующим образом:
//--- Check if price is within defined zones and take action if (zoneHigh > 0 && zoneLow > 0) { double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
Это этап, которого нам удалось достичь на данный момент.
Полный код, необходимый для автоматизации системы зон восстановления, выглядит так:
//+------------------------------------------------------------------+ //| MARTINGALE EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- Define utility variables for later use #define ZONE_H "ZH" // Define a constant for the high zone line name #define ZONE_L "ZL" // Define a constant for the low zone line name #define ZONE_T_H "ZTH" // Define a constant for the target high zone line name #define ZONE_T_L "ZTL" // Define a constant for the target low zone line name //--- Include trade instance class #include <Trade/Trade.mqh> // Include the trade class for trading functions CTrade obj_trade; // Create an instance of the CTrade class for trading operations //--- Declare variables to hold indicator data int rsi_handle; // Handle for the RSI indicator double rsiData[]; // Array to store RSI data int totalBars = 0; // Variable to keep track of the total number of bars double overBoughtLevel = 70.0; // Overbought level for RSI double overSoldLevel = 30.0; // Oversold level for RSI double zoneHigh = 0; // Variable to store the high zone price double zoneLow = 0; // Variable to store the low zone price double zoneTargetHigh = 0; // Variable to store the target high zone price double zoneTargetLow = 0; // Variable to store the target low zone price //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the RSI indicator rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Return initialization result return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove RSI indicator from memory IndicatorRelease(rsi_handle); ArrayFree(rsiData); // Free the RSI data array } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Retrieve the current Ask and Bid prices double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double zoneRange = 200 * _Point; // Define the range for the zones double zoneTarget = 400 * _Point; // Define the target range for the zones //--- Variables to track trading status static int lastDirection = 0; // -1 = sell, 1 = buy static double recovery_lot = 0.0; // Lot size for recovery trades static bool isBuyDone = false, isSellDone = false; // Flags to track trade completion //--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > 0 && zoneTargetLow > 0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); // Close the current position deleteZoneLevels(); // Delete all drawn zone levels for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); // Close positions by ticket } } } //--- Reset all zone and direction variables zoneHigh = 0; zoneLow = 0; zoneTargetHigh = 0; zoneTargetLow = 0; lastDirection = 0; recovery_lot = 0; } } //--- Check if price is within defined zones and take action if (zoneHigh > 0 && zoneLow > 0) { double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } //--- Update bars and check for new bars int bars = iBars(_Symbol, PERIOD_CURRENT); if (totalBars == bars) return; // Exit if no new bars totalBars = bars; // Update the total number of bars //--- Exit if there are open positions if (PositionsTotal() > 0) return; //--- Copy RSI data and check for oversold/overbought conditions if (!CopyBuffer(rsi_handle, 0, 1, 2, rsiData)) return; //--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } } //--- Check for overbought condition and open a sell position else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } } //+------------------------------------------------------------------+ //| FUNCTION TO DRAW HORIZONTAL ZONE LINES | //+------------------------------------------------------------------+ void drawZoneLevel(string levelName, double price, color clr, int width) { ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width } //+------------------------------------------------------------------+ //| FUNCTION TO DELETE DRAWN ZONE LINES | //+------------------------------------------------------------------+ void deleteZoneLevels() { ObjectDelete(0, ZONE_H); // Delete the high zone line ObjectDelete(0, ZONE_L); // Delete the low zone line ObjectDelete(0, ZONE_T_H); // Delete the target high zone line ObjectDelete(0, ZONE_T_L); // Delete the target low zone line }
Мы успешно автоматизировали стратегию Zone Recovery для торговли на рынке Форекс. Теперь ее необходимо протестировать и оценить ее производительность, чтобы убедиться, что она соответствует поставленным целям.
Результаты тестирования на истории
Результаты тестирования в тестере стратегий представлены ниже.
График:
Результаты:
Заключение
Мы рассмотрели основные шаги для автоматизации известной стратегии мартингейла Zone Recovery на языке MQL5. Мы дали базовое определение, описали стратегию и показали, как ее можно реализовать средствами MQL5. Вы можете использовать полученные знания для разработки более сложных систем зон восстановления, которые впоследствии можно оптимизировать для получения лучших результатов.
Код предназначен только для того, чтобы помочь вам освоить основы создания торговой системы зон восстановления на рынке Форекс. Продемонстрированные результаты не гарантируют будущей эффективности. Применяйте полученные знания для создания и оптимизации своих систем, соответствующих вашему стилю торговли, с осторожностью.
В статье все этапы создания системы изложены в хронологическом порядке. Надеюсь, статья окажется полезной для вас и послужит отправной точкой на пути к созданию лучшей и полностью оптимизированной системы Zone Recovery. В приложенном файле содержатся все примеры, приведенные в статье. Для достижения оптимальных результатов рекомендуется изучить код для его применения к пользовательской стратегии.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/15067
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.






- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования