A very common bug in MT5 Expert Advisors: the code calls trade.Buy(...), it returns true, and the EA assumes it is now in a position. Then the next tick opens another one, or the EA manages a trade that does not exist.
The reason is that true from CTrade.Buy or OrderSend only means the request passed the local checks and was sent. Whether the trade server accepted it is in the result code.
Check the retcode, not just the return value
#include <Trade\Trade.mqh>
CTrade trade;
bool OpenBuy(double lots, double sl, double tp)
{
if(!trade.Buy(lots, _Symbol, 0.0, sl, tp, "entry"))
{
PrintFormat("Request not sent: error %d", GetLastError());
return false;
}
uint code = trade.ResultRetcode();
if(code == TRADE_RETCODE_DONE || code == TRADE_RETCODE_PLACED)
return true;
PrintFormat("Server rejected buy: %u %s", code, trade.ResultRetcodeDescription());
return false;
}
TRADE_RETCODE_DONE means a market order was filled. TRADE_RETCODE_PLACED means a pending order was accepted. Anything else is a rejection you should handle.
The rejections you will actually see
-
TRADE_RETCODE_REQUOTEandTRADE_RETCODE_PRICE_OFF: price moved. Refresh prices and retry a limited number of times. -
TRADE_RETCODE_INVALID_STOPS: stop loss or take profit too close. ReadSYMBOL_TRADE_STOPS_LEVELand widen them. -
TRADE_RETCODE_NO_MONEY: not enough free margin. Recalculate the lot size, do not retry. -
TRADE_RETCODE_MARKET_CLOSED: session is closed. Wait, do not loop. -
TRADE_RETCODE_INVALID_FILL: the broker does not support the filling mode you sent. See below.
Set the filling mode the broker supports
Many EAs work on one broker and fail on another only because of the filling mode. Ask the symbol what it allows:
void SetFilling()
{
long modes = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if((modes & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
trade.SetTypeFilling(ORDER_FILLING_FOK);
else if((modes & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
trade.SetTypeFilling(ORDER_FILLING_IOC);
else
trade.SetTypeFilling(ORDER_FILLING_RETURN);
}
Call it once in OnInit after you create the CTrade object.
Confirm the position before you manage it
Even after TRADE_RETCODE_DONE, base your management logic on what is really open: loop over PositionsTotal(), match your magic number and symbol, and only then trail or close. That way a rejected order, a manual close or a restart never leaves the EA believing something that is not true.
Small change, but it removes a whole class of "the EA went crazy" support tickets. If you have hit a retcode that is not on this list, drop it in the comments and I will explain what it means.
Top comments (4)
I would keep TRADE_RETCODE_DONE_PARTIAL as a separate outcome rather than treating everything outside DONE and PLACED as a rejection. With IOC, a partial fill can leave real exposure. Retrying the full requested volume can then add more than intended. Would you add a partial-fill case and compare the filled volume with the requested volume before deciding whether anything remains to send?
Yes, that is a real gap in the snippet. TRADE_RETCODE_DONE_PARTIAL should be its own branch. I would read trade.ResultVolume() and compare it with the requested volume, treat the filled part as a live position straight away so its stop and take profit are set, and then decide on the remainder separately. Resending the full original size is exactly how an EA ends up with more exposure than planned. For IOC fills I usually send only requested minus filled, normalized to the volume step, and skip it if that is below the symbol minimum. Thanks for pointing it out.
I would make sending that remainder a fresh decision, rather than an automatic retry. The partial fill may already use the current risk allowance, or the entry condition may have changed before the next request. A test where the first fill succeeds but the remaining size is no longer allowed would check that the EA keeps the filled exposure without adding more.
Agreed, that is the safer design. The remainder should go back through the same checks as a new entry: current risk allowance including the filled part, spread, and whether the signal is still valid. Your test case is a good one to keep permanently, since it is exactly the situation where an automatic retry would double the exposure without anyone noticing.