![Модифицированный советник Grid-Hedge в MQL5 (Часть II): Создание простого сеточного советника](https://c.mql5.com/2/64/Modified_Grid-Hedge_EA_in_MQL5_uPart_IIt_Making_a_Simple_Grid_EA_600x314.jpg)
Модифицированный советник Grid-Hedge в MQL5 (Часть II): Создание простого сеточного советника
Введение
Это вторая часть нашей серии статей "Модифицированный советник Grid-Hedge в MQL5". Сначала вспомним, что мы рассматривали в предыдущей статье. В первой части мы исследовали классическую стратегию хеджирования, автоматизировали ее с помощью советника и провели тестирование в тестере стратегий, проанализировав некоторые первоначальные результаты. Это стало первым шагом на нашем пути к созданию модифицированного советника Grid-Hedge — сочетания классических стратегий хеджирования и сетки.
Ранее я упоминал, что вторая часть будет посвящена оптимизации классической стратегии хеджирования. Однако из-за непредвиденных задержек мы пока переключим внимание на классическую стратегию сетки.
В этой статье мы углубимся в классическую сеточную стратегию, автоматизируем ее с помощью советника на MQL5 и проведем тесты в тестере стратегий для анализа результатов и получения полезной информации о стратегии.
Итак, давайте рассмотрим нюансы классической сеточной стратегии.
Вот краткий обзор того, что мы рассмотрим в этой статье:
- Классическая сеточная стратегия
- Автоматизации классической сеточной стратегии
- Тестирование классической сеточной стратегии на истории
- Заключение
Классическая сеточная стратегия
Прежде чем двигаться дальше, давайте углубимся в саму стратегию.
Мы начинаем наш подход с позиции на покупку, хотя не менее целесообразно начать и с позиции на продажу. Пока давайте сосредоточимся на сценарии покупки. Мы начинаем с определенной начальной цены, используя для простоты небольшой размер лота – скажем, 0,01, минимально возможный размер лота. С этой точки зрения есть два потенциальных результата: цена может вырасти или упасть. Если цена вырастет, мы получим прибыль, размер которой зависит от величины повышения. Затем мы получаем эту прибыль, как только цена достигает заранее определенного уровня тейк-профита. Проблема возникает, когда цена начинает падать – именно здесь в игру вступает наша классическая сеточная стратегия, предлагающая гарантию для обеспечения прибыли.
Если цена упадет на определенную величину (для простоты объяснения, скажем, на 15 пунктов), мы исполним еще один ордер на покупку. Ордер размещается по начальной цене минус 15 пунктов, и здесь мы увеличиваем размер лота — для простоты мы удваиваем его, эффективно применяя множитель размера лота, равный 2. Разместив новый ордер на покупку, мы снова сталкиваемся с двумя сценариями: цена может вырасти или упасть. Повышение цены приведет к получению прибыли от этого нового ордера из-за увеличения размера лота. Более того, этот рост не только приносит прибыль, но и снижает потери по нашему первоначальному ордеру на покупку с размером лота 0,01. Точка, в которой наша чистая прибыль равна нулю, — это когда цена достигает средневзвешенного уровня цен обоих ордеров. Это средневзвешенное значение можно рассчитать следующим образом:
В этом расчете X i представляет цену, по которой размещаются ордера на покупку, а w i обозначает соответствующие веса, определяемые множителем размера лота. До этого момента средневзвешенный уровень цен W можно рассчитать так:
W = 1 × (начальная цена) + 2 × (начальная цена − 15 pips) 3 Здесь X 1 — начальная цена, X 2 — начальная цена минус 15 пунктов, w 1 равно 1 и w 2 равно 2.
Точка, в которой чистая прибыль достигает нуля, — это когда уровень цен выравнивается с рассчитанным средневзвешенным уровнем цен. Демонстрация этой концепции заключается в следующем:
Упростим и будем считать, что начальная цена равна x, по которой открывается ордер на покупку лотом 0,01. Если затем цена упадет на 15 пунктов, мы размещаем новый ордер на покупку в точке x - 15 и удваиваем размер лота до 0,02. Средневзвешенный уровень цен в этой точке рассчитывается как (1 × x + 2 × (x − 15)) / 3, что равно x − 10. Это на 10 пунктов ниже первоначального ценового уровня.
Когда цена достигает x - 15, первоначальный ордер на покупку 0,01 несет убыток в размере 1,5 USD (при условии, что перед нами - пара EURUSD, где 1 USD эквивалентен 10 пипсам). Если затем цена поднимется до средневзвешенного уровня x − 10, мы возместим убыток в 0,5 USD по первоначальному ордеру. Кроме того, мы получаем прибыль от нового ордера на покупку 0,02, поскольку цена выросла на 5 пунктов с уровня x − 15. За счет удвоенного размера лота наш выигрыш здесь также увеличивается в два раза и составляет 1 USD. Таким образом, наша общая чистая прибыль равна 0 USD (что составляет -1,5 USD + 0,5 USD + 1 USD).
Если цена растет от средневзвешенного уровня x − 10, мы продолжим получать прибыль, пока она не достигнет уровня тейк-профита.
Если цена вместо того, чтобы расти, продолжает падать от средневзвешенного уровня цен, мы применяем ту же стратегию. Если цена упадет ниже x - 15 еще на 15 пунктов, новый ордер на покупку будет размещен в x - 30, при этом размер лота снова увеличится вдвое по сравнению с предыдущим, теперь до 0,04 (2 раза по 0,02). Новый средневзвешенный уровень цен рассчитывается как (1 × x + 2 × (x − 15) + 4 × (x − 30)) / 7, что упрощается до x − 21.428. Таким образом, уровень располагается на 21,428 пункта ниже начального уровня цен.
На ценовом уровне x - 30 первоначальный ордер на покупку 0,01 грозит убытком в размере 3,0 USD (при условии, что перед нами - пара EURUSD, где 1 USD эквивалентен 10 пипсам). Второй ордер на покупку 0,02 приносит убыток в 2 раза по 1,5 USD, что соответствует 3,0 USD, что приводит к общей потере 6,0 USD. Однако, если цена затем поднимется до средневзвешенного уровня x - 21,428, убыток по первоначальному ордеру на покупку 0,01 частично возместится на 0,8572 USD. Кроме того, убыток по второму ордеру 0,02 компенсируется в 2 раза по 0,8572 USD, что составляет 1,7144 USD. Кроме того, мы получаем прибыль от нового ордера на покупку 0,04, поскольку цена выросла на 8,572 пункта с уровня x − 30. Из-за увеличенного в четыре раза размера лота (0,04) наш выигрыш также увеличивается в четыре раза, составив 0,8572 USD х 4 = 3,4288 USD. Таким образом, общая чистая прибыль составит примерно 0 USD (-6 + 0.8572 + 1.7144 + 3.4288 = 0.0004 USD). Эта цифра не совсем равна нулю из-за округления при расчете средневзвешенного уровня цен.
Если цена продолжит подниматься от средневзвешенного уровня x − 21,428, мы продолжим накапливать положительную прибыль до достижения уровня тейк-профита. И наоборот, если цена снова падает, мы повторяем процесс, удваивая размер лота на каждой итерации и соответствующим образом корректируя средневзвешенный уровень цены. Такой подход гарантирует, что мы последовательно достигаем чистой прибыли в размере около 0 USD каждый раз, когда цена достигает средневзвешенного уровня цен, что постепенно приводит к положительной прибыли.
Тот же процесс применим и к ордерам на продажу. При этом цикл начинается с первоначального ордера на продажу. Давайте разберем его:
Мы начинаем с позиции на продажу по определенной начальной цене, используя для простоты небольшой размер лота, скажем, 0,01 (минимально возможный размер). Отсюда возможны два исхода: цена может либо вырасти, либо упасть. Если цена падает, мы получаем прибыль в зависимости от степени падения и получаем эту прибыль, когда цена достигает нашего уровня тейк-профита. Однако ситуация становится сложной, когда цена начинает расти, и в этот момент для обеспечения прибыли используется наша классическая сеточная стратегия.
Предположим, цена увеличивается на определенную величину, для простоты объяснения, скажем, на 15 пунктов. В ответ мы размещаем еще один ордер на продажу по начальной цене плюс 15 пунктов и для простоты удваиваем размер лота, делая множитель размера лота равным 2. После размещения нового ордера на продажу цена может либо продолжить рост, либо начать падать. Если цена упадет, мы получим прибыль от этого нового ордера на продажу из-за увеличения размера лота. Кроме того, это уменьшение не только приводит к прибыли, но и уменьшает убыток по нашему первоначальному ордеру на продажу размером лота 0,01. Мы достигаем нулевой чистой прибыли, когда цена достигает средневзвешенного уровня цен обоих ордеров — концепцию, которую мы уже изучали при обсуждении ордеров на покупку.
Формула расчета средневзвешенного значения остается прежней с учетом соответствующих цен и размеров лотов ордеров. Этот стратегический подход гарантирует, что даже на колеблющихся рынках наша позиция остается защищенной от потерь, достигая точек безубыточности или прибыли по мере движения рынка.
Для простоты предположим, что начальная цена равна x, где размещен ордер на продажу с размером лота 0,01. Если затем цена вырастет на 15 пунктов, мы размещаем новый ордер на продажу по координате x + 15, удваивая размер лота до 0,02. Средневзвешенный уровень цен на этом этапе рассчитывается как (1 × x + 2 × ( x + 15 )) / 3, что упрощается до x + 10, или на 10 пунктов выше начального уровня цен.
Когда цена достигает x + 15, первоначальный ордер на продажу 0,01 несет убыток в размере 1,5 USD (при условии, что перед нами - пара EURUSD, где 1 USD эквивалентен 10 пипсам). Если затем цена упадет до средневзвешенного уровня x + 10, мы компенсируем убыток в 0,5 USD по первоначальному ордеру на продажу. Кроме того, мы также получаем прибыль от нового ордера на продажу 0,02, поскольку цена снизилась на 5 пунктов от уровня x + 15. Из-за удвоенного размера лота наша прибыль также увеличивается вдвое, в результате чего мы получаем 1 USD. Следовательно, наша общая чистая прибыль равна 0 USD (рассчитывается как -1,5 + 0,5 + 1).
Если цена продолжит снижаться от средневзвешенного уровня x + 10, мы будем получать положительную прибыль до достижения уровня тейк-профита. Эта стратегия эффективно уравновешивает убытки и прибыль при различных ценовых движениях, обеспечивая сценарий чистой прибыли или безубыточности в различных рыночных условиях.
Если цена, вопреки снижению, продолжает расти от средневзвешенного уровня цен, применяем ту же стратегию, что и раньше. Допустим, цена превышает уровень x + 15 еще на 15 пунктов. В ответ мы размещаем новый ордер на продажу на x + 30, снова увеличивая размер лота в два раза по сравнению с предыдущим, теперь до 0,04 (2 раза по 0,02). Пересмотренный средневзвешенный уровень цен затем рассчитывается как (1 × x + 2 × (x + 15) + 4 × (x + 30)) / 7, что упрощается до x + 21,428 или на 21,428 пункта выше начального уровня цен.
На ценовом уровне x - 30 первоначальный ордер на продажу 0,01 грозит убытком в размере 3,0 USD (при условии, что перед нами - пара EURUSD, где 1 USD эквивалентен 10 пипсам). Второй ордер на продажу 0,02 приносит убыток в 2 раза по 1,5 USD, что соответствует 3,0 USD, что приводит к общей потере 6,0 USD. Однако если затем цена упадет до средневзвешенного уровня x + 21,428, мы частично возместим убыток по первоначальному ордеру на продажу 0,01 на 0,8572 USD. Мы дополнительно компенсируем убыток по второму ордеру 0,02 в 2 раза по 0,8572 USD, что составляет 1,7144 USD. Более того, мы получаем прибыль от нового ордера на продажу 0,04, поскольку цена снизилась на 8,572 пункта с уровня x + 30. Из-за увеличенного в четыре раза размера лота (0,04) наш выигрыш также увеличивается в четыре раза, составив 0,8572 USD х 4 = 3,4288 USD. Таким образом, общая чистая прибыль составит примерно 0 USD (-6 + 0,8572 + 1,7144 + 3,4288 = 0,0004 USD). Эта цифра не совсем равна нулю из-за округления при расчете средневзвешенного уровня цен.
Если цена продолжит падение от средневзвешенного уровня x + 21,428, мы будем накапливать положительную прибыль до достижения уровня тейк-профита. И наоборот, если цена продолжает расти, мы повторяем процесс, удваивая размер лота на каждой итерации и соответствующим образом корректируя средневзвешенный уровень цены. Такой подход гарантирует, что мы последовательно достигаем чистой прибыли в размере около 0 USD каждый раз, когда цена достигает средневзвешенного уровня цен, что постепенно приводит к положительной прибыли.
Автоматизации классической сеточной стратегии
Теперь давайте рассмотрим автоматизацию этой классической сеточной стратегии.
Сначала мы объявим несколько входных переменных в глобальном пространстве:
input bool initialPositionBuy = true; input double distance = 15; input double takeProfit = 5; input double initialLotSize = 0.01; input double lotSizeMultiplier = 2;
- initialPositionBuy - логическая переменная, определяющая тип начальной позиции – на покупку или продажу. При true начальной позицией будет покупка, в противном случае - продажа.
- distance - фиксированное расстояние между ордерами в пунктах.
- takeProfit - расстояние в пипсах выше средней цены всех открытых позиций, на котором все позиции будут закрыты с общей чистой прибылью.
- initialLotSize - размер лота первой позиции.
- lotSizeMultiplier - множитель размера лота для каждой позиции.
Это входные переменные, которые мы будем изменять для различных целей, например, для оптимизации нашей стратегии. Теперь мы определим еще несколько переменных в глобальном пространстве:
bool gridCycleRunning = false; double lastPositionLotSize, lastPositionPrice, priceLevelsSumation, totalLotSizeSummation;
Эти переменные будут использоваться для следующих целей:
- gridCycleRunning - логическая переменная, которая будет иметь значение true, если цикл выполняется, и false в противном случае. По умолчанию она равна false.
- lastPositionLotSize - двойная переменная, созданная для хранения размера лота последней открытой позиции в любой заданный момент времени цикла.
- lastPositionPrice - двойная переменная, созданная для хранения уровня открытой цены последней позиции в любой момент времени цикла.
- priceLevelsSumation - сумма всех уровней открытых цен всех позиций, которая будет использоваться при расчете среднего уровня цен.
- totalLotSizeSummation - суммирование по всем размерам лотов всех позиций, которое в дальнейшем будет использоваться при расчете среднего уровня цен.
//+------------------------------------------------------------------+ //| Hedge Cycle Intialization Function | //+------------------------------------------------------------------+ void StartGridCycle() { double initialPrice = initialPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); ENUM_ORDER_TYPE positionType = initialPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0); lastPositionLotSize = initialLotSize; lastPositionPrice = initialPrice; ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10); ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed); priceLevelsSumation = initialLotSize * lastPositionPrice; totalLotSizeSummation = initialLotSize; if(m_trade.ResultRetcode() == 10009) gridCycleRunning = true; } //+------------------------------------------------------------------+В функции StartGridCycle() мы сначала создаем двойную переменную InitialPrice, которая будет хранить цену ask или bid, в зависимости от переменной InitialPositionBuy. В частности, если параметр InitialPositionBuy равен true, мы сохраняем цену ask; при false - цену bid. Это различие имеет решающее значение, поскольку позиция на покупку может быть открыта только по цене ask, а позиция на продажу должна быть открыта по цене bid.
ENUM_ORDER_TYPE positionType = initialPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; CTrade m_trade; m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0);
Теперь мы откроем позицию на покупку или продажу в зависимости от значения InitialPositionBuy. Для этого создадим переменную PositionType типа ENUM_ORDER_TYPE. ENUM_ORDER_TYPE — это предопределенный тип пользовательской переменной в MQL5, способный принимать целочисленные значения от 0 до 8, каждое из которых представляет определенный тип ордера, как определено в следующей таблице:
Целочисленные значения | Идентификатор |
---|---|
0 | ORDER_TYPE_BUY |
1 | ORDER_TYPE_SELL |
2 | ORDER_TYPE_BUY_LIMIT |
3 | ORDER_TYPE_SELL_LIMIT |
4 | ORDER_TYPE_BUY_STOP |
5 | ORDER_TYPE_SELL_STOP |
6 | ORDER_TYPE_BUY_STOP_LIMIT |
7 | ORDER_TYPE_SELL_STOP_LIMIT |
8 | ORDER_TYPE_CLOSE_BY |
Действительно, внутри ENUM_ORDER_TYPE значения 0 и 1 соответствуют ORDER_TYPE_BUY и ORDER_TYPE_SELL, соответственно. Для наших целей мы сосредоточимся на этих двух. Использование идентификаторов, а не их целочисленных значений, является преимуществом, поскольку идентификаторы более интуитивно понятны и их легче запомнить.
Поэтому мы устанавливаем для позиции PositionType значение ORDER_TYPE_BUY, если параметр InitialPositionBuy равен true; в противном случае мы устанавливаем значение ORDER_TYPE_SELL.
Чтобы продолжить, мы сначала импортируем стандартную торговую библиотеку Trade.mqh в глобальное пространство. Это делается с помощью следующего оператора:
#include <Trade/Trade.mqh>
CTrade m_trade;
Чтобы продолжить, мы также определим экземпляр класса CTrade, который назовем m_trade. Этот экземпляр будет использоваться для управления торговыми операциями. Затем, чтобы открыть позицию, мы используем функцию PositionOpen из m_trade, которая обрабатывает фактическое открытие позиций на покупку или продажу на основе установленных нами параметров, включая тип позиции и другие соответствующие настройки торговли.
m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0);
Используя функцию PositionOpen из экземпляра m_trade, мы предоставляем ей несколько параметров, необходимых для открытия позиции:
-
_Symbol - первый параметр _Symbol относится к текущему торговому символу.
-
positionType - второй параметр positionType мы определили ранее на основе значения initialPositionBuy. Это определяет, является ли открытая позиция покупкой или продажей.
-
initialLotSize - размер лота для позиции, определенный нашей входной переменной initialLotSize.
-
initialPrice - цена, по которой открывается позиция. Это определяется переменной InitialPrice, которая содержит цену спроса или предложения в зависимости от типа открываемой позиции.
-
SL (Stop Loss) и TP (Take Profit) - последние два параметра предназначены для установки стоп-лосса (SL) и тейк-профита (TP). В данной конкретной стратегии мы изначально не задаем эти параметры, так как закрытие ордеров будет определяться логикой стратегии, особенно когда цена достигнет средней цены плюс значения тейк-профита.
Теперь перейдем к следующей части кода:
lastPositionLotSize = initialLotSize; lastPositionPrice = initialPrice; ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10); ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed);
-
Установка переменных lastPositionLotSize и lastPositionPrice:
- Мы инициализировали переменную LastPositionLotSize, определенную в глобальном пространстве, равной InitialLotSize. Эта переменная имеет решающее значение, поскольку она отслеживает размер лота последней открытой позиции, позволяя рассчитать размер лота для следующего ордера путем умножения его на входной множитель.
- Аналогично, для параметра LastPositionPrice также установлено значение InitialPrice. Эта переменная, также определенная в глобальном пространстве, необходима для определения уровня цены, при котором следует открывать последующие ордера.
-
Создание горизонтальной линии для визуализации:
- Чтобы улучшить визуальное представление нашей стратегии на графике, мы используем функцию ObjectCreate. Этой функции присвоены следующие параметры:
- 0 для текущего графика
- Next Position Price - имя объекта
- OBJ_HLINE - тип объекта горизонтальной линии
- Два дополнительных 0, один - для подокна, а другой - для даты и времени, поскольку для горизонтальной линии нужен только уровень цены.
- Рассчитанный уровень цены для следующего ордера (lastPositionPrice - distance * _Point * 10) в качестве последнего параметра
- Чтобы установить красный цвет этой горизонтальной линии, мы используем функцию ObjectSetInteger со свойством OBJPROP_COLOR, установленным в clrRed.
- Чтобы улучшить визуальное представление нашей стратегии на графике, мы используем функцию ObjectCreate. Этой функции присвоены следующие параметры:
-
Переход к управлению средней ценой в сетевом цикле:
- Выполнив эти первоначальные шаги, мы переходим к управлению средней ценой в рамках цикла сетки. Это включает в себя динамический расчет и корректировку средней цены по мере открытия новых позиций и развития рынка, что является ключевым компонентом стратегии сетевой торговли.
Давайте перейдем к следующей части реализации кода.
priceLevelsSumation = initialLotSize * lastPositionPrice; totalLotSizeSummation = initialLotSize;
-
Установка priceLevelsSumation :
- Мы определили priceLevelsSumation в глобальном пространстве для расчета средневзвешенной цены всех открытых позиций. Изначально, поскольку имеется только один ордер, мы устанавливаем priceLevelsSumation, равную значению lastPositionPrice, умноженному на соответствующий вес, который является размером лота ордера Эта настройка подготавливает переменную для накопления большего количества ценовых уровней, каждый из которых умножается на соответствующий размер лота по мере открытия новых позиций.
-
Инициализируем totalLotSizeSummation:
- Переменная totalLotSizeSummation изначально установлена равной initialLotSize. Это имеет смысл в контексте формулы средневзвешенного значения, где нам нужно деление на общие веса. Вначале, при наличии только одного ордера, общий вес равен размеру лота этого ордера. По мере того, как мы открываем больше позиций, мы будем добавлять их веса (размеры лотов) к этой сумме, таким образом динамически обновляя общий вес.
Теперь приступим к последней части функции StartGridCycle():
if(m_trade.ResultRetcode() == 10009) gridCycleRunning = true;Setting hedgeCycleRunning :
- Переменная hedgeCycleRunning устанавливается в значение true только после успешного открытия позиции. Это проверяется с помощью функции ResultRetcode() из экземпляра CTrade с именем trade. Код возврата "10009" свидетельствует об успешном размещении заказа. (Примечание: различные коды возврата могут использоваться для разных результатов торговых запросов.)
- Использование hedgeCycleRunning имеет решающее значение для стратегии, поскольку оно сигнализирует о начале цикла сетки. Значение этого флага станет более очевидным в последующих частях кода.
Завершив работу функции StartGridCycle(), которая инициирует стратегию сетки, мы переходим к функции OnTick(). Мы разделяем функцию на 5 сегментов, каждый из которых отвечает за определенный аспект торговой логики:
- Запуск цикла сетки
- Обработка позиций на покупку
- Обработка позиций на продажу
- Функция закрытия цикла сетки
- Обработка закрытия позиций
- Запуск цикла сетки:
double price = initialPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); if(!gridCycleRunning) { StartGridCycle(); }
-
Объявление переменной цены:
- Объявлена новая переменная с именем Price. Его значение определяется флагом InitialPositionBuy:
- Если параметр InitialPositionBuy имеет значение true, цена устанавливается на текущую цену ASK.
- Если параметр InitialPositionBuy имеет значение false, цена устанавливается на текущую цену BID.
- Объявлена новая переменная с именем Price. Его значение определяется флагом InitialPositionBuy:
-
Условное выполнение на основе gridCycleRunning:
- Следующий шаг включает условную проверку переменной gridCycleRunning:
- Если GridCycleRunning имеет значение false, это означает, что цикл сетки еще не начался или завершил предыдущий цикл. В этом случае мы выполняем функцию StartGridCycle(), которая была подробно объяснена ранее. Эта функция инициализирует цикл сетки, открывая первую позицию и настраивая необходимые параметры.
- Если GridCycleRunning равен true, это означает, что цикл сетки уже активен. В этом случае я предпочитаю пока ничего не делать. Это решение позволяет существующему сетевому циклу продолжать работу на основе установленной логики, не инициируя новый цикл и не вмешиваясь в текущий.
- Следующий шаг включает условную проверку переменной gridCycleRunning:
Этот подход эффективно управляет началом и продолжением цикла сетки, гарантируя, что торговая стратегия соответствует запланированному рабочему потоку. Перейдем к следующим шагам реализации стратегии сетки.
-
- Обработка позиций на покупку:
if(initialPositionBuy && price <= lastPositionPrice - distance * _Point * 10 && gridCycleRunning) { double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2); m_trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, newPositionLotSize, price, 0, 0); lastPositionLotSize *= lotSizeMultiplier; lastPositionPrice = price; ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10); ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed); priceLevelsSumation += newPositionLotSize * lastPositionPrice; totalLotSizeSummation += newPositionLotSize; ObjectCreate(0, "Average Price", OBJ_HLINE, 0, 0, priceLevelsSumation / totalLotSizeSummation); ObjectSetInteger(0, "Average Price", OBJPROP_COLOR, clrGreen); }
Здесь у нас есть еще один оператор if, который имеет 3 условия:
-
Условие initialPositionBuy:
- Этот сегмент кода выполняется только в том случае, если переменная InitialPositionBuy равна true. Это условие гарантирует, что логика обработки позиций на покупку отделена от логики обработки позиций на продажу.
- Этот сегмент кода выполняется только в том случае, если переменная InitialPositionBuy равна true. Это условие гарантирует, что логика обработки позиций на покупку отделена от логики обработки позиций на продажу.
-
Условие цены:
- Второе условие проверяет, меньше или равна переменная цена lastPositionPrice - distance * _Point * 10. Это условие имеет решающее значение для определения момента открытия новой позиции на покупку. Здесь используется операция вычитания (-), согласующаяся с логикой направления, определенной в нашей сеточной стратегии для позиций на покупку.
- Второе условие проверяет, меньше или равна переменная цена lastPositionPrice - distance * _Point * 10. Это условие имеет решающее значение для определения момента открытия новой позиции на покупку. Здесь используется операция вычитания (-), согласующаяся с логикой направления, определенной в нашей сеточной стратегии для позиций на покупку.
-
Условие gridCycleRunning:
- Третье условие требует, чтобы переменная gridCycleRunning имела значение true, что указывает на то, что цикл сетки в данный момент активен. Это важно для обеспечения открытия новых позиций только в рамках текущего торгового цикла.
- Третье условие требует, чтобы переменная gridCycleRunning имела значение true, что указывает на то, что цикл сетки в данный момент активен. Это важно для обеспечения открытия новых позиций только в рамках текущего торгового цикла.
Если все три условия выполнены, советник переходит к открытию новой позиции на покупку. Однако перед этим он рассчитывает размер лота для новой позиции:
- Новая двойная переменная newPositionLotSize объявляется и устанавливается равной LastPositionLotSize, умноженной на lotSizeMultiplier.
- Полученный размер лота затем нормализуется до двух десятичных знаков, чтобы быть действительным, поскольку размер лота должен быть кратен 0,01.
Такой подход гарантирует открытие новых позиций соответствующих размеров в соответствии с правилами стратегии сетки. Впоследствии советник использует функцию PositionOpen() из экземпляра класса CTrade с именем m_trade (объявленного ранее в глобальном пространстве) для открытия позиции на покупку с рассчитанным размером лота.
Продолжая логику, следующим шагом будет обновление переменной LastPositionLotSize. Это крайне важно для сохранения точности размеров лотов в последующих ордерах:
- LastPositionLotSize устанавливается равным самому себе, умноженному на lotSizeMultiplier. Важно отметить, что это умножение не требует нормализации до двух знаков после запятой.
- Причина отказа от нормализации проиллюстрирована на примере сценария, в котором lotSizeMultiplier равен 1,5, а initialLotSize равен 0,01. Умножение 0,01 на 1,5 дает 0,015. Это значение при нормализации до двух десятичных знаков округляется обратно до 0,01. Это создает цикл, в котором размер лота постоянно остается равным 0,01, несмотря на умножение.
- Чтобы избежать этой проблемы и гарантировать, что размеры лотов увеличиваются должным образом, lastPositionLotSize обновляется с использованием ненормализованного произведения самого себя и lotSizeMultiplier. Этот шаг имеет решающее значение для правильной работы стратегии сетки, особенно с дробными множителями.
Благодаря этому обновлению советник точно отслеживает и корректирует размеры лотов для новых позиций, поддерживая запланированное развитие стратегии сетки.
Продолжая процесс, следующий шаг включает обновление lastPositionPrice и визуализацию следующего уровня позиции на графике:
- Обновление lastPositionPrice:
- Переменная LastPositionPrice обновляется и становится равной цене, которая определяется условием InitialPositionBuy. Поскольку первое условие оператора if обеспечивает вход только в том случае, если параметр InitialPositionBuy имеет значение true, в этом контексте цена будет соответствовать цене ASK.
- Переменная LastPositionPrice обновляется и становится равной цене, которая определяется условием InitialPositionBuy. Поскольку первое условие оператора if обеспечивает вход только в том случае, если параметр InitialPositionBuy имеет значение true, в этом контексте цена будет соответствовать цене ASK.
- Визуализация следующего уровня позиции:
- На графике рисуется горизонтальная линия под названием Next Position Price (цена следующей позиции). Эта линия представляет собой уровень цены, на котором будет открыт следующий ордер.
- Поскольку параметр InitialPositionBuy имеет значение true, что указывает на то, что следующей позицией будет покупка, уровень цены для этой линии устанавливается равным LastPositionPrice (который был только что обновлен) минус расстояние (как указано во входных данных), умноженное на _Point, а затем умноженное на 10.
- Эта горизонтальная линия создается с помощью функции ObjectCreate(), а ее цвет устанавливается на clrRed с использованием дополнительных функций свойств объекта для упрощения визуализации. Это наглядное пособие помогает легко определить уровень цен для следующего потенциального ордера на покупку.
Обновляя lastPositionPrice и визуально отмечая следующий уровень ордера, советник эффективно готовится к последующим шагам цикла сетки, гарантируя, что каждая новая позиция соответствует критериям стратегии и визуально отслеживается на графике.
Теперь уточним расчет среднего уровня цен, уделив особое внимание средневзвешенному значению:
-
Обновление priceLevelsSummation и totalLotSizeSummation:
- Чтобы обновить средневзвешенную цену, мы добавляем произведение LastPositionPrice и newPositionLotSize в PriceLevelsSummation.
- Для totalLotSizeSummation мы просто добавляем значение newPositionLotSize.
- Эти обновления имеют решающее значение для отслеживания совокупных уровней цен и общих размеров лотов всех позиций.
-
Расчет средневзвешенного уровня цены:
- Средний уровень цен, который в данном контексте является средневзвешенным, рассчитывается путем деления priceLevelsSummation на totalLotSizeSummation.
- Этот расчет точно отражает среднюю цену всех открытых позиций с учетом соответствующих размеров лотов.
-
Визуализация средневзвешенного уровня цен:
- На графике на рассчитанном уровне средней цены с помощью функции ObjectCreate() создается еще одна горизонтальная линия.
- Цвет этой линии установлен на clrGreen, что отличает ее от другой горизонтальной линии, указывающей цену следующей позиции.
- Важно отметить, что для этого расчета используется нормализованное значение LastPositionLotSize, умноженное на лотSizeMultiplier. Это гарантирует, что учитывается реальный размер лота вновь открытой позиции, обеспечивая точное средневзвешенное значение.
Благодаря этим шагам советник не только отслеживает средний уровень цен всех открытых позиций, но и визуально представляет его на графике. Это позволяет легко контролировать и принимать решения на основе текущего состояния сетевого цикла и рыночных условий.
-
- Обработка позиций на продажу:
if(!initialPositionBuy && price >= lastPositionPrice + distance * _Point * 10 && gridCycleRunning) { double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2); m_trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, newPositionLotSize, price, 0, 0); lastPositionLotSize *= lotSizeMultiplier; lastPositionPrice = price; ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice + distance * _Point * 10); ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed); priceLevelsSumation += newPositionLotSize * lastPositionPrice; totalLotSizeSummation += newPositionLotSize; ObjectCreate(0, "Average Price", OBJ_HLINE, 0, 0, priceLevelsSumation / totalLotSizeSummation); ObjectSetInteger(0, "Average Price", OBJPROP_COLOR, clrGreen); }
Здесь у нас есть еще один оператор if, который имеет 3 условия:
-
Условие initialPositionBuy:
- Этот сегмент кода выполняется только в том случае, если параметр InitialPositionBuy равен false. Это условие гарантирует, что логика обрабатывает позиции продажи отдельно от позиций покупки.
- Этот сегмент кода выполняется только в том случае, если параметр InitialPositionBuy равен false. Это условие гарантирует, что логика обрабатывает позиции продажи отдельно от позиций покупки.
-
Условие цены:
- Второе условие проверяет, больше или равна цена lastPositionPrice + distance * _Point * 10. Это важно для определения подходящего момента для открытия новой позиции на продажу. В этом случае используется операция сложения (+), что соответствует логике направления нашей стратегии для позиций на продажу.
- Второе условие проверяет, больше или равна цена lastPositionPrice + distance * _Point * 10. Это важно для определения подходящего момента для открытия новой позиции на продажу. В этом случае используется операция сложения (+), что соответствует логике направления нашей стратегии для позиций на продажу.
-
Условие gridCycleRunning:
- Третье условие требует, чтобы GridCycleRunning имело значение true, указывая на то, что цикл сетки активно выполняется. Это гарантирует, что новые позиции открываются только в рамках текущего торгового цикла.
- Третье условие требует, чтобы GridCycleRunning имело значение true, указывая на то, что цикл сетки активно выполняется. Это гарантирует, что новые позиции открываются только в рамках текущего торгового цикла.
Если все три условия выполнены, советник приступает к открытию новой позиции на продажу:
- Двойная переменная newPositionLotSize объявляется и устанавливается равной значению lastPositionLotSize, умноженному на lotSizeMultiplier.
- Новый размер лота затем нормализуется до двух десятичных знаков, чтобы быть действительным, поскольку размер лота должен быть кратен 0,01.
- Наконец, советник использует функцию PositionOpen() из экземпляра класса CTrade с именем m_trade (объявленного ранее в глобальном пространстве) для открытия позиции на продажу с рассчитанным размером лота.
Такой подход гарантирует, что новые позиции на продажу открываются через соответствующие промежутки времени и в соответствующих размерах, соблюдая правила стратегии сетки и поддерживая развитие стратегии.
На следующем этапе обработки позиций на продажу мы сосредоточимся на правильном обновлении переменной lastPositionLotSize:
- Переменная lastPositionLotSize обновляется и становится равной самой себе, умноженной на lotSizeMultiplier. Этот шаг имеет решающее значение для поддержания прогресса размеров лотов в сеточном цикле.
- Важно отметить, что это умножение не требует нормализации до двух знаков после запятой. Это решение имеет решающее значение, чтобы избежать потенциальной проблемы с дробными множителями.
- Для иллюстрации - при lotSizeMultiplier, равном 1,5, и initialLotSize, равном 0,01, результат умножения составит 0,015. Нормализация этого значения до двух десятичных знаков приведет к округлению обратно до 0,01. Повторение этого процесса будет постоянно давать размер лота 0,01, создавая цикл и идя вразрез с целью стратегии.
- Чтобы обойти эту проблему, для параметра lastPositionLotSize устанавливается ненормализованное произведение самого себя и lotSizeMultiplier. Такой подход гарантирует соответствующее увеличение размеров лотов, особенно при работе с множителями, которые приводят к дробным размерам лотов.
Обновляя lastPositionLotSize без нормализации, советник эффективно отслеживает и корректирует размеры лотов для новых позиций на продажу, гарантируя, что сеточная стратегия работает должным образом.
-
Обновление lastPositionPrice:
- Переменная lastPositionPrice обновляется до равной цены, которая будет либо ценой ASK, либо ценой BID в зависимости от значения InitialPositionBuy.
- В данном случае, поскольку мы вводим этот сегмент кода только в том случае, если параметр InitialPositionBuy равен false (согласно первому условию), для lastPositionPrice устанавливается цена BID.
-
Отображение горизонтальной линии для следующей позиции:
- На графике рисуется горизонтальная линия под названием Next Position Price (цена следующей позиции). Эта линия указывает уровень цены, на котором будет открыт следующий ордер (в данном случае - на продажу).
- Уровень цены для этой горизонтальной линии устанавливается как lastPositionPrice (который был обновлен) плюс расстояние, указанное во входных данных, умноженное на _Point, а затем еще раз умноженное на 10. Этот расчет определяет соответствующий уровень для следующего ордера на продажу.
- Линия создается с помощью функции ObjectCreate() — предопределенной функции в MQL5 для рисования объектов на графиках.
- Цвет этой линии установлен на clrRed, что улучшает ее видимость и облегчает различение на графике.
Путем правильной установки lastPositionPrice и визуального представления уровня следующего ордера советник эффективно подготавливается к последующим ордерам на продажу, гарантируя, что они соответствуют правилам стратегии сетки и легко идентифицируются на графике.
При расчете среднего уровня цен и особенно с учетом средневзвешенного значения для позиций на продажу, этот процесс включает в себя:
-
Обновление priceLevelsSummation и totalLotSizeSummation:
- Значение lastPositionPrice, умноженное на newPositionLotSize, добавляется к priceLevelsSummation. На этом этапе накапливаются уровни цен всех открытых позиций, каждая из которых взвешивается по соответствующему размеру лота.
- Значение newPositionLotSize добавляется к totalLotSizeSummation. Эта переменная отслеживает совокупные размеры лотов всех позиций.
-
Расчет средневзвешенного уровня цены:
- Средний уровень цен получается путем деления PriceLevelsSummation на totalLotSizeSummation. Этот расчет дает средневзвешенную цену всех открытых позиций.
- Средний уровень цен получается путем деления PriceLevelsSummation на totalLotSizeSummation. Этот расчет дает средневзвешенную цену всех открытых позиций.
-
Визуализация средневзвешенного уровня цен:
- Горизонтальная линия создается на уровне средневзвешенной цены с помощью функции ObjectCreate(). Это визуальное представление помогает отслеживать среднюю цену всех позиций.
- Цвет этой линии установлен на clrGreen, что позволяет легко отличить ее от других линий на графике.
- Важно отметить, что в этих расчетах используется нормализованное значение lastPositionLotSize, умноженное на lotSizeMultiplier. Это гарантирует, что учитываются реальные размеры лотов открытых позиций, обеспечивая точный расчет средневзвешенного значения.
Этот метод расчета и визуализации средневзвешенного уровня цен имеет решающее значение для эффективного управления позициями продажи в сеточном цикле, позволяя принимать обоснованные решения на основе текущего состояния рынка и позиции.
-
- Функция закрытия цикла сетки:
//+------------------------------------------------------------------+ //| Stop Function for a particular Grid Cycle | //+------------------------------------------------------------------+ void StopGridCycle() { gridCycleRunning = false; ObjectDelete(0, "Next Position Price"); ObjectDelete(0, "Average Price"); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { m_trade.PositionClose(ticket); } } } //+------------------------------------------------------------------+
-
Установка gridCycleRunning на False:
- Первое действие в этой функции — установить для логической переменной gridCycleRunning значение false. Это изменение означает, что цикл сетки больше не активен и находится в процессе закрытия.
- Первое действие в этой функции — установить для логической переменной gridCycleRunning значение false. Это изменение означает, что цикл сетки больше не активен и находится в процессе закрытия.
-
Удаление объектов графика:
- Используем предопределенную функцию ObjectDelete() для удаления с графика двух конкретных объектов - Next Position Price (цена следующей позиции) и Average Price (средняя цена). Этот шаг очищает график от маркеров, указывая на то, что цикл завершается и эти уровни цен больше не актуальны.
- Используем предопределенную функцию ObjectDelete() для удаления с графика двух конкретных объектов - Next Position Price (цена следующей позиции) и Average Price (средняя цена). Этот шаг очищает график от маркеров, указывая на то, что цикл завершается и эти уровни цен больше не актуальны.
-
Закрытие всех позиций:
- Затем функция переходит к перебору всех открытых позиций.
- Каждая позиция выбирается индивидуально по номеру тикета.
- Как только позиция выбрана, она закрывается с помощью функции PositionClose() из экземпляра m_trade. Номер тикета позиции передается в качестве параметра этой функции.
- Такой системный подход гарантирует, что все позиции, открытые в рамках цикла сетки, будут закрыты, что фактически завершает торговую деятельность для этого конкретного цикла.
Выполняя эти шаги, функция закрытия цикла сетки методично и эффективно закрывает все позиции, сбрасывает торговую среду и подготавливает советник к новому циклу сетки.
-
- Обработка закрытия позиций:
if(gridCycleRunning) { if(initialPositionBuy && price >= (priceLevelsSumation / totalLotSizeSummation) + takeProfit * _Point * 10) StopGridCycle(); if(!initialPositionBuy && price <= (priceLevelsSumation / totalLotSizeSummation) - takeProfit * _Point * 10) StopGridCycle(); }
В последнем разделе "Обработка закрытия позиций" процесс закрытия ордеров контролируется оператором if, зависящим от определенных условий:
- Основным условием выполнения любого действия является то, что значение gridCycleRunning должно быть равно true, что указывает на активный цикл сетки.
- Внутри него есть еще две условные проверки, основанные на значении initialPositionBuy:
- Если параметр initialPositionBuy равен true, а текущая цена превышает средневзвешенную цену на число пипсов, равное takeProfit (указанному во входных переменных), то выполняется функция StopGridCycle().
- И наоборот, если параметр initialPositionBuy равен false, а текущая цена ниже уровня средневзвешенной цены на число пипсов, равное takeProfit, то выполняется StopGridCycle().
Эти условия гарантируют остановку цикла сетки и закрытие позиций на основе указанных критериев тейк-профита относительно средневзвешенного уровня цен.
Тестирование классической сеточной стратегии на истории
Автоматизация нашей классической сеточной стратегии завершена. Пришло время оценить ее эффективность в реальных условиях.
Для тестирования на истории будут использоваться следующие входные параметры:
- initialPositionBuy: true
- distance: 15 пипсов
- takeProfit: 5 пипсов
- initialLotSize: 0.01
- lotSizeMultiplier: 2.0
Тестирование будет проводиться на паре EURUSD с 1 ноября по 22 декабря 2023 года. Выбранное кредитное плечо составляет 1:500, стартовый депозит - 10 000 USD. Что касается таймфрейма, то он не имеет отношения к нашей стратегии, поэтому подойдет любой, не влияющий на результат. Этот тест, хотя и охватывает относительно короткий период чуть менее двух месяцев, призван служить репрезентативной выборкой для потенциальных результатов в течение более длительного периода.
Посмотрим на результаты:
Результаты довольно хороши. Просматривая результаты тестирования на истории, мы наблюдаем интересные тенденции, представленные синими и зелеными линиями на графике. Давайте разберем, что означает каждая линия и как их движение отражает эффективность стратегии:
-
Синяя и зеленая линии:
- Синяя линия представляет баланс счета, а зеленая линия - эквити.
- Наблюдается примечательная закономерность: баланс увеличивается, эквити уменьшается и в конечном итоге они сходятся в одной точке.
-
Объяснение колебаний баланса:
- Стратегия требует одновременного закрытия всех ордеров цикла, что в идеале должно привести к увеличению баланса. Однако на графике видна закономерность роста и падения баланса, которая требует дальнейшего пояснения.
- Такое колебание объясняется небольшой задержкой (в доли секунды) закрытия ордеров. Несмотря на то, что ордера закрываются почти одновременно, график фиксирует постепенные обновления баланса.
- Первоначально баланс увеличивается, поскольку прибыльные позиции закрываются первыми. Позиции закрываются циклически, начиная с последней (наиболее прибыльной) позиции, на что указывает функция PositionsTotal(). Таким образом, восходящее и кратковременное нисходящее движение линии баланса можно игнорировать, сосредоточившись вместо этого на чистом восходящем тренде.
-
Движение линии эквити:
- В соответствии с балансом эквити сначала падает, а затем растет. Это связано с тем, что прибыльные позиции закрываются первыми, временно уменьшая эквити.
- Движение зеленой линии эквити следует той же логике, что и синяя линия баланса, что приводит к тому же выводу об общей положительной тенденции.
Таким образом, несмотря на незначительные колебания, отраженные на графике из-за последовательности закрытия ордеров и небольших задержек, общая тенденция указывает на успешный результат стратегии, о чем свидетельствует окончательное сближение и движение вверх линий баланса и эквити.
Результаты тестирования на истории демонстрируют прибыльность стратегии классической сетки. Однако важно признать существенное ограничение, присущее этой стратегии: необходимость в долгом удержании позиции.
Стратегия часто требует значительного капитала, чтобы противостоять просадкам, которые происходят до достижения прибыльности. Способность удерживать позиции в условиях неблагоприятных движений рынка, не сталкиваясь с маржин-коллами и необходимостью закрывать позиции с убытком, имеет решающее значение.
Устранение этого ограничения станет ключевым моментом в последующих частях нашей серии, в которых мы рассмотрим методы оптимизации стратегии. Целью будет повышение эффективности стратегии и снижение необходимости в долгом удержании позиции, что сделает стратегию более доступной и менее рискованной для трейдеров с различным размером капитала.
Заключение
В этом выпуске мы углубились в тонкости классической сеточной стратегии и успешно автоматизировали ее с помощью советника на MQL5. Мы также рассмотрели некоторые первоначальные результаты этой стратегии, подчеркнув ее потенциал и области для улучшения.
Однако наша работа по оптимизации таких стратегий далека от завершения. Будущие части серии будут посвящены тонкой настройке стратегии, в частности оттачиванию оптимальных значений входных параметров, таких как distance, takeProfit, initialLotSize and lotSizeMultiplier, чтобы максимизировать прибыль и минимизировать просадки. Интересным аспектом нашего предстоящего исследования будет определение эффективности направления начальной позиции (покупка или продажа), которая может варьироваться в зависимости от различных валют и рыночных условий.
В наших последующих статьях нас ждет много интересного. Мы раскроем еще больше ценных идей и методов. Благодарю за внимание и надеюсь, что мои статьи были полезны для вас. Если у вас есть темы или идеи, которые вы хотели бы обсудить в следующей части этой серии, ваши предложения всегда приветствуются.
Удачного программирования! Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/13906
![Прогнозирование на основе глубокого обучения и открытие ордеров с помощью пакета MetaTrader 5 python и файла модели ONNX](https://c.mql5.com/2/64/Deep_Learning_Forecast_and_ordering_with_Python_and_MetaTrader5_python_packag___LOGOe.png)
![Риск-менеджер для алгоритмической торговли](https://c.mql5.com/2/77/Risk_manager_for_algorithmic_trading___LOGO__2.png)
![Нейросети — это просто (Часть 90): Частотная интерполяция временных рядов (FITS)](https://c.mql5.com/2/78/Neural_networks_are_easy_tPart_90x__LOGO.png)
![Разрабатываем мультивалютный советник (Часть 10): Создание объектов из строки](https://c.mql5.com/2/77/Developing_a_multi-currency_advisor_2Part_101___LOGO.png)
![MQL5 - Язык торговых стратегий для клиентского терминала MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Здравствуйте, Селевкус,
100 лотов будут стоить 20,000 USD (при условии, что 2 USD - 0.01 лота, EURUSD обычно выше этого), плюс все предыдущие лоты добавят еще 20,000 USD.
Это, безусловно, проблема даже для самых богатых из нас, к тому же многие брокеры устанавливают ограничение на суммарный размер лота в 200.
Наша главная цель - никогда не дойти до этого и закрыть цикл до этого в прибыли, поэтому мы будем оптимизировать стратегию в последующих частях.
С уважением,
Эта серия просто завораживает.
Исходный код действительно хорошо написан и прост в использовании.
С уважением.
Здравствуйте, Лайонел,
Очень ценю ваши добрые слова, надеюсь, вы останетесь до конца серии.
С уважением,
Спасибо за статью @Kailash Bai Mina, я новичок в MQL5, и такой контент очень помогает новичкам вроде меня. С нетерпением жду следующей части.
Здравствуйте, Хавьер,
Надеюсь, вы достигнете больших успехов в изучении MQL5. Извините за задержку с последующими частями, мы опубликуем их как можно скорее.
С уважением,