MQL5 Martingale Grid Strategy: Combined EA Code, Risk & Backtest
This educational hedging-account EA combines chart-symbol grid spacing with increasing requested volume at each additional level. It shows how to count successful entries, enforce a prospective open-volume cap, and wait for basket closures before resetting a side. Increasing size changes exposure and does not ensure recovery. No reproducible 2021–2025 backtest supports performance figures for this source.
Strategy logic
Entry conditions
On the first tick, the EA requests an initial buy and sell sequentially; they are not an atomic pair. Additional buys require the ask to fall one grid step below the lowest open buy, and sells require the bid to rise one step above the highest open sell. The requested next volume is initial volume multiplied by the multiplier to the number of existing successful entries in that side’s cycle. MaxLevels includes the seed. Lots are rounded down to broker steps, and a new request that would exceed the matching open-volume cap is rejected. No additional level is counted after a failed request.
Exit conditions
Each side starts closing when its floating profit plus swap reaches the target in account currency; commissions and realized outcomes are not included in that trigger. Closing is latched and retried until that side has no matching positions, then a later tick can seed it again. There is no cash-loss or drawdown-percentage stop and no broker-side SL/TP. The volume cap restricts new exposure, not money lost. If existing matching volume exceeds the cap or the initial pair cannot be established, the EA halts new entries and retries closing both sides. Restart state is not reconstructed: initialization rejects existing matching positions. Manual or broker-initiated closures are not reconciled into the level counters; stop the template and review the account before attempting to resume.
MQL5 Expert Advisor Code
//+------------------------------------------------------------------+
//| MartingaleGrid EA — MQL5 Example for pineify.app |
//| Combines fixed grid spacing with martingale lot progression |
//| DISCLAIMER: High drawdown strategy. Test on demo before live. |
//+------------------------------------------------------------------+
#property copyright "Pineify.app"
#property version "1.00"
#property strict
//--- Input parameters
input double InpInitialLot = 0.01; // Initial lot size
input double InpMartingale = 2.0; // Martingale multiplier
input int InpGridStep = 500; // Grid step in points
input double InpTakeProfitMoney = 20.0; // Floating target in account currency
input double InpMaxLots = 5.0; // Matching open-volume cap, not a cash-loss limit
input int InpMaxLevels = 6; // Maximum grid levels per direction
input int InpMagic = 202406; // Magic number
//--- Global variables
int g_buyLevels = 0, g_sellLevels = 0;
bool g_started = false, g_halted = false;
bool g_buyClosing = false, g_sellClosing = false;
//+------------------------------------------------------------------+
//| Expert initialization |
//+------------------------------------------------------------------+
int OnInit()
{
if(InpInitialLot <= 0 || InpMartingale < 1 || InpGridStep <= 0 ||
InpMaxLevels < 1 || InpMaxLots <= 0 || InpTakeProfitMoney <= 0 || InpMagic < 0)
return INIT_PARAMETERS_INCORRECT;
if(AccountInfoInteger(ACCOUNT_MARGIN_MODE) != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
return INIT_PARAMETERS_INCORRECT;
if(TotalLots() > 0) { Print("Existing matching positions require reconciliation"); return INIT_FAILED; }
double seed = NormalizeLot(InpInitialLot);
if(seed <= 0 || 2 * seed > InpMaxLots) return INIT_PARAMETERS_INCORRECT;
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert tick handler |
//+------------------------------------------------------------------+
void OnTick()
{
if(TotalLots() > InpMaxLots + 0.00000001) g_halted = true;
if(g_halted) { CloseAll(); return; }
if(!g_started)
{
g_started = true;
bool buyOk = OpenOrder(ORDER_TYPE_BUY, InpInitialLot);
if(buyOk) g_buyLevels = 1;
bool sellOk = buyOk && OpenOrder(ORDER_TYPE_SELL, InpInitialLot);
if(sellOk) g_sellLevels = 1;
if(!buyOk || !sellOk) { g_halted = true; CloseAll(); }
return;
}
if(BasketProfit(ORDER_TYPE_BUY) >= InpTakeProfitMoney) g_buyClosing = true;
if(BasketProfit(ORDER_TYPE_SELL) >= InpTakeProfitMoney) g_sellClosing = true;
if(g_buyClosing)
{
CloseByType(ORDER_TYPE_BUY);
if(LowestOpenPrice(ORDER_TYPE_BUY) == 0) { g_buyClosing = false; g_buyLevels = 0; }
}
if(g_sellClosing)
{
CloseByType(ORDER_TYPE_SELL);
if(HighestOpenPrice(ORDER_TYPE_SELL) == 0) { g_sellClosing = false; g_sellLevels = 0; }
}
if(g_buyClosing || g_sellClosing) return;
if(g_buyLevels == 0)
{
if(OpenOrder(ORDER_TYPE_BUY, InpInitialLot)) g_buyLevels = 1;
return;
}
if(g_sellLevels == 0)
{
if(OpenOrder(ORDER_TYPE_SELL, InpInitialLot)) g_sellLevels = 1;
return;
}
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double lowestBuy = LowestOpenPrice(ORDER_TYPE_BUY);
double highestSell = HighestOpenPrice(ORDER_TYPE_SELL);
if(lowestBuy > 0 && (lowestBuy - ask) / _Point >= InpGridStep && g_buyLevels < InpMaxLevels)
if(OpenOrder(ORDER_TYPE_BUY, InpInitialLot * MathPow(InpMartingale, g_buyLevels))) g_buyLevels++;
if(highestSell > 0 && (bid - highestSell) / _Point >= InpGridStep && g_sellLevels < InpMaxLevels)
if(OpenOrder(ORDER_TYPE_SELL, InpInitialLot * MathPow(InpMartingale, g_sellLevels))) g_sellLevels++;
}
//+------------------------------------------------------------------+
//| Expert deinitialization |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("MartingaleGrid EA removed. Reason: ", reason);
}
//+------------------------------------------------------------------+
//| Open a market order |
//+------------------------------------------------------------------+
bool OpenOrder(ENUM_ORDER_TYPE type, double lots)
{
lots = NormalizeLot(lots);
if(lots <= 0 || TotalLots() + lots > InpMaxLots + 0.00000001) return false;
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = lots;
req.type = type;
req.price = (type == ORDER_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
req.deviation = 10;
req.magic = InpMagic;
req.comment = "MartGrid";
req.type_filling = ORDER_FILLING_FOK;
if(!OrderSend(req, res) || res.retcode != TRADE_RETCODE_DONE)
{
Print("OrderSend failed: ", res.retcode, " ", res.comment);
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Close all positions of given type |
//+------------------------------------------------------------------+
void CloseByType(ENUM_ORDER_TYPE type)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket) &&
PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagic &&
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == (ENUM_POSITION_TYPE)type)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = PositionGetDouble(POSITION_VOLUME);
req.type = (type == ORDER_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
req.price = (req.type == ORDER_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
req.deviation = 10;
req.magic = InpMagic;
req.position = ticket;
req.type_filling = ORDER_FILLING_FOK;
if(!OrderSend(req, res) || res.retcode != TRADE_RETCODE_DONE)
Print("Basket close not complete: ", res.retcode, " ", res.comment);
}
}
}
//+------------------------------------------------------------------+
//| Close all positions |
//+------------------------------------------------------------------+
void CloseAll()
{
CloseByType(ORDER_TYPE_BUY);
CloseByType(ORDER_TYPE_SELL);
}
//+------------------------------------------------------------------+
//| Sum floating profit for one direction |
//+------------------------------------------------------------------+
double BasketProfit(ENUM_ORDER_TYPE type)
{
double profit = 0;
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket) &&
PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagic &&
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == (ENUM_POSITION_TYPE)type)
profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
}
return profit;
}
//+------------------------------------------------------------------+
//| Total open lots across all positions |
//+------------------------------------------------------------------+
double TotalLots()
{
double total = 0;
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket) &&
PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagic)
total += PositionGetDouble(POSITION_VOLUME);
}
return total;
}
//+------------------------------------------------------------------+
//| Lowest open price among buy positions |
//+------------------------------------------------------------------+
double LowestOpenPrice(ENUM_ORDER_TYPE type)
{
double lowest = DBL_MAX;
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket) &&
PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagic &&
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == (ENUM_POSITION_TYPE)type)
lowest = MathMin(lowest, PositionGetDouble(POSITION_PRICE_OPEN));
}
return (lowest == DBL_MAX) ? 0 : lowest;
}
//+------------------------------------------------------------------+
//| Highest open price among sell positions |
//+------------------------------------------------------------------+
double HighestOpenPrice(ENUM_ORDER_TYPE type)
{
double highest = 0;
for(int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket) &&
PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagic &&
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == (ENUM_POSITION_TYPE)type)
highest = MathMax(highest, PositionGetDouble(POSITION_PRICE_OPEN));
}
return highest;
}
//+------------------------------------------------------------------+
//| Normalize lot to broker minimum step |
//+------------------------------------------------------------------+
double NormalizeLot(double lots)
{
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
if(lotStep <= 0 || !MathIsValidNumber(lots)) return 0;
lots = MathFloor(MathMin(maxLot, lots) / lotStep) * lotStep;
if(lots < minLot) return 0;
return NormalizeDouble(lots, 8);
}
//+------------------------------------------------------------------+Copy this code into MetaEditor, save it in the MQL5/Experts folder, and compile with F7.
Generate a custom Multi-pair martingale-grid EA
Describe the entry, exit, position, order, and risk rules you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Strategy orders are simulated; live automation needs a separate integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
The calculation can be implemented on either platform, but the runtime model and buffer APIs differ.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.