Blog
Pine Script strategy.close to MQL5: Close Positions Safely
Quick Answer
strategy.close("Long") in Pine Script closes the open entries that were opened with the id "Long". strategy.close_all() closes everything. MQL5 has no entry ids, so you have to decide how to find your own positions, then close each one with CTrade.
The usual approach is to filter by symbol and magic number, and, if you need one close per entry id, by the position comment. On a netting account there is only one position per symbol, so you close that one position.
Key Facts
| Pine Script | MQL5 |
|---|---|
| strategy.close("Long") | Find the position with that entry id (comment), then trade.PositionClose(ticket) |
| strategy.close_all() | Loop over your positions (symbol and magic) and close each one |
| Entry id such as "Long" | Stored in the order comment, or replaced by a separate magic number |
| One position per entry id | Hedging account: many positions. Netting account: one position per symbol |
| Close happens on the next bar's open by default | You choose the moment: usually on a new bar, after the signal |
The Pine Script Version
//@version=5
strategy("Close example", overlay = true)
if longCondition
strategy.entry("Long", strategy.long)
if exitCondition
strategy.close("Long")
if panicCondition
strategy.close_all()
Check the Pine documentation for how your strategy settings (such as process_orders_on_close) decide when the close is filled. By default, the order from a bar-close signal is filled on the next bar, so the close price in a Pine backtest is not the same as a price you get by closing right after the signal in MQL5.
Step 1: Tag Positions So You Can Find Them Again
When the EA opens a position, give it a magic number and a comment that holds the Pine entry id:
#include <Trade\Trade.mqh>
CTrade trade;
input long MagicNumber = 123456;
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
return INIT_SUCCEEDED;
}
bool OpenBuy(double lots, string entryId)
{
if(!trade.Buy(lots, _Symbol, 0.0, 0.0, 0.0, entryId))
{
PrintFormat("Buy failed. Retcode: %u, %s",
trade.ResultRetcode(),
trade.ResultRetcodeDescription());
return false;
}
return true;
}
The comment is a convenience, not a guarantee. Some brokers shorten or change comments, and some operations (such as a partial close) can rewrite them. If you need a reliable link between a Pine id and a position, use a different magic number for each id, and keep the comment only as a label for humans.
Step 2: Close by Entry Id
This function closes every position of this EA, on this symbol, whose comment matches the id:
int CloseById(string entryId)
{
int closed = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber)
continue;
if(PositionGetString(POSITION_COMMENT) != entryId)
continue;
if(trade.PositionClose(ticket))
closed++;
else
PrintFormat("Close failed for #%I64u. Retcode: %u, %s",
ticket,
trade.ResultRetcode(),
trade.ResultRetcodeDescription());
}
return closed;
}
The loop runs backwards. Closing a position removes it from the list, and a forward loop can then skip the next one. The magic number and symbol filters stop the EA from touching positions that belong to you manually or to another EA. See how to filter positions by magic number in MQL5 for more detail on this filter.
Step 3: Close Everything (strategy.close_all)
strategy.close_all() is the same loop without the comment test:
int CloseAll()
{
int closed = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber)
continue;
if(trade.PositionClose(ticket))
closed++;
else
PrintFormat("Close failed for #%I64u. Retcode: %u, %s",
ticket,
trade.ResultRetcode(),
trade.ResultRetcodeDescription());
}
return closed;
}
Do not remove the symbol and magic filters to "close everything". Without them, the function would also close positions that this EA did not open.
Hedging vs Netting
- Hedging account: every entry is its own position, so closing
"Long"closes only the positions you filter, and other entries stay open. This is closest to Pine's behavior. - Netting account: all trades on a symbol merge into one position.
strategy.close("Long")cannot close "only the Long entry", because there is only one position. In that case, closing means closing the whole position or reducing its volume.
Check the account type before you rely on per-entry closes:
long mode = AccountInfoInteger(ACCOUNT_MARGIN_MODE);
if(mode == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
Print("Hedging account: positions are separate.");
else
Print("Netting or exchange account: one position per symbol.");
Close Part of a Position
Pine can close a percentage with strategy.close(id, qty_percent = 50). In MQL5 you close a part of the volume with the volume overload of PositionClosePartial:
bool ClosePartial(ulong ticket, double volumeToClose)
{
if(!PositionSelectByTicket(ticket))
return false;
double minVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double current = PositionGetDouble(POSITION_VOLUME);
// Round down to a valid volume step
volumeToClose = MathFloor(volumeToClose / step) * step;
if(volumeToClose < minVol || volumeToClose >= current)
return false; // too small, or this would be a full close
return trade.PositionClosePartial(ticket, volumeToClose);
}
Round the volume down to the symbol's volume step, and make sure both the closed part and the remaining part are at least the minimum volume. Otherwise the broker rejects the request.
When to Run the Close
A Pine strategy evaluates its logic once per bar, and the close is filled on a later bar. In an EA, run the signal check on a new bar, as in the crossover guide, then call CloseById or CloseAll. Closing on every tick can react to an unfinished bar and close a trade that Pine would have kept.
If the Close Fails
Always read the retcode. Common causes are a market that is closed, no connection, or a requote on a fast market. The full list is in the MQL5 trade retcodes guide. Some brokers also reject closes that fall inside the freeze level, which is covered in the invalid stops article.
Common Mistakes
- Looping forward while closing. The index shifts after each close, so some positions are skipped. Loop backwards.
- No magic number or symbol filter. The EA closes manual trades or positions from other EAs.
- Relying on the comment alone. A broker can change it. Use a magic number per entry id when it matters.
- Expecting per-entry close on a netting account. There is only one position per symbol.
- Ignoring the result.
PositionClosecan fail. Check the return value and the retcode. - Closing on every tick. Run the logic on a new bar to match Pine's bar-based model.
- Partial close with an invalid volume. Round to the volume step and respect the minimum.
FAQ
Convert Entries, Exits and Closes Together
An entry, a stop, a take profit and a close all have to work together, and a wrong filter can touch another position. For the entry mapping, see strategy.entry() to MQL5 CTrade, and for stops and targets, see Pine Script strategy.exit to MQL5. CodeFlowOS translates Pine Script into MQL5 and lets you review the generated code before you test it in the MT5 Strategy Tester. You can try it on the converter page.
Related Guides
- Pine Script strategy.exit to MQL5: SL, TP and Trailing: set the stop loss and take profit that go with your close logic.
- Pine Script strategy.entry() to MQL5 CTrade: the entry side of the same conversion.
- How to Filter Positions by Magic Number in MQL5: find only your own EA's positions before closing.
This article is educational. It does not describe trading performance and is not financial advice. Always compile and test code in MetaEditor and the Strategy Tester before using it on a live account.