English Deutsch 日本語
preview
Автоматизация торговых стратегий в MQL5 (Часть 26): Создание системы усреднения на основе пин-баров для многопозиционной торговли

Автоматизация торговых стратегий в MQL5 (Часть 26): Создание системы усреднения на основе пин-баров для многопозиционной торговли

MetaTrader 5Трейдинг |
89 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В своей предыдущей статье (Часть 25) мы разработали систему торговли по линиям тренда на MetaQuotes Language 5 (MQL5). В этой системе используется аппроксимация методом наименьших квадратов для определения линий поддержки и сопротивления тренда, генерируются автоматизированные сделки на основе касаний ценой с визуальной обратной связью. В Части 26 мы создадим программу усреднения по пин-барам, которая определяет свечные паттерны пин-баров для открытия сделок и управляет несколькими позициями с помощью стратегии усреднения, включающей трейлинг-стопы, перевод в безубыток и дашборд для мониторинга в реальном времени. В статье рассмотрим следующие темы:

  1. Изучение модели усреднения по пин‑барам
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

В итоге у вас будет мощная стратегия в MQL5 для торговли на основе пин-баров, готовая к настройке. Перейдём к реализации!


Изучение модели усреднения по пин‑барам

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

PIN BAR FRAMEWORK

Наш подход будет сосредоточен на обнаружении этих пин-баров в пределах текущего таймфрейма и использовании стратегии усреднения для открытия дополнительных позиций, если рынок движется против первоначальной сделки. Это должно улучшить общий результат торговли и одновременно ограничить риск с помощью трейлинг-стопа и перевода в безубыток. Чтобы добиться этого, мы сначала определим пин-бары относительно уровней поддержки или сопротивления, полученных на основе закрытия свечи на предыдущем периоде H4, чтобы обеспечить соответствие сделок значимым рыночным зонам.

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

STRATEGY FRAMEWORK


Реализация средствами MQL5

Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку "Индикаторы" (Indicators), перейдите на вкладку "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования мы начнем с объявления некоторых входных параметров и глобальных переменных, что сделает программу более динамичной.

//+------------------------------------------------------------------+
//|                                      a. Pin Bar Averaging EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2025, Allan Munene Mutiiria."
#property link        "https://t.me/Forex_Algo_Trader"
#property version     "1.00"
#property strict

#include <Trade\Trade.mqh>                         //--- Include Trade library for trading operations
CTrade obj_Trade;                                  //--- Instantiate trade object

//+------------------------------------------------------------------+
//| Trading signal enumeration                                       |
//+------------------------------------------------------------------+
enum EnableTradingBySignal {                       //--- Define trading signal enum
   ENABLED  = 1,                                   // Enable trading signals
   DISABLED = 0                                    // Disable trading signals
};

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input bool   useSignalMode = DISABLED;             // Set signal mode (ENABLED/DISABLED)
input int    orderDistancePips = 50;               // Set order distance (pips)
input double lotMultiplier = 1;                    // Set lot size multiplier
input bool   useRSIFilter = false;                 // Enable RSI filter
input int    magicNumber = 123456789;              // Set magic number
input double initialLotSize = 0.01;                // Set initial lot size
input int    compoundPercent = 2;                  // Set compounding percent (0 for fixed lots)
input int    maxOrders = 5;                        // Set maximum orders
input double stopLossPips = 400;                   // Set stop loss (pips)
input double takeProfitPips = 200;                 // Set take profit (pips)
input bool   useAutoTakeProfit = true;             // Enable auto take profit
input bool   useTrailingStop = true;               // Enable trailing stop
input double trailingStartPips = 15;               // Set trailing start (pips)
input double breakevenPips = 10;                   // Set breakeven (pips)
input string orderComment = "Forex_Algo_Trader";   // Set order comment
input color  lineColor = clrBlue;                  // Set line color
input int    lineWidth = 2;                        // Set line width

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
bool   isTradingAllowed();                         //--- Declare trading allowed check
double slBreakevenMinus = 0;                       //--- Initialize breakeven minus
double normalizedPoint;                            //--- Declare normalized point
ulong  currentTicket = 0;                          //--- Initialize current ticket
double buyCount, currentBuyLot, totalBuyLots;      //--- Declare buy metrics
double sellCount, currentSellLot, totalSellLots;   //--- Declare sell metrics
double totalSum, totalSwap;                        //--- Declare total sum and swap
double buyProfit, sellProfit, totalOperations;     //--- Declare profit and operations
double buyWeightedSum, sellWeightedSum;            //--- Declare weighted sums
double buyBreakEvenPrice, sellBreakEvenPrice;      //--- Declare breakeven prices
double minBuyLot, minSellLot;                      //--- Declare minimum lot sizes
double maxSellPrice, minBuyPrice;                  //--- Declare price extremes

Чтобы заложить основу системы усреднения по пин-барам в MQL5, автоматизирующей торговлю по паттернам pin bar и поддерживающей надёжное управление позициями, мы сначала подключаем библиотеку "<Trade\Trade.mqh>" и создаем объект "obj_Trade" как объект CTrade для обработки торговых операций, таких как открытие и закрытие позиций. Затем определим перечисление "EnableTradingBySignal" со значениями "ENABLED" (1) и "DISABLED" (0), чтобы контролировать, используются ли торговые сигналы для управления позициями. Далее настроим входные параметры для пользовательской настройки советника: логическое значение для переключения режима сигнала, расстояние между ордерами в пипсах, множитель размера лота, переключатель фильтра RSI, магический номер для идентификации позиций, начальный размер лота, процент компаундинга (0 для фиксированных лотов), максимальное количество ордеров, стоп-лосс и тейк-профит в пипсах, переключатели автоматического тейк-профита и трейлинг-стопа, уровень активации трейлинг-стопа и безубыток в пипсах, комментарий к ордеру, а также цвет и ширину линий для визуальных индикаторов.

Наконец, объявляем глобальные переменные: функцию "isTradingAllowed" для проверки торговых условий, "slBreakevenMinus", инициализированную значением 0 для корректировки стоп-лосса, "normalizedPoint" для масштабирования цены, "currentTicket" для отслеживания сделок, счетчиков и сумм, таких как "buyCount", "currentBuyLot", "totalBuyLots", "sellCount", "currentSellLot", "totalSellLots", "totalSum", "totalSwap", "buyProfit", "sellProfit", "totalOperations", "buyWeightedSum", "sellWeightedSum", "buyBreakEvenPrice", "sellBreakEvenPrice", "minBuyLot", "minSellLot", "maxSellPrice" и "minBuyPrice", а также переменные для расчёта метрик позиций, устанавливая основную структуру советника для обнаружения пин-баров и усреднения. Теперь можно перейти к инициализации программы, поскольку большая часть работы будет выполнена на каждом тике.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   normalizedPoint = _Point;                       //--- Initialize point value
   if (_Digits == 5 || _Digits == 3) {             //--- Check for 5 or 3 digit symbols
      normalizedPoint *= 10;                       //--- Adjust point value
   }
   ChartSetInteger(0, CHART_SHOW_GRID, false);     //--- Disable chart grid
   obj_Trade.SetExpertMagicNumber(magicNumber);    //--- Set magic number for trade object
   obj_Trade.SetTypeFilling(ORDER_FILLING_IOC);    //--- Set order filling type
   return(INIT_SUCCEEDED);                         //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0);                            //--- Delete all chart objects
   ChartRedraw(0);                                 //--- Redraw chart
}

Во-первых, в обработчике OnInit мы инициализируем "normalizedPoint" значением _Point и корректируем его, умножая на 10 для 5- или 3-значных символов с помощью _Digits, чтобы обеспечить точные расчеты цены; отключаем сетку графика с помощью ChartSetInteger, устанавливая CHART_SHOW_GRID в значение false для более наглядного отображения. Настраиваем "obj_Trade" с помощью "SetExpertMagicNumber", используя "magicNumber" для идентификации сделок. Устанавливаем тип исполнения ордера на "ORDER_FILLING_IOC" с помощью "SetTypeFilling" и возвращаем "INIT_SUCCEEDED" для подтверждения успешной инициализации. Затем мы переходим к обработчику OnDeinit, где удаляем все объекты графика с помощью ObjectsDeleteAll, чтобы очистить визуальные элементы, такие как дашборд и линии, которые определим позже. Это сделано для гарантированной очистки графика и вызова ChartRedraw для обновления графика перед завершением работы. Прежде чем углубиться в сложную торговую логику, давайте определим некоторые вспомогательные функции, которые нам понадобятся, чтобы сделать программу динамичной и простой в обслуживании.

//+------------------------------------------------------------------+
//| Count total trades                                               |
//+------------------------------------------------------------------+
int CountTrades() {
   int positionCount = 0;                         //--- Initialize position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL || PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check trade type
         positionCount++;                         //--- Increment position count
      }
   }
   return(positionCount);                         //--- Return total count
}

//+------------------------------------------------------------------+
//| Count buy trades                                                 |
//+------------------------------------------------------------------+
int CountTradesBuy() {
   int buyPositionCount = 0;                      //--- Initialize buy position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyPositionCount++;                      //--- Increment buy count
      }
   }
   return(buyPositionCount);                      //--- Return buy count
}

//+------------------------------------------------------------------+
//| Count sell trades                                                |
//+------------------------------------------------------------------+
int CountTradesSell() {
   int sellPositionCount = 0;                     //--- Initialize sell position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellPositionCount++;                     //--- Increment sell count
      }
   }
   return(sellPositionCount);                     //--- Return sell count
}

//+------------------------------------------------------------------+
//| Normalize price                                                  |
//+------------------------------------------------------------------+
double NormalizePrice(double price) {
   return(NormalizeDouble(price, _Digits));       //--- Normalize price to symbol digits
}

