//+------------------------------------------------------------------+
//|                                              PositionCloser.mqh  |
//+------------------------------------------------------------------+
#ifndef POSITIONCLOSER_MQH
#define POSITIONCLOSER_MQH

//+------------------------------------------------------------------+
//| CPositionCloser                                                  |
//| Closes every open position with a reduced-to-zero market order.  |
//| Iteration runs backward through the position list so that        |
//| closing one position never shifts the index of a position that   |
//| still needs to be visited.                                       |
//+------------------------------------------------------------------+
class CPositionCloser
  {
private:
   ENUM_ORDER_TYPE_FILLING SelectFillingMode(const string symbol) const;
   bool              CloseSinglePosition(const ulong ticket);

public:
                     CPositionCloser(void);
                    ~CPositionCloser(void);

   void              CloseAll(int &closed_count, int &failed_count);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPositionCloser::CPositionCloser(void)
  {
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CPositionCloser::~CPositionCloser(void)
  {
  }

//+------------------------------------------------------------------+
//| SelectFillingMode                                                |
//| Returns a filling mode the symbol actually advertises support    |
//| for, since a request with an unsupported filling mode is         |
//| rejected by the client terminal before it ever reaches the       |
//| trade server.                                                    |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING CPositionCloser::SelectFillingMode(const string symbol) const
  {
   long filling_flags = ::SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);

//--- prefer FOK, then IOC, and only fall back to Return if neither is advertised
   if((filling_flags & SYMBOL_FILLING_FOK) != 0)
      return(ORDER_FILLING_FOK);
   if((filling_flags & SYMBOL_FILLING_IOC) != 0)
      return(ORDER_FILLING_IOC);

   return(ORDER_FILLING_RETURN);
  }

//+------------------------------------------------------------------+
//| CloseSinglePosition                                              |
//| Closes a single position by ticket, logging the ticket, symbol,  |
//| volume, and close price on success, or the failure reason on     |
//| rejection.                                                       |
//+------------------------------------------------------------------+
bool CPositionCloser::CloseSinglePosition(const ulong ticket)
  {
   if(!::PositionSelectByTicket(ticket))
      return(false);

   string symbol         = ::PositionGetString(POSITION_SYMBOL);
   long   position_type  = ::PositionGetInteger(POSITION_TYPE);
   double volume         = ::PositionGetDouble(POSITION_VOLUME);

//--- a long closes at bid, a short closes at ask
   double close_price = (position_type == POSITION_TYPE_BUY)
                        ? ::SymbolInfoDouble(symbol, SYMBOL_BID)
                        : ::SymbolInfoDouble(symbol, SYMBOL_ASK);

   MqlTradeRequest request;
   MqlTradeResult  result;
   ::ZeroMemory(request);
   ::ZeroMemory(result);

//--- Full volume, opposite order type: reduces the position to zero
//--- rather than opening a new, separate position
   request.action         = TRADE_ACTION_DEAL;
   request.position       = ticket;
   request.symbol         = symbol;
   request.volume         = volume;
   request.price          = close_price;
   request.deviation      = 10;
   request.type           = (position_type == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
   request.type_filling   = SelectFillingMode(symbol);

   bool send_ok = ::OrderSend(request, result);

   if(send_ok && (result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED))
     {
      ::PrintFormat("CPositionCloser: closed ticket=%I64u symbol=%s volume=%.2f price=%.5f",
                    ticket, symbol, volume, result.price);
      return(true);
     }

   ::PrintFormat("CPositionCloser: failed to close ticket=%I64u symbol=%s volume=%.2f "
                 "retcode=%d comment=%s",
                 ticket, symbol, volume, result.retcode, result.comment);
   return(false);
  }

//+------------------------------------------------------------------+
//| CloseAll                                                         |
//| Closes every open position, iterating backward through the       |
//| position list so a closed position never disturbs the index of   |
//| a position still waiting to be processed.                        |
//+------------------------------------------------------------------+
void CPositionCloser::CloseAll(int &closed_count, int &failed_count)
  {
   closed_count = 0;
   failed_count = 0;

//--- Backward loop: PositionsTotal() shrinks by one after every close,
//--- so a forward loop would skip whichever position slides into the
//--- just-vacated index
   for(int i = ::PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = ::PositionGetTicket(i);
      if(ticket == 0)
         continue;

      if(CloseSinglePosition(ticket))
         closed_count++;
      else
         failed_count++;
     }
  }

#endif // POSITIONCLOSER_MQH
//+------------------------------------------------------------------+