Problem Description
Solution
To build a trading robot (Expert Advisor) that adjusts trade size based on the result of the previous trade, you need to implement a custom money management function.
This specific logic—increasing size after a win and decreasing after a loss—is often referred to as a Positive Progression or Anti-Martingale strategy. It aims to capitalize on winning streaks while preserving capital during losing streaks.
The Strategy Logic
- Entry Signal: For demonstration, we will use a simple Moving Average Crossover.
- Money Management:
- First Trade: Use
BaseLot. - After a Win: Multiply the previous lot size by
WinMultiplier(e.g., 1.5). - After a Loss: Multiply the previous lot size by
LossMultiplier(e.g., 0.5).
- First Trade: Use
- Safety: We will clamp the lot size so it never goes below the broker's minimum or above a defined maximum.
MQL4 Code Implementation
Copy and paste the following code into your MetaEditor (File -> New -> Expert Advisor).
//+------------------------------------------------------------------+
//| DynamicPositionSizingEA.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 BaseLot = 0.1; // Starting Lot Size
input double WinMultiplier = 1.5; // Multiplier after a WIN (e.g. 1.5 = +50%)
input double LossMultiplier = 0.5; // Multiplier after a LOSS (e.g. 0.5 = -50%)
input double MaxLot = 5.0; // Maximum allowed Lot Size
input int MagicNumber = 123456; // Unique ID for this EA
input int StopLoss = 50; // Stop Loss in points
input int TakeProfit = 100; // Take Profit in points
input int FastMA = 10; // Fast Moving Average Period
input int SlowMA = 20; // Slow Moving Average Period
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Basic validation
if(WinMultiplier < 1.0) Print("Warning: WinMultiplier is < 1.0. Lot size will decrease on wins.");
if(LossMultiplier > 1.0) Print("Warning: LossMultiplier is > 1.0. Lot size will increase on losses.");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Check if we are allowed to trade
if(!IsTradeAllowed()) return;
// 2. Check for open positions (We only want one trade at a time for this logic)
if(CountOpenTrades() > 0) return;
// 3. Define Entry Signals (Simple MA Crossover)
double fastCurr = iMA(NULL, 0, FastMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double slowCurr = iMA(NULL, 0, SlowMA, 0, MODE_EMA, PRICE_CLOSE, 0);
double fastPrev = iMA(NULL, 0, FastMA, 0, MODE_EMA, PRICE_CLOSE, 1);
double slowPrev = iMA(NULL, 0, SlowMA, 0, MODE_EMA, PRICE_CLOSE, 1);
// 4. Calculate Dynamic Lot Size
double tradeLot = CalculateDynamicLot();
// 5. Execute Trades
// Buy Signal: Fast MA crosses above Slow MA
if(fastCurr > slowCurr && fastPrev <= slowPrev)
{
double sl = (StopLoss > 0) ? Ask - StopLoss * Point : 0;
double tp = (TakeProfit > 0) ? Ask + TakeProfit * Point : 0;
int ticket = OrderSend(Symbol(), OP_BUY, tradeLot, Ask, 3, sl, tp, "Dynamic Buy", MagicNumber, 0, clrGreen);
if(ticket < 0) Print("OrderSend failed with error #", GetLastError());
}
// Sell Signal: Fast MA crosses below Slow MA
if(fastCurr < slowCurr && fastPrev >= slowPrev)
{
double sl = (StopLoss > 0) ? Bid + StopLoss * Point : 0;
double tp = (TakeProfit > 0) ? Bid - TakeProfit * Point : 0;
int ticket = OrderSend(Symbol(), OP_SELL, tradeLot, Bid, 3, sl, tp, "Dynamic Sell", MagicNumber, 0, clrRed);
if(ticket < 0) Print("OrderSend failed with error #", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Function to calculate lot size based on history |
//+------------------------------------------------------------------+
double CalculateDynamicLot()
{
// Get Broker limitations
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double step = MarketInfo(Symbol(), MODE_LOTSTEP);
// Find the last closed trade for this EA
int lastTicket = -1;
datetime lastCloseTime = 0;
for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderCloseTime() > lastCloseTime)
{
lastCloseTime = OrderCloseTime();
lastTicket = OrderTicket();
}
}
}
}
// If no history exists, return BaseLot
if(lastTicket == -1) return BaseLot;
// Select the last ticket to analyze profit
if(OrderSelect(lastTicket, SELECT_BY_TICKET))
{
double lastLot = OrderLots();
double profit = OrderProfit(); // Profit in currency
double newLot = lastLot;
// LOGIC: Increase on Win, Decrease on Loss
if(profit > 0)
{
newLot = lastLot * WinMultiplier;
}
else
{
newLot = lastLot * LossMultiplier;
}
// Safety: If the calculation makes the lot smaller than base,
// you might want to reset to BaseLot.
// Uncomment the line below to enforce BaseLot as a floor.
// if(newLot < BaseLot) newLot = BaseLot;
// Normalize Lot Size to Broker Steps
newLot = MathFloor(newLot / step) * step;
// Clamp between Min and Max
if(newLot < minLot) newLot = minLot;
if(newLot > maxLot) newLot = maxLot;
if(newLot > MaxLot) newLot = MaxLot; // User defined max
return newLot;
}
return BaseLot;
}
//+------------------------------------------------------------------+
//| Helper to count open trades |
//+------------------------------------------------------------------+
int CountOpenTrades()
{
int count = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
count++;
}
}
return count;
}
Key Components Explained
-
CalculateDynamicLot()Function:- This is the core of your request. It scans the account history (
MODE_HISTORY) to find the most recently closed trade that matches the EA'sMagicNumber. - It checks
OrderProfit(). - If profit is positive, it applies
WinMultiplier. - If profit is negative, it applies
LossMultiplier.
- This is the core of your request. It scans the account history (
-
Normalization:
- Brokers have specific lot steps (e.g., 0.01 or 0.1). The code uses
MathFloor(newLot / step) * stepto ensure the calculated lot size is valid for your broker.
- Brokers have specific lot steps (e.g., 0.01 or 0.1). The code uses
-
Safety Clamps:
- The code checks
MODE_MINLOTandMODE_MAXLOT(broker limits) and theMaxLotinput (your personal risk limit) to prevent the robot from opening invalid or excessively large trades.
- The code checks
-
CountOpenTrades():- This ensures the robot only manages one trade at a time. This is crucial for this specific money management strategy because "increasing after a win" requires the previous trade to be fully closed to determine the outcome.
How to Test This
- Compile the code (F7 in MetaEditor).
- Open the Strategy Tester in MT4 (Ctrl+R).
- Select "DynamicPositionSizingEA".
- Important: Set
WinMultiplierto1.5andLossMultiplierto0.5(or your preferred settings). - Run the test and switch to the "Results" tab. You will see the "Size" column changing based on the profit of the previous trade.
Q&A: Quantitative Trading & MQL4
Q: What is the risk of using a Positive Progression strategy?
A: While it capitalizes on winning streaks, a single loss after a long winning streak can wipe out a significant portion of profits if the WinMultiplier is too aggressive. It is mathematically safer than Martingale (doubling on loss), but still increases drawdown volatility.
Q: Can I use this logic with pending orders?
A: Yes, but the logic provided calculates the lot size at the moment the order is sent. If you place a pending order, the lot size is fixed at placement. If another trade closes after you place the pending order but before it triggers, the pending order will not update its lot size automatically.
Q: How does OrderProfit() handle commissions and swaps?
A: OrderProfit() usually returns the gross profit/loss. To be precise, you should calculate OrderProfit() + OrderCommission() + OrderSwap(). If your strategy relies on small scalping wins, ignoring commissions might cause the robot to think a trade was a "win" when it was actually a net loss.
Q: Why do we use MagicNumber?
A: The MagicNumber allows the EA to distinguish its own trades from manual trades or trades opened by other robots running on the same account. Without it, the money management logic might calculate lot sizes based on trades it didn't execute.