//+------------------------------------------------------------------+
//| Get lot digit for normalization                                  |
//+------------------------------------------------------------------+
int fnGetLotDigit() {
   double lotStepValue = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP); //--- Get lot step value
   if (lotStepValue == 1) return(0);              //--- Return 0 for step 1
   if (lotStepValue == 0.1) return(1);            //--- Return 1 for step 0.1
   if (lotStepValue == 0.01) return(2);           //--- Return 2 for step 0.01
   if (lotStepValue == 0.001) return(3);          //--- Return 3 for step 0.001
   if (lotStepValue == 0.0001) return(4);         //--- Return 4 for step 0.0001
   return(1);                                     //--- Default to 1
}

//+------------------------------------------------------------------+
//| Check buy orders for specific magic number                       |
//+------------------------------------------------------------------+
int CheckBuyOrders(int magic) {
   int buyOrderCount = 0;                         //--- Initialize buy order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            buyOrderCount++;                      //--- Increment buy count
            break;                                //--- Exit loop
         }
      }
   }
   return(buyOrderCount);                         //--- Return buy order count
}

//+------------------------------------------------------------------+
//| Check sell orders for specific magic number                      |
//+------------------------------------------------------------------+
int CheckSellOrders(int magic) {
   int sellOrderCount = 0;                         //--- Initialize sell order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                   //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            sellOrderCount++;                      //--- Increment sell count
            break;                                 //--- Exit loop
         }
      }
   }
   return(sellOrderCount);                         //--- Return sell order count
}

//+------------------------------------------------------------------+
//| Check total buy orders                                           |
//+------------------------------------------------------------------+
int CheckTotalBuyOrders(int magic) {
   int totalBuyOrderCount = 0;                      //--- Initialize total buy order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);          //--- Get position ticket
      if (ticket == 0) continue;                    //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            totalBuyOrderCount++;                   //--- Increment buy count
         }
      }
   }
   return(totalBuyOrderCount);                      //--- Return total buy count
}

//+------------------------------------------------------------------+
//| Check total sell orders                                          |
//+------------------------------------------------------------------+
int CheckTotalSellOrders(int magic) {
   int totalSellOrderCount = 0;                      //--- Initialize total sell order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);           //--- Get position ticket
      if (ticket == 0) continue;                     //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            totalSellOrderCount++;                   //--- Increment sell count
         }
      }
   }
   return(totalSellOrderCount);                      //--- Return total sell count
}

//+------------------------------------------------------------------+
//| Check market buy orders                                          |
//+------------------------------------------------------------------+
int CheckMarketBuyOrders() {
   int marketBuyCount = 0;                        //--- Initialize market buy count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            marketBuyCount++;                     //--- Increment buy count
         }
      }
   }
   return(marketBuyCount);                        //--- Return market buy count
}

//+------------------------------------------------------------------+
//| Check market sell orders                                         |
//+------------------------------------------------------------------+
int CheckMarketSellOrders() {
   int marketSellCount = 0;                       //--- Initialize market sell count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            marketSellCount++;                    //--- Increment sell count
         }
      }
   }
   return(marketSellCount);                       //--- Return market sell count
}

//+------------------------------------------------------------------+
//| Close all buy positions                                          |
//+------------------------------------------------------------------+
void CloseBuy() {
   while (CheckMarketBuyOrders() > 0) {           //--- Check buy orders exist
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
         ulong ticket = PositionGetTicket(i);      //--- Get position ticket
         if (ticket == 0) continue;               //--- Skip invalid tickets
         if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check symbol and magic
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Close all sell positions                                         |
//+------------------------------------------------------------------+
void CloseSell() {
   while (CheckMarketSellOrders() > 0) {          //--- Check sell orders exist
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
         ulong ticket = PositionGetTicket(i);      //--- Get position ticket
         if (ticket == 0) continue;               //--- Skip invalid tickets
         if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check symbol and magic
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Calculate lot size                                               |
//+------------------------------------------------------------------+
double GetLots() {
   double calculatedLot;                          //--- Initialize calculated lot
   double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); //--- Get minimum lot
   double maxLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX); //--- Get maximum lot
   if (compoundPercent != 0) {                    //--- Check compounding
      calculatedLot = NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE) * compoundPercent / 100 / 10000, fnGetLotDigit()); //--- Calculate compounded lot
      if (calculatedLot < minLot) calculatedLot = minLot; //--- Enforce minimum lot
      if (calculatedLot > maxLot) calculatedLot = maxLot; //--- Enforce maximum lot
   } else {
      calculatedLot = initialLotSize;             //--- Use fixed lot size
   }
   return(calculatedLot);                         //--- Return calculated lot
}

//+------------------------------------------------------------------+
//| Check account free margin                                        |
//+------------------------------------------------------------------+
double AccountFreeMarginCheck(string symbol, int orderType, double volume) {
   double marginRequired = 0.0;                   //--- Initialize margin required
   double price = orderType == ORDER_TYPE_BUY ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get price
   double calculatedMargin;                       //--- Declare calculated margin
   bool success = OrderCalcMargin(orderType == ORDER_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, symbol, volume, price, calculatedMargin); //--- Calculate margin
   if (success) marginRequired = calculatedMargin; //--- Set margin if successful
   return AccountInfoDouble(ACCOUNT_MARGIN_FREE) - marginRequired; //--- Return free margin
}

//+------------------------------------------------------------------+
//| Check if trading is allowed                                      |
//+------------------------------------------------------------------+
bool isTradingAllowed() {
   bool isAllowed = false;                        //--- Initialize allowed flag
   return(true);                                  //--- Return true
}

Здесь мы реализуем служебные функции программы для управления подсчетом сделок, закрытием позиций, расчетом размера лота, проверкой маржи и разрешениями на совершение сделок, обеспечивая надежную обработку торговых операций. Сначала создадим функции для подсчёта сделок: Функция "CountTrades" подсчитывает общее количество позиций, перебирая PositionsTotal, проверяя наличие действительных тикетов с помощью PositionGetTicket, сопоставляя "Symbol" и "magicNumber" и увеличивая "positionCount" для позиций на покупку или продажу; "CountTradesBuy" и "CountTradesSell" подсчитывают позиции на покупку и продажу соответственно, фильтруя по POSITION_TYPE_BUY или "POSITION_TYPE_SELL"; "CheckBuyOrders" и "CheckSellOrders" обнаруживают как минимум одну позицию на покупку или продажу с определенным магическим числом, прерывая операцию после первого совпадения; "CheckTotalBuyOrders" и "CheckTotalSellOrders" подсчитывают все позиции на покупку или продажу с помощью магического числа; и "CheckMarketBuyOrders" и "CheckMarketSellOrders" подсчитывают позиции на покупку или продажу с заданным магическим числом.

Затем мы реализуем функцию "NormalizePrice" для нормализации цен до _Digits с помощью NormalizeDouble и "fnGetLotDigit" для возврата соответствующей десятичной точности для размеров лота на основе SYMBOL_VOLUME_STEP (например, 0 для 1, 1 для 0.1). Далее разработаем функции "CloseBuy" и "CloseSell" для закрытия всех позиций на покупку или продажу, перебирая позиции в цикле, проверяя значения "Symbol" и "magicNumber" и используя "obj_Trade.PositionClose" до тех пор, пока "CheckMarketBuyOrders" или "CheckMarketSellOrders" не вернут 0. Наконец, мы реализуем функцию "GetLots" для расчета размера лота на основе "compoundPercent" (нормализация "AccountInfoDouble (ACCOUNT_BALANCE) * compoundPercent / 100 / 10000" с помощью функции "fnGetLotDigit", ограниченной параметрами SYMBOL_VOLUME_MIN и "SYMBOL_VOLUME_MAX") или "initialLotSize", а также "AccountFreeMarginCheck" для вычисления доступной маржи путем расчета требуемой маржи с помощью OrderCalcMargin для заданного типа ордера и объема, и "isTradingAllowed" в качестве заполнителя, возвращающего true. Для визуализации нам понадобятся функции для рисования линий и меток на графике.

//+------------------------------------------------------------------+
//| Draw support/resistance line                                     |
//+------------------------------------------------------------------+
void MakeLine(double price) {
   string name = "level";                         //--- Set line name
   if (ObjectFind(0, name) != -1) {               //--- Check if line exists
      ObjectMove(0, name, 0, iTime(Symbol(), PERIOD_CURRENT, 0), price); //--- Move line
      return;                                     //--- Exit function
   }
   ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); //--- Create horizontal line
   ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor); //--- Set color
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(0, name, OBJPROP_WIDTH, lineWidth); //--- Set width
   ObjectSetInteger(0, name, OBJPROP_BACK, true); //--- Set to background
}

//+------------------------------------------------------------------+
//| Create dashboard label                                           |
//+------------------------------------------------------------------+
void LABEL(string labelName, string fontName, int fontSize, int xPosition, int yPosition, color textColor, int corner, string labelText) {
   if (ObjectFind(0, labelName) < 0) {            //--- Check if label exists
      ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0); //--- Create label
   }
   ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); //--- Set label text
   ObjectSetString(0, labelName, OBJPROP_FONT, fontName); //--- Set font
   ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetInteger(0, labelName, OBJPROP_COLOR, textColor); //--- Set text color
   ObjectSetInteger(0, labelName, OBJPROP_CORNER, corner); //--- Set corner
   ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, xPosition); //--- Set x position
   ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, yPosition); //--- Set y position
}

Для создания визуальных элементов программы мы разрабатываем функцию "MakeLine", которая рисует горизонтальную линию на заданном значении "price" для обозначения уровня поддержки или сопротивления, задавая ей имя "level", проверяя ее существование с помощью ObjectFind, перемещая ее с помощью ObjectMove в текущее время бара из iTime, если она найдена, или создавая ее с помощью ObjectCreate как OBJ_HLINE, и устанавливая "OBJPROP_COLOR" в "lineColor", OBJPROP_STYLE в "STYLE_SOLID", "OBJPROP_WIDTH" в "lineWidth", а "OBJPROP_BACK" в true, используя ObjectSetInteger для размещения фона.

