//+------------------------------------------------------------------+
//|                                               VR Locker Lite.mq5 |
//|                                      Copyright 2025, Trading-Go. |
//+------------------------------------------------------------------+
#property copyright "@ Voldemar"                                      // Информация об авторских правах
#property link      "https://www.mql5.com/en/channels/tradingo-go-en" // Ссылка на канал
#property version   "26.020"                                          // Версия советника

#include <Trade\Trade.mqh>        CTrade        trade;                // Подключает класс Trade для исполнения ордеров
#include <Trade\PositionInfo.mqh> CPositionInfo posit;                // Подключает класс PositionInfo для управления позициями

//+------------------------------------------------------------------+
//|                     Входные параметры                             |
//+------------------------------------------------------------------+
input double  iStartLots   = 0.01;                                     // Начальный торговый объём (размер лота)
input double  iMultiplier  = 2.0;                                      // Множитель для увеличения объёма при усреднении
input double  iProfitPlus  = 30;                                       // Цель прибыли в пунктах, добавляется к средней цене
input int     iStep        = 200;                                      // Расстояние в пунктах для добавления к убыточным позициям
input int     iMagicNumber = 227;                                      // Уникальный идентификатор позиций советника
input int     iSlippage    = 30;                                       // Максимально допустимое проскальзывание в пунктах

// Глобальные переменные
double lt  = 0;                                                        // Переменная для рассчитанного размера лота (с учётом спецификации символа)
double Bid = 0;                                                        // Хранение текущей цены Bid
double Ask = 0;                                                        // Хранение текущей цены Ask

//+------------------------------------------------------------------+
//| Функция инициализации советника                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
// --- Задать MagicNumber объекту CTrade для идентификации ордеров советника
   trade.SetExpertMagicNumber(iMagicNumber);                           // Назначить магический номер объекту trade
// --- Задать проскальзывание объекту CTrade для исполнения ордеров
   trade.SetDeviationInPoints(iSlippage);                              // Установить допустимое проскальзывание в пунктах
// --- Задать режим заполнения ордеров по спецификации символа (IOC или FOK)
   trade.SetTypeFillingBySymbol(_Symbol);                              // Настроить политику заполнения ордеров
// --- Задать режим хеджирования (позволяет несколько позиций по одному символу)
   trade.SetMarginMode();                                              // Включить режим хеджирования
// ---
   return(INIT_SUCCEEDED);                                             // Вернуть код успешной инициализации
  }
