The basic outline for designing a simple Cost Averaging Martingale EA is quite straightforward, as follows:
- Setting up the input
- Loop processing
- Checking positions
- Placing new entry orders
- Placing additional entry (cost averaging) orders
- Placing position close orders
By copying and pasting the content of this article, you can create the simplest Cost Averaging Martingale EA.
We’ve tried to explain the meaning of the code as thoroughly as possible for beginners, so even if you’re new to this, feel free to take on the challenge of creating your own EA.
At the end of this article, we have made an .mq4 file available for download that compiles all the code introduced here. If you have difficulty compiling the code by copying and pasting in order, or if you find it cumbersome to copy and paste one by one, please make use of the file.
EA Logic Overview
The logic of the EA we’re creating here is as simplified as possible, with complex conditions kept to a minimum.
| Initial lot | 0.01 lot |
| Entry | Takes a new position immediately if within the active hours |
| Cost averaging width | Price width of 2.00 for a minimum price change of 0.01 |
| Martingale multiplier | Martingale method (1.5 times multiplier) |
| Take-profit target | 1.00USD per position |
| Operating hours | Full operation during trading hours |
| Forced close | None (positions carried over to the next day) |
Setting up the Input
This section mainly determines the logic of the EA. It is also the part that can be configured externally when running the EA in MT4.
input double first_lot = 0.01; // Initial lot size
input double nanpin_range = 200; // Cost averaging width
input double profit_target = 1.00; // Profit target
input int magic_number = 10001; // Magic number
static int ticket_number; // Ticket number
double slippage = 10; // Slippage
Initial Lot (first_lot)
This setting is for the lot size when taking the first position. The minimum is 0.01, so we’ll set it to 0.01 for now.
Cost Averaging Width (nanpin_range)
This setting determines the price range after the first position, at which the next position will be taken. A setting of 200, in the case of GOLD, means a price range of 2.00. (Strictly speaking, the unit will be determined later with *Point.)
Profit Target (profit_target)
Set the profit target in monetary terms. Here, the unit is in yen, and the setting is 1.00USD. When an unrealized profit of 1.00USD per position occurs, a close order will be executed based on this logic.
Magic Number (magic_number)
This is like an identification number that exists in every EA. It doesn’t matter what number you use, as long as it doesn’t conflict with another EA running simultaneously. It has no bearing on the logic. Here, we have arbitrarily set it to 10001.
Ticket Number (ticket_number)
In the case of MT4, each order has a ticket number (similar to an order number). Just think of this as a placeholder for the number. It has no bearing on the logic.
Slippage (slippage)
In MT4, you can set an acceptable range for slippage when placing orders. However, some brokers may ignore this setting. XMTrading is one of them; even if you set an acceptable slippage range when placing an order, it will be ignored. However, as the official explanation below states, slippage rarely occurs.
Does slippage occur?
With our company, slippage rarely occurs. However, especially during important economic news announcements, rapid market price movements may cause your order to be executed at a rate different from the one requested.
We will set it to 10 for now.
Loop Processing
Now that the initial settings are done, we’ll move on to the loop processing.
void OnTick()
void OnTick()
{
This is one of the event functions called when the EA receives a new tick.
To put it more simply, it is a process that is repeated each time the price changes.
Therefore, most of the main logic will be written inside this function.
From here on, we’ll be discussing content to be written within void OnTick().
Variable Definition
First, let’s define the variables that will be used within the loop processing.
int cnt;
int current_buy_position;//most recent buy position
int current_sell_position;//most recent sell position
int buy_position;//number of buy positions
int sell_position;//number of sell positions
double buy_profit;//unrealized profit and loss for buy positions
double sell_profit;//unrealized profit and loss for sell positions
current_buy_position, current_sell_position
current_buy_position is a variable to record the most recent buy position. For example, if you have three buy positions, it would represent the third buy position. current_sell_position works similarly.
buy_position, sell_position
buy_position represents the number of buy positions. It is used to determine how many buy positions are held. sell_position works similarly.
buy_profit, sell_profit
buy_profit represents the total unrealized profit and loss for buy positions. If multiple buy positions are held, this value represents the cumulative unrealized profit and loss across all positions. It is used to determine whether to execute a close order. sell_profit works similarly.
Checking Positions
Here, we perform the process of checking positions.
Specifically, we obtain the number of buy positions and sell positions, as well as the latest position number (cnt), which will be used for the next order.
buy_position=0; // Initialize the number of buy positions
sell_position=0; // Initialize the number of sell positions
current_buy_position=-1; // Initialize the latest buy position
current_sell_position=-1; // Initialize the latest sell position
for(cnt=0;cnt<OrdersTotal();cnt++) // Checking positions
{
if(OrderSelect(cnt,SELECT_BY_POS)==false)continue;
if(OrderMagicNumber()!=magic_number)continue;
if(OrderType()==OP_BUY)
{
current_buy_position=cnt;
buy_position+=1;
buy_profit=buy_profit+OrderProfit();
};
if(OrderType()==OP_SELL)
{
current_sell_position=cnt;
sell_position+=1;
sell_profit=sell_profit+OrderProfit();
};
}
To briefly explain the content, we check all positions in order and,
if it’s a buy position,
- Record the position number (cnt) in current_buy_position.
- Increase buy_position by 1.
- Add the profit of that position to buy_profit.
if it’s a sell position,
- Record the position number (cnt) in current_sell_position.
- Increase sell_position by 1.
- Add the profit of that position to sell_profit.
This is the process being performed.
New Entry Orders
Next, we have new entry orders. The entry conditions are simply “when no position is held.”
if(buy_position==0) // If no buy positions are held
{
ticket_number=OrderSend(
Symbol(), // Currency pair
OP_BUY, // buy:OP_BUY, sell:OP_SELL
first_lot, // Lot size
Ask, // Order price
slippage, // Slippage
0, // Stop loss
0, // Take profit
"first_buy", // Order comment
magic_number, // Magic number
0, // Order expiration
Blue // Arrow color
);
}
if(sell_position==0) // If no sell positions are held
{
ticket_number=OrderSend(
Symbol(), // Currency pair
OP_SELL, // buy:OP_BUY, sell:OP_SELL
first_lot, // Lot size
Bid, // Order price
slippage, // Slippage
0, // Stop loss
0, // Take profit
"first_sell", // Order comment
magic_number, // Magic number
0, // Order expiration
Red // Arrow color
);
}
Here, we are simply using the OrderSend function.
This function is used to send new entry orders. If there are no buy positions, a new buy order is sent, and if there are no sell positions, a new sell order is sent. The parameters used in the function include the currency pair, order type (buy or sell), lot size, order price, slippage, stop loss, take profit, order comment, magic number, order expiration, and arrow color.
OP_BUY and OP_SELL represent market orders in trading. The order prices, Ask and Bid, indicate the current prices at the time of the order.
In other words, if you don’t have an existing buy or sell position, you would immediately place a market order at the current price (even though it’s a market order, you still specify the price). The lot size for this order is set by the initial input, called “first_lot” (initial lot).
Order comments are displayed when you confirm an order in MT4. You don’t have to set them, but for this example, we’ll use “first_buy” and “first_sell” as the comments.
Arrows refer to the color of the trade history displayed on MT4 charts. In this example, we will set buy orders to blue (Blue) and sell orders to red (Red). There’s no problem if you don’t specify these colors.
Additional Entry (Martingale) Orders
Now let’s move on to the crucial part, setting up Martingale orders.
That being said, there’s nothing particularly difficult about it. The only differences from a regular entry order are:
- Orders are only placed when the conditions for the Martingale range are met
- Lot size is increased by 1.5 times the previous lot size
That’s about it.
if(buy_position>0) //If holding one or more buy positions
{
OrderSelect(current_buy_position,SELECT_BY_POS); //Select the latest buy position
if(Ask<(OrderOpenPrice()-nanpin_range*Point)) //Check if the current price has reached the Martingale range
{
ticket_number=OrderSend(
Symbol(), //Currency pair
OP_BUY, //Buy:OP_BUY, Sell:OP_SELL
round(OrderLots()*1.5*100)/100, //Lot size
Ask, //Order price
slippage, //Slippage
0, //Stop-loss
0, //Take-profit
"nanpin_buy", //Order comment
magic_number, //Magic number
0, //Order expiration time
Blue //Arrow color
);
}
}
if(sell_position>0) //If holding one or more sell positions
{
OrderSelect(current_sell_position,SELECT_BY_POS); //Select the latest sell position
if(Bid>(OrderOpenPrice()+nanpin_range*Point)) //Check if the current price has reached the Martingale range
{
ticket_number=OrderSend(
Symbol(), //Currency pair
OP_SELL, //Buy:OP_BUY, Sell:OP_SELL
round(OrderLots()*1.5*100)/100, //Lot size
Bid, //Order price
slippage, //Slippage
0, //Stop-loss
0, //Take-profit
"nanpin_sell", //Order comment
magic_number, //Magic number
0, //Order expiration time
Red //Arrow color
);
}
}
Selecting the Latest Buy Position
OrderSelect(current_buy_position,SELECT_BY_POS); //Select the latest buy position
We will use a function called OrderSelect to obtain information about the latest position.
For example, suppose you have two buy positions as follows:
- Lot size: 0.01, Price: 1742.21 (buy position)
- Lot size: 0.02, Price: 1740.21 (buy position)
In this case, the latest position refers to the second position obtained later. Here, we use the number (cnt) obtained during the position confirmation with current_buy_position.
Has the Current Price Reached the Averaging Down Range?
if(Ask<(OrderOpenPrice()-nanpin_range*Point)) //Check if the current price has reached the averaging down range
We use the OrderOpenPrice function to obtain the price of the selected latest position. In the previous example, this would mean obtaining the price of 1740.21.
nanpin_range is the averaging down range of 200 set by Input. Point is the minimum unit determined for each currency pair (trading target). In the case of XMTrading’s GOLD, it means 0.01.
In other words, this if statement represents the condition:
Position Close Order
Finally, we have the position close order.
The code snippet below is quite simple, but it utilizes two functions called buyClose and sellClose, which will be defined separately later:
if(buy_position>0&&buy_profit>profit_target*buy_position)
{
buyClose(Green);//Close all buy positions
}
if(sell_position>0&&sell_profit>profit_target*sell_position)
{
sellClose(Yellow);//Close all sell positions
}
Position Close Conditions
The conditions for placing a close order for buy positions are met when the following two conditions are satisfied simultaneously:
- Having one or more buy positions
- The total unrealized profit of buy positions exceeds the profit target multiplied by the number of buy positions
This is represented by the formula above. Specifically, for example, if the number of buy positions is 3, and the profit target (profit_target) is set at 143 JPY, when the total unrealized profit of buy positions exceeds 429 JPY, which is three times the profit target, a close order for buy positions will be placed.
Functions to Close Positions
Define the following separately outside of “void OnTick()”. Use these custom functions to call the position close orders mentioned earlier.
buyClose Function
This function closes all buy positions held.
It closes all buy positions with the same magic number.
void buyClose(color clr)
{
int i;
for(i=OrdersTotal()-1; i>=0; i--)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false)
continue;
if(OrderSymbol()!=Symbol()||OrderMagicNumber()!=magic_number)
continue;
if(OrderType()==OP_BUY)
OrderClose(OrderTicket(),OrderLots(),Bid,NormalizeDouble(slippage,0),clr);
}
}
sellClose Function
This is a function to close all sell positions you currently hold.
It closes all sell positions with the same magic number.
void sellClose(color clr)
{
int i;
for(i=OrdersTotal()-1; i>=0; i--)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false)
continue;
if(OrderSymbol()!=Symbol()||OrderMagicNumber()!=magic_number)
continue;
if(OrderType()==OP_SELL)
OrderClose(OrderTicket(),OrderLots(),Ask,NormalizeDouble(slippage,0),clr);
}
}
Conclusion
By combining what we have seen so far, a basic averaging down Martingale EA is complete.
How did you find it? You may have realized that its structure is simpler than you thought.
Download the .mq4 file
You can download the code that I’ve introduced in this post from the link below:


Comments