Quick Answer
Before a bot trades real money, check what it does when things go wrong, not only what it does when the signal is right. The checks that catch the most problems are:
- Every trade has a stop loss.
- The lot size is calculated and checked against the symbol limits.
- The bot skips trading when the spread is too wide.
- Its own trades are identifiable (magic number in MQL5, label in cTrader).
- It looks for an existing position before it opens another one.
- The signal uses closed bars, and the logic does not run on every tick for no reason.
- The result of every trade call is checked.
- No hidden martingale-style sizing.
- A daily loss limit stops trading after a bad day.
Passing these checks does not mean a bot is profitable or safe. It means the code does not contain the most common avoidable mistakes. A bot should still be tested on a demo account and started with a small size.
From the author: I convert and review trading bots across Pine Script, MQL5 and cTrader C#. The same handful of problems show up again and again, and almost none of them is about the strategy idea. They are about missing stops, fixed lot sizes, doubled orders and unchecked results. This guide lists them in the order I would check them.
Key Facts
| Check | What goes wrong without it | |---|---| | Stop loss on every trade | One bad trade has no planned limit | | Calculated, normalized lot size | A fixed lot does not match the account, or the broker rejects the volume | | Spread check | Entries at the widest spread, for example at rollover or during news | | Magic number or label | The bot cannot tell its trades from other trades on the account | | Position check | A signal that stays true opens a new trade every tick or every bar | | Closed-bar signal | The signal appears and disappears while the bar is still forming | | Checked trade result | The bot assumes a trade opened when the broker refused it | | No hidden martingale | Position size grows after losses without a visible limit | | Daily loss limit | A bad day has no automatic stop |
1. Every Trade Has a Stop Loss
A trade without a stop loss has no planned worst case. Set the stop in the same call that opens the trade, so the position is never open without one.
MQL5:
#include <Trade/Trade.mqh>
CTrade trade;
input int StopLossPoints = 300;
void OpenBuy(double lots)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = NormalizeDouble(ask - StopLossPoints * _Point, _Digits);
trade.Buy(lots, _Symbol, ask, sl, 0.0);
}
cTrader C#:
[Parameter("Stop loss (pips)", DefaultValue = 30, MinValue = 1)]
public double StopLossPips { get; set; }
private void OpenBuy(double volumeInUnits)
{
var result = ExecuteMarketOrder(TradeType.Buy, SymbolName, volumeInUnits, "MyBot", StopLossPips, null);
if (!result.IsSuccessful)
Print("Order failed: {0}", result.Error);
}
If the stop is too close, the broker can reject it. See MQL5 error 10016 (invalid stops) for the minimum distance rules.
2. Lot Size Is Calculated and Checked
A fixed 0.1 lot means the same size on a small and on a large account. A better habit is to size the trade from the account and the stop distance, and then to round the result to what the symbol allows (minimum, maximum and step).
The full method, with the formulas, is in MQL5 lot size calculation. In cTrader, Symbol.NormalizeVolumeInUnits(volume) does the rounding to the allowed volume step.
3. Spread Check Before Entry
A signal at the moment of a wide spread costs more than the same signal at a normal spread. Add a maximum spread input and skip the trade when it is exceeded.
MQL5:
input int MaxSpreadPoints = 25;
bool SpreadOk()
{
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
return spread <= MaxSpreadPoints;
}
cTrader C#:
[Parameter("Max spread (pips)", DefaultValue = 2.0, MinValue = 0.1)]
public double MaxSpreadPips { get; set; }
private bool SpreadOk()
{
return Symbol.Spread / Symbol.PipSize <= MaxSpreadPips;
}
The right limit depends on the symbol and the strategy. Look at your own broker's typical spread first.
4. Magic Number (MQL5) or Label (cTrader)
If the bot cannot recognise its own trades, it cannot safely count them, manage them or close them. It can also touch trades that belong to you or to another bot.
MQL5: set a magic number and filter by it. The full pattern is in MQL5: filter positions by magic number.
input long MagicNumber = 240501;
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
return INIT_SUCCEEDED;
}
cTrader: pass a label when you open the trade, and use the same label when you look for your trades.
private const string BotLabel = "MyBot";
// opening: ExecuteMarketOrder(..., BotLabel, ...)
// finding: filter Positions by p.Label == BotLabel (see the next section)
A magic number that is hardcoded in the source is easy to forget when you run two copies of the same bot. Make it an input.
5. Check for an Existing Position
A signal such as "fast average is above slow average" stays true for many bars. If the bot does not check what is already open, it can open a new trade on every bar, or on every tick.
MQL5:
bool HasPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == MagicNumber)
return true;
}
return false;
}
cTrader C# (add using System.Linq; at the top of the file):
private bool HasPosition()
{
return Positions.Any(p => p.Label == BotLabel && p.SymbolName == SymbolName);
}
Decide on purpose whether the bot may hold more than one trade, and put that limit in the code.
6. Closed-Bar Signals, and Not Every Tick
A signal that reads the bar that is still forming can be true now and false a few seconds later. Backtests that use only closed bars will not show that. Read the last closed bar (shift 1 in MQL5, Last(1) in cTrader), and run the entry logic once per new bar unless the strategy truly needs tick data. The Pine Script version of this idea is in Pine Script close[1] in MQL5.
MQL5 new-bar check:
bool IsNewBar()
{
static datetime lastBar = 0;
datetime current = iTime(_Symbol, _Period, 0);
if(current == lastBar) return false;
lastBar = current;
return true;
}
In cTrader, put the entry logic in OnBar() instead of OnTick().
Related mistakes: creating an indicator inside OnTick() or OnBar() (create it once at start), and calling Sleep() or Thread.Sleep() in the main handler, which blocks the bot.
7. Check the Result of Every Trade Call
A trade request can fail: invalid stops, not enough money, market closed, requote. If the bot ignores the result, it carries on as if the trade existed.
In MQL5, read the return code after the call. MQL5 trade return codes lists what each code means. Check data calls as well, because CopyBuffer can return fewer values than you asked for; see the CopyBuffer guide.
if(!trade.Buy(lots, _Symbol, ask, sl, 0.0))
Print("Buy failed: ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription());
In cTrader, check result.IsSuccessful as in the first example.
8. Look for Hidden Martingale Sizing
Code that multiplies the lot after a loss (lots = lots * 2) is martingale-style sizing. It can look good in a short test and grow a position very fast in a losing streak. This is not a bug in the code sense, so the compiler will not warn you. Search for places where the lot or volume is multiplied, and make sure you know what the maximum size can become.
9. Add a Daily Loss Limit
A bot that has no limit keeps trading on a bad day. A daily loss limit stops new trades after a loss you chose in advance. The MQL5 implementation is in MQL5 daily loss limit.
Before You Go Live: A Routine
- Run the code checks above, or paste the code into the CodeFlowOS Code Review.
- Backtest with every-tick or real-tick data where the platform allows it. Every tick vs real ticks explains the difference.
- Check robustness, not only the final number: walk-forward analysis and Monte Carlo testing.
- Run on a demo account for long enough to see different market conditions and the bot's logs.
- Start live with a small size, watch the first trades one by one, and compare them with what the code should have done.
Backtest and demo results do not predict live results. Spread, slippage, latency and market conditions differ.
Common Mistakes
- No stop loss, or a stop that is set in a second call.
- A fixed lot size that never changes with the account.
- No magic number or label, or the same number in two bots.
- No position check, so one lasting signal opens many trades.
- Trading on the forming bar.
- Ignoring the result of the trade call.
- Martingale sizing hidden in a helper function.
- Going live straight after one good backtest.
FAQ
Related Guides
- MQL5 lot size calculation
- MQL5 daily loss limit
- MQL5: filter positions by magic number
- MQL5 trade return codes
- Every tick vs real ticks in MT5
- Pine Script to cTrader C#: How a Strategy Becomes a cBot
- MQL5 trailing stop EA: update stops without loosening risk
- MQL5 unsupported filling mode (10030): FOK, IOC and Return
- cTrader C# error CS0103: fix "name does not exist" in a cBot
- Pine Script strategy.exit to MQL5: stop loss, take profit and trailing exits
Check Your Bot's Code
Paste an MQL5 Expert Advisor or a cTrader cBot into the CodeFlowOS Code Review. It runs rule-based checks like the ones above, shows the findings with line numbers, and groups them by severity. Code Review is free during the early-bird period. The checks are pattern-based: a finding means "look here", not "this is a bug", and no finding does not mean the bot is safe. If the code does not compile, the Error Fixer can suggest a fix (1 credit per fix, up to 3 attempts), and you should read the change before you use it.
This article is educational. It does not describe trading performance and is not financial advice. Trading carries risk, including the loss of your capital. Always build the code on your platform, test it on a demo account and start with a small size.