Затем мы переходим к реализации функции "LABEL", которая будет создавать или обновлять метки дашборда, проверяя, существует ли "labelName", создавая объект OBJ_LABEL с помощью функции "ObjectCreate", если нет, и устанавливая свойства с помощью функции "ObjectSetString" для "OBJPROP_TEXT" в значение "labelText" и "OBJPROP_FONT" в значение "fontName", а также "ObjectSetInteger" для "OBJPROP_FONTSIZE" в значение "fontSize", "OBJPROP_COLOR" в значение "textColor", OBJPROP_CORNER в значение "corner", "OBJPROP_XDISTANCE" в значение "xPosition", а "OBJPROP_YDISTANCE" в значение y-позиции. Затем мы можем определить служебные функции индикатора, которые будем использовать.

//+------------------------------------------------------------------+
//| Calculate ATR indicator                                          |
//+------------------------------------------------------------------+
double MyiATR(string symbol, ENUM_TIMEFRAMES timeframe, int period, int shift) {
   int handle = iATR(symbol, timeframe, period);  //--- Create ATR handle
   if (handle == INVALID_HANDLE) return 0;        //--- Check invalid handle
   double buffer[1];                              //--- Declare buffer
   if (CopyBuffer(handle, 0, shift, 1, buffer) != 1) buffer[0] = 0; //--- Copy ATR value
   IndicatorRelease(handle);                      //--- Release handle
   return buffer[0];                              //--- Return ATR value
}

