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.
#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 thePositionGetString()andPositionGetInteger()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, soInpMagic = 0closes only manual trades.PositionClose(ticket).CTradebuilds 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 theCTradeobject's (0, manual trades, unless you calledSetExpertMagicNumber()). 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.