Complete MQL5 guide & code

MQL5 Martingale EA: Audit the Sizing Logic and Recovery Claims

A martingale EA increases position size after losses; a high observed win rate alone cannot establish that the process is sustainable. This guide explains what to inspect in loss tracking, volume escalation, exit requests, and restart behavior. The code below is explicitly a fixed-lot EMA baseline for comparison, not a functioning martingale recovery system. No historical performance report or claim that an alternative has lower drawdown is supplied.

A sizing scenario, not a performance result

Starting at 0.01 lot and multiplying by 2 gives 0.02 after one escalation and 10.24 after ten. These are arithmetic requested volumes, not proof that a broker accepts them or an account can finance them. A lot size alone cannot determine a percentage loss per pip without equity and contract values.

Audit the transition from loss to larger size

The original example had multiplier inputs but no escalation transition. The baseline below makes that limitation explicit; it does not add an unverified recovery engine.

  • Identify which closed position or deals define a loss, including costs and partial exits.
  • Verify the lot formula, rounding, minimum/maximum volume, aggregate exposure, and margin checks.
  • Inspect what stops the sequence, how rejected orders are handled, and whether a close really completed.
  • Restart with an open position and a prior loss: state must be reconciled, not silently reset.

Implementation details & mechanics

Core implementation pattern

Save the baseline under MQL5/Experts. It evaluates the two completed EMA bars once per configured timeframe bar and opens a fixed-volume position on a crossover when no matching position exists. The former multiplier and maximum-lot inputs were unused, and there was no loss-history or sequence transition implementation. Those inactive controls have been removed rather than presented as a working martingale. Use the checklist above when reviewing a separate EA that actually escalates size.

Error handling & safety rules

The baseline requests an individual fixed-distance target, with no broker-side stop-loss. A tick-level guard compares whole-account balance minus equity with current balance, latches entry off at the configured threshold, and retries closing matching symbol/magic positions. It is not a cumulative basket recovery target, peak drawdown limit, or atomic close. Other account trades can trigger it, and execution failures can exceed the threshold. State resets on reinitialization; the guard does not provide a persistent account-wide limit.

MQL5 Code Implementation

//+------------------------------------------------------------------+
//|  MartingaleEA.mq5                                                |
//|  Fixed-lot EMA baseline for auditing martingale claims            |
//|  WARNING: Martingale strategies carry extreme drawdown risk.     |
//|  Never trade with funds you cannot afford to lose.               |
//+------------------------------------------------------------------+
#property copyright "Pineify Educational Example"
#property version   "1.00"
#property strict

//--- Input parameters
input double InpLotStart       = 0.01;   // Initial lot size
input int    InpTakeProfit     = 10;     // Take profit per trade (pips)
input double InpMaxDrawdownPct = 20.0;   // Max basket drawdown % before force-close
input int    InpEmaFast        = 20;     // Fast EMA period
input int    InpEmaSlow        = 50;     // Slow EMA period
input ENUM_TIMEFRAMES InpTF    = PERIOD_M15; // Trading timeframe
input ulong  InpMagic          = 202501; // Magic number

//--- Global variables
int    g_emaFastHandle = INVALID_HANDLE;
int    g_emaSlowHandle = INVALID_HANDLE;
double g_currentLot    = 0.0;
bool   g_halted = false;

//+------------------------------------------------------------------+
//| Expert initialisation                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpLotStart <= 0 || InpTakeProfit <= 0 || InpMaxDrawdownPct <= 0 ||
      InpMaxDrawdownPct >= 100 || InpEmaFast <= 0 || InpEmaSlow <= InpEmaFast)
      return INIT_PARAMETERS_INCORRECT;
   g_emaFastHandle = iMA(_Symbol, InpTF, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
   g_emaSlowHandle = iMA(_Symbol, InpTF, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);

   if(g_emaFastHandle == INVALID_HANDLE || g_emaSlowHandle == INVALID_HANDLE)
     {
      Print("ERROR: Failed to create EMA indicators.");
      return INIT_FAILED;
     }

   g_currentLot  = InpLotStart;
   Print("Fixed-lot baseline initialized; no loss escalation is implemented.");
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_emaFastHandle != INVALID_HANDLE) IndicatorRelease(g_emaFastHandle);
   if(g_emaSlowHandle != INVALID_HANDLE) IndicatorRelease(g_emaSlowHandle);
   Print("MartingaleEA deinitialised. Reason=", reason);
  }

//+------------------------------------------------------------------+
//| Count open positions for this EA                                 |
//+------------------------------------------------------------------+
int CountPositions()
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
        {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
            PositionGetInteger(POSITION_MAGIC) == (long)InpMagic)
            count++;
        }
     }
   return count;
  }

