Problem Description
Solution
Here is the complete MQL4 code for an Expert Advisor (EA) that implements a Martingale strategy. This code checks the result of the last closed trade and doubles the lot size if that trade was a loss.
MQL4 Code
//+------------------------------------------------------------------+
//| MartingaleExample.mq4 |
//| Copyright 2023, Quantitative Trading AI |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative Trading AI"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- Input Parameters
input double InpBaseLot = 0.1; // Starting Lot Size
input double InpMultiplier = 2.0; // Martingale Multiplier (2.0 = Double)
input int InpStopLoss = 50; // Stop Loss in points
input int InpTakeProfit = 50; // Take Profit in points
input int InpMagicNumber = 123456; // Magic Number to identify orders
input int InpSlippage = 3; // Maximum slippage in points
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Basic validation
if(InpBaseLot <= 0 || InpMultiplier < 1.0)
{
Print("Error: Invalid Input Parameters.");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Check if there are currently open orders for this symbol/magic number
if(CountOpenOrders() > 0) return; // Wait for the current trade to close
// 2. Calculate the Lot Size based on the last closed trade
double tradeLotSize = GetMartingaleLots();
// 3. Entry Logic (Placeholder: Simple Random Entry for demonstration)
// REPLACE THIS BLOCK WITH YOUR ACTUAL STRATEGY LOGIC (RSI, MA, etc.)
int ticket = -1;
// Example: Always enter BUY if no trades exist (for testing logic)
double sl = Ask - InpStopLoss * Point;
double tp = Ask + InpTakeProfit * Point;
// Normalize prices
sl = NormalizeDouble(sl, Digits);
tp = NormalizeDouble(tp, Digits);
ticket = OrderSend(Symbol(), OP_BUY, tradeLotSize, Ask, InpSlippage, sl, tp, "Martingale Buy", InpMagicNumber, 0, clrGreen);
if(ticket < 0)
{
Print("OrderSend failed with error #", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Function to calculate Lot Size based on history |
//+------------------------------------------------------------------+
double GetMartingaleLots()
{
// Iterate through closed orders from the most recent to the oldest
for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
// Filter by Symbol and Magic Number
if(OrderSymbol() == Symbol() && OrderMagicNumber() == InpMagicNumber)
{
// Check only Buy or Sell orders (ignore cancelled pending orders)
if(OrderType() <= OP_SELL)
{
// If the last trade was a LOSS (Profit < 0)
if(OrderProfit() < 0)
{
double newLot = OrderLots() * InpMultiplier;
// Verify lot size against broker limits
return VerifyLotSize(newLot);
}
// If the last trade was a WIN (Profit >= 0)
else
{
return InpBaseLot; // Reset to base lot
}
}
}
}
}
// If no history is found (first trade), return base lot
return InpBaseLot;
}
//+------------------------------------------------------------------+
//| Helper: Verify and Normalize Lot Size |
//+------------------------------------------------------------------+
double VerifyLotSize(double lot)
{
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
// 1. Round the lot to the nearest step
if(lotStep > 0)
lot = MathFloor(lot / lotStep) * lotStep;
// 2. Clamp between Min and Max
if(lot < minLot) lot = minLot;
if(lot > maxLot) lot = maxLot;
return lot;
}
//+------------------------------------------------------------------+
//| Helper: Count Open Orders |
//+------------------------------------------------------------------+
int CountOpenOrders()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == InpMagicNumber)
{
count++;
}
}
}
return count;
}
Code Explanation
-
GetMartingaleLots()Function:- This is the core logic. It loops through the account history using
OrdersHistoryTotal(). - It iterates backwards (
i >= 0) to find the most recent closed trade first. - It filters trades using
OrderSymbol()andOrderMagicNumber()to ensure it only looks at trades generated by this specific EA. - Logic: If
OrderProfit() < 0(Loss), it multiplies the previous lot size byInpMultiplier(default 2.0). If the profit is positive, it resets toInpBaseLot.
- This is the core logic. It loops through the account history using
-
VerifyLotSize()Function:- Calculated lot sizes can sometimes be invalid (e.g., 0.1 * 2 = 0.2 is fine, but specific multipliers might result in 0.13333).
- This function retrieves the broker's
MODE_MINLOT,MODE_MAXLOT, andMODE_LOTSTEPusingMarketInfo. - It normalizes the calculated lot to match the broker's requirements, preventing
OrderSendError 131 (Invalid Trade Volume).
-
OnTick()Execution:- The EA first checks if a trade is already open using
CountOpenOrders(). It waits for the current trade to close before calculating the next lot size. - Note: I have included a placeholder
OrderSendcommand that simply buys. You should replace the entry logic comment with your specific strategy (e.g., Moving Average crossover or RSI).
- The EA first checks if a trade is already open using
Risk Warning
Martingale strategies carry a high risk of ruining the account. Because the lot size doubles exponentially ($0.1, 0.2, 0.4, 0.8, 1.6, 3.2...$), a consecutive losing streak can quickly hit the broker's maximum lot limit or trigger a Margin Call. Ensure you test this in the Strategy Tester before using it on a live account.
Q&A
Q: How do I change the multiplier to 1.5 instead of doubling?
A: Change the input parameter InpMultiplier from 2.0 to 1.5 in the EA settings window.
Q: Will this work if I restart the terminal?
A: Yes. Because the code uses MODE_HISTORY to look at past closed trades, it will remember the last trade result even if the terminal is restarted, provided the account history is loaded.
Q: Why does the EA reset to the base lot size after a win?
A: This is standard Martingale logic to recover losses and then return to the baseline risk. If you wish to keep the lot size high, you would need to modify the else block inside GetMartingaleLots.