A martingale Expert Advisor increases the position after every loss, so that the next win pays back everything lost before it plus a small profit. Its backtests tend to look the same: a smooth rising line, a very high share of winning series, and then a drop that takes a large part of the account in one go.
This guide puts numbers on both halves: how often a martingale wins, how likely the long losing streak is, what it costs when it comes, and why the expected result doesn't change. If you still want to run one, the last part is an MQL5 EA with the guardrails that limit the damage. It compiled in MetaEditor with 0 errors and 0 warnings.
Two kinds of martingale EA
- Classic. One trade at a time, each with a stop and a target. After a loss, the next trade is the multiplier times bigger. A win ends the series and the size goes back to the start.
- Grid (averaging down). Positions stay open. Each time price moves a set number of pips against the basket, the EA adds a bigger position. The whole basket closes when price comes back to a target beyond its average entry.
The martingale calculator computes both, level by level.
Why it wins so often
Take a classic martingale on EUR/USD: start at 0.01 lot, double after each loss, 20-pip stop and 20-pip target, at most 8 trades in a series. One pip on one lot is worth $10.
Every series that ends with a win nets $2, the first trade's target: the winning trade's profit covers all the losses before it plus $2. A series only fails if all 8 trades lose. At a 50% win rate that happens in 0.39% of series, so 99.6% of series end in profit. That's the smooth line.
The losing streak always comes
Over many trades, the question isn't whether a long streak happens but when. Exact probabilities of at least one streak of losses of a given length, for independent trades:
| Win rate | Trades | 6 losses in a row | 8 in a row | 10 in a row | 12 in a row |
|---|---|---|---|---|---|
| 40% | 250 | 99.5% | 82.5% | 45.2% | 19.1% |
| 40% | 1,000 | >99.9% | 99.9% | 91.5% | 58.2% |
| 50% | 250 | 86.8% | 38.4% | 11.2% | 2.9% |
| 50% | 1,000 | >99.9% | 86.1% | 38.5% | 11.4% |
| 60% | 250 | 45.9% | 9.2% | 1.5% | 0.2% |
| 60% | 1,000 | 91.7% | 32.4% | 6.1% | 1.0% |
At a 50% win rate, a run of 8 losses within 1,000 trades is 86% likely. Real markets make it worse, not better: losses cluster in trends, when a mean-reverting entry keeps fading a move that keeps going.
What it costs when it comes
Doubling from 0.01 lot with a 20-pip stop at $10 per pip per lot:
| Trades in the series | Last lot | Loss if all of them lose |
|---|---|---|
| 6 | 0.32 | $126 |
| 8 | 1.28 | $510 |
| 10 | 5.12 | $2,046 |
| 12 | 20.48 | $8,190 |
| 15 | 163.84 | $65,534 |
Each extra level doubles the loss of a full series, and the win it protects is still $2. Long before the last levels, the broker's maximum volume (error 10014) or your free margin (error 10019) may refuse the order, which ends the series at the worst moment.
The average doesn't change
If one trade has no edge, the expected result of a martingale series made of such trades is zero too. In the example above at a 50% win rate, the 99.6% of series that make $2 and the 0.39% that lose $510 average out to exactly $0.00 per series, the same as a single flat trade.
With a real edge, say a 55% win rate at 1:1, a flat 0.01-lot trade is worth +$0.20 on average. A martingale series is worth +$1.14 on average, because it bets more after losses, and it still loses the full $510 in 0.17% of series. The martingale doesn't create an edge. It changes the shape of the results: many small wins, rare large losses.
Grid martingales: the open loss
A grid version keeps every position open, so the risk is the open loss as price runs against the basket. Start at 0.10 lot, double every 20 pips, at most 8 positions: when the eighth opens, 140 pips from the first entry, the basket holds 25.5 lots with an open loss of $4,940. On a $5,000 account, a move of about 140 pips against the first entry wipes it out, margin calls aside. At that point price only needs to come back 29.4 pips for the basket to close in profit, which is why these EAs often survive, until a move doesn't come back.
With smaller steps (0.01 lot, multiplier 1.5) the same 140 pips cost $166 on 0.49 lots, and the account can take far more. The trade-off is the same as for the classic version: the further the EA can go, the more it loses when it gets there.
Guardrails if you run one anyway
- Cap the number of levels and accept the loss when a series runs out, instead of letting it grow.
- Cap the lot size independently of the level, so a bug or a high multiplier can't open a giant position.
- Set an equity stop from the starting balance: below it, the EA closes everything and stops.
- Size the full-series loss as a share of the account you'd accept losing in one go. The martingale calculator shows it before you test.
- Test on real ticks over years, with real spreads. Martingales are sensitive to the modelling mode; see MT5 Strategy Tester modelling modes.
- On a funded account, a single full series can breach the daily loss limit by itself. Check it with the prop firm drawdown calculator.
An MQL5 EA with hard caps
This EA trades one position at a time in the direction of a 50-period moving average, with a fixed stop and target in points. After a losing trade it multiplies the lot, up to MaxLevels trades and never above MaxLots; a win, or the last level, starts a new series. Below EquityStopPct from the starting balance it closes everything and halts. The entry rule is a placeholder: the point is the structure around it.
// Capped martingale: an example of the guardrails a martingale EA needs, not a strategy to trade.
#include <Trade/Trade.mqh>
input double StartLots = 0.01; // first lot of a series
input double Multiplier = 2.0; // lot multiplier after a losing trade
input int MaxLevels = 6; // trades in a series before it is abandoned
input double MaxLots = 0.50; // no single trade above this size
input int StopPoints = 200; // stop-loss distance in points
input int TakePoints = 200; // take-profit distance in points
input double EquityStopPct = 10.0; // stop everything at this % loss from the starting balance
input int MaPeriod = 50; // entry filter: trade in the direction of this SMA
input ulong Magic = 261111;
CTrade trade;
int maHandle = INVALID_HANDLE;
double startBalance = 0.0;
bool halted = false;
int level = 0; // 0 = first trade of a series
ulong lastDeal = 0; // last closing deal already counted
datetime lastBar = 0;
int OnInit()
{
maHandle = iMA(_Symbol, _Period, MaPeriod, 0, MODE_SMA, PRICE_CLOSE);
if(maHandle == INVALID_HANDLE)
return INIT_FAILED;
if(StartLots <= 0.0 || Multiplier < 1.0 || MaxLevels < 1 || MaxLots < StartLots || EquityStopPct <= 0.0)
return INIT_PARAMETERS_INCORRECT;
trade.SetExpertMagicNumber(Magic);
startBalance = AccountInfoDouble(ACCOUNT_BALANCE);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
IndicatorRelease(maHandle);
}
// Volume rounded down to the broker's step and kept within its limits; 0 if it can't be traded.
double NormalizeLots(double lots)
{
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minV = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxV = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
if(step <= 0.0)
return 0.0;
lots = MathFloor(lots / step + 1e-9) * step;
if(lots < minV)
return 0.0;
return NormalizeDouble(MathMin(lots, maxV), 8);
}
bool HasPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == (long)Magic)
return true;
}
return false;
}
void CloseAll()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == (long)Magic)
trade.PositionClose(ticket);
}
}
// After each closed trade: a loss moves the series up one level, a win (or the last level) starts a new series.
void UpdateLevel()
{
if(!HistorySelect(0, TimeCurrent()))
return;
for(int i = HistoryDealsTotal() - 1; i >= 0; i--)
{
ulong deal = HistoryDealGetTicket(i);
if(deal == 0 || HistoryDealGetInteger(deal, DEAL_MAGIC) != (long)Magic || HistoryDealGetString(deal, DEAL_SYMBOL) != _Symbol)
continue;
if(HistoryDealGetInteger(deal, DEAL_ENTRY) != DEAL_ENTRY_OUT)
continue;
if(deal == lastDeal)
return; // nothing new since the last check
lastDeal = deal;
double result = HistoryDealGetDouble(deal, DEAL_PROFIT) + HistoryDealGetDouble(deal, DEAL_SWAP) + HistoryDealGetDouble(deal, DEAL_COMMISSION);
if(result < 0.0 && level + 1 < MaxLevels)
level++;
else
{
if(result < 0.0)
PrintFormat("Series lost all %d levels: starting again from %.2f lots", MaxLevels, StartLots);
level = 0;
}
return;
}
}
void OnTick()
{
if(halted)
return;
// Equity floor, checked on every tick: close everything and stop for good.
double floorLevel = startBalance * (1.0 - EquityStopPct / 100.0);
if(AccountInfoDouble(ACCOUNT_EQUITY) <= floorLevel)
{
CloseAll();
halted = true;
PrintFormat("Equity stop hit at %.2f (floor %.2f): trading halted", AccountInfoDouble(ACCOUNT_EQUITY), floorLevel);
return;
}
datetime t = iTime(_Symbol, _Period, 0);
if(t == lastBar)
return;
lastBar = t;
if(HasPosition())
return;
UpdateLevel();
double lots = StartLots * MathPow(Multiplier, level);
if(lots > MaxLots)
{
PrintFormat("Level %d would trade %.2f lots, above the %.2f cap: series abandoned", level + 1, lots, MaxLots);
level = 0;
lots = StartLots;
}
lots = NormalizeLots(lots);
if(lots <= 0.0)
return;
double ma[];
if(CopyBuffer(maHandle, 0, 1, 1, ma) != 1)
return;
double close1 = iClose(_Symbol, _Period, 1);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(close1 > ma[0])
trade.Buy(lots, _Symbol, ask, NormalizeDouble(ask - StopPoints * _Point, _Digits), NormalizeDouble(ask + TakePoints * _Point, _Digits), StringFormat("level %d", level + 1));
else
trade.Sell(lots, _Symbol, bid, NormalizeDouble(bid + StopPoints * _Point, _Digits), NormalizeDouble(bid - TakePoints * _Point, _Digits), StringFormat("level %d", level + 1));
}
How the guardrails work:
- The series level comes from the trade history, not from a counter that a restart would reset:
UpdateLevel()reads the last closing deal of this EA (its magic number and symbol) and counts it once. - The result includes swap and commission, so a trade that hit its target but lost on costs counts as a loss.
MaxLevelsandMaxLotsend a series instead of extending it. With the defaults, 0.01 lot doubled reaches 0.32 lot at level 6, under the 0.50 cap.- The equity stop runs on every tick, before the new-bar check, so it reacts inside the bar.
NormalizeLots()rounds down to the broker's volume step and respects its minimum and maximum, the same rule as in calculating lot size from risk in MQL5.
Sources
- MQL5 Reference: HistorySelect() and HistoryDealGetDouble(): reading closed deals.
- MQL5 Reference: symbol properties: SYMBOL_VOLUME_STEP, SYMBOL_VOLUME_MIN and SYMBOL_VOLUME_MAX.
- The streak probabilities are exact (a dynamic program over the length of the current losing run), computed in Python.
Build it with Edgoro
Describe your EA in plain English, guardrails included ("no more than 5 levels, never above 0.5 lot, stop trading at 10% below the starting balance"), and the MT5 Expert Advisor generator writes the MQL5, compiled in MetaEditor before you get it. Then test it in the Strategy Tester on real ticks.
This article is for education and is not a recommendation to use martingale or grid strategies. They can lose a large part of an account in a single series. Test on a demo account first and never risk money you can't afford to lose.
Frequently asked questions
Why do martingale EAs blow up?
Because the size grows geometrically after each loss while the profit of a winning series stays small. A long losing streak is rare per series but likely over many trades: at a 50% win rate, 8 losses in a row within 1,000 trades has an 86% chance. When it comes, it costs far more than all the small wins.
Does a martingale increase expected profit?
No. With trades that have no edge, the expected result of a martingale series is zero, like a flat trade. It changes how results are distributed: many small wins and rare large losses.
How many levels should a martingale EA have?
As few as the loss of a full series allows. With 0.01 lot doubled and a 20-pip stop at $10 per pip per lot, 8 levels risk $510 and 12 levels $8,190, while a winning series still makes $2. Decide the loss you accept first, then the levels.
What guardrails should a martingale EA have?
A maximum number of levels, a maximum lot size, an equity stop that closes everything below a set loss from the starting balance, and a series level read from the trade history so a restart doesn't reset it.
Is a grid martingale safer than a classic one?
Not inherently. A grid keeps every position open, so the risk is the open loss when price keeps moving against the basket. With 0.10 lot doubled every 20 pips over 8 positions, a move of about 140 pips wipes out a $5,000 account.
Want our studies and guides first when you search? Add Edgoro as a preferred source on Google.