//+------------------------------------------------------------------+
//| Функция тика советника - вызывается на каждом новом тике        |
//+------------------------------------------------------------------+
void OnTick()
  {

// Переменные для анализа позиций
   int total = ::PositionsTotal(), b = 0, s = 0;                       // Получить общее число позиций, инициализировать счётчики Buy/Sell
   double   BuyPricSumm = 0,   SelPricSumm = 0,                        // Сумма (цена * объём) для расчёта средневзвешенной цены
            BuyLotsSumm = 0,   SelLotsSumm = 0;                        // Суммарный объём по покупкам и продажам

   double   BuyLowPrice = 0,   SelHigtPrice = 0;                       // Отслеживать минимальную цену Buy и максимальную цену Sell
   double   BuyLowLots  = 0,   SelHigtLots  = 0;                       // Отслеживать объёмы на экстремальных ценах

// Получить текущие рыночные цены
   Bid = ::SymbolInfoDouble(_Symbol, SYMBOL_BID);                      // Получить текущую цену Bid
   Ask = ::SymbolInfoDouble(_Symbol, SYMBOL_ASK);                      // Получить текущую цену Ask

   if(Bid <= 0 || Ask <= 0)                                            // Проверить корректность цен
      return;                                                          // Выйти, если цены недействительны

// Цикл по всем позициям для анализа текущего портфеля
   for(int i = 0; i < total; i++)                                      // Перебрать все открытые позиции
      if(posit.SelectByIndex(i))                                       // Выбрать позицию по индексу
         if(posit.Symbol() == _Symbol)                                 // Проверить совпадение символа с текущим
            if(posit.Magic() == iMagicNumber)                          // Проверить принадлежность позиции этому советнику
               if(posit.PositionType() == POSITION_TYPE_BUY)           // Проверить, является ли позиция покупкой (BUY)
                 {
                  b++;                                                 // Увеличить счётчик позиций Buy
                  BuyPricSumm += posit.PriceOpen() * posit.Volume();   // Добавить взвешенную цену открытия (цена * объём) для расчёта среднего
                  BuyLotsSumm += posit.Volume();                       // Добавить объём покупки к суммарному

                  if(posit.PriceOpen() < BuyLowPrice || BuyLowPrice == 0) // Найти наименьшую цену открытия Buy для усреднения
                    {
                     BuyLowPrice = posit.PriceOpen();                  // Обновить наименьшую цену Buy
                     BuyLowLots = posit.Volume();                      // Сохранить объём на наименьшей цене для расчёта с множителем
                    }
                 }
               else
                  if(posit.PositionType() == POSITION_TYPE_SELL)       // Проверить, является ли позиция продажей (SELL)
                    {
                     s++;                                              // Увеличить счётчик позиций Sell
                     SelPricSumm += posit.PriceOpen() * posit.Volume(); // Добавить взвешенную цену открытия для продаж
                     SelLotsSumm += posit.Volume();                    // Добавить объём продажи к суммарному

                     if(posit.PriceOpen() > SelHigtPrice || SelHigtPrice == 0) // Найти наибольшую цену открытия Sell для усреднения
                       {
                        SelHigtPrice = posit.PriceOpen();              // Обновить наибольшую цену Sell
                        SelHigtLots = posit.Volume();                  // Сохранить объём на наибольшей цене для расчёта с множителем
                       }
                    }

// Логика управления позициями BUY
   if(b == 0)                                                          // Если нет открытых позиций Buy
      if((lt = VolumeCalc(iStartLots)) > 0)                            // Рассчитать и проверить начальный размер лота
         if(trade.CheckVolume(_Symbol, lt, Ask, ORDER_TYPE_BUY))       // Проверить допустимость объёма для символа
            if(CheckMargin(ORDER_TYPE_BUY, lt))                        // Проверить наличие достаточной свободной маржи
// >>>>>>>>>>>>>>>>>> ОТКРЫТИЕ ПЕРВОГО ОРДЕРА BUY <<<<<<<<<<<<<<<<<<<<
               if(trade.Buy(lt))                                       // Открыть начальную позицию BUY
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                  return;                                              // Выйти после открытия позиции, чтобы избежать двойного срабатывания

// Добавление к существующим позициям BUY при падении цены
   if(b > 0)                                                           // Если позиции Buy уже существуют
     {
      double limit_lots = ::SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_LIMIT); // Получить максимально допустимый объём

      if((BuyLowPrice - Ask) >= iStep * _Point)                        // Проверить, упала ли цена ниже минимального входа на величину шага
         if((BuyLotsSumm + BuyLowLots * iMultiplier) < limit_lots || limit_lots == 0) // Проверить лимит объёма
            if((lt = VolumeCalc(BuyLowLots * iMultiplier)) > 0)        // Рассчитать новый объём с применением множителя
               if(CheckMargin(ORDER_TYPE_BUY, lt))                     // Проверить наличие маржи
                  if(trade.CheckVolume(_Symbol, lt, Ask, ORDER_TYPE_BUY)) // Проверить допустимость объёма
                     if(trade.Buy(BuyLowLots * iMultiplier))           // Добавить к убыточной позиции с множителем
                        return;                                        // Выйти после добавления позиции
     }

// Логика управления позициями SELL
   if(s == 0)                                                          // Если нет открытых позиций Sell
      if((lt = VolumeCalc(iStartLots)) > 0)                            // Рассчитать и проверить начальный размер лота
         if(trade.CheckVolume(_Symbol, lt, Bid, ORDER_TYPE_SELL))      // Проверить допустимость объёма для символа
            if(CheckMargin(ORDER_TYPE_SELL, lt))                       // Проверить наличие достаточной свободной маржи
// >>>>>>>>>>>>>>>>>> ОТКРЫТИЕ ПЕРВОГО ОРДЕРА SELL <<<<<<<<<<<<<<<<<<<
               if(trade.Sell(lt))                                      // Открыть начальную позицию SELL
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                  return;                                              // Выйти после открытия позиции

