MetaTrader MQL Course. Module 12: Using a Trailing Stop in an Expert Advisor

Using a Trailing Stop in an Expert Advisor

In the last module we implemented a channel strategy.   If you’d like to review that module, click here: Using Trading Channels in an Expert Advisor.

In this module we are going to implement a trailing stop. This will require using many MQL functions including the “OrderModify” function.

Usually a trade must be profitable before applying a trailing stop, so we’ll use the MQL function OrderProfit() to find out if the trade is profitable.

The “OrderProfit” belongs to the group of functions that can be used after the “OrderSelect()” function has been called to get all kinds of information about the current order (or trade).  This was covered in an earlier module, to review it click here: Trade Managing Using OrderSelect.

Each time our EA is called we’ll check if our trade is profitable and then we’ll check if the stoploss should be closer.  If the stoploss should be closer, we’ll use the MQL function “OrderModify” to adjust the stoploss value.

Naturally, we’ll have to implement the logic slightly different depending on the type of trade – a buy or sell.

We’ll create a new user-function called “fnTrailingStop” that will accept one parameter: the number of points to trail.

This is the definition of the “OrderModify” MQL function.

bool OrderModify( int ticket, double price, double stoploss, double takeprofit, datetime expiration, color arrow_color)

The ticket parameter is the ticket number of the order that will be modified.

The price parameter is only used for pending orders. I just set it to the open price.

The stoploss parameter is the new stoploss value.

The takeprofit parameter is the new takeprofit value.

The expiration parameter is only used for pending orders. You can set it to zero.

The arrow_color parameter is the color of the arrow to draw on the chart.

The math used to determine the stoploss can be a little confusing. The MQL function OrderStopLoss returns the current stoploss of the order as a price value, for example 1.2345.  And of course the Bid variable holds the current Bid price, for example 1.2395.

The trailing stop value is usually and integer value, for example 15.  A mistake I made when I first worked with MQL was to subtract the trailing stop value from the Bid price, for example:

1.2395 – 15 = -13.7065

You need to multiple the trailing stop value by the MQL Point() function!

1.2395 – 15*Point() = 1.2395 – 0.0015 = 1.2380

So for a Buy trade, the logic for checking the stoploss (in MQL code) is:

OrderStopLoss() < (Bid-(TrailingStop*Point)) )

Next, let’s see the MQL code for using a trailing stop within an Expert Advisor

 This is the MQL code for the complete Expert Advisor with the “fnTrailingStop” function.

// these are all externs so they can be changed when the EA is attached to a chart
// the values set are default values
extern int stoploss=200;
extern int takeprofit=200;
extern double lots = 1.0;
extern int magic_number=12345;
extern int rsi_period=12;
extern double rsi_buy_level=75.0;
extern double rsi_sell_level=25.0;

extern int close_day=5;
extern int close_hour=14;

int start()
{
// get the rsi value
double rsi_value = iRSI(Symbol(), Period(), rsi_period, PRICE_CLOSE, 0);
// this variable will hold the number of trades open for this EA (as defined by magic number)
int my_trades=0;
// this variable will holds the total number of trades for the entire account
int all_trades=OrdersTotal();

// use a for loop to cycle through all of the trades, from 0 up to all_trades
for( int cnt=0;cnt<all_trades;cnt++ )
{
// use OrderSelect to get the info for each trade, cnt=0 the first time, then 1, 2, .., etc
// if OrderSelect fails it returns false, so we just continue
if( OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES) == false )
continue;

// compare the magic_number of our EA (as passed in as an input parameter) to the order’s magic number
// if they are equal, increment my_trades
if( magic_number == OrderMagicNumber() )
{
my_trades++;

if( OrderType() == OP_BUY )
{
// this is the explicit close logic for a buy order
// if the rsi OR it is friday
if( (rsi_value < rsi_sell_level) || (fnCheckExit(close_day, close_hour) == true) )
{
// use the ticket info to close
// note: the price should be Bid for a Buy order
// using OrderLots()will close the entire order
OrderClose(OrderTicket(), OrderLots(), Bid, 3, Green);
}
}

if( OrderType() == OP_SELL )
{
// this is the explicit close logic for a sell order
// if the rsi OR it is friday
if( (rsi_value > rsi_buy_level) || (fnCheckExit(close_day, close_hour) == true) )
{
// use the ticket info to close
// note: the price should be Ask for a Sell order
// using OrderLots()will close the entire order
OrderClose(OrderTicket(), OrderLots(), Ask, 3, Green);
}
}
}
}

// my_trades should either be 1 or 0. if it is greater than zero, then we just exit
if( my_trades > 0 )
return(0);

// call the fnCheckEntry to see if we should open a buy
if(fnCheckChannel() == OP_BUY)
{
// call our user function with a SELL parameter
fnOpenTrade(Symbol(), OP_BUY);
// exit after trying to open a trade
return(0);
}

// call the fnCheckEntry to see if we should open a sell
if(fnCheckChannel() == OP_SELL)
{
// call our user function with a SELL parameter
fnOpenTrade(Symbol(), OP_SELL);
// exit after trying to open a trade
return(0);
}

fnTrailingStop(15);
return(0);
}