//+------------------------------------------------------------------+
//| Check bullish engulfing pattern                                  |
//+------------------------------------------------------------------+
bool BullishEngulfingExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) <= iClose(Symbol(), PERIOD_CURRENT, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) >= iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) >= 10 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 10 * _Point) { //--- Check bullish engulfing conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bullish harami pattern                                     |
//+------------------------------------------------------------------+
bool BullishHaramiExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 2) < iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 1) < iClose(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) > MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) > 4 * (iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1))) { //--- Check bullish harami conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check doji at bottom pattern                                     |
//+------------------------------------------------------------------+
bool DojiAtBottomExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 3) - iClose(Symbol(), PERIOD_CURRENT, 3) >= 8 * _Point && MathAbs(iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2)) <= 1 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 8 * _Point) { //--- Check doji at bottom conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check doji at top pattern                                        |
//+------------------------------------------------------------------+
bool DojiAtTopExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 3) - iOpen(Symbol(), PERIOD_CURRENT, 3) >= 8 * _Point && MathAbs(iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2)) <= 1 * _Point && iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1) >= 8 * _Point) { //--- Check doji at top conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bearish harami pattern                                     |
//+------------------------------------------------------------------+
bool BearishHaramiExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 2) > iClose(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 2) < iOpen(Symbol(), PERIOD_CURRENT, 1) && iClose(Symbol(), PERIOD_CURRENT, 2) > iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 1) > iClose(Symbol(), PERIOD_CURRENT, 1) && iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2) > MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2) > 4 * (iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1))) { //--- Check bearish harami conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check long up candle pattern                                     |
//+------------------------------------------------------------------+
bool LongUpCandleExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 2) < iClose(Symbol(), PERIOD_CURRENT, 2) && iHigh(Symbol(), PERIOD_CURRENT, 2) - iLow(Symbol(), PERIOD_CURRENT, 2) >= 40 * _Point && iHigh(Symbol(), PERIOD_CURRENT, 2) - iLow(Symbol(), PERIOD_CURRENT, 2) > 2.5 * MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) < iOpen(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1) > 10 * _Point) { //--- Check long up candle conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check long down candle pattern                                   |
//+------------------------------------------------------------------+
bool LongDownCandleExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) > iClose(Symbol(), PERIOD_CURRENT, 1) && iHigh(Symbol(), PERIOD_CURRENT, 1) - iLow(Symbol(), PERIOD_CURRENT, 1) >= 40 * _Point && iHigh(Symbol(), PERIOD_CURRENT, 1) - iLow(Symbol(), PERIOD_CURRENT, 1) > 2.5 * MyiATR(Symbol(), PERIOD_CURRENT, 14, 1)) { //--- Check long down candle conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bearish engulfing pattern                                  |
//+------------------------------------------------------------------+
bool BearishEngulfingExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) >= iClose(Symbol(), PERIOD_CURRENT, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) <= iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) >= 10 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 10 * _Point) { //--- Check bearish engulfing conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Calculate average range over 4 days                              |
//+------------------------------------------------------------------+
double AveRange4() {
   double rangeSum = 0;                           //--- Initialize range sum
   int count = 0;                                 //--- Initialize count
   int index = 1;                                 //--- Initialize index
   while (count < 4) {                            //--- Loop until 4 days
      MqlDateTime dateTime;                       //--- Declare datetime structure
      TimeToStruct(iTime(Symbol(), PERIOD_CURRENT, index), dateTime); //--- Convert time
      if (dateTime.day_of_week != 0) {            //--- Check non-Sunday
         rangeSum += iHigh(Symbol(), PERIOD_CURRENT, index) - iLow(Symbol(), PERIOD_CURRENT, index); //--- Add range
         count++;                                 //--- Increment count
      }
      index++;                                    //--- Increment index
   }
   return(rangeSum / 4.0);                        //--- Return average range
}

//+------------------------------------------------------------------+
//| Check buy pinbar                                                 |
//+------------------------------------------------------------------+
bool IsBuyPinbar() {
   double currentOpen, currentClose, currentHigh, currentLow; //--- Declare current candle variables
   double previousHigh, previousLow, previousClose, previousOpen; //--- Declare previous candle variables
   double currentRange, previousRange, currentHigherPart, currentHigherPart1; //--- Declare range variables
   currentOpen = iOpen(Symbol(), PERIOD_CURRENT, 1); //--- Get current open
   currentClose = iClose(Symbol(), PERIOD_CURRENT, 1); //--- Get current close
   currentHigh = iHigh(Symbol(), PERIOD_CURRENT, 0); //--- Get current high
   currentLow = iLow(Symbol(), PERIOD_CURRENT, 1); //--- Get current low
   previousOpen = iOpen(Symbol(), PERIOD_CURRENT, 2); //--- Get previous open
   previousClose = iClose(Symbol(), PERIOD_CURRENT, 2); //--- Get previous close
   previousHigh = iHigh(Symbol(), PERIOD_CURRENT, 2); //--- Get previous high
   previousLow = iLow(Symbol(), PERIOD_CURRENT, 2); //--- Get previous low
   currentRange = currentHigh - currentLow;       //--- Calculate current range
   previousRange = previousHigh - previousLow;    //--- Calculate previous range
   currentHigherPart = currentHigh - currentRange * 0.4; //--- Calculate higher part
   currentHigherPart1 = currentHigh - currentRange * 0.4; //--- Calculate higher part
   double averageDailyRange = AveRange4();        //--- Get average daily range
   if ((currentClose > currentHigherPart1 && currentOpen > currentHigherPart) && //--- Check close/open in higher third
       (currentRange > averageDailyRange * 0.5) && //--- Check pinbar size
       (currentLow + currentRange * 0.25 < previousLow)) { //--- Check nose length
      double lowArray[3];                         //--- Declare low array
      CopyLow(Symbol(), PERIOD_CURRENT, 3, 3, lowArray); //--- Copy low prices
      int minIndex = ArrayMinimum(lowArray);      //--- Find minimum low index
      if (lowArray[minIndex] > currentLow) return(true); //--- Confirm buy pinbar
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check sell pinbar                                                |
//+------------------------------------------------------------------+
bool IsSellPinbar() {
   double currentOpen, currentClose, currentHigh, currentLow; //--- Declare current candle variables
   double previousHigh, previousLow, previousClose, previousOpen; //--- Declare previous candle variables
   double currentRange, previousRange, currentLowerPart, currentLowerPart1; //--- Declare range variables
   currentOpen = iOpen(Symbol(), PERIOD_CURRENT, 1); //--- Get current open
   currentClose = iClose(Symbol(), PERIOD_CURRENT, 1); //--- Get current close
   currentHigh = iHigh(Symbol(), PERIOD_CURRENT, 1); //--- Get current high
   currentLow = iLow(Symbol(), PERIOD_CURRENT, 1); //--- Get current low
   previousOpen = iOpen(Symbol(), PERIOD_CURRENT, 2); //--- Get previous open
   previousClose = iClose(Symbol(), PERIOD_CURRENT, 2); //--- Get previous close
   previousHigh = iHigh(Symbol(), PERIOD_CURRENT, 2); //--- Get previous high
   previousLow = iLow(Symbol(), PERIOD_CURRENT, 2); //--- Get previous low
   currentRange = currentHigh - currentLow;       //--- Calculate current range
   previousRange = previousHigh - previousLow;    //--- Calculate previous range
   currentLowerPart = currentLow + currentRange * 0.4; //--- Calculate lower part
   currentLowerPart1 = currentLow + currentRange * 0.4; //--- Calculate lower part
   double averageDailyRange = AveRange4();        //--- Get average daily range
   if ((currentClose < currentLowerPart1 && currentOpen < currentLowerPart) && //--- Check close/open in lower third
       (currentRange > averageDailyRange * 0.5) && //--- Check pinbar size
       (currentHigh - currentRange * 0.25 > previousHigh)) { //--- Check nose length
      double highArray[3];                        //--- Declare high array
      CopyHigh(Symbol(), PERIOD_CURRENT, 3, 3, highArray); //--- Copy high prices
      int maxIndex = ArrayMaximum(highArray);     //--- Find maximum high index
      if (highArray[maxIndex] < currentHigh) return(true); //--- Confirm sell pinbar
   }
   return(false);                                 //--- Return false
}

Здесь мы реализуем функции для обнаружения свечных паттернов и расчета среднего истинного диапазона (ATR) для нашей системы. Сначала создадим функцию "MyiATR", которая вычисляет ATR, создавая хэндл с помощью функции iATR для данного инструмента, таймфрейма и периода, возвращая 0, если хэндл недействителен, копируя значение ATR в буфер с помощью CopyBuffer, освобождая хэндл с помощью IndicatorRelease и возвращая значение ATR.

Затем перейдём к реализации функций обнаружения свечных паттернов: Функция "BullishEngulfingExists" проверяет, поглощает ли текущая свеча предыдущую медвежью свечу со значительными размерами тела; "BullishHaramiExists" идентифицирует небольшую бычью свечу внутри более крупной медвежьей свечи, используя "MyiATR" для сравнения размеров; "DojiAtBottomExists" обнаруживает доджи между медвежьей и бычьей свечой для паттерна утренняя звезда; "DojiAtTopExists" идентифицирует доджи между бычьей и медвежьей свечой для паттерна вечерняя звезда; "BearishHaramiExists" проверяет наличие небольшой медвежьей свечи внутри более крупной бычьей свечи; "LongUpCandleExists" подтверждает сильную бычью свечу, за которой следует медвежья, используя ATR; "LongDownCandleExists" обнаруживает сильную медвежью свечу; и "BearishEngulfingExists" подтверждает, что медвежья свеча поглощает бычью.

Наконец, мы реализуем функции "IsBuyPinbar" и "IsSellPinbar", которые определяют пин-бары, проверяя, находятся ли цена закрытия и открытия текущей свечи в верхней или нижней трети ее диапазона, превышает ли диапазон половину среднего дневного диапазона, рассчитанного с помощью функции "AveRange4" (которая усредняет диапазон максимума и минимума за четыре дня, кроме воскресенья), и выходит ли носик пин-бара за пределы минимума или максимума предыдущей свечи, что подтверждается сравнением недавних минимумов или максимумов с функциями CopyLow или CopyHigh и "ArrayMinimum" или ArrayMaximum функциями. Затем можно определить некоторые функции, чтобы получить тип сигнала для отображения, а также взвешенную цену для управления позицией.

//+------------------------------------------------------------------+
//| Analyze candlestick patterns                                     |
//+------------------------------------------------------------------+
string CandleStick_Analyzer() {
   string candlePattern, comment1 = "", comment2 = "", comment3 = ""; //--- Initialize pattern strings
   string comment4 = "", comment5 = "", comment6 = "", comment7 = ""; //--- Initialize pattern strings
   string comment8 = "", comment9 = "";                               //--- Initialize pattern strings
   if (BullishEngulfingExists()) comment1 = " Bullish Engulfing ";    //--- Check bullish engulfing
   if (BullishHaramiExists()) comment2 = " Bullish Harami ";          //--- Check bullish harami
   if (LongUpCandleExists()) comment3 = " Bullish LongUp ";           //--- Check long up candle
   if (DojiAtBottomExists()) comment4 = " MorningStar Doji ";         //--- Check morning star doji
   if (DojiAtTopExists()) comment5 = " EveningStar Doji ";            //--- Check evening star doji
   if (BearishHaramiExists()) comment6 = " Bearish Harami ";          //--- Check bearish harami
   if (BearishEngulfingExists()) comment7 = " Bearish Engulfing ";    //--- Check bearish engulfing
   if (LongDownCandleExists()) comment8 = " Bearish LongDown ";       //--- Check long down candle
   candlePattern = comment1 + comment2 + comment3 + comment4 + comment5 + comment6 + comment7 + comment8 + comment9; //--- Combine patterns
   return(candlePattern);                                             //--- Return combined pattern
}

//+------------------------------------------------------------------+
//| Calculate average price for order type                           |
//+------------------------------------------------------------------+
double rata_price(int orderType) {
   double totalVolume = 0;                        //--- Initialize total volume
   double weightedOpenSum = 0;                    //--- Initialize weighted open sum
   double averagePrice = 0;                       //--- Initialize average price
   for (int positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber && (PositionGetInteger(POSITION_TYPE) == orderType)) { //--- Check position match
         totalVolume += PositionGetDouble(POSITION_VOLUME); //--- Add volume
         weightedOpenSum += (PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN)); //--- Add weighted open
      }
   }
   if (totalVolume != 0) {                        //--- Check non-zero volume
      averagePrice = weightedOpenSum / totalVolume; //--- Calculate average price
   }
   return(averagePrice);                          //--- Return average price
}

Для более эффективного управления позициями создадим функцию "CandleStick_Analyzer", которая инициализирует строковые переменные, такие как с "comment1" по "comment9", пустыми, проверяет наличие свечных паттернов с помощью уже определенных нами функций, таких как "BullishEngulfingExists", присваивает описательные строки (например, "Bullish Engulfing") соответствующим переменным в случае обнаружения паттернов и объединяет их в "candlePattern", чтобы вернуть объединенную строку обнаруженных паттернов для отображения на дашборде.

Затем перейдем к реализации функции "rata_price", которая вычисляет средневзвешенную цену для указанного "orderType" (покупка или продажа), инициализируя "totalVolume" и "weightedOpenSum" нулями, перебирая "PositionsTotal" для суммирования POSITION_VOLUME и произведения "POSITION_VOLUME" и POSITION_PRICE_OPEN для позиций, соответствующих параметрам "Symbol", "magicNumber" и "orderType", используя "PositionGetTicket", PositionGetString и "PositionGetInteger", и вычисляя "averagePrice" как "weightedOpenSum / totalVolume", если "totalVolume" не равен нулю, возвращая результат. Это обеспечивает критически важный анализ паттернов для торговых сигналов и точные расчеты средней цены для усреднения и корректировки тейк-профита. Что касается позиций, сначала нам нужно будет получить их показатели. Для этого определим логику.

//+------------------------------------------------------------------+
//| Calculate position metrics                                       |
//+------------------------------------------------------------------+
void calculatePositionMetrics() {
   buyCount = 0;                                  //--- Reset buy count
   currentBuyLot = 0;                             //--- Reset current buy lot
   totalBuyLots = 0;                              //--- Reset total buy lots
   sellCount = 0;                                 //--- Reset sell count
   currentSellLot = 0;                            //--- Reset current sell lot
   totalSellLots = 0;                             //--- Reset total sell lots
   totalSum = 0;                                  //--- Reset total sum
   totalSwap = 0;                                 //--- Reset total swap
   buyProfit = 0;                                 //--- Reset buy profit
   sellProfit = 0;                                //--- Reset sell profit
   buyWeightedSum = 0;                            //--- Reset buy weighted sum
   sellWeightedSum = 0;                           //--- Reset sell weighted sum
   buyBreakEvenPrice = 0;                         //--- Reset buy breakeven price
   sellBreakEvenPrice = 0;                        //--- Reset sell breakeven price
   minBuyLot = 9999;                              //--- Initialize min buy lot
   minSellLot = 9999;                             //--- Initialize min sell lot
   maxSellPrice = 0;                              //--- Initialize max sell price
   minBuyPrice = 999999999;                       //--- Initialize min buy price
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyCount++;                              //--- Increment buy count
         totalOperations++;                       //--- Increment total operations
         currentBuyLot = PositionGetDouble(POSITION_VOLUME); //--- Set current buy lot
         buyProfit += PositionGetDouble(POSITION_PROFIT); //--- Add buy profit
         totalBuyLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total buy lots
         minBuyLot = MathMin(minBuyLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min buy lot
         buyWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         minBuyPrice = MathMin(minBuyPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update min buy price
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellCount++;                             //--- Increment sell count
         totalOperations++;                       //--- Increment total operations
         currentSellLot = PositionGetDouble(POSITION_VOLUME); //--- Set current sell lot
         sellProfit += PositionGetDouble(POSITION_PROFIT); //--- Add sell profit
         totalSellLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total sell lots
         minSellLot = MathMin(minSellLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min sell lot
         sellWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         maxSellPrice = MathMax(maxSellPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update max sell price
      }
   }
   if (totalBuyLots > 0) {                        //--- Check buy lots
      buyBreakEvenPrice = buyWeightedSum / totalBuyLots; //--- Calculate buy breakeven
   }
   if (totalSellLots > 0) {                       //--- Check sell lots
      sellBreakEvenPrice = sellWeightedSum / totalSellLots; //--- Calculate sell breakeven
   }
}

Для вычисления основных показателей, необходимых для эффективного управления несколькими позициями, мы используем функцию "calculatePositionMetrics". Во-первых, для точного отслеживания обнулим ключевые переменные или установим их соответствующие начальные значения. Затем перейдем к перебору всех позиций, используя PositionsTotal, получим тикет каждой позиции с помощью PositionGetTicket, пропускаем недействительные тикеты или несовпадающие инструменты с помощью PositionGetString, а для позиций на покупку (POSITION_TYPE_BUY) увеличим "buyCount" и "totalOperations", установим "currentBuyLot", прибавим "POSITION_PROFIT" к "buyProfit" и "POSITION_VOLUME" к "totalBuyLots", обновим "minBuyLot" с помощью MathMin, прибавим взвешенную цену открытия к "buyWeightedSum" и обновим "minBuyPrice". Для позиций на продажу (POSITION_TYPE_SELL) выполним аналогичные обновления для показателей позиций на продажу. Наконец, вычислим "buyBreakEvenPrice" как "buyWeightedSum / totalBuyLots", если "totalBuyLots" положительное, и "sellBreakEvenPrice" как "sellWeightedSum / totalSellLots", если "totalSellLots" положительное, обеспечивая средневзвешенные цены для управления безубыточностью и гарантируем точное отслеживание показателей позиций для усреднения и обеспечения контроля рисков. С помощью этих функций мы готовы приступить к логике открытия позиций. Мы сделаем это в OnTick обработчике.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime previousBarTime = 0;           //--- Store previous bar time
   if (previousBarTime != iTime(Symbol(), PERIOD_CURRENT, 0)) { //--- Check new bar
      previousBarTime = iTime(Symbol(), PERIOD_CURRENT, 0); //--- Update previous bar time
      ChartRedraw(0);                             //--- Redraw chart
   } else {
      return;                                     //--- Exit if not new bar
   }
   if (iVolume(Symbol(), PERIOD_H4, 0) > iVolume(Symbol(), PERIOD_H4, 1)) return; //--- Exit if volume increased
   double supportResistanceLevel = NormalizeDouble(iClose(Symbol(), PERIOD_H4, 1), _Digits); //--- Get support/resistance level
   ObjectDelete(0, "level");                      //--- Delete existing level line
   MakeLine(supportResistanceLevel);              //--- Draw support/resistance line
   if (SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) > 150) return; //--- Exit if spread too high
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         totalSellPositions++;                    //--- Increment sell count
      }
   }
}

