Автоматизация торговых стратегий на MQL5 (Часть 7): Создание советника по сеточной торговле с динамическим масштабированием лотов
Введение
В предыдущей статье (Часть 6) мы разработали автоматизированную систему поиска ордер-блоков на MetaQuotes Language 5 (MQL5). Теперь, в Части 7, мы сосредоточимся на сеточной торговле - стратегии, которая заключает сделки с фиксированными ценовыми интервалами в сочетании с динамическим масштабированием лотов для оптимизации риска и прибыли. Этот подход адаптирует размер позиций в зависимости от рыночных условий с целью повышения прибыльности и улучшения управления рисками. В статье рассмотрим следующее:
В итоге у вас будет полнофункциональная программа для сеточной торговли с динамическим масштабированием лотов, готовая к тестированию и оптимизации. Начнём!
План стратегии
Сеточная торговля - это систематический подход, который заключается в размещении ордеров на покупку и продажу с заранее определенными ценовыми интервалами, позволяя трейдерам извлекать выгоду из колебаний рынка, не требуя точного прогнозирования тренда. Эта стратегия извлекает выгоду из волатильности рынка, постоянно открывая и закрывая сделки в пределах определенного ценового диапазона. Чтобы повысить его эффективность, мы внедрим динамическое масштабирование лотов, которое позволит корректировать размеры позиций в зависимости от заранее определенных условий, таких как баланс счета, волатильность или результаты предыдущих сделок. Наша система сеточной торговли будет работать со следующими ключевыми компонентами:
- Структура сетки – Мы определим интервал между ордерами.
- Правила входа и исполнения – Мы будем определять, когда открывать сеточные сделки, основываясь на фиксированных расстояниях, используя стратегию Индикатор скользящей средней.
- Динамическое масштабирование лота - Мы реализуем адаптивный механизм определения размера лота, который будет регулировать размеры позиций в зависимости от рыночных условий или предопределенных параметров риска.
- Управление сделками – мы включим механизмы стоп-лосса, тейк-профита и опциональные механизмы безубыточности для эффективного управления рисками.
- Стратегия выхода – Мы разработаем логику закрытия позиций, основанную на целевых показателях прибыли, лимитах риска или разворотах тренда.
В двух словах, вот визуализация всего плана стратегии для простоты понимания.