// Добавление к существующим позициям SELL при росте цены
   if(s > 0)                                                           // Если позиции Sell уже существуют
     {
      double limit_lots = ::SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_LIMIT); // Получить максимально допустимый объём

      if((Bid - SelHigtPrice) >= iStep * _Point)                       // Проверить, выросла ли цена выше максимального входа на величину шага
         if((SelLotsSumm + SelHigtLots * iMultiplier) < limit_lots || limit_lots == 0) // Проверить лимит объёма
            if((lt = VolumeCalc(SelHigtLots * iMultiplier)) > 0)       // Рассчитать новый объём с применением множителя
               if(CheckMargin(ORDER_TYPE_SELL, lt))                    // Проверить наличие маржи
                  if(trade.CheckVolume(_Symbol, lt, Bid, ORDER_TYPE_SELL)) // Проверить допустимость объёма
                     if(trade.Sell(SelHigtLots * iMultiplier))         // Добавить к убыточной позиции с множителем
                        return;                                        // Выйти после добавления позиции
     }

// ===
// Рассчитать средние цены для уровней тейк-профита
// ===
   double BuyAwerage = 0;                                              // Переменная для средней цены позиций Buy
   double SelAwerage = 0;                                              // Переменная для средней цены позиций Sell

   if(b >= 2)                                                          // Рассчитывать только если открыто 2+ позиции Buy
      BuyAwerage = NormalizeDouble(BuyPricSumm / BuyLotsSumm + iProfitPlus * _Point, _Digits); // Средневзвешенная + цель прибыли
   if(s >= 2)                                                          // Рассчитывать только если открыто 2+ позиции Sell
      SelAwerage = NormalizeDouble(SelPricSumm / SelLotsSumm - iProfitPlus * _Point, _Digits); // Средневзвешенная - цель прибыли

// ===
// Модифицировать позиции для установки уровней тейк-профита
// ===
   for(int i = 0; i < total; i++)                                      // Пройти по всем позициям повторно для установки TP
      if(posit.SelectByIndex(i))                                       // Выбрать позицию по индексу
         if(posit.Symbol() == _Symbol)                                 // Проверить совпадение символа
            if(posit.Magic() == iMagicNumber)                          // Проверить принадлежность позиции советнику
              {
               if(BuyAwerage > 0)                                      // Если средняя цена Buy рассчитана
                  if(posit.PositionType() == POSITION_TYPE_BUY)        // Проверить, является ли позиция покупкой
                     if(NormalizeDouble(posit.TakeProfit(), _Digits) != BuyAwerage) // Проверить, требует ли TP обновления
                        trade.PositionModify(posit.Ticket(), 0, BuyAwerage); // Изменить уровень TP на новое среднее + прибыль

               if(SelAwerage > 0)                                      // Если средняя цена Sell рассчитана
                  if(posit.PositionType() == POSITION_TYPE_SELL)       // Проверить, является ли позиция продажей
                     if(NormalizeDouble(posit.TakeProfit(), _Digits) != SelAwerage) // Проверить, требует ли TP обновления
                        trade.PositionModify(posit.Ticket(), 0, SelAwerage); // Изменить уровень TP на новое среднее - прибыль
              }
  }
//+------------------------------------------------------------------+
//| Функция деинициализации советника                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)                                       // Функция вызывается при удалении советника с графика
  {
// Очистка не нужна - позиции остаются открытыми
  }
//+------------------------------------------------------------------+
//| Проверка наличия достаточной свободной маржи для сделки         |
//+------------------------------------------------------------------+
bool CheckMargin(ENUM_ORDER_TYPE aType, double aLot)
  {
   double fMargin = 0;                                                // Переменная для хранения требуемой маржи
   if(Ask > 0 && Bid > 0)                                             // Убедиться в корректности цен
      if(::OrderCalcMargin(aType, _Symbol, aLot, aType == 0 ? Ask : Bid, fMargin)) // Рассчитать требуемую маржу
         return(fMargin < ::AccountInfoDouble(ACCOUNT_MARGIN_FREE));  // Вернуть true, если свободной маржи достаточно
   return (false);                                                    // Вернуть false, если проверка маржи не прошла
  }
//+------------------------------------------------------------------+
//| Расчёт и нормализация размера лота по спецификации символа      |
//+------------------------------------------------------------------+
double VolumeCalc(double aLot)
  {
   double lots = 0;
   double stepvol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);    // Получить минимальный шаг лота для текущего символа
   if(stepvol > 0.0)                                                  // Проверить корректность шага объёма
      lots = stepvol * (int)(aLot / stepvol);                         // Рассчитать лот: округлить вниз до ближайшего допустимого шага
//---
   double minvol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);      // Получить минимально допустимый лот для символа
   if(lots < minvol)                                                  // Проверить, не ниже ли рассчитанный лот минимума
      lots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);            // Установить минимально допустимый объём

   return(lots);                                                      // Вернуть нормализованный размер лота
  }
//+------------------------------------------------------------------+