В обработчике OnTick реализуем начальную логику системы усреднения на основе пин-баров для управления торговыми решениями и визуальным обновлением каждого нового бара. Сначала проверим наличие нового бара, сравнивая "previousBarTime" (статическое значение, инициализированное нулем) со временем текущего бара из iTime для текущего инструмента и периода при нулевом сдвиге, обновим "previousBarTime" и вызовем ChartRedraw, если обнаружен новый бар, или завершаем работу, если нет.

Затем прерываем дальнейшую обработку, если объем текущего бара H4, рассчитанный по iVolume, превышает объем предыдущего бара, что автор использует как фильтр неблагоприятных условий. Далее, используя iClose и NormalizeDouble, вычислим уровень поддержки/сопротивления как нормализованную цену закрытия предыдущего бара на H4. Удалим любую существующую линию "level" с помощью ObjectDelete и нарисуем новую горизонтальную линию с помощью "MakeLine" на этом уровне. Наконец, проверим, превышает ли спред, полученный из SymbolInfoInteger, 150 пунктов, и в этом случае прекращаем дальнейшую обработку. А также подсчитываем открытые позиции, перебирая PositionsTotal, используя функцию "PositionGetTicket" для получения тикетов, пропускаем недействительные или несовпадающие позиции по инструменту и "magicNumber", и увеличиваем значения "totalBuyPositions" или "totalSellPositions" для позиций на покупку или продажу, определенных с помощью PositionGetInteger функции. Такая первоначальная настройка гарантирует, что советник обрабатывает сделки только на новых барах с благоприятными условиями и поддерживает обновленную визуальную привязку. После компиляции получаем следующий результат.

SUPPORT RESISTANCE LEVEL MARKER

На изображении видно, что мы динамически отмечаем линии поддержки и сопротивления на графике. Теперь нам нужно перейти к динамическому добавлению позиций.

if (CheckMarketBuyOrders() < 70 && CheckMarketSellOrders() < 70) { //--- Check order limits
   if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == DISABLED) { //--- Check buy condition
      if (IsBuyPinbar() && totalBuyPositions < maxOrders && (isTradingAllowed() || totalBuyPositions > 0)) { //--- Check buy pinbar and limits
         double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, _Digits); //--- Calculate buy stop loss
         double buyTakeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + takeProfitPips * normalizedPoint, _Digits); //--- Calculate buy take profit
         if (AccountFreeMarginCheck(Symbol(), ORDER_TYPE_BUY, GetLots()) > 0) { //--- Check margin
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_ASK), buyStopLoss, buyTakeProfit, orderComment); //--- Open buy position
            if (useAutoTakeProfit) {             //--- Check auto take profit
               ModifyTP(ORDER_TYPE_BUY, rata_price(ORDER_TYPE_BUY) + takeProfitPips * normalizedPoint); //--- Modify take profit
            }
            CloseSell();                         //--- Close sell positions
         }
      }
   }
   if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == DISABLED) { //--- Check sell condition
      if (IsSellPinbar() && totalSellPositions < maxOrders && (isTradingAllowed() || totalSellPositions > 0)) { //--- Check sell pinbar and limits
         double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, _Digits); //--- Calculate sell stop loss
         double sellTakeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - takeProfitPips * normalizedPoint, _Digits); //--- Calculate sell take profit
         if (AccountFreeMarginCheck(Symbol(), ORDER_TYPE_SELL, GetLots()) > 0) { //--- Check margin
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_BID), sellStopLoss, sellTakeProfit, orderComment); //--- Open sell position
            if (useAutoTakeProfit) {             //--- Check auto take profit
               ModifyTP(ORDER_TYPE_SELL, rata_price(ORDER_TYPE_SELL) - takeProfitPips * normalizedPoint); //--- Modify take profit
            }
            CloseBuy();                          //--- Close buy positions
         }
      }
   }
}
if (CountTrades() == 0) {                       //--- Check no trades
   if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == ENABLED) { //--- Check buy signal mode
      if (IsBuyPinbar() && CountTrades() < maxOrders) { //--- Check buy pinbar and limit
         obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_ASK), SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + (takeProfitPips * normalizedPoint), orderComment); //--- Open buy position
      }
   }
}
if (CountTrades() == 0) {                       //--- Check no trades
   if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == ENABLED) { //--- Check sell signal mode
      if (IsSellPinbar() && CountTrades() < maxOrders) { //--- Check sell pinbar and limit
         obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_BID), SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_BID) - (takeProfitPips * normalizedPoint), orderComment); //--- Open sell position
      }
   }
}

Продолжим реализацию функции тиков, добавляя логику для открытия новых позиций на основе сигналов пин-баров и рыночных условий. Сначала проверим, находятся ли открытые ордера на покупку и продажу ниже 70, используя функции "CheckMarketBuyOrders" и "CheckMarketSellOrders", чтобы убедиться, что советник не превышает практически допустимые пределы. Затем, если параметр "useSignalMode" установлен в значение "DISABLED", мы оцениваем условия покупки: когда "supportResistanceLevel" превышает текущую цену открытия по iOpen, обнаруживается пин-бар на покупку с помощью параметра "IsBuyPinbar", "totalBuyPositions" находится ниже "maxOrders", и торговля разрешена с помощью параметра "isTradingAllowed" или существуют существующие ордера на покупку, мы рассчитываем "buyStopLoss" и "buyTakeProfit" с помощью SymbolInfoDouble , используя значения "stopLossPips" и "takeProfitPips", скорректированные с помощью "normalizedPoint", проверяем маржу с помощью "AccountFreeMarginCheck", открываем позицию на покупку с помощью "obj_Trade".

Открываем позицию через "PositionOpen", используя "GetLots", изменим тейк-профит с помощью "ModifyTP", если "useAutoTakeProfit" равно true, и закроем позиции на продажу с помощью "CloseSell"; аналогичная логика применяется для условий продажи, когда "supportResistanceLevel" ниже цены открытия, с использованием "IsSellPinbar". Далее, если сделок нет ("CountTrades" равно 0), а "useSignalMode" установлен в "ENABLED", открываем позицию на покупку по пин-бару с параметрами "IsBuyPinbar" и "CountTrades" ниже "maxOrders", используя "obj_Trade.PositionOpen" с рассчитанными стоп-лоссом и тейк-профитом. Аналогично для позиций на продажу с параметром "IsSellPinbar", обеспечивая открытие позиций советником на основе сигналов пин-бара на ключевых уровнях с надлежащим управлением рисками. После компиляции получаем следующий результат.

CONFIRMED SIGNAL

Теперь, когда сигналы подтверждаются и позиции открываются, нам необходимо управлять этими сигналами. Мы определим для этого несколько функций.