Сочетая структурированную сетевую систему с адаптивным определением размера лота, мы создадим советника, который максимизирует доходность при эффективном управлении рисками. Далее мы реализуем эти концепции в MQL5.
Реализация средствами MQL5
Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку «Индикаторы» (Indicators), перейдите на вкладку "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования нам нужно будет объявить некоторые глобальные переменные, которые будем использовать во всей программе.
//+------------------------------------------------------------------+ //| Copyright 2025, Forex Algo-Trader, Allan. | //| "https://t.me/Forex_Algo_Trader" | //+------------------------------------------------------------------+ #property copyright "Forex Algo-Trader, Allan" #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property description "This EA trades based on Grid Strategy" #property strict #include <Trade/Trade.mqh> //--- Include trading library CTrade obj_Trade; //--- Trading object instance //--- Closure Mode Enumeration and Inputs enum ClosureMode { CLOSE_BY_PROFIT, //--- Use total profit (in currency) to close positions CLOSE_BY_POINTS //--- Use a points threshold from breakeven to close positions }; input group "General EA Inputs" input ClosureMode closureMode = CLOSE_BY_POINTS; //Select closure mode double breakevenPoints = 50 * _Point; //--- Points offset to add/subtract to/from breakeven //--- Global Variables double TakeProfit; //--- Current take profit level double initialLotsize = 0.1; //--- Initial lot size for the first trade double takeProfitPts = 200 * _Point; //--- Take profit distance in points double profitTotal_inCurrency = 100; //--- Profit target (in currency) to close positions double gridSize; //--- Price level at which grid orders are triggered double gridSize_Spacing = 500 * _Point; //--- Grid spacing in points double LotSize; //--- Current lot size (increased with grid orders) bool isTradeAllowed = true; //--- Flag to allow trade on a new bar int totalBars = 0; //--- Count of bars seen so far int handle; //--- Handle for the Moving Average indicator double maData[]; //--- Array for Moving Average data
Здесь мы включаем библиотеку "Trade/Trade.mqh" с помощью #include и инстанцируем объект "obj_Trade" для обработки своих сделок. Определяем перечисление "ClosureMode" с параметрами для закрытия позиций и настраиваем такие пользовательские входные данные, как "ClosureMode" и "breakevenPoints". Далее объявляем переменные для управления уровнями тейк-профита, начальным размером лота, сеточным интервалом и динамическим размером лота, а также флажки и счетчики для контроля над сделками и данные индикатора скользящей средней. Затем нам нужно объявить прототипы для наших ключевых функций, которые будут структурировать программу следующим образом.
//--- Function Prototypes void CheckAndCloseProfitTargets(); //--- Closes all positions if total profit meets target void ExecuteInitialTrade(double ask, double bid); //--- Executes the initial BUY/SELL trade (initial positions) void ManageGridPositions(double ask, double bid); //--- Adds grid orders when market moves to grid level (grid positions) void UpdateMovingAverage(); //--- Updates MA indicator data from its buffer bool IsNewBar(); //--- Checks if a new bar has formed double CalculateWeightedBreakevenPrice(); //--- Calculates the weighted average entry price for positions void CheckBreakevenClose(double ask, double bid); //--- Closes positions if price meets breakeven+/- threshold void CloseAllPositions(); //--- Closes all open positions
Что касается функций, мы реализуем "CheckAndCloseProfitTargets", чтобы отслеживать общую прибыльность и закрывать позиции, как только наша цель будет достигнута, а также "ExecuteInitialTrade", чтобы запустить стратегию с помощью первоначального ордера на покупку или продажу. "ManageGridPositions" будет добавлять дополнительные ордера с заданными сеточными интервалами по мере движения рынка, в то время как "UpdateMovingAverage" гарантирует актуальность данных нашего индикатора для принятия решений. "IsNewBar" определяет новые бары, чтобы предотвратить несколько сделок на одной и той же свече, "CalculateWeightedBreakevenPrice" вычисляет среднюю цену входа по позициям, а "CheckBreakevenClose" использует эту информацию для закрытия сделок при выполнении благоприятных условий. Наконец, "CloseAllPositions" будет методично закрывать все открытые сделки, когда это необходимо.
После установки всего этого в "глобальную область видимости" мы готовы продолжить инициализацию программы, которая выполняется с помощью обработчика событий "OnInit".
//+------------------------------------------------------------------+ //--- Expert initialization function //+------------------------------------------------------------------+ int OnInit(){ //--- Initialize the Moving Average indicator (Period: 21, SMA, Price: Close) handle = iMA(_Symbol, _Period, 21, 0, MODE_SMA, PRICE_CLOSE); if (handle == INVALID_HANDLE){ Print("ERROR: UNABLE TO INITIALIZE THE INDICATOR. REVERTING NOW!"); return (INIT_FAILED); } ArraySetAsSeries(maData, true); //--- Ensure MA data array is in series order return(INIT_SUCCEEDED); }
Здесь мы инициализируем программу посредством настройки нашего индикатора скользящей средней с помощью функции iMA с периодом 21, типом SMA и PRICE_CLOSE для использования данных о ценах при закрытии. Мы проверяем, корректен ли хэндл индикатора — если это не так (INVALID_HANDLE), мы выводим сообщение об ошибке и возвращаем INIT_FAILED, чтобы остановить запуск программы. Наконец, вызываем функцию ArraySetAsSeries для массива "maData", чтобы убедиться, что данные скользящей средней расположены в корректном порядке, прежде чем возвращать INIT_SUCCEEDED для подтверждения успешной инициализации. После корректной инициализации можем перейти к обработчику событий OnTick, чтобы разработать логику для открытия позиций и управления ими.
//+------------------------------------------------------------------+ //--- Expert tick function //+------------------------------------------------------------------+ void OnTick(){ //--- Allow new trade signals on a new bar if(IsNewBar()) isTradeAllowed = true; //--- Update the Moving Average data UpdateMovingAverage(); }
Поскольку нам надо проверять сделки не на каждом тике, а на каждом баре, вызываем функцию "IsNewBar" и используем ее для установки переменной "IsTradeAllowed" значения true, если формируется новый бар. Затем вызываем функцию, отвечающую за получение значений скользящей средней. Определения функции указаны ниже.
//+-------------------------------------------------------------------+ //--- Function: UpdateMovingAverage //--- Description: Copies the latest data from the MA indicator buffer. //+-------------------------------------------------------------------+ void UpdateMovingAverage(){ if(CopyBuffer(handle, 0, 1, 3, maData) < 0) Print("Error: Unable to update Moving Average data."); } //+-------------------------------------------------------------------+ //--- Function: IsNewBar //--- Description: Checks if a new bar has been formed. //+-------------------------------------------------------------------+ bool IsNewBar(){ int bars = iBars(_Symbol, _Period); if(bars > totalBars){ totalBars = bars; return true; } return false; }
Здесь мы реализуем "UpdateMovingAverage" для обновления данных нашего индикатора путем копирования последних значений из буфера скользящей средней с помощью функции CopyBuffer. Если вызов этой функции завершается неудачей, выводим сообщение об ошибке, предупреждающее нас о том, что обновление прошло неудачно. В функции "IsNewBar" мы проверяем, сформировался ли новый бар, сравнивая текущее количество баров, полученное с помощью функции iBars, с нашим сохраненным количеством «totalBars». Если количество увеличилось, обновляем "totalBars" и возвращаем значение "true", указывая, что для принятия торговых решений доступен новый бар. Затем продолжаем использовать функцию tick для совершения сделок на основе полученных значений индикатора.
//--- Reset lot size if no positions are open if(PositionsTotal() == 0) LotSize = initialLotsize; //--- Retrieve recent bar prices for trade signal logic double low1 = iLow(_Symbol, _Period, 1); double low2 = iLow(_Symbol, _Period, 2); double high1 = iHigh(_Symbol, _Period, 1); double high2 = iHigh(_Symbol, _Period, 2); //--- Get current Ask and Bid prices (normalized) double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- If no positions are open and trading is allowed, check for an initial trade signal if(PositionsTotal() == 0 && isTradeAllowed){ ExecuteInitialTrade(ask, bid); }
Здесь мы сначала проверяем, не открыты ли какие-либо позиции, используя функцию PositionsTotal, и если это так, сбрасываем значение "LotSize" на "initialLotsize". Затем извлекаем последние цены баров, используя команды iLow и iHigh, чтобы зафиксировать максимумы и минимумы двух предыдущих баров, которые помогут сформировать наши торговые сигналы. Затем получаем текущие цены "ask" и "bid" с помощью SymbolInfoDouble, нормализуя их посредством NormalizeDouble для обеспечения точности. Наконец, если торговля разрешена (как указано в "isTradeAllowed") и в данный момент нет открытых позиций, вызываем функцию "ExecuteInitialTrade" с ценами "ask" и "bid", чтобы начать нашу первую сделку. Определение функции приведено ниже.
//+---------------------------------------------------------------------------+ //--- Function: ExecuteInitialTrade //--- Description: Executes the initial BUY or SELL trade based on MA criteria. //--- (These are considered "initial positions.") //+---------------------------------------------------------------------------+ void ExecuteInitialTrade(double ask, double bid){ //--- BUY Signal: previous bar's low above MA and bar before that below MA if(iLow(_Symbol, _Period, 1) > maData[1] && iLow(_Symbol, _Period, 2) < maData[1]){ gridSize = ask - gridSize_Spacing; //--- Set grid trigger below current ask TakeProfit = ask + takeProfitPts; //--- Set TP for BUY if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Initial Buy")) Print("Initial BUY order executed at ", ask, " with LotSize: ", LotSize); else Print("Initial BUY order failed at ", ask); isTradeAllowed = false; } //--- SELL Signal: previous bar's high below MA and bar before that above MA else if(iHigh(_Symbol, _Period, 1) < maData[1] && iHigh(_Symbol, _Period, 2) > maData[1]){ gridSize = bid + gridSize_Spacing; //--- Set grid trigger above current bid TakeProfit = bid - takeProfitPts; //--- Set TP for SELL if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Initial Sell")) Print("Initial SELL order executed at ", bid, " with LotSize: ", LotSize); else Print("Initial SELL order failed at ", bid); isTradeAllowed = false; } }
Здесь мы реализуем функцию "ExecuteInitialTrade", чтобы открыть начальную сделку на основе значений "maData". Мы извлекаем низкие цены из двух предыдущих баров, используя функцию iLow, а высокие цены - с помощью функции iHigh. В отношении сигнала на покупку мы проверяем, находится ли минимум предыдущего бара выше, чем "maData", в то время как предыдущий бар был ниже него. Если это условие выполнено, устанавливаем "gridSize" ниже текущего "ask", используя "gridSize_Spacing", чтобы определить следующий уровень сетки, рассчитываем "TakeProfit", добавляя "takeProfitPts" к "ask", и заключаем сделку на покупку, используя метод "obj_Trade.Buy".
В отношении сигнала на продажу проверяем, находится ли максимум предыдущего бара ниже "maData", в то время как предыдущий бар был выше него. Если значение равно true, устанавливаем "gridSize" над "bid", определяем "TakeProfit" путем вычитания "takeProfitPts" из "bid", и пытаемся заключить сделку на продажу, используя "obj_Trade.Sell". Как только сделка совершена, устанавливаем для параметра "isTradeAllowed" значение false, чтобы предотвратить дополнительные входы до тех пор, пока не будут выполнены дополнительные условия. Ниже представлен результат.

