MetaTrader MQL Course. Module 13: Using a Trendline in an Expert Advisor

Using a Trendline in an Expert Advisor

In the last module we implemented a trailing stop strategy.   If you’d like to review that module, click here: Using a Trailing Stop in an Expert Advisor.

In this module our Expert Advisor is going to read a user-drawn trendline from a chart and determine if the price has broken the trendline.

First we need a short math lesson.  Sorry, there’s just no way around it!

If you remember back in school, there was an equation for a straight line (on a x-y graph):

y = mx + b          where:

  •  m is the slope of the line
  • b is the offset
  • x is the value on the horizontal axis
  • y is the value on the vertical axis

Furthermore, the slope is defined as the change in the y values divided by the change in the x values.  Here is the equation:

m = (y2-y1)/(x2-x1)        where:

y2, y1, x2 and x1 are values taken from the line.

And, finally, b is defined as:

b = y1 – m(x1)

We are going to use these equations to determine if the price has broken the trendline. In the chart below, the thick red line is a user-drawn trendline.  The drew the 2 horizontal and vertical lines to illustrate the y2, y1, x2 and x1 values.

trendline

Basically we are going to use the equation y=mx+b to find the projected value of the trend line at any value of x.  The y value is price and the x value is the time.  We are interest in the value of the trendline at time “now” – you know, right now.

You see, once we know the projected value of the trendline for the current time (now), we can compare it to the current price. That’s what we are looking for: to see if the current price is above or below the value of the trendline.

That’s it for the math. Now back to the MQL.  We’ll need to use the MQL function ObjectGet.  This is the definition of the “ObjectGet” function.

double ObjectGet( string name, int index)

The parameter name is the name of the trendline.  This is really important as this is the only way we can find our trendline.  After you manually draw a trendline on a MetaTrader price chart, select and right click, choose properties and define the name.  This is the same name you’ll need to set as input to the EA.

The parameter index defines the value that you want to get.  There are many. The indices we’ll use here are:

  • OBJPROP_TIME1
  • OBJPROP_TIME2
  • OBJPROP_PRICE1
  • OBJPROP_PRICE2

We also use the MQL function “TimeCurrent” to get the current time. The time values that we use here are all integer values – they are the number of seconds that have elapsed since January 1, 1970.

Lastly, we need to define the type of trendline that is drawn: a trendline that is drawn above the price points, or a trendline that is drawn below the price points.

We’ll create a new user-define function called “fnGetTrendLineSignal”, it will take 2 parameters: the name of the trendline and the type of trendline, and will return OP_BUY, OP_SELL or -1.

This is the MQL code using the “fnGetTrendLineSignal” function as a trade entry. (Note that the “fnGetTrendLineSignal” can be used as an trade exit as well as an trade entry.)

// 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;

extern string trendline_name=”mytrendline”;
extern int trendline_type=0; // 0=upper, 1=lower

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(fnGetTrendLineSignal(trendline_name, trendline_type) == 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(fnGetTrendLineSignal(trendline_name, trendline_type) == 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);
}
}

}
}
}
}

int fnGetTrendLineSignal(string strName, int type)
{
// get the four coordinates
double x1 = ObjectGet( strName, OBJPROP_TIME1);
double y1 = ObjectGet( strName, OBJPROP_PRICE1);
double x2 = ObjectGet( strName, OBJPROP_TIME2);
double y2 = ObjectGet( strName, OBJPROP_PRICE2);

// calculate the slope of the line (m)
double m = (y2-y1)/(x2-x1);

// calcualte the offset (b)
double b = y1 -m*x1;

// get the current x value
double time = TimeCurrent();

// calculate the value (y) of the projected trendline at (x): y = mx + b
double value = m*time + b;

// if type is an upper trend line (line is above price points)
if( type == 0 )
{
// price has broken the trendline
// the bid price is higher than the projected value
if( Bid > value )
return(OP_BUY);
}

// if type an is lower trend line (line is below price points)
if( type == 1 )
{
// price has broken the trendline
// the ask price is lower than the projected value
if( Ask < value )
return(OP_SELL);
}

return(-1);
}

Copy and paste this code into the MetaEditor and experiment with it.  See if you can remove the need for the “type” parameter and have the EA determine for itself if the trendline was drawn above or below most of the price points.

MQL offers many “Object” functions. An object in this context refers to anything that can be drawn on the price charts: lines, arrows, etc.  We’ll continue to cover more of the object functions in later modules.