//+------------------------------------------------------------------+
//| Update stop loss and take profit                                 |
//+------------------------------------------------------------------+
void updateStopLossTakeProfit() {
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);           //--- Get position ticket
      if (ticket == 0) continue;                     //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         double buyTakeProfitLevel = (buyBreakEvenPrice + takeProfitPips * _Point) * (takeProfitPips > 0); //--- Calculate buy take profit
         double buyStopLossLevel = PositionGetDouble(POSITION_SL); //--- Get current stop loss
         if (slBreakevenMinus > 0) {                 //--- Check breakeven adjustment
            buyStopLossLevel = (buyBreakEvenPrice - slBreakevenMinus * _Point); //--- Set breakeven stop loss
         }
         if (buyCount == 1) {                        //--- Check single buy position
            buyTakeProfitLevel = NormalizePrice(PositionGetDouble(POSITION_PRICE_OPEN) + takeProfitPips * _Point) * (takeProfitPips > 0); //--- Set take profit
            if (laterUseSL > 0) {                    //--- Check unused stop loss
               buyStopLossLevel = (PositionGetDouble(POSITION_PRICE_OPEN) - laterUseSL * _Point); //--- Set stop loss
            }
         }
         buyTakeProfitLevel = NormalizePrice(buyTakeProfitLevel); //--- Normalize take profit
         buyStopLossLevel = NormalizePrice(buyStopLossLevel); //--- Normalize stop loss
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) >= buyTakeProfitLevel && buyTakeProfitLevel > 0) { //--- Check take profit hit
            obj_Trade.PositionClose(ticket);         //--- Close position
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) <= buyStopLossLevel) { //--- Check stop loss hit
            obj_Trade.PositionClose(ticket);         //--- Close position
         }
         if (NormalizePrice(PositionGetDouble(POSITION_TP)) != buyTakeProfitLevel || NormalizePrice(PositionGetDouble(POSITION_SL)) != buyStopLossLevel) { //--- Check modification needed
            obj_Trade.PositionModify(ticket, buyStopLossLevel, buyTakeProfitLevel); //--- Modify position
         }
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         double sellTakeProfitLevel = (sellBreakEvenPrice - takeProfitPips * _Point) * (takeProfitPips > 0); //--- Calculate sell take profit
         double sellStopLossLevel = PositionGetDouble(POSITION_SL); //--- Get current stop loss
         if (slBreakevenMinus > 0) {                //--- Check breakeven adjustment
            sellStopLossLevel = (sellBreakEvenPrice + slBreakevenMinus * _Point); //--- Set breakeven stop loss
         }
         if (sellCount == 1) {                      //--- Check single sell position
            sellTakeProfitLevel = (PositionGetDouble(POSITION_PRICE_OPEN) - takeProfitPips * _Point) * (takeProfitPips > 0); //--- Set take profit
            if (laterUseSL > 0) {                   //--- Check unused stop loss
               sellStopLossLevel = (PositionGetDouble(POSITION_PRICE_OPEN) + laterUseSL * _Point); //--- Set stop loss
            }
         }
         sellTakeProfitLevel = NormalizePrice(sellTakeProfitLevel); //--- Normalize take profit
         sellStopLossLevel = NormalizePrice(sellStopLossLevel); //--- Normalize stop loss
         if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) <= sellTakeProfitLevel) { //--- Check take profit hit
            obj_Trade.PositionClose(ticket);        //--- Close position
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= sellStopLossLevel && sellStopLossLevel > 0) { //--- Check stop loss hit
            obj_Trade.PositionClose(ticket);        //--- Close position
         }
         if (NormalizePrice(PositionGetDouble(POSITION_TP)) != sellTakeProfitLevel || NormalizePrice(PositionGetDouble(POSITION_SL)) != sellStopLossLevel) { //--- Check modification needed
            obj_Trade.PositionModify(ticket, sellStopLossLevel, sellTakeProfitLevel); //--- Modify position
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Add averaging order                                              |
//+------------------------------------------------------------------+
void addAveragingOrder() {
   int positionIndex = 0;                         //--- Initialize position index
   double lastOpenPrice = 0;                      //--- Initialize last open price
   double lastLotSize = 0;                        //--- Initialize last lot size
   bool isLastBuy = false;                        //--- Initialize buy flag
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   double supportResistanceLevel = iClose(Symbol(), PERIOD_H4, 1); //--- Get support/resistance level
   for (positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice > PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check lower price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = true;                        //--- Set buy flag
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice < PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check higher price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = false;                       //--- Clear buy flag
         totalSellPositions++;                    //--- Increment sell count
      }
   }
   if (isLastBuy) {                               //--- Check buy position
      if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check buy condition
         if (IsBuyPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_BID) <= lastOpenPrice - (orderDistancePips * _Point)) { //--- Check buy pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_ASK), SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + (takeProfitPips * normalizedPoint), orderComment); //--- Open buy position
            isLastBuy = false;                    //--- Clear buy flag
            return;                               //--- Exit function
         }
      }
   } else if (!isLastBuy) {                       //--- Check sell position
      if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check sell condition
         if (IsSellPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= lastOpenPrice + (orderDistancePips * _Point)) { //--- Check sell pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_BID), SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_BID) - (takeProfitPips * normalizedPoint), orderComment); //--- Open sell position
            return;                               //--- Exit function
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Add averaging order with auto take profit                        |
//+------------------------------------------------------------------+
void addAveragingOrderWithAutoTP() {
   int positionIndex = 0;                         //--- Initialize position index
   double lastOpenPrice = 0;                      //--- Initialize last open price
   double lastLotSize = 0;                        //--- Initialize last lot size
   bool isLastBuy = false;                        //--- Initialize buy flag
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   double supportResistanceLevel = iClose(Symbol(), PERIOD_H4, 1); //--- Get support/resistance level
   for (positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice > PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check lower price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = true;                        //--- Set buy flag
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice < PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check higher price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = false;                       //--- Clear buy flag
         totalSellPositions++;                    //--- Increment sell count
      }
   }
   if (isLastBuy) {                               //--- Check buy position
      if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check buy condition
         if (IsBuyPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_BID) <= lastOpenPrice - (orderDistancePips * _Point)) { //--- Check buy pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_ASK), 0, 0, orderComment); //--- Open buy position
            calculatePositionMetrics();           //--- Calculate position metrics
            updateStopLossTakeProfit();           //--- Update stop loss and take profit
            isLastBuy = false;                    //--- Clear buy flag
            return;                               //--- Exit function
         }
      }
   } else if (!isLastBuy) {                       //--- Check sell position
      if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check sell condition
         if (IsSellPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= lastOpenPrice + (orderDistancePips * _Point)) { //--- Check sell pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_BID), 0, 0, orderComment); //--- Open sell position
            calculatePositionMetrics();           //--- Calculate position metrics
            updateStopLossTakeProfit();           //--- Update stop loss and take profit
            return;                               //--- Exit function
         }
      }
   }
}

Здесь мы реализуем функции "updateStopLossTakeProfit" и "addAveragingOrder", а также "addAveragingOrderWithAutoTP", для управления стоп-лоссами, тейк-профитами и усреднением позиций, обеспечивая динамическую корректировку позиций. Сначала разработаем функцию "updateStopLossTakeProfit", которая перебирает все позиции. Для позиций на покупку (POSITION_TYPE_BUY) вычислим "buyTakeProfitLevel" на основе "buyBreakEvenPrice" плюс "takeProfitPips * _Point", если значение "takeProfitPips" положительное, получаем текущий стоп-лосс с помощью PositionGetDouble, корректируем его до "buyBreakEvenPrice - slBreakevenMinus * _Point", если значение "slBreakevenMinus" положительное, или для отдельных позиций ("buyCount == 1"), установим тейк-профит и стоп-лосс на основе POSITION_PRICE_OPEN, скорректированного с учетом тейк-профита и стоп-лосса, нормализуем оба уровня с помощью "NormalizePrice", закрываем позиции с помощью "obj_Trade.PositionClose", если цена Bid достигает тейк-профита или стоп-лосса, и изменяем позиции с помощью "obj_Trade.PositionModify", если уровни различаются. Аналогичная логика применяется и к позициям на продажу с использованием параметров "sellBreakEvenPrice" и цены Ask.

Затем перейдём к реализации функции "addAveragingOrder", которая отслеживает последнюю позицию, перебираем PositionsTotal, обновляем "lastOpenPrice" до самой низкой цены покупки или самой высокой цены продажи и "lastLotSize" до наибольшего объема, устанавливаем значение "isLastBuy" соответствующим образом. Для позиций на покупку, если "supportResistanceLevel" превышает текущую цену открытия и обнаружен пин-бар на покупку с помощью "IsBuyPinbar", а цена Bid находится ниже "lastOpenPrice" на "orderDistancePips * _Point", откроем позицию на покупку с помощью "obj_Trade.PositionOpen", используя размер лота "lastLotSize * lotMultiplier", нормализованного с помощью "fnGetLotDigit", с рассчитанными стоп-лоссом и тейк-профитом, и сбрасываем "isLastBuy". Для позиций на продажу проверяем, находится ли цена Ask выше "lastOpenPrice" на "orderDistancePips * _Point", и открываем позицию на продажу аналогичным образом.

Наконец, реализуем функцию "addAveragingOrderWithAutoTP", которая следует той же логике, что и "addAveragingOrder", но открывает позиции без начального стоп-лосса или тейк-профита (установленных на 0), вызывает "calculatePositionMetrics" для обновления таких показателей, как "buyBreakEvenPrice", и вызывает "updateStopLossTakeProfit" для установки уровней безубыточности, обеспечивая динамическую корректировку для усредняющих сделок. Теперь можно вызывать эти функции в логике обработки тиков, чтобы они вступили в силу.

if (useSignalMode == ENABLED && CountTradesBuy() >= 1 && CountTradesBuy() < maxOrders && useAutoTakeProfit == false) { //--- Check buy averaging
   addAveragingOrder();                        //--- Add buy averaging order
}
if (useSignalMode == ENABLED && CountTradesSell() >= 1 && CountTradesSell() < maxOrders && useAutoTakeProfit == false) { //--- Check sell averaging
   addAveragingOrder();                        //--- Add sell averaging order
}
if (useSignalMode == ENABLED && CountTradesBuy() >= 1 && CountTradesBuy() < maxOrders && useAutoTakeProfit == true) { //--- Check buy averaging with auto TP
   addAveragingOrderWithAutoTP();              //--- Add buy averaging order with auto TP
}
if (useSignalMode == ENABLED && CountTradesSell() >= 1 && CountTradesSell() < maxOrders && useAutoTakeProfit == true) { //--- Check sell averaging with auto TP
   addAveragingOrderWithAutoTP();              //--- Add sell averaging order with auto TP
}

Завершим реализацию логики тиков добавлением логики для обработки усреднения сделок при определенных условиях, что расширяет возможности советника по динамическому наращиванию позиций. Во-первых, если параметр "useSignalMode" установлен в значение "ENABLED", мы проверяем, существует ли хотя бы одна позиция на покупку с помощью "CountTradesBuy", и количество позиций на покупку меньше значения "maxOrders". Если "useAutoTakeProfit" равно false, вызовем функцию "addAveragingOrder" для открытия дополнительной позиции на покупку на основе критериев обнаружения пин-баров и ценового расстояния, используя умноженный размер лота.

Затем перейдем к применению той же логики для позиций на продажу, проверяя значение "CountTradesSell" и вызывая функцию "addAveragingOrder", если "useAutoTakeProfit" имеет значение false, чтобы добавить позицию на продажу при аналогичных условиях. Далее, для позиций на покупку, когда параметр "useAutoTakeProfit" имеет значение true, вызовем функцию "addAveragingOrderWithAutoTP", чтобы открыть позицию на покупку без первоначального стоп-лосса или тейк-профита. После этого обновим метрики и скорректируем уровни, основанные на точке безубыточности. Наконец, повторим эти действия для позиций на продажу, когда "useAutoTakeProfit" имеет значение true, вызовем "addAveragingOrderWithAutoTP", чтобы добавить позицию на продажу с динамической корректировкой стоп-лосса и тейк-профита. Эта логика обеспечит эффективное управление усреднением сделок в сигнальном режиме советником, адаптируясь к рыночным движениям. После компиляции получаем следующий результат.

AVERAGING SAMPLE

Теперь, когда мы добавили опцию усреднения, осталось только добавить логику трейлинг-стопа для управления рисками. Для точного контроля рисков эту логику необходимо будет выполнять на каждом тике, поэтому мы добавим её вне логики ограничения баров.