На изображении видно, что у нас есть подтвержденные сделки, находящиеся в процессе исполнения. Теперь нам нужно перейти к управлению сделками, открывая позиции в сетке.
//--- If positions exist, manage grid orders if(PositionsTotal() > 0){ ManageGridPositions(ask, bid); }
Мы проверяем, есть ли открытые позиции, используя функцию PositionsTotal. Если количество позиций больше нуля, мы вызываем функцию "ManageGridPositions" для обработки дополнительных сеточных сделок. Функция использует "ask" и "bid" в качестве параметров для определения надлежащих уровней цен для размещения новых сеточных ордеров на основе движения рынка. Реализация фрагмент кода функции представлена ниже.
//+------------------------------------------------------------------------+ //--- Function: ManageGridPositions //--- Description: When an initial position exists, grid orders are added //--- if the market moves to the grid level. (These orders are //--- considered "grid positions.") The lot size is doubled //--- with each grid order. //+------------------------------------------------------------------------+ void ManageGridPositions(double ask, double bid){ for(int i = PositionsTotal()-1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ int positionType = (int)PositionGetInteger(POSITION_TYPE); //--- Grid management for BUY positions if(positionType == POSITION_TYPE_BUY){ if(ask <= gridSize){ LotSize *= 2; //--- Increase lot size for grid order if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Grid Position BUY")) Print("Grid BUY order executed at ", ask, " with LotSize: ", LotSize); else Print("Grid BUY order failed at ", ask); gridSize = ask - gridSize_Spacing; //--- Update grid trigger } } //--- Grid management for SELL positions else if(positionType == POSITION_TYPE_SELL){ if(bid >= gridSize){ LotSize *= 2; //--- Increase lot size for grid order if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Grid Position SELL")) Print("Grid SELL order executed at ", bid, " with LotSize: ", LotSize); else Print("Grid SELL order failed at ", bid); gridSize = bid + gridSize_Spacing; //--- Update grid trigger } } } } }
Мы реализуем функцию "ManageGridPositions" для управления сеточными ордерами. Выполняем перебор всех открытых позиций в обратном порядке, используя цикл for loop, и извлекаем тикет каждой позиции с помощью функции PositionGetTicket. Затем следует выбрать позицию, используя PositionSelectByTicket и определить, является ли это сделкой на покупку или продажу с использованием PositionGetInteger с параметром POSITION_TYPE. Если это позиция на покупку, проверяем факт того, что рыночная цена "ask" достигла "gridSize» или опустилась ниже. Если значение равно true, удваиваем "LotSize" и выполняем новый сеточный ордер на покупку, используя функцию "obj_Trade.Buy". Если заказ выполнен успешно, выводим сообщение с подтверждением; в противном случае выводим сообщение об ошибке. Затем обновляем "gridSize" до следующего уровня сетки ниже.
Аналогично, если позиция SELL (продажа), мы проверяем факт того, что "bid" достигла или превысила "gridSize". Если значение равно true, удваиваем "LotSize" и размещаем новый сеточный ордер на продажу, используя функцию "obj_Trade.Sell". Затем триггер сетки "gridSize" обновляется до следующего уровня выше. После открытия сеточных позиций нам нужно отслеживать позиции и управлять ими, закрывая их, как только мы достигнем указанного ниже значения.
//--- Check if total profit meets the target (only used if closureMode == CLOSE_BY_PROFIT) if(closureMode == CLOSE_BY_PROFIT) CheckAndCloseProfitTargets();
Если для параметра "closureMode" установлено значение "CLOSE_BY_PROFIT", вызываем функцию "CheckAndCloseProfitTargets", чтобы проверить, достигла ли общая прибыль заданного целевого показателя, и, соответственно, закрываем все позиции. Объявление функции приведено ниже.
//+----------------------------------------------------------------------------+ //--- Function: CheckAndCloseProfitTargets //--- Description: Closes all positions if the combined profit meets or exceeds //--- the user-defined profit target. //+----------------------------------------------------------------------------+ void CheckAndCloseProfitTargets(){ if(PositionsTotal() > 1){ double totalProfit = 0; for(int i = PositionsTotal()-1; i >= 0; i--){ ulong tkt = PositionGetTicket(i); if(PositionSelectByTicket(tkt)) totalProfit += PositionGetDouble(POSITION_PROFIT); } if(totalProfit >= profitTotal_inCurrency){ Print("Profit target reached (", totalProfit, "). Closing all positions."); CloseAllPositions(); } } }
Чтобы гарантировать, что все позиции будут закрыты, если общая накопленная прибыль достигнет или превысит заданный целевой показатель прибыли, мы сначала проверяем, есть ли более одной открытой позиции, используя PositionsTotal. Инициализируем "totalProfit", чтобы отслеживать совокупную прибыль по всем позициям. Затем проходим циклом по всем открытым позициям, извлекая тикет каждой позиции с помощью PositionGetTicket и выбрав его, используя PositionSelectByTicket. Для каждой выбранной позиции извлекаем ее прибыль, используя PositionGetDouble с параметром POSITION_PROFIT и добавляем ее в "totalProfit". Если значение "totalProfit" соответствует или превышает значение "profitTotal_inCurrency", выводим сообщение о том, что целевой уровень прибыли достигнут, и вызываем функцию "CloseAllPositions", определение которой приведено ниже, для закрытия всех открытых сделок.
//+------------------------------------------------------------------+ //--- Function: CloseAllPositions //--- Description: Iterates through and closes all open positions. //+------------------------------------------------------------------+ void CloseAllPositions(){ for(int i = PositionsTotal()-1; i >= 0; i--){ ulong posTkt = PositionGetTicket(i); if(PositionSelectByTicket(posTkt)){ if(obj_Trade.PositionClose(posTkt)) Print("Closed position ticket: ", posTkt); else Print("Failed to close position ticket: ", posTkt); } } }
Функция просто выполняет перебор по всем открытым позициям и для каждой выбранной позиции закрывает ее с помощью метода "obj_Trade.PositionClose". Наконец, определяем логику закрытия позиций в безубыток.
//--- If using CLOSE_BY_POINTS and more than one position exists (i.e. grid), check breakeven closure if(closureMode == CLOSE_BY_POINTS && PositionsTotal() > 1) CheckBreakevenClose(ask, bid);
Если для параметра "closureMode" установлено значение "CLOSE_BY_POINTS" и открыто более одной позиции, вызываем функцию "CheckBreakevenClose" с параметрами "ask" и "bid", чтобы определить, достигла ли цена порога безубыточности, что позволяет закрывать позиции на основе заранее определенных точек безубыточности. Ниже приведено определение функции.
//+----------------------------------------------------------------------------+ //--- Function: CalculateWeightedBreakevenPrice //--- Description: Calculates the weighted average entry price (breakeven) //--- of all open positions (assumed to be in the same direction). //+----------------------------------------------------------------------------+ double CalculateWeightedBreakevenPrice(){ double totalCost = 0; double totalVolume = 0; int posType = -1; //--- Determine the type from the first position for(int i = 0; i < PositionsTotal(); i++){ ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ posType = (int)PositionGetInteger(POSITION_TYPE); break; } } //--- Sum the cost and volume for positions matching the type for(int i = 0; i < PositionsTotal(); i++){ ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ if(PositionGetInteger(POSITION_TYPE) == posType){ double price = PositionGetDouble(POSITION_PRICE_OPEN); double volume = PositionGetDouble(POSITION_VOLUME); totalCost += price * volume; totalVolume += volume; } } } if(totalVolume > 0) return(totalCost / totalVolume); else return(0); } //+-----------------------------------------------------------------------------+ //--- Function: CheckBreakevenClose //--- Description: When using CLOSE_BY_POINTS and multiple positions exist, //--- calculates the weighted breakeven price and checks if the //--- current price has moved the specified points in a profitable //--- direction relative to breakeven. If so, closes all positions. //+-----------------------------------------------------------------------------+ void CheckBreakevenClose(double ask, double bid){ //--- Ensure we have more than one position (grid positions) if(PositionsTotal() <= 1) return; double weightedBreakeven = CalculateWeightedBreakevenPrice(); int posType = -1; //--- Determine the trade type from one of the positions for(int i = 0; i < PositionsTotal(); i++){ ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ posType = (int)PositionGetInteger(POSITION_TYPE); break; } } if(posType == -1) return; //--- For BUY positions, profit when Bid >= breakeven + threshold if(posType == POSITION_TYPE_BUY){ if(bid >= weightedBreakeven + breakevenPoints){ Print("Closing BUY positions: Bid (", bid, ") >= Breakeven (", weightedBreakeven, ") + ", breakevenPoints); CloseAllPositions(); } } //--- For SELL positions, profit when Ask <= breakeven - threshold else if(posType == POSITION_TYPE_SELL){ if(ask <= weightedBreakeven - breakevenPoints){ Print("Closing SELL positions: Ask (", ask, ") <= Breakeven (", weightedBreakeven, ") - ", breakevenPoints); CloseAllPositions(); } } }
Здесь мы рассчитываем цену безубыточности для всех открытых позиций и определяем, переместилась ли рыночная цена на заданное расстояние за ее пределы, чтобы закрыть позиции с прибылью. В "CalculateWeightedBreakevenPrice" мы вычисляем взвешенную цену безубыточности, суммируя общую стоимость всех открытых позиций с помощью POSITION_PRICE_OPEN и взвешивая ее с помощью "POSITION_VOLUME". Сначала мы определяем тип позиции (BUY или SELL) по первой открытой позиции, используя POSITION_TYPE. Затем перебираем все позиции, суммируя общую стоимость и объем для позиций, соответствующих указанному типу. Если общий объем больше нуля, возвращаем взвешенную цену безубыточности путем деления общей стоимости на общий объем. В противном случае возвращаем ноль.
В "CheckBreakevenClose" сначала подтверждаем наличие нескольких открытых позиций, используя функцию PositionsTotal. Затем получаем взвешенную цену безубыточности, вызывая "CalculateWeightedBreakevenPrice". Определяем тип позиции, выбирая позицию и извлекая POSITION_TYPE. Если тип неверен, выходим из функции. Для позиций на ПОКУПКУ проверяем, достигла ли цена "bid" или превысила "weightedBreakeven" плюс "breakevenPoints". Если это так, выводим сообщение и вызываем команду "CloseAllPositions". Для позиций на ПРОДАЖУ проверяем, не опустилась ли цена "ask" ниже "weightedBreakeven" минус "breakevenPoints". Если это условие выполнено, также выводим сообщение и вызываем функцию "CloseAllPositions" для сохранения прибыли. После компиляции и запуска программы получаем следующий результат.

