MetaTrader MQL Course. Module 14: Using MQL to Partially Close an Order

Using MQL to Partially Close an Order

In the last module we read the value of a user-drawn trendline and determined if the price broke trough the trendline.   If you’d like to review that module, click here: Using a Trendline in an Expert Advisor.

In this module we are going to implement a partial close of an open order. As a trade increases in profit, we’ll close a portion of the trade.

We’ll use the MQL function OrderClose (click here: Closing a Trade Using OrderClose to review). We’ll also use the MQL function MarketInfo (click here: Opening Trades for Any MetaTrader Symbol to review).

We’ll create user inputs for 2 profit levels and 2 lots sizes.  For example:

profit_level_1 = 10     lot_size_1 = 5.0

profit_level_2 = 25     lot_size_2 = 2.0

We’ll create a user function called “fnFormatLot” to format the lot size.  The OrderClose function will fail if the lot size is too large, too small or does not conform to the account type.  For example, a standard account can not close 1.2 lots, however a mini-account can close 1.2.  Furthermore some mini-accounts can close 1.23 lots and all micro account can as well.

The “fnFormatLot” function uses the MarketInfo function with modes of MODE_LOTSTEP, MODE_MINLOT and MODE_MAXLOT to correctly format the new lot size.

We’ll also create a user-function called “fnPartialClose” to implement the partial close. Each time the EA is called it is checked for any open orders that are profitable and then sets the lot size accordingly.  The arithmetic will need to be slightly different for a buy trade versus a sell trade.

This is the MQL code for the complete Expert Advisor implementing the “fnPartialClose” 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;

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

extern int profit_level_1 = 10;
extern double lot_size_1 = 5;
extern int profit_level_2 = 25;
extern double lot_size_2 = 2;

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);
fnPartialClose();
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);
}

void fnPartialClose()
{
double close_lot_size=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() )
{
if( OrderType() == OP_BUY )
{
// check each level
if( ((Bid – OrderOpenPrice()) * Point) > profit_level_1 )
{
if( OrderLots() > lot_size_1 )
{
close_lot_size = fnFormatLot(OrderLots() – lot_size_1);
OrderClose(OrderTicket(), close_lot_size, Bid, 3, Blue);
return;
}
}

// check each level
if( ((Bid – OrderOpenPrice()) * Point) > profit_level_2 )
{
if( OrderLots() > lot_size_2 )
{
close_lot_size = fnFormatLot(OrderLots() – lot_size_2);
OrderClose(OrderTicket(), close_lot_size, Bid, 3, Blue);
return;
}
}
}

if( OrderType() == OP_SELL )
{
// check each level
if( ((OrderOpenPrice() – Ask) * Point) > profit_level_1 )
{
if( OrderLots() > lot_size_1 )
{
close_lot_size = fnFormatLot(OrderLots() – lot_size_1);
OrderClose(OrderTicket(), close_lot_size, Ask, 3, Blue);
return;
}
}

// check each level
if( ((OrderOpenPrice() – Ask) * Point) > profit_level_2 )
{
if( OrderLots() > lot_size_2 )
{
close_lot_size = fnFormatLot(OrderLots() – lot_size_2);
OrderClose(OrderTicket(), close_lot_size, Ask, 3, Blue);
return;
}
}
}

}

}

}

double fnFormatLot(double dLots)
{
double min, max, step, lots;
int lotdigits=1;

step = MarketInfo(Symbol(), MODE_LOTSTEP);

if( step == 0.01 )
lotdigits=2;

if( step == 0.1 )
lotdigits=1;

lots = StrToDouble(DoubleToStr(dLots, lotdigits));

min = MarketInfo(Symbol(), MODE_MINLOT);
if( lots < min )
return(min);

max = MarketInfo(Symbol(), MODE_MAXLOT);
if( lots > max )
return(max);

return(lots);
}

Note where the “fnPartialClose” is called.  A good enhancement would be to create a user function to set the lot size for a certain takeprofit, for example “fnSetLotSize(double new_lot_size, int profit)”.

Why? Because there is some repeated code within the “fnPartialClose” function.  Elimination of replicated code leads to a higher quality, easier to maintain Expert Advisor.