double setPointValue = normalizedPoint;         //--- Set point value for calculations
if (useTrailingStop && trailingStartPips > 0 && breakevenPips < trailingStartPips) { //--- Check trailing stop conditions
   double averageBuyPrice = rata_price(ORDER_TYPE_BUY); //--- Calculate average buy price
   double trailingReference = 0;                //--- Initialize trailing reference
   for (int iTrade = 0; iTrade < PositionsTotal(); iTrade++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(iTrade); //--- Get position ticket
      if (ticket == 0) continue;                //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (useAutoTakeProfit) {               //--- Check auto take profit
            trailingReference = averageBuyPrice; //--- Use average buy price
         } else {                               //--- Use open price
            trailingReference = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set open price
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingReference > trailingStartPips * setPointValue) { //--- Check trailing condition
            if (SymbolInfoDouble(_Symbol, SYMBOL_BID) - ((trailingStartPips - breakevenPips) * setPointValue) > PositionGetDouble(POSITION_SL)) { //--- Check stop loss adjustment
               obj_Trade.PositionModify(ticket, SymbolInfoDouble(_Symbol, SYMBOL_BID) - ((trailingStartPips - breakevenPips) * setPointValue), PositionGetDouble(POSITION_TP)); //--- Modify position
            }
         }
      }
   }
   double averageSellPrice = rata_price(ORDER_TYPE_SELL); //--- Calculate average sell price
   for (int iTrade2 = 0; iTrade2 < PositionsTotal(); iTrade2++) { //--- Iterate through positions
      ulong ticket2 = PositionGetTicket(iTrade2); //--- Get position ticket
      if (ticket2 == 0) continue;               //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (useAutoTakeProfit) {               //--- Check auto take profit
            trailingReference = averageSellPrice; //--- Use average sell price
         } else {                               //--- Use open price
            trailingReference = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set open price
         }
         if (trailingReference - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > trailingStartPips * setPointValue) { //--- Check trailing condition
            if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) + ((trailingStartPips - breakevenPips) * setPointValue) < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0) { //--- Check stop loss adjustment
               obj_Trade.PositionModify(ticket2, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + ((trailingStartPips - breakevenPips) * setPointValue), PositionGetDouble(POSITION_TP)); //--- Modify position
            }
         }
      }
   }
}

Мы реализуем логику трейлинг-стопа, сначала устанавливая значение параметра "setPointValue" равным "normalizedPoint" для обеспечения согласованности расчетов цены, а затем проверяя, истинно ли значение параметра "useTrailingStop", положительно ли значение параметра "trailingStartPips" и меньше ли значение параметра "breakevenPips" значения "trailingStartPips", чтобы гарантировать корректность условий трейлинга. Затем перейдем к обработке позиций на покупку, вычисляя "averageBuyPrice" с помощью "rata_price" для ORDER_TYPE_BUY, перебирая все позиции, чтобы получить действительные тикеты позиций на покупку, соответствующие "Symbol" и "magicNumber". Установим "trailingReference" в значение "averageBuyPrice", если "useAutoTakeProfit" истинно, или в значение "POSITION_PRICE_OPEN" в противном случае, и изменяем стоп-лосс с помощью "obj_Trade.PositionModify" на SYMBOL_BID - (trailingStartPips - breakevenPips) * setPointValue", если цена Bid превышает "trailingReference" на "trailingStartPips * setPointValue", а новый стоп-лосс выше текущего.

Далее применим аналогичную логику для позиций на продажу, вычислим "averageSellPrice" с помощью "rata_price" для "ORDER_TYPE_SELL", перебирая позиции. Установим "trailingReference" равным "averageSellPrice" или POSITION_PRICE_OPEN и изменим стоп-лосс на "SYMBOL_ASK + (trailingStartPips - breakevenPips) * setPointValue", если цена Ask ниже "trailingReference" на "trailingStartPips * setPointValue", а новый стоп-лосс ниже или не установлен. Наконец, обеспечим сохранение существующего уровня тейк-профита при внесении изменений с помощью функции "PositionGetDouble(POSITION_TP)" и вызовем ChartRedraw в вызывающей функции для обновления графика. После компиляции получаем следующий результат.

До трейлинг-стопа:

BEFORE TRAILING STOP

После трейлинг-стопа:

AFTER TRAILING STOP

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

//+------------------------------------------------------------------+
//| Display dashboard information                                    |
//+------------------------------------------------------------------+
void Display_Info() {
   buyCount = 0;                                  //--- Reset buy count
   currentBuyLot = 0;                             //--- Reset current buy lot
   totalBuyLots = 0;                              //--- Reset total buy lots
   sellCount = 0;                                 //--- Reset sell count
   currentSellLot = 0;                            //--- Reset current sell lot
   totalSellLots = 0;                             //--- Reset total sell lots
   totalSum = 0;                                  //--- Reset total sum
   totalSwap = 0;                                 //--- Reset total swap
   buyProfit = 0;                                 //--- Reset buy profit
   sellProfit = 0;                                //--- Reset sell profit
   buyWeightedSum = 0;                            //--- Reset buy weighted sum
   sellWeightedSum = 0;                           //--- Reset sell weighted sum
   buyBreakEvenPrice = 0;                         //--- Reset buy breakeven price
   sellBreakEvenPrice = 0;                        //--- Reset sell breakeven price
   minBuyLot = 9999;                              //--- Initialize min buy lot
   minSellLot = 9999;                             //--- Initialize min sell lot
   maxSellPrice = 0;                              //--- Initialize max sell price
   minBuyPrice = 999999999;                       //--- Initialize min buy price
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyCount++;                              //--- Increment buy count
         totalOperations++;                       //--- Increment total operations
         currentBuyLot = PositionGetDouble(POSITION_VOLUME); //--- Set current buy lot
         buyProfit += PositionGetDouble(POSITION_PROFIT); //--- Add buy profit
         totalBuyLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total buy lots
         minBuyLot = MathMin(minBuyLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min buy lot
         buyWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         minBuyPrice = MathMin(minBuyPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update min buy price
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellCount++;                             //--- Increment sell count
         totalOperations++;                       //--- Increment total operations
         currentSellLot = PositionGetDouble(POSITION_VOLUME); //--- Set current sell lot
         sellProfit += PositionGetDouble(POSITION_PROFIT); //--- Add sell profit
         totalSellLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total sell lots
         minSellLot = MathMin(minSellLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min sell lot
         sellWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         maxSellPrice = MathMax(maxSellPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update max sell price
      }
   }
   if (totalBuyLots > 0) {                        //--- Check buy lots
      buyBreakEvenPrice = buyWeightedSum / totalBuyLots; //--- Calculate buy breakeven
   }
   if (totalSellLots > 0) {                       //--- Check sell lots
      sellBreakEvenPrice = sellWeightedSum / totalSellLots; //--- Calculate sell breakeven
   }
   int minutesRemaining, secondsRemaining;        //--- Declare time variables
   minutesRemaining = (int)(PeriodSeconds() - (TimeCurrent() - iTime(Symbol(), PERIOD_CURRENT, 0))); //--- Calculate remaining time
   secondsRemaining = minutesRemaining % 60;      //--- Calculate seconds
   minutesRemaining = minutesRemaining / 60;      //--- Calculate minutes
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   string spreadPrefix = "", minutesPrefix = "", secondsPrefix = ""; //--- Initialize prefixes
   if (currentSpread < 10) spreadPrefix = "..";   //--- Set spread prefix for single digit
   else if (currentSpread < 100) spreadPrefix = "."; //--- Set spread prefix for double digit
   if (minutesRemaining < 10) minutesPrefix = "0"; //--- Set minutes prefix
   if (secondsRemaining < 10) secondsPrefix = "0"; //--- Set seconds prefix
   int blinkingColorIndex;                        //--- Declare blinking color index
   color equityColor = clrGreen;                  //--- Initialize equity color
   if (AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE) < 0.0) { //--- Check negative equity
      equityColor = clrRed;                       //--- Set equity color to red
   }
   color profitColor = (buyProfit + sellProfit >= 0) ? clrGreen : clrRed; //--- Set profit color
   MqlDateTime currentDateTime;                   //--- Declare datetime structure
   TimeToStruct(TimeCurrent(), currentDateTime);  //--- Convert current time
   if (currentDateTime.sec >= 0 && currentDateTime.sec < 10) { //--- Check first 10 seconds
      blinkingColorIndex = clrRed;                //--- Set red color
   }
   if (currentDateTime.sec >= 10 && currentDateTime.sec < 20) { //--- Check next 10 seconds
      blinkingColorIndex = clrOrange;             //--- Set orange color
   }
   if (currentDateTime.sec >= 20 && currentDateTime.sec < 30) { //--- Check next 10 seconds
      blinkingColorIndex = clrBlue;               //--- Set blue color
   }
   if (currentDateTime.sec >= 30 && currentDateTime.sec < 40) { //--- Check next 10 seconds
      blinkingColorIndex = clrDodgerBlue;         //--- Set dodger blue color
   }
   if (currentDateTime.sec >= 40 && currentDateTime.sec < 50) { //--- Check next 10 seconds
      blinkingColorIndex = clrYellow;             //--- Set yellow color
   }
   if (currentDateTime.sec >= 50 && currentDateTime.sec <= 59) { //--- Check last 10 seconds
      blinkingColorIndex = clrYellow;             //--- Set yellow color
   }
   if (ObjectFind(0, "DashboardBG") < 0) {        //--- Check dashboard background
      ObjectCreate(0, "DashboardBG", OBJ_RECTANGLE_LABEL, 0, 0, 0); //--- Create dashboard background
      ObjectSetInteger(0, "DashboardBG", OBJPROP_CORNER, 0); //--- Set corner
      ObjectSetInteger(0, "DashboardBG", OBJPROP_XDISTANCE, 100); //--- Set x distance
      ObjectSetInteger(0, "DashboardBG", OBJPROP_YDISTANCE, 20); //--- Set y distance
      ObjectSetInteger(0, "DashboardBG", OBJPROP_XSIZE, 260); //--- Set width
      ObjectSetInteger(0, "DashboardBG", OBJPROP_YSIZE, 300); //--- Set height
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BGCOLOR, clrLightGray); //--- Set background color
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set border type
      ObjectSetInteger(0, "DashboardBG", OBJPROP_COLOR, clrBlack); //--- Set border color
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BACK, false); //--- Set to foreground
   }
   if (ObjectFind(0, "CLOSE ALL") < 0) {          //--- Check close all button
      ObjectCreate(0, "CLOSE ALL", OBJ_BUTTON, 0, 0, 0); //--- Create close all button
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_CORNER, 0); //--- Set corner
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_XDISTANCE, 110); //--- Set x distance
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_YDISTANCE, 280); //--- Set y distance
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_XSIZE, 240); //--- Set width
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_YSIZE, 25); //--- Set height
      ObjectSetString(0, "CLOSE ALL", OBJPROP_TEXT, "Close All Positions"); //--- Set button text
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_COLOR, clrWhite); //--- Set text color
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_BGCOLOR, clrRed); //--- Set background color
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_BORDER_COLOR, clrBlack); //--- Set border color
   }
   string headerText = "Pin Bar Averaging EA";    //--- Set header text
   LABEL("Header", "Impact", 20, 110, 20, clrNavy, 0, headerText); //--- Create header label
   string copyrightText = "Copyright 2025, Allan Munene Mutiiria"; //--- Set copyright text
   LABEL("Copyright", "Arial", 9, 110, 55, clrBlack, 0, copyrightText); //--- Create copyright label
   string linkText = "https://t.me/Forex_Algo_Trader"; //--- Set link text
   LABEL("Link", "Arial", 9, 110, 70, clrBlue, 0, linkText); //--- Create link label
   string accountHeader = "Account Information";  //--- Set account header
   LABEL("AccountHeader", "Arial Bold", 10, 110, 90, clrBlack, 0, accountHeader); //--- Create account header label
   string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Set balance text
   LABEL("Balance", "Arial", 9, 120, 105, clrBlack, 0, balanceText); //--- Create balance label
   string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Set equity text
   LABEL("Equity", "Arial", 9, 120, 120, equityColor, 0, equityText); //--- Create equity label
   string marginText = "Free Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2); //--- Set margin text
   LABEL("Margin", "Arial", 9, 120, 135, clrBlack, 0, marginText); //--- Create margin label
   string profitText = "Open Profit: " + DoubleToString(buyProfit + sellProfit, 2); //--- Set profit text
   LABEL("Profit", "Arial", 9, 120, 150, profitColor, 0, profitText); //--- Create profit label
   string positionsText = "Buy Positions: " + IntegerToString((int)buyCount) + " Sell Positions: " + IntegerToString((int)sellCount); //--- Set positions text
   LABEL("Positions", "Arial", 9, 120, 165, clrBlack, 0, positionsText); //--- Create positions label
   string buyBEText = "Buy Break Even: " + (buyCount > 0 ? DoubleToString(buyBreakEvenPrice, _Digits) : "-"); //--- Set buy breakeven text
   LABEL("BuyBE", "Arial", 9, 120, 180, clrBlack, 0, buyBEText); //--- Create buy breakeven label
   string sellBEText = "Sell Break Even: " + (sellCount > 0 ? DoubleToString(sellBreakEvenPrice, _Digits) : "-"); //--- Set sell breakeven text
   LABEL("SellBE", "Arial", 9, 120, 195, clrBlack, 0, sellBEText); //--- Create sell breakeven label
   string spreadText = "Spread: " + spreadPrefix + IntegerToString((int)currentSpread) + " points"; //--- Set spread text
   LABEL("Spread", "Arial", 9, 120, 210, clrBlack, 0, spreadText); //--- Create spread label
   string timeText = "Time to next bar: " + minutesPrefix + IntegerToString(minutesRemaining) + ":" + secondsPrefix + IntegerToString(secondsRemaining); //--- Set time text
   LABEL("Time", "Arial", 9, 120, 225, clrBlack, 0, timeText); //--- Create time label
   string pinbarText;                             //--- Declare pinbar text
   if (IsBuyPinbar()) pinbarText = "Buy Pinbar";  //--- Check buy pinbar
   else if (IsSellPinbar()) pinbarText = "Sell Pinbar"; //--- Check sell pinbar
   else pinbarText = "None";                      //--- Set no pinbar
   LABEL("Pinbar", "Arial", 9, 120, 240, clrBlack, 0, "Pinbar Signal: " + pinbarText); //--- Create pinbar label
   string patternText = "Candle Pattern: " + CandleStick_Analyzer(); //--- Set candlestick pattern text
   LABEL("Pattern", "Arial", 9, 120, 255, clrBlack, 0, patternText); //--- Create pattern label
}