На графике видно, что позиции открываются и управляются с помощью сеточной системы и закрываются при достижении определенных уровней закрытия, что позволяет достичь нашей цели по созданию сеточной системы с динамическим размером лота. Осталось провести повторное тестирование программы, и это будет выполнено в следующем разделе.
Тестирование на истории
После тщательного тестирования на истории мы получили следующие результаты.
График тестирования на истории:

Отчет о тестировании на истории:

Заключение
В заключение мы продемонстрировали процесс разработки советника (EA) на MQL5, использующего динамическую стратегию сеточной торговли. Объединив такие ключевые элементы, как размещение сеточных ордеров, динамическое масштабирование лотов и целевое управление прибылью и безубыточностью, мы создали систему, которая адаптируется к колебаниям рынка с целью оптимизации соотношения риска и прибыли и восстановления после неблагоприятных колебаний цен.
Отказ от ответственности: Содержание настоящей статьи предназначено только для целей обучения. Торговля сопряжена со значительным финансовым риском, а поведение рынка может быть крайне непредсказуемым. Хотя описываемые стратегии предлагают структурированный подход к сеточной торговле, они не гарантируют прибыльность в будущем. Перед началом реальной торговли необходимы тщательное тестирование на истории и управление рисками.
Применяя эти методы, вы сможете усовершенствовать свои системы сеточной торговли, улучшить свой анализ рынка и усовершенствовать свои алгоритмические торговые стратегии. Желаем удачи в вашем торговом путешествии!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17190
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Обучение глубоких спайкинговых моделей (SEW-ResNet)
От начального до среднего уровня: Struct (II)
Как опубликовать код в CodeBase: Практическое руководство
Добавляем пользовательскую LLM в торгового робота (Часть 5): Разработка и тестирование торговой стратегии с помощью LLM(IV) — Тестирование торговой стратегии
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
это нормально..... лучший советник .
4 линии сбивают с толку новичка
это нормально..... лучший советник .
4 линии сбивают с толку новичка
Хорошо
прекрасная статья - спасибо вам большое... изучаю торговый подход буду ставить с правками у себя на торги на пользовательских символах хэджевых!
проверяю....
проверка....
examine....