In this article, by copying and pasting the content, you can create the simplest cost-averaging Martingale-style Forex trading tool (referred to as the Nanpin-Martin EA) in MQL5, not MQL4.
Automated Forex trading usually involves using EA (Expert Advisors) built with languages like MQL4 and MQL5, which are used on the MT4 and MT5 platforms. While MT4 used to be the standard, MT5 has recently started gaining traction. MT5 has several advantages over MT4, such as faster performance and easier backtesting. It can be said that MT5 is better than MT4 for beginners who are serious about starting.
I have tried to explain the code’s meaning as carefully as possible for beginners, so please give EA creation with MQL5 a try, even if you are a beginner.
Also, at the end of this article, you can download the .mq5 file containing the code introduced in this article. If you have trouble compiling the code by copying and pasting it in order, or if you find it cumbersome to copy and paste one piece at a time, please make use of the downloadable file.
Nanpin-Martin EA Logic
The basic design of the Nanpin-Martin EA is very simple, as shown below:
- Setting Inputs
- Loop processing
- Checking positions
- Placing new entry orders
- Placing additional entry (cost-averaging) orders
- Placing position close orders
Logic Overview
Complex conditions have been simplified as much as possible.
| Initial lot | 0.01 lot |
| Entry | Opens a new position immediately if within the operating hours |
| Cost-averaging width | Price width of 2.00 for a minimum price change of 0.01 |
| Martingale ratio | Martingale method (ratio of 1.5 times) |
| Take profit target | 1.00USD per position |
| Operating hours | Full-time operation during trading hours |
| Forced close | None (positions are carried over to the next day) |
The logic of the EA created here is the same as the one created in MT4 and Python. If necessary, compare the differences in code.