Реализуем функцию "Display_Info", чтобы создать всеобъемлющий дашборд для мониторинга сделок в реальном времени. Сначала сбрасываем ключевые показатели, такие как "buyCount" и другие, затем проходим по всем позициям, чтобы обновить эти показатели для позиций на покупку и продажу, соответствующих "Symbol", увеличивая количество, суммируя прибыль, объемы, взвешенные цены открытия, отслеживая минимальные/максимальные цены и рассчитывая цены безубыточности, если это применимо.

Затем вычислим оставшееся до следующего бара время, используя PeriodSeconds за вычетом разницы между текущим временем и iTime, преобразуем его в "minutesRemaining" и "secondsRemaining". Установим префиксы форматирования для отображения спреда и времени. Далее определим "equityColor" (зеленый или красный в зависимости от соотношения эквити и баланса) и "profitColor" (зеленый или красный в зависимости от общей прибыли). А также установим индекс мигающего цвета на основе текущей секунды для визуального эффекта. Наконец, создадим фон дашборда с помощью ObjectCreate в качестве OBJ_RECTANGLE_LABEL, если "DashboardBG" не существует, кнопку "CLOSE ALL" в качестве "OBJ_BUTTON", а также несколько меток с использованием функции "LABEL" для отображения названия советника, информации об авторских правах, ссылки, информации о торговом счете (баланс, эквити, свободная маржа, прибыль), количества позиций, цен безубыточности, спреда, времени до следующего бара, сигнала пин-бара и свечных паттернов из "CandleStick_Analyzer", обеспечивая чёткую и динамичную визуализацию торговой информации. Для этой кнопки реализуем обработку в OnChartEvent обработчике.

//+------------------------------------------------------------------+
//| Handle chart events                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_OBJECT_CLICK) {           //--- Check object click event
      if (sparam == "CLOSE ALL") {                //--- Check close all button
         ObjectSetInteger(0, "CLOSE ALL", OBJPROP_STATE, false); //--- Reset button state
         for (int positionIndex = PositionsTotal() - 1; positionIndex >= 0; positionIndex--) { //--- Iterate through positions
            ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
            if (ticket == 0) continue;            //--- Skip invalid tickets
            if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

В функции OnChartEvent проверим, совпадает ли "id" события с CHARTEVENT_OBJECT_CLICK для обнаружения кликов по объектам на графике. Затем проверим, является ли нажатый объект "sparam" кнопкой "CLOSE ALL", и если да, то сбросим состояние кнопки на false, используя ObjectSetInteger с параметром "OBJPROP_STATE". Далее переберем все позиции, получая тикет каждой позиции с помощью функции PositionGetTicket, пропускаем недействительные тикеты и проверяем, совпадает ли инструмент позиции с текущим значением "Symbol" с помощью PositionGetString функции. Наконец совпадающие позиции закроем с помощью "obj_Trade.PositionClose", которая выполняет команду пользователя по закрытию всех позиций. Тем самым обеспечим предоставление кнопкой "Close All Positions" на дашборде удобного способа ручного управления открытыми сделками. После вызова функции в OnTick и компиляции получим следующий результат.

COMPLETE PIN BAR AVERAGING SYSTEM

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


Тестирование на истории

После тщательного тестирования на истории мы получили следующие результаты.

График тестирования на истории:

График

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

REPORT


Заключение

В заключение отметим, что мы создали систему усреднения на основе пин-баров в MQL5. В ней используются свечные паттерны пин-баров для открытия сделок и управления несколькими позициями с помощью стратегии усреднения, улучшенной трейлинг-стопами, переводом в безубыток, а также дашбордом для мониторинга в реальном времени. Благодаря модульным компонентам, таким как функции "CandleStick_Analyzer" и "addAveragingOrder", эта программа предлагает дисциплинированный подход к торговле на разворотах с настраиваемыми средствами контроля рисков.

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

Используя представленные концепции и методы реализации, можно адаптировать эту систему усреднения на основе пин-баров к своему стилю торговли, улучшая свои алгоритмические стратегии. Удачной торговли! 

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19087

Прикрепленные файлы |
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Построение моделей волатильности в MQL5 (Часть I): Первичная реализация Построение моделей волатильности в MQL5 (Часть I): Первичная реализация
В этой статье мы представляем библиотеку MQL5 для моделирования волатильности, разработанную так, чтобы функционировать аналогично пакету arch в Python. В настоящее время библиотека поддерживает спецификацию распространённых моделей условного среднего: HAR, AR, Constant Mean и Zero Mean, а также моделей условной волатильности: Constant Variance, ARCH и GARCH.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Тестер стратегий для Python и MetaTrader 5 (Часть 02): Работа с барами, тиками и реализация встроенных функций в симуляторе Тестер стратегий для Python и MetaTrader 5 (Часть 02): Работа с барами, тиками и реализация встроенных функций в симуляторе
В этой статье мы представим функции, аналогичные тем, которые предоставляет модуль Python–MetaTrader 5, предоставляя симулятору привычный интерфейс и собственный механизм внутренней обработки баров и тиков.