Edgoro
Script · MQL5

Close all positions in MQL5 (by symbol and magic number)

Short answer

Loop from PositionsTotal() - 1 down to 0, get each ticket with PositionGetTicket(i), skip the positions whose POSITION_SYMBOL or POSITION_MAGIC don't match, and close the rest with trade.PositionClose(ticket). Loop backwards: closing a position shifts the index of the ones after it, so a forward loop skips some of them.

UsesPositionsTotalPositionGetTicketCTrade::PositionCloseOrdersTotalCTrade::OrderDelete

The code

A script with inputs: an empty symbol and a magic number of -1 close every position on the account. It sends real closing orders, so try it on a demo account first.

Save it as MQL5/Scripts/close-all-positions.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
#property script_show_inputs
#include <Trade/Trade.mqh>

input string InpSymbol   = "";    // Symbol to close ("" = every symbol)
input long   InpMagic    = -1;    // Magic number to close (-1 = any, 0 = manual trades)
input bool   InpPending  = false; // Also delete pending orders
input int    InpAttempts = 3;     // Attempts per position

CTrade trade;

bool Matches(const string symbol, const long magic)
  {
   if(InpSymbol != "" && symbol != InpSymbol)
      return false;
   return InpMagic < 0 || magic == InpMagic;
  }

// Price moved or no quote: worth another try. Anything else (market closed, trading disabled...) won't fix itself.
bool Retryable(const uint rc)
  {
   return rc == TRADE_RETCODE_REQUOTE || rc == TRADE_RETCODE_PRICE_CHANGED || rc == TRADE_RETCODE_PRICE_OFF;
  }

int CloseMatchingPositions()
  {
   int failed = 0;
   // Walk backwards: closing a position shifts the index of every position after it
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);   // also selects the position
      if(ticket == 0)
         continue;
      if(!Matches(PositionGetString(POSITION_SYMBOL), PositionGetInteger(POSITION_MAGIC)))
         continue;

      bool closed = false;
      for(int attempt = 1; attempt <= InpAttempts && !closed; attempt++)
        {
         trade.PositionClose(ticket);
         uint rc = trade.ResultRetcode();
         if(rc == TRADE_RETCODE_DONE)
            closed = true;
         else if(rc == TRADE_RETCODE_DONE_PARTIAL)
            continue;                         // part of it is still open: close the rest
         else
           {
            PrintFormat("Position #%I64u: retcode %u (%s)", ticket, rc, trade.ResultRetcodeDescription());
            if(!Retryable(rc))
               break;
            Sleep(300);
           }
        }
      if(!closed)
         failed++;
     }
   return failed;
  }

int DeleteMatchingOrders()
  {
   int failed = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
     {
      ulong ticket = OrderGetTicket(i);   // also selects the order
      if(ticket == 0)
         continue;
      if(!Matches(OrderGetString(ORDER_SYMBOL), OrderGetInteger(ORDER_MAGIC)))
         continue;
      if(!trade.OrderDelete(ticket) || trade.ResultRetcode() != TRADE_RETCODE_DONE)
        {
         PrintFormat("Order #%I64u: retcode %u (%s)", ticket, trade.ResultRetcode(), trade.ResultRetcodeDescription());
         failed++;
        }
     }
   return failed;
  }

void OnStart()
  {
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED))
     {
      Print("Algo trading is off: nothing was closed");
      return;
     }
   trade.SetDeviationInPoints(20);
   int failed = CloseMatchingPositions();
   if(InpPending)
      failed += DeleteMatchingOrders();
   if(failed > 0)
      PrintFormat("%d position(s) or order(s) are still open: see the messages above", failed);
   else
      Print("Done: every matching position is closed");
  }

How it works

  • The backwards loop. Positions are indexed 0 to PositionsTotal() - 1. Close position 0 in a forward loop and the old position 1 becomes position 0 while the loop moves on to 1: every other position is skipped.
  • PositionGetTicket(i) returns the ticket and also selects the position, so the PositionGetString() and PositionGetInteger() calls right after it read that position.
  • Matches(). An empty symbol means every symbol and a magic number of -1 means every position. Trades opened by hand carry the magic number 0, so InpMagic = 0 closes only manual trades.
  • PositionClose(ticket). CTrade builds the opposite deal for the whole volume, at the Bid for a buy and the Ask for a sell, and names the position in the request, which is what closes that exact position on a hedging account.
  • Retryable(). A requote (10004), a changed price (10020) or no quotes (10021) are worth another try after a short pause. A closed market or disabled trading won't fix itself in 300 milliseconds, so the script stops trying and reports it.
  • TRADE_RETCODE_DONE_PARTIAL. Part of the volume was closed and the rest is still open: the loop sends another close for what's left.

Pitfalls

  • Looping forwards. The most common reason a close-all routine leaves positions open. The same applies to OrdersTotal() when deleting pending orders.
  • trade.PositionClose(_Symbol) closes one position. The overload that takes a symbol closes a single position, and on a hedging account only one whose magic number equals the CTrade object's (0, manual trades, unless you called SetExpertMagicNumber()). Close by ticket in a loop.
  • The market is closed. Outside the symbol's session every close fails with 10018 market closed. Check the session before a scheduled close-all, for example a Friday close.
  • Fast markets. In news spikes closes come back with 10004 requote or 10021; the retry loop handles a few, and a wider slippage (SetDeviationInPoints()) helps on Instant Execution symbols.
  • Netting accounts. There is one position per symbol, shared by every EA trading it, so a magic number filter can't separate EAs there. MQL5 magic numbers explained covers why.

Variations

  • As a function in an EA. Move CloseMatchingPositions() into the EA, pass the EA's own magic number, and call it from an exit rule, a daily loss guard or a timer that closes before the weekend.
  • Only winners or only losers. Add a check on PositionGetDouble(POSITION_PROFIT) in the loop, for example to close the losing side of a grid.
  • Close opposite positions against each other. On a hedging account, trade.PositionCloseBy(ticket, oppositeTicket) closes a buy against a sell of the same symbol in one operation, so only one spread is paid instead of two.
  • Part of a position. To close a fraction instead, see partial close.

Questions

Why does my close-all loop leave some positions open?

Usually because it loops forwards: each close shifts the remaining positions down one index and the loop skips them. Loop from PositionsTotal() - 1 down to 0. If positions remain after that, the Experts tab shows the retcode of each refused close.

How do I close only the positions my EA opened?

Filter on POSITION_MAGIC: compare it with the magic number the EA sets with trade.SetExpertMagicNumber(). Add a POSITION_SYMBOL check if the same EA runs on several symbols and should only close the chart's.

Is there one MQL5 function that closes every position?

No. MQL5 has no close-all call: you loop over the positions and close each one by ticket, as the script does. With many positions, each close is a separate round trip to the server, so prices can move between the first and the last.

This program is a minimal example written to show one technique, not a trading system, and none of it is a recommendation to trade. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

All MQL5 errors

Want it inside a complete EA?

Edgoro's MT5 EA generator can build exit rules like this into a complete EA: a close-all at a set time, after a daily loss, or when a signal reverses. A clean build proves the code compiles, not that a strategy works: test it on a demo account.