# Grid Recovery Martingale EA - Complete MQL4 Source Code
This article provides a practical Expert Advisor that implements a grid recovery strategy with martingale lot progression. Unlike simple martingale EAs that risk blowing accounts, this EA includes multiple safety mechanisms: equity protection, maximum drawdown limit, profit target, and order expiration. The EA opens an initial position and places pending orders at fixed grid intervals.
Strategy Logic
The EA trades in one direction based on a configurable trend bias or both directions. When price moves against a position, the EA opens a new position at a predefined grid distance with increased lot size (martingale). The grid continues until the basket reaches profit target or hits the maximum drawdown limit. The EA includes an equity-based trailing stop to protect profits.
Complete MQL4 Code
```mql4
//+------------------------------------------------------------------+
//| GridRecoveryEA.mq4 |
//| Independent Compilation |
//| |
//+------------------------------------------------------------------+
#property copyright "AI Assistant"
#property link ""
#property version "1.00"
#property strict
//--- Grid parameters
input double InitialLot = 0.01; // Initial lot size
input double LotMultiplier = 1.5; // Martingale multiplier (1.0=no martingale)
input int GridDistance = 30; // Grid distance in pips
input int MaxGridLevels = 10; // Maximum grid levels (positions)
input bool TradeDirection = 1; // 0=both, 1=only buy, 2=only sell
//--- Risk management
input double BasketProfitTarget = 10.0; // Basket profit target in account currency
input double MaxDrawdownPercent = 20.0; // Maximum drawdown percent (0=off)
input int EquityTrailingStart = 5.0; // Start equity trailing when profit > this ($)
input int EquityTrailingStep = 2.0; // Equity trailing step ($)
//--- Order management
input int StopLoss = 0; // Stop loss per order (0=off)
input int TakeProfit = 0; // Take profit per order (0=off)
input int OrderExpiryMinutes = 0; // Order expiry in minutes (0=off)
input int Slippage = 3; // Allowed slippage
//--- Time & filters
input bool UseTimeFilter = false; // Enable time filter
input int StartHour = 8; // Trading start hour
input int EndHour = 20; // Trading end hour
input int MagicNumber = 202412; // EA magic number
//--- Global variables
double currentEquityPeak = 0;
datetime lastOrderTime = 0;
bool emergencyStop = false;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
if(InitialLot <= 0 || LotMultiplier < 1.0)
{
Print("Invalid lot parameters");
return(INIT_PARAMETERS_INCORRECT);
}
if(MaxGridLevels < 1) MaxGridLevels = 1;
currentEquityPeak = AccountEquity();
emergencyStop = false;
Print("Grid Recovery EA initialized. Peak equity: ", currentEquityPeak);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("Grid Recovery EA removed. Reason: ", reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
static datetime lastCheck = 0;
// Check every 5 seconds to avoid over-processing
if(TimeCurrent() - lastCheck < 5) return;
lastCheck = TimeCurrent();
// Update equity peak for trailing protection
double currentEquity = AccountEquity();
if(currentEquity > currentEquityPeak)
currentEquityPeak = currentEquity;
// Emergency stop check
if(emergencyStop)
{
Comment("EMERGENCY STOP ACTIVATED");
if(CountTotalPositions() > 0)
CloseAllPositions();
return;
}
// Check daily equity drawdown
if(MaxDrawdownPercent > 0)
{
double drawdownPercent = (currentEquityPeak - currentEquity) / currentEquityPeak * 100;
if(drawdownPercent >= MaxDrawdownPercent)
{
Print("Max drawdown reached: ", drawdownPercent, "%");
emergencyStop = true;
return;
}
}
// Check equity trailing stop
ManageEquityTrailingStop();
// Check basket profit target
double basketProfit = CalculateBasketProfit();
if(basketProfit >= BasketProfitTarget && BasketProfitTarget > 0)
{
Print("Basket profit target achieved: ", basketProfit);
CloseAllPositions();
return;
}
// Check time filter
if(UseTimeFilter)
{
int currentHour = TimeHour(TimeCurrent());
if(currentHour < StartHour || currentHour >= EndHour)
{
Comment("Outside trading hours");
return;
}
}
// Manage existing grid - open next level if needed
ManageGrid();
// Initial entry - no positions
if(CountTotalPositions() == 0 && CountPendingOrders() == 0)
{
OpenInitialPosition();
}
// Remove expired orders
if(OrderExpiryMinutes > 0)
RemoveExpiredOrders();
// Display info
DisplayInfo(basketProfit);
}
//+------------------------------------------------------------------+
//| Open initial position |
//+------------------------------------------------------------------+
void OpenInitialPosition()
{
int cmd = -1;
if(TradeDirection == 0)
{
// Random or trend-based - here using simple close comparison
double close1 = iClose(Symbol(), 0, 1);
double close2 = iClose(Symbol(), 0, 2);
cmd = (close1 > close2) ? OP_BUY : OP_SELL;
}
else if(TradeDirection == 1)
cmd = OP_BUY;
else if(TradeDirection == 2)
cmd = OP_SELL;
if(cmd != -1)
OpenOrder(cmd, InitialLot);
}
//+------------------------------------------------------------------+
//| Manage grid - open next level when price reaches grid distance |
//+------------------------------------------------------------------+
void ManageGrid()
{
int totalPositions = CountTotalPositions();
if(totalPositions >= MaxGridLevels) return;
if(totalPositions == 0) return;
// Find the worst position (farthest from current price)
double farthestPrice = 0;
int farthestType = -1;
double farthestOpenPrice = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() <= OP_SELL)
{
double distance = 0;
if(OrderType() == OP_BUY)
distance = (OrderOpenPrice() - Bid) / Point / 10;
else
distance = (Ask - OrderOpenPrice()) / Point / 10;
if(distance > farthestPrice)
{
farthestPrice = distance;
farthestType = OrderType();
farthestOpenPrice = OrderOpenPrice();
}
}
}
}
// Check if price moved far enough to open next level
bool shouldOpen = false;
double nextLot = InitialLot * MathPow(LotMultiplier, totalPositions);
if(farthestType == OP_BUY && farthestPrice >= GridDistance)
{
if(Bid <= farthestOpenPrice - GridDistance * Point * 10)
shouldOpen = true;
}
else if(farthestType == OP_SELL && farthestPrice >= GridDistance)
{
if(Ask >= farthestOpenPrice + GridDistance * Point * 10)
shouldOpen = true;
}
if(shouldOpen && nextLot <= MaxLotsAllowed())
{
OpenOrder(farthestType, NormalizeDouble(nextLot, 2));
Print("Opening grid level ", totalPositions + 1, " with lot ", nextLot);
}
}
//+------------------------------------------------------------------+
//| Open market order |
//+------------------------------------------------------------------+
void OpenOrder(int cmd, double lot)
{
double price = (cmd == OP_BUY) ? Ask : Bid;
double sl = 0, tp = 0;
if(StopLoss > 0)
{
if(cmd == OP_BUY)
sl = price - StopLoss * Point * 10;
else
sl = price + StopLoss * Point * 10;
}
if(TakeProfit > 0)
{
if(cmd == OP_BUY)
tp = price + TakeProfit * Point * 10;
else
tp = price - TakeProfit * Point * 10;
}
int ticket = OrderSend(Symbol(), cmd, lot, price, Slippage, sl, tp, "GridRecovery", MagicNumber, 0, clrNONE);
if(ticket > 0)
{
lastOrderTime = TimeCurrent();
Print("Order opened: ", (cmd == OP_BUY ? "BUY" : "SELL"), " Lot: ", lot, " Ticket: ", ticket);
}
else
{
Print("Order failed. Error: ", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Close all positions and pending orders |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), Slippage, clrNONE);
else if(OrderType() > OP_SELL)
OrderDelete(OrderTicket(), clrNONE);
}
}
}
}
//+------------------------------------------------------------------+
//| Count total positions (market orders) |
//+------------------------------------------------------------------+
int CountTotalPositions()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| Count pending orders |
//+------------------------------------------------------------------+
int CountPendingOrders()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() > OP_SELL)
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| Calculate total profit of basket |
//+------------------------------------------------------------------+
double CalculateBasketProfit()
{
double profit = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
profit += OrderProfit() + OrderSwap() + OrderCommission();
}
}
}
return profit;
}
//+------------------------------------------------------------------+
//| Equity trailing stop protection |
//+------------------------------------------------------------------+
void ManageEquityTrailingStop()
{
if(EquityTrailingStart <= 0) return;
static double lastTrailingEquity = 0;
double currentEquity = AccountEquity();
double peakSinceTrailing = currentEquityPeak;
if(peakSinceTrailing >= currentEquityPeak + EquityTrailingStart)
{
double drawdown = peakSinceTrailing - currentEquity;
if(drawdown >= EquityTrailingStep && lastTrailingEquity == 0)
{
lastTrailingEquity = peakSinceTrailing - EquityTrailingStep;
}
if(lastTrailingEquity > 0 && currentEquity <= lastTrailingEquity)
{
Print("Equity trailing stop triggered");
CloseAllPositions();
emergencyStop = true;
}
}
else
{
lastTrailingEquity = 0;
}
}
//+------------------------------------------------------------------+
//| Remove expired pending orders |
//+------------------------------------------------------------------+
void RemoveExpiredOrders()
{
if(OrderExpiryMinutes <= 0) return;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderType() > OP_SELL)
{
datetime orderTime = OrderOpenTime();
if(TimeCurrent() - orderTime >= OrderExpiryMinutes * 60)
{
OrderDelete(OrderTicket(), clrNONE);
Print("Expired order deleted: ", OrderTicket());
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| Calculate maximum allowed lot based on account balance |
//+------------------------------------------------------------------+
double MaxLotsAllowed()
{
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double balanceBased = AccountBalance() * 0.1 / 100000; // 10% risk per 100k
double allowed = MathMin(maxLot, balanceBased);
if(lotStep > 0)
allowed = MathFloor(allowed / lotStep) * lotStep;
return MathMax(allowed, 0.01);
}
//+------------------------------------------------------------------+
//| Display information on chart |
//+------------------------------------------------------------------+
void DisplayInfo(double basketProfit)
{
string info = "";
info += "=== Grid Recovery EA ===\n";
info += "Positions: " + (string)CountTotalPositions() + "/" + (string)MaxGridLevels + "\n";
info += "Basket Profit: " + DoubleToStr(basketProfit, 2) + "\n";
info += "Current Equity: " + DoubleToStr(AccountEquity(), 2) + "\n";
info += "Peak Equity: " + DoubleToStr(currentEquityPeak, 2) + "\n";
info += "Drawdown: " + DoubleToStr((currentEquityPeak - AccountEquity()) / currentEquityPeak * 100, 2) + "%";
Comment(info);
}
//+------------------------------------------------------------------+
```
Parameter Explanation
| Parameter | Description | Recommended Value |
|-----------|-------------|-------------------|
| InitialLot | Starting lot size | 0.01 for small accounts |
| LotMultiplier | Martingale multiplier (1.0=no martingale) | 1.3-1.8 |
| GridDistance | Distance between grid levels in pips | 25-50 |
| MaxGridLevels | Maximum number of grid positions | 5-10 |
| TradeDirection | 0=both, 1=only buy, 2=only sell | 0 |
| BasketProfitTarget | Close all when basket profit reaches this | 5-20 |
| MaxDrawdownPercent | Emergency stop at this drawdown | 15-30 |
| EquityTrailingStart | Start trailing after profit threshold | 5-10 |
| EquityTrailingStep | Trail step distance | 2-5 |
| StopLoss | Per-order stop loss (0=off) | 0 |
| TakeProfit | Per-order take profit (0=off) | 0 |
| OrderExpiryMinutes | Auto-delete pending orders | 60-240 |
| MagicNumber | EA identifier | Any unique number |
Compilation & Installation
1. Copy code to MetaEditor (F4 in MT4)
2. Click Compile (F7) - expect 0 errors
3. Attach EA to any chart (H1 recommended)
4. Set parameters in Inputs tab
5. Enable AutoTrading
Important Safety Notes
Compilation Tips
Reference
Independently compiled. Grid recovery logic based on common basket trading principles with enhanced equity protection mechanisms.
*For premium EAs with AI signal filtering and advanced risk management, explore our professional EA suite with full backtest reports and 24/7 support.*