//+------------------------------------------------------------------+
//|                                          MagicNumberHelpers.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Select position by symbol and magic number                       |
//+------------------------------------------------------------------+
bool PositionSelectByMagic(string symbol, ulong magic_number)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(!PositionSelectByTicket(ticket))
         continue;
      if(PositionGetString(POSITION_SYMBOL) == symbol &&
         (ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
         return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| Select position by ticket and verify magic number and symbol     |
//+------------------------------------------------------------------+
bool PositionSelectByTicketAndMagic(ulong ticket, ulong magic_number)
{
   if(!PositionSelectByTicket(ticket))
      return false;
   
   return (PositionGetInteger(POSITION_MAGIC) == magic_number);
}

//+------------------------------------------------------------------+
//| Select position by ticket and verify symbol, magic number        |
//+------------------------------------------------------------------+
bool PositionSelectByTicketSymbolAndMagic(ulong ticket, string symbol, ulong magic_number)
{
   if(!PositionSelectByTicket(ticket))
      return false;
   
   return (PositionGetString(POSITION_SYMBOL) == symbol && 
           PositionGetInteger(POSITION_MAGIC) == magic_number);
}

//+------------------------------------------------------------------+
//| Check if position exists with correct magic number               |
//+------------------------------------------------------------------+
bool PositionExistsByMagic(string symbol, ulong magic_number)
{
   return PositionSelectByMagic(symbol, magic_number);
}

//+------------------------------------------------------------------+
//| Get position ticket by symbol and magic number                   |
//+------------------------------------------------------------------+
ulong GetPositionTicketByMagic(string symbol, ulong magic_number)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(!PositionSelectByTicket(ticket))
         continue;
      if(PositionGetString(POSITION_SYMBOL) == symbol &&
         (ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
         return ticket;
   }
   return 0;
}

//+------------------------------------------------------------------+
//| Close position by symbol and magic number                        |
//+------------------------------------------------------------------+
bool ClosePositionByMagic(CTrade &trade_obj, string symbol, ulong magic_number)
{
   ulong ticket = GetPositionTicketByMagic(symbol, magic_number);
   if(ticket == 0)
      return false;
   
   return trade_obj.PositionClose(ticket);
}

//+------------------------------------------------------------------+
//| Modify position by symbol and magic number                        |
//+------------------------------------------------------------------+
bool ModifyPositionByMagic(CTrade &trade_obj, string symbol, ulong magic_number, 
                         double sl, double tp)
{
   ulong ticket = GetPositionTicketByMagic(symbol, magic_number);
   if(ticket == 0)
      return false;
   
   return trade_obj.PositionModify(ticket, sl, tp);
}

//+------------------------------------------------------------------+
//| Get position profit by symbol and magic number                   |
//+------------------------------------------------------------------+
double GetPositionProfitByMagic(string symbol, ulong magic_number)
{
   if(!PositionSelectByMagic(symbol, magic_number))
      return 0.0;
   
   return PositionGetDouble(POSITION_PROFIT);
}

//+------------------------------------------------------------------+
//| Net P/L including swap (and commission when exposed by broker)   |
//+------------------------------------------------------------------+
double GetPositionNetProfitByMagic(string symbol, ulong magic_number)
{
   if(!PositionSelectByMagic(symbol, magic_number))
      return 0.0;

   double net = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
   return net;
}

//+------------------------------------------------------------------+
//| True when an open position for this magic is not in profit        |
//+------------------------------------------------------------------+
bool IsUnprofitablePositionByMagic(string symbol, ulong magic_number)
{
   if(!PositionExistsByMagic(symbol, magic_number))
      return false;

   return (GetPositionNetProfitByMagic(symbol, magic_number) <= 0.0);
}

//+------------------------------------------------------------------+
//| Close unprofitable position to make room for a new entry signal  |
//| Returns true when no position remains (flat or closed successfully)|
//+------------------------------------------------------------------+
bool United_PrepareEntrySlot(CTrade &trade_obj, string symbol, ulong magic_number, bool closeUnprofitableOnSignal)
{
   if(!PositionExistsByMagic(symbol, magic_number))
      return true;

   if(!closeUnprofitableOnSignal)
      return false;

   if(!IsUnprofitablePositionByMagic(symbol, magic_number))
      return false;

   if(!ClosePositionByMagic(trade_obj, symbol, magic_number))
      return false;

   return !PositionExistsByMagic(symbol, magic_number);
}

//+------------------------------------------------------------------+
//| Get position type by symbol and magic number                     |
//+------------------------------------------------------------------+
ENUM_POSITION_TYPE GetPositionTypeByMagic(string symbol, ulong magic_number)
{
   if(!PositionSelectByMagic(symbol, magic_number))
      return WRONG_VALUE;
   
   return (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
}

//+------------------------------------------------------------------+
//| Count positions by symbol and magic number                       |
//+------------------------------------------------------------------+
int CountPositionsByMagic(string symbol, ulong magic_number)
{
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;
      if(!PositionSelectByTicket(ticket))
         continue;
      if(PositionGetString(POSITION_SYMBOL) == symbol &&
         (ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
         count++;
   }
   return count;
}

//+------------------------------------------------------------------+
//| Align volume to SYMBOL_VOLUME_STEP / min / max (avoids Invalid volume) |
//+------------------------------------------------------------------+
double United_NormalizeVolume(const string symbol, double volume)
{
   double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   if(lotStep <= 0.0)
      lotStep = 0.01;

   double v = MathFloor(volume / lotStep) * lotStep;

   if(v < minLot)
      v = minLot;
   if(v > maxLot)
      v = maxLot;

   int digits = (int)MathCeil(-MathLog10(lotStep));
   if(digits < 0)
      digits = 0;
   if(digits > 8)
      digits = 8;

   return NormalizeDouble(v, digits);
}

//+------------------------------------------------------------------+