Setting Input
This mainly determines the logic of the EA. It also allows you to input and configure settings from outside when running on MT5.
input double first_lot = 0.01;//Initial lot size
input double nanpin_range = 200;//Martingale range
input double profit_target = 1.00;//Profit target
input int magic_number = 10001; //Magic number
double slippage = 10;//Slippage
Initial Lot Size (first_lot)
This setting determines the lot size when opening the first position. The minimum is 0.01, so for now, we will set it to 0.01.
Martingale Range (nanpin_range)
This setting determines the price range at which the next position will be opened after the first position. When set to 200, it means a price range of 2.00 for GOLD. (Strictly speaking, the unit will be determined later by multiplying by *Point.)
Profit Target (profit_target)
The profit target is set in monetary terms. In this case, the unit is yen, and the setting is 1.00USD. The logic is to close the order when a floating profit of 1.00 USD per position occurs.
Magic Number (magic_number)
This is a unique identification number present in any EA. As long as you don’t run other EAs simultaneously, any number will suffice. It does not affect the logic. In this case, we have arbitrarily set it to 10001.
Slippage (slippage)
In MT5, you can set the allowable range of slippage when placing an order. However, some brokers may invalidate the setting. XMTrading is one of them, and even if you set the allowable range of slippage when placing an order, it will be invalidated. However, as stated in the following official explanation, slippage rarely occurs.
Does slippage occur?
With our service, slippage rarely occurs. However, especially during important economic news releases, rapid market price increases or decreases may cause your order to be executed at a rate different from the one requested.
For now, we will arbitrarily set it to 10.
Loop Processing
With the initial settings completed, we now proceed to loop processing.
void OnTick()
void OnTick()
{
This is one of the event functions, called when the Expert Advisor (EA) receives a new tick.
To put it more simply, it refers to the process that repeats every time the price changes.
Therefore, most of the main logic will be written within this function.
For a while, the content will be focused on what is written inside the void OnTick() function.
Variable Definitions
First, let’s define the variables used in the loop processing.
int buy_position = 0;//number of buy positions
int sell_position = 0;//number of sell positions
double buy_profit = 0.0;//total profit/loss of buy positions
double sell_profit = 0.0;//total profit/loss of sell positions
double current_buy_lot = 0.0;//lot size of the latest buy position
double current_sell_lot = 0.0;//lot size of the latest sell position
double current_buy_price = 0.0;//price of the latest buy position
double current_sell_price = 0.0;//price of the latest sell position
buy_position, sell_position
buy_position represents the number of buy positions. It is used to determine how many buy positions are being held. sell_position serves the same purpose for sell positions.
buy_profit, sell_profit
buy_profit represents the total profit/loss of all buy positions. If multiple buy positions are held, this will be the cumulative profit/loss of all positions. It is used to determine whether to place a close order or not. sell_profit serves the same purpose for sell positions.
current_buy_lot, current_sell_lot
current_buy_lot represents the lot size of the latest buy position. This variable is used to record the latest lot size to implement a setting of 1.5 times the lot size of the previous position when performing averaging down. current_sell_lot serves the same purpose for sell positions.
current_buy_price, current_sell_price
current_buy_price represents the acquisition price of the latest buy position. This variable is used to record the latest position price to implement a setting of averaging down when the price deviates by a certain width from the previous position’s acquisition price. current_sell_price serves the same purpose for sell positions.
Checking Positions
Here, we will perform the process of checking positions.
Specifically, we will obtain the number of buy and sell positions held and the latest position number (cnt), which will be used for the next order.
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionGetSymbol(i)!="")
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
buy_position++;
buy_profit += PositionGetDouble(POSITION_PROFIT);
current_buy_lot = PositionGetDouble(POSITION_VOLUME);
current_buy_price = PositionGetDouble(POSITION_PRICE_OPEN);
}
else
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
sell_position++;
sell_profit += PositionGetDouble(POSITION_PROFIT);
current_sell_lot = PositionGetDouble(POSITION_VOLUME);
current_sell_price = PositionGetDouble(POSITION_PRICE_OPEN);
}
}
}
To briefly explain the content, it checks all the positions in order,
If it’s a buy position,
- Increment buy_position by 1.
- Add the profit of that position to buy_profit.
- Record the lot size in current_buy_lot (since it’s overwritten repeatedly, the information of the last buy position will remain in the end).
- Record the acquisition price in current_buy_price (since it’s overwritten repeatedly, the information of the last buy position will remain in the end).
If it’s a sell position,
- Increment sell_position by 1.
- Add the profit of that position to sell_profit.
- Record the lot size in current_sell_lot (since it’s overwritten repeatedly, the information of the last sell position will remain in the end).
- Record the acquisition price in current_sell_price (since it’s overwritten repeatedly, the information of the last sell position will remain in the end).
This is the kind of processing being performed.
Obtaining the Latest Price (Tick)
Here, we’ll include a process that’s necessary in MT5 but not required in MT4.
MqlTick last_tick;
SymbolInfoTick(_Symbol,last_tick);
double Ask=last_tick.ask;
double Bid=last_tick.bid;
In the case of MT4, Ask and Bid can be used as built-in variables, but for MT5, you need to include a process to obtain Ask and Bid from the latest tick (last_tick) as shown above.
New Entry Order
Next, let’s discuss the new entry order. The entry condition is simply that “if no position is held.”
if(buy_position == 0)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; // Market order
request.type = ORDER_TYPE_BUY; // Order type
request.magic = magic_number; // Magic number
request.symbol = Symbol(); // Currency pair name
request.volume = first_lot; // Lot size
request.price = Ask;// Order price
request.deviation = slippage; // Slippage
request.comment = "first_buy"; // Comment
request.type_filling = ORDER_FILLING_IOC; // Volume execution policy
OrderSend(request, result);
}
if(sell_position == 0)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; // Market order
request.type = ORDER_TYPE_SELL; // Order type
request.magic = magic_number; // Magic number
request.symbol = Symbol(); // Currency pair name
request.volume = first_lot; // Lot size
request.price = Bid;// Order price
request.deviation = slippage; // Slippage
request.comment = "first_sell"; // Comment
request.type_filling = ORDER_FILLING_IOC; // Volume execution policy
OrderSend(request, result);
}
OrderSend Function
In MT5, the OrderSend function is used, which is quite different from the MT4 OrderSend function. In the case of MT5, you create something like an order information called “request structure (request)” and send the request using the OrderSend function.
Market Order
TRADE_ACTION_DEAL represents a market order.
Order Type
ORDER_TYPE_BUY and ORDER_TYPE_SELL represent buy and sell orders, respectively. The order prices Ask and Bid represent the current prices.
In other words, if you don’t have a buy or sell position, you immediately place a market order at the current price (even with market orders, you specify the price when placing the order), and the lot size is set to the initially configured first_lot (initial lot) from the Input.
Comment
The comment is displayed when checking the order in MT5. It’s not necessary to set it, but in this case, we will set it as “first_buy” or “first_sell.”
Volume Execution Policy
Unlike MT4, you need to set request.type_filling. At least for XMTrading, ORDER_FILLING_IOC should be fine. For more details, please refer to ENUM_ORDER_TYPE_FILLING.
Adding Additional Entry (Martingale) Orders
Now, let’s move on to the crucial setting of the martingale orders.
However, there’s nothing particularly difficult about it, and the differences from the regular entry orders are:
- Placing orders only when the martingale range condition is met
- Increasing the lot size by 1.5 times the previous lot size
That’s about it.
if(buy_position > 0)
{
if(Ask < current_buy_price - nanpin_range * Point()) // Check if the current price has reached the martingale range
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; // Market order
request.type = ORDER_TYPE_BUY; // Order type
request.magic = magic_number; // Magic number
request.symbol = Symbol(); // Currency pair name
request.volume = round(current_buy_lot*1.5*100)/100; // Lot size
request.price = Ask;// Order price
request.deviation = slippage; // Slippage
request.comment = "nanpin_buy"; // Comment
request.type_filling = ORDER_FILLING_IOC; // Volume execution policy
OrderSend(request, result);
}
}
if(sell_position > 0)
{
if(Bid > current_sell_price + nanpin_range * Point()) // Check if the current price has reached the martingale range
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; // Market order
request.type = ORDER_TYPE_SELL; // Order type
request.magic = magic_number; // Magic number
request.symbol = Symbol(); // Currency pair name
request.volume = round(current_sell_lot*1.5*100)/100; // Lot size
request.price = Bid;// Order price
request.deviation = slippage; // Slippage
request.comment = "nanpin_sell"; // Comment
request.type_filling = ORDER_FILLING_IOC; // Volume execution policy
OrderSend(request, result);
}
}
Checking If the Current Price Has Reached the Martingale Range
if(Ask < current_buy_price - nanpin_range * Point()) // Check if the current price has reached the martingale range
current_buy_price is the price of the latest position obtained during position verification. For example, let’s say the price is 1740.21.
nanpin_range is the martingale range of 200 set in Input. Point is the minimum unit specific to each currency pair (trading target). In the case of XMTrading’s GOLD, it means 0.01.
In other words, this if statement can be interpreted as:
Position Close Order
Lastly, we have the position close order.
It’s straightforward, as shown below, but here we use the buyClose and sellClose functions, which will be defined separately later on.
if(buy_position>0&&buy_profit>profit_target*buy_position)
{
buyClose();//Close all buy positions
}
if(sell_position>0&&sell_profit>profit_target*sell_position)
{
sellClose();//Close all sell positions
}
Position Close Conditions
The conditions for executing a buy position close order are as follows, when both of these are met simultaneously:
- Having one or more buy positions
- The total unrealized profit of buy positions exceeding the profit target × the number of buy positions
These conditions are expressed in the formula above. Specifically, for example, if there were three buy positions, and the profit target (profit_target) was set at 1.00USD, a buy position close order would be executed when the total unrealized profit of buy positions exceeds 3.00USD, which is three times the profit target.
Functions for Settling Positions
The following should be defined separately outside the “void OnTick()” function. We define the original functions and call them in the position close order section mentioned earlier.
buyClose Function
This is a function that closes all the buy positions you hold.
It closes all buy positions with the same magic number.
void buyClose()
{
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionGetSymbol(i)!="")
{
if(PositionGetInteger(POSITION_MAGIC)==magic_number)
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.position =PositionGetTicket(i); // Position ticket
request.action = TRADE_ACTION_DEAL; // Market order
request.type = ORDER_TYPE_SELL; // Order type
request.magic = magic_number; // Magic number
request.symbol = Symbol(); // Currency pair name
request.volume = PositionGetDouble(POSITION_VOLUME); // Lot size
request.price = SymbolInfoDouble(_Symbol,SYMBOL_BID); // Order price
request.deviation = slippage; // Slippage
request.type_filling = ORDER_FILLING_IOC; // Volume execution policy
OrderSend(request, result);
}
}
}
}
}
sellClose Function
This function closes all the sell positions that you currently hold.
It closes all sell positions with the same magic number.
void sellClose()
{
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionGetSymbol(i)!="")
{
if(PositionGetInteger(POSITION_MAGIC)==magic_number)
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.position =PositionGetTicket(i); // Position Ticket
request.action = TRADE_ACTION_DEAL; // Market order
request.type = ORDER_TYPE_BUY; // Order type
request.magic = magic_number; // Magic number
request.symbol = Symbol(); // Currency pair name
request.volume = PositionGetDouble(POSITION_VOLUME); // Lot size
request.price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); // Order price
request.deviation = slippage; // Slippage
request.type_filling = ORDER_FILLING_IOC; // Volume execution policy
OrderSend(request, result);
}
}
}
}
}
Conclusion
With the combination of the elements we have looked at so far, the basic Grid-Martingale EA (for MT5) is now complete.
How did you find it? I think you must have realized that its structure is much simpler than expected.
Download the .mq5 file
You can download the code introduced in this article from the link below.


Comments