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 (1)
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?