void fnOpenTrade(string symbol, int type)
{
// notice that the symbol is passed in as a parameter
// get the bid value for whatever symbol was sent in
double my_bid = MarketInfo(symbol, MODE_BID);
// get the ask value for whatever symbol was sent in
double my_ask = MarketInfo(symbol, MODE_ASK);
// get the point value for whatever symbol was sent in
double my_point = MarketInfo(symbol, MODE_POINT);

// now instead of using the Bid, Ask and Point values that give the value for the
// symbol that the EA is running, use our values from above

int status =
OrderSend( symbol, // the synbol for this chart
type, // a buy order
lots, // number of lots
my_bid, // use the ask price for a BUY
3, // allow the price up to move 3 points
my_bid + (stoploss*my_point), // stop
my_ask – (takeprofit*my_point), // limit
“My Simple EA”, // comment to see in Terminal
magic_number, // a unique # to id this trade
0, // expiration, doesn’t work
Red // a blue arrow
);

if( status < 0 )
Comment(“OrderSend Failed!! Error=”, GetLastError());
}

// This function returns true to indicate it’s time to close
bool fnCheckExit(int day, int hour)
{
// if today is Friday(5) AND the hour is at, or past, 2:00 PM
if( (DayOfWeek() == day) && (Hour() >= hour) )
return(true);

return(false);
}

int fnCheckEntry()
{
double dx_plus_now = iADX(Symbol(), Period(), 14, PRICE_CLOSE, MODE_PLUSDI, 0);
double dx_plus_before = iADX(Symbol(), Period(), 14, PRICE_CLOSE, MODE_PLUSDI, 1);
double dx_minus_now = iADX(Symbol(), Period(), 14, PRICE_CLOSE, MODE_MINUSDI, 0);
double dx_minus_before = iADX(Symbol(), Period(), 14, PRICE_CLOSE, MODE_MINUSDI, 1);

if( (dx_plus_before < dx_minus_before) && (dx_plus_now > dx_minus_now) )
return(OP_BUY);

if( (dx_plus_before > dx_minus_before) && (dx_plus_now < dx_minus_now) )
return(OP_SELL);

return(-1);
}

int fnCheckChannel()
{
// get the bar (or candle) of the highest high
int high_bar = iHighest(Symbol(), Period(), MODE_HIGH, 24, 0);
// use the bar as an index to get the high value
double high = iHigh(Symbol(), Period(), high_bar );

// get the bar (or candle) of the lowest low
int low_bar = iLowest(Symbol(), Period(), MODE_LOW, 24, 0);
// use the bar as an index to get the low value
double low = iLow(Symbol(), Period(), low_bar );

// buy if the price breaks the high of the channel
if( Ask > high )
return(OP_BUY);

// sell if the price breaks the low of the channel
if( Bid < low )
return(OP_SELL);

return(-1);
}
// apply a trailing stop to any trades that match our magic number
void fnTrailingStop(int TrailingStop)
{
// this variable will holds the total number of trades for the entire account
int all_trades=OrdersTotal();

// use a for loop to cycle through all of the trades, from 0 up to all_trades
for( int cnt=0;cnt<all_trades;cnt++ )
{
// use OrderSelect to get the info for each trade, cnt=0 the first time, then 1, 2, .., etc
// if OrderSelect fails it returns false, so we just continue
if( OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES) == false )
continue;

// compare the magic_number of our EA (as passed in as an input parameter) to the order’s magic number
// if they are equal, increment my_trades
if( magic_number == OrderMagicNumber() )
{
// if this trade has a profit greater than zero
if( OrderProfit() > 0 )
{
// for a Buy trade
if( OrderType() == OP_BUY )
{
// if the stoploss is within TrailingStop points of the current bid price
if( OrderStopLoss() < (Bid-(TrailingStop*Point)) )
{
// modify the order: everything stays the same but the stoploss
OrderModify(OrderTicket(), OrderOpenPrice(), (Bid-(Point*TrailingStop)), OrderTakeProfit(), 0, Yellow);
return(0);
}
}

// for a Sell trade
if( OrderType() == OP_SELL )
{
// if the stoploss is within TrailingStop points of the current bid price
if( OrderStopLoss() > (Ask+(TrailingStop*Point)) )
{
// modify the order: everything stays the same but the stoploss
OrderModify(OrderTicket(),OrderOpenPrice(),(Ask+(TrailingStop*Point)),OrderTakeProfit(), 0, Purple);
return(0);
}
}

}
}
}
}

Notice that the “fnTrailingStop” function is called at the end of the main function.  The reason for this is that according to the MetaTrader documentation an Expert Advisor should exit after making a request to the trade server.  The request can be to open, close or modify a trade.

Actually the safest thing to do is to force your Expert Advisor to sleep for about 5 seconds after making a request to the trading server.  This amount of time should guarantee that the request has had time to complete.  The MQL function “Sleep” can be used to pause your EA. The “Sleep” function has one parameter named “milliseconds”.  To put your EA to sleep for 5 seconds you would pass in 5000: “Sleep(5000);”  (5000 milliseconds equals 5 seconds).

In practice, simply exiting the EA usually allows sufficient time for any request to complete.  But if you see too many ERR_TRADE_CONTEXT_BUSY errors, then you may want to add the “Sleep” function to your EA.

To make this EA more usable, as an exercise you should provide the trailing stop as an input to the EA and perhaps enhance the function so that it can modify a trade for any currency symbol, not just the symbol on the current chart.  To review the module where this was introduced, click here: Opening Trades for Any MetaTrader Symbol.

Next module we’ll start working with user-drawn trend lines!