Blog
Pine Script strategy.exit to MQL5: SL, TP and Trailing
Quick Answer
strategy.exit in Pine Script attaches a stop loss and a take profit (and optionally a trailing stop) to an entry. In MQL5 you do the same by setting sl and tp on the position, either when you open it or afterwards with PositionModify.
The main trap is the unit. Pine loss and profit are measured in ticks, and the price versions (stop and limit) are absolute prices. MQL5 expects prices, so you convert ticks with SYMBOL_TRADE_TICK_SIZE, round the result, and check that it respects the stops level.
Key Facts
| Pine Script | Meaning | MQL5 equivalent |
|---|---|---|
| strategy.exit(..., stop=price) | Stop loss as a price | sl argument or PositionModify |
| strategy.exit(..., limit=price) | Take profit as a price | tp argument or PositionModify |
| strategy.exit(..., loss=ticks) | Stop loss distance in ticks | Entry price minus (ticks times tick size) for a buy |
| strategy.exit(..., profit=ticks) | Take profit distance in ticks | Entry price plus (ticks times tick size) for a buy |
| trail_points, trail_offset | Trailing stop in ticks | Your own trailing code, run each tick or bar |
| from_entry="Long" | Exit tied to one entry id | Position found by symbol and magic number |
The Pine Script Version
strategy.entry("Long", strategy.long)
strategy.exit("Exit Long", from_entry = "Long", loss = 200, profit = 400)
This opens a long position and sets a stop 200 ticks below the entry price and a target 400 ticks above it. When you give both a price (stop, limit) and a distance (loss, profit) for the same side, check the Pine documentation to see which one takes priority in your version.
Ticks, Points and Pips
These three units are easy to confuse:
- Tick: the smallest price step the symbol allows. In MQL5 it is
SYMBOL_TRADE_TICK_SIZE. - Point: the smallest price unit,
SYMBOL_POINT. On many forex symbols the tick size and the point are equal, but not on every symbol. - Pip: a trader's convention. On a 5-digit forex symbol one pip is 10 points.
Convert ticks to a price distance like this:
double TicksToPrice(int ticks)
{
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
return ticks * tickSize;
}
double NormalizePrice(double price)
{
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickSize <= 0.0)
return NormalizeDouble(price, _Digits);
return NormalizeDouble(MathRound(price / tickSize) * tickSize, _Digits);
}
Option 1: Set SL and TP When You Open the Order
The simplest port passes both levels to the order call:
#include <Trade\Trade.mqh>
CTrade trade;
input int LossTicks = 200;
input int ProfitTicks = 400;
input long MagicNumber = 123456;
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
return INIT_SUCCEEDED;
}
void OpenBuyWithExit(double lots)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = NormalizePrice(ask - TicksToPrice(LossTicks));
double tp = NormalizePrice(ask + TicksToPrice(ProfitTicks));
if(!trade.Buy(lots, _Symbol, 0.0, sl, tp, "Long"))
{
PrintFormat("Buy failed. Retcode: %u, %s",
trade.ResultRetcode(),
trade.ResultRetcodeDescription());
}
}
There is a small difference from Pine here. Pine measures loss and profit from the entry fill price. This version measures from the Ask at the moment you send the order, and the real fill can differ slightly, for example because of slippage. If you need the distance to match the actual fill, use option 2.
Option 2: Set SL and TP From the Actual Entry Price
Open the position, find it, read POSITION_PRICE_OPEN, and then set the levels:
ulong FindPosition(long magic)
{
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) != magic)
continue;
return ticket;
}
return 0;
}
bool SetExitFromEntry(ulong ticket, int lossTicks, int profitTicks)
{
if(!PositionSelectByTicket(ticket))
return false;
double open = PositionGetDouble(POSITION_PRICE_OPEN);
long type = PositionGetInteger(POSITION_TYPE);
double dLoss = TicksToPrice(lossTicks);
double dGain = TicksToPrice(profitTicks);
double sl, tp;
if(type == POSITION_TYPE_BUY)
{
sl = open - dLoss;
tp = open + dGain;
}
else
{
sl = open + dLoss;
tp = open - dGain;
}
sl = NormalizePrice(sl);
tp = NormalizePrice(tp);
if(!trade.PositionModify(ticket, sl, tp))
{
PrintFormat("PositionModify failed. Retcode: %u, %s",
trade.ResultRetcode(),
trade.ResultRetcodeDescription());
return false;
}
return true;
}
Call FindPosition(MagicNumber) after the order is filled, and pass the ticket to SetExitFromEntry(). To see how to filter positions safely, read how to filter positions by magic number in MQL5.
Check the Stops Level First
A stop that is too close to the market is rejected with retcode 10016. Before you send SL and TP, or modify them, compare them with SYMBOL_TRADE_STOPS_LEVEL. The full explanation, including the spread trap and a validation helper, is in MQL5 invalid stops (10016). Other return codes are covered in the MQL5 trade retcodes guide.
Trailing Stops
Pine's trail_points and trail_offset are also measured in ticks. MQL5 gives you no trailing stop to switch on, so you write one and run it on each new bar or tick. Here is a basic version for a buy position:
void TrailBuy(ulong ticket, int offsetTicks)
{
if(!PositionSelectByTicket(ticket))
return;
if(PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY)
return;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double newSl = NormalizePrice(bid - TicksToPrice(offsetTicks));
double curSl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
// Only move the stop in the profitable direction
if(newSl > curSl)
trade.PositionModify(ticket, newSl, tp);
}
Validate the new stop against the stops level and the freeze level before you modify it, as described in the invalid stops post. Pine's trailing logic runs on its own bar-by-bar model, while this code reacts to live prices, so trades can close at different prices and times.
Netting vs Hedging Accounts
strategy.exit(from_entry = "Long") ties an exit to a specific entry. MT5 account types matter here:
- Hedging account: you can hold several positions on one symbol, so each entry can have its own SL and TP, which is closest to Pine's behavior.
- Netting account: all trades on a symbol merge into one position. You cannot give each entry its own exit, because there is only one position and one SL and TP.
Check the mode before you rely on per-entry exits:
long mode = AccountInfoInteger(ACCOUNT_MARGIN_MODE);
if(mode == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
Print("Hedging account: separate positions are possible.");
else
Print("Netting or exchange account: one position per symbol.");
Common Mistakes
- Using ticks as prices. Passing
200as a stop price instead of converting it to a price distance. - Treating ticks, points and pips as the same. The result is a stop that is 10 times too wide or too tight.
- Measuring from the signal price. Pine measures from the entry fill. Use option 2 if you need the same reference.
- Skipping the stops level check. The order fails with 10016.
- Forgetting the magic number. Without it the EA can select or modify another EA's position.
- Assuming netting behaves like hedging. Per-entry exits are not possible on a netting account.
- Expecting the same intrabar results. When SL and TP could both be reached inside one bar, Pine uses its own price-path assumption, while MT5 uses real or generated ticks. See why a Pine Script strategy gives different results in MQL5.
FAQ
What is strategy.exit in MQL5?
There is no function with that name. You set sl and tp on the position when you open it, or change them later with PositionModify.
Are Pine Script loss and profit in pips?
They are in ticks, not pips. Convert them to a price distance with the symbol's tick size, then round the price.
How do I convert Pine ticks to MQL5 price?
Multiply the tick count by SYMBOL_TRADE_TICK_SIZE, add or subtract it from the entry price depending on the direction, and normalize the result.
Can I have a separate exit for each entry in MT5?
On a hedging account, yes, because each entry is its own position. On a netting account all entries merge into one position.
How do I make an exit match Pine's entry price?
Wait until the position is open, read POSITION_PRICE_OPEN, and calculate SL and TP from that price.
Convert Entries and Exits Together
For the entry side of the same conversion, see strategy.entry() to MQL5 CTrade. Getting entries, exits, units and stop checks right by hand takes time. 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.
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.