//+------------------------------------------------------------------+
//| Close all positions for this EA                                  |
//+------------------------------------------------------------------+
void CloseAll()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if(PositionGetInteger(POSITION_MAGIC) != (long)InpMagic) continue;

      MqlTradeRequest  req  = {};
      MqlTradeResult   res  = {};
      req.action    = TRADE_ACTION_DEAL;
      req.symbol    = _Symbol;
      req.volume    = PositionGetDouble(POSITION_VOLUME);
      req.type      = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                      ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      req.price     = (req.type == ORDER_TYPE_SELL)
                      ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
                      : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      req.deviation = 10;
      req.magic     = InpMagic;
      req.position  = ticket;
      req.comment   = "Baseline CloseAll";
      req.type_filling = ORDER_FILLING_FOK;

      if(!OrderSend(req, res) || res.retcode != TRADE_RETCODE_DONE)
         Print("CloseAll failed ticket=", ticket, " error=", GetLastError());
     }

   g_currentLot = InpLotStart;

  }

//+------------------------------------------------------------------+
//| Send a market order                                              |
//+------------------------------------------------------------------+
bool SendMarketOrder(ENUM_ORDER_TYPE type, double lot, int tpPips)
  {
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int    digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double pipSize = (digits == 3 || digits == 5) ? point * 10 : point;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   double price = (type == ORDER_TYPE_BUY) ? ask : bid;
   double tp    = (type == ORDER_TYPE_BUY)
                  ? NormalizeDouble(price + tpPips * pipSize, digits)
                  : NormalizeDouble(price - tpPips * pipSize, digits);

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};
   req.action    = TRADE_ACTION_DEAL;
   req.symbol    = _Symbol;
   double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double minimum = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maximum = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   if(step <= 0) return false;
   double volume = MathFloor(MathMin(maximum, lot) / step) * step;
   if(volume < minimum) return false;
   req.volume = NormalizeDouble(volume, 8);
   req.type      = type;
   req.price     = price;
   req.tp        = tp;
   req.deviation = 10;
   req.magic     = InpMagic;
   req.comment   = "FixedLotBaseline";
   req.type_filling = ORDER_FILLING_FOK;

   bool sent = OrderSend(req, res);
   bool ok = sent && res.retcode == TRADE_RETCODE_DONE;
   if(!ok)
      Print("OrderSend failed type=", EnumToString(type),
            " lot=", lot, " retcode=", res.retcode, " ", res.comment);
   return ok;
  }

//+------------------------------------------------------------------+
//| Expert tick handler                                              |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Enforce drawdown guard on basket
   double equity  = AccountInfoDouble(ACCOUNT_EQUITY);
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   if(balance > 0 && ((balance - equity) / balance * 100.0) >= InpMaxDrawdownPct)
      g_halted = true;
   if(g_halted)
     {
      CloseAll();
      return;
     }

   //--- Only act on new bar
   static datetime lastBar = 0;
   datetime currentBar = iTime(_Symbol, InpTF, 0);
   if(currentBar <= 0 || currentBar == lastBar) return;

   //--- Read EMA values
   double emaFast[2], emaSlow[2];
   if(CopyBuffer(g_emaFastHandle, 0, 1, 2, emaFast) < 2) return;
   if(CopyBuffer(g_emaSlowHandle, 0, 1, 2, emaSlow) < 2) return;
   lastBar = currentBar;

   bool bullCross = (emaFast[0] < emaSlow[0]) && (emaFast[1] > emaSlow[1]);
   bool bearCross = (emaFast[0] > emaSlow[0]) && (emaFast[1] < emaSlow[1]);

   int positions = CountPositions();

   //--- No open positions: wait for fresh crossover signal
   if(positions == 0)
     {
      g_currentLot = InpLotStart;
   

      if(bullCross)
         SendMarketOrder(ORDER_TYPE_BUY,  g_currentLot, InpTakeProfit);
      else if(bearCross)
         SendMarketOrder(ORDER_TYPE_SELL, g_currentLot, InpTakeProfit);

      return;
     }

   // Existing position: wait for its target or the account-equity guard.
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor, save it in your MQL5 folder (Experts, Scripts, or Include), and compile with F7.

Generate custom MQL5 code with Pineify AI

Describe the trade execution, indicator calculations, risk management, or data processing logic you need. Pineify generates editable MQL5 source code. Review its logic, request a syntax check, and compile and test the final file in MetaEditor.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionSeries evaluated on chart updatesEvent handlers with explicit buffers
DeploymentRuns in TradingView with alertsRuns in the MT5 terminal or on a VPS
Broker accessStrategy orders are simulated; live automation needs a separate integrationDirect broker connectivity
BacktestingStrategy Tester for strategy scriptsStrategy Tester for Expert Advisors
Code complexitySimpler, functional syntaxC++-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.