By copying and pasting the content of this article, it’s possible to create the simplest grid-based Martingale style forex trading tool (referred to as Nanpin-Martin-Bot here) using Python instead of MQL.
We strive to explain the meaning of the code as carefully as possible for beginners, so we encourage beginners to try creating an FX automated trading bot in Python.
Automated Trading Tool (bot) Logic
The basic design outline of a Nanpin-Martin-Bot is very simple, as follows:
- Setting up the input
- Loop processing
- Checking the position
- New entry order
- Additional entry (grid) order
- Position close order
Logic Overview
We have simplified the conditions as much as possible, avoiding complexity.
| Initial lot | 0.01lot |
| Entry | Take a new position immediately if within operating hours |
| Grid width | Price width of 2.00 for the minimum price change of 0.01 |
| Martingale multiplier | Martingale method (multiplier of 1.5 times) |
| Profit target | 1.00 USD per position |
| Operating hours | Full operation during trading hours |
| Forced close | None (carry positions over to the next day) |
Python Environment Setup
In this article, we assume that the basic Python environment is set up and that Jupyter Notebook is used.
Installing MetaTrader5 for Python
First, install MetaTrader5. You can install it by entering the following command in Jupyter Notebook:
pip install MetaTrader5
This process only needs to be executed once, and there’s no problem to run it repeatedly afterwards.
Importing MetaTrader5 for Python
From here, enter the code in order in Jupyter Notebook. Once you’ve entered and saved everything, the file will serve as your FX trading bot.
First, import the installed MetaTrader5 for Python using the following command:
import MetaTrader5 as mt5
This will allow you to call functions defined in Python’s MT5 by using the format “mt5.function_name”.
Entering Login Information
In Python, you need to enter your login information to connect to MT5 via an API. Prepare the same information you would use to log into MT5:
login_ID = 12345678 # Enter your own login ID
login_server = 'XMTrading-MT5' # Enter your own login server
login_password = 'XXXXXXXXX' # Enter your own login password
The above is just a sample, so please replace it with your own login information.
Connecting to MT5
Connect to MT5 with the trading account specified by the login information above.
# Connect to MetaTrader5 with the trading account specified by the login information
if not mt5.initialize(login=login_ID, server=login_server,password=login_password):
print("initialize() failed, error code =",mt5.last_error())
quit()
At this point, MT5 should launch automatically. However, it might be better to launch MT5 beforehand to avoid any issues with subsequent processes.
Installing MT5
The above steps assume that MT5 is already installed. Please proceed with MT5 installed.
If you haven’t installed it yet, download and install the “PC Compatible MT5” from the link below.
Allow Algorithmic Trading
Once MT5 is open, go to the Options under Tools and check the box for “Allow Algorithmic Trading”.
Also, make sure that “Disable Algorithmic Trading via External Python API” is unchecked.
Otherwise, you won’t be able to access MT5 from Python.
Setting Inputs
As an initial setup, we’ll define several inputs.
symbol = 'GOLD' # Trading target
first_lot = 0.01 # Initial lot
nanpin_range = 200 # Nanpin width
profit_target = 1.00 # Profit target
magic_number = 10001 # Magic number
slippage = 10 # Slippage
Trading Target (symbol)
Choose the trading target (currency pair). In this case, we’ll use XMTrading’s GOLD, so we’ll input ‘GOLD’.
Initial Lot (first_lot)
This is the lot size setting for acquiring the first position. The minimum is 0.01, so we’ll set it to 0.01 for now.
Nanpin Width (nanpin_range)
This is the price range setting for how much the price should move after the first position before going for the next position. If you set it to 200, in the case of GOLD, it means a price range of 2.00. (Strictly speaking, the unit will be determined later as *Point.)
Profit Target (profit_target)
Set the profit target in terms of the amount. The unit here is yen, and the setting is 1.00 USD. The logic is to place a close order when a profit of 1.00 USD per position is generated.
Magic Number (magic_number)
This is like an identification number that exists in any EA. If you’re not running it alongside other EAs, any number will do. It has no effect on the logic. We’ll arbitrarily set it to 10001 here.
Slippage (slippage)
In MT4, it is possible to set the allowable range of slippage when placing an order. However, some brokers may invalidate the setting. XMTrading is one of them, and the allowable range of slippage will be invalidated when placing an order. However, as explained in the official description below, slippage rarely occurs.
We’ll arbitrarily set it to 10 here.
Acquiring Various Information
We’ll obtain the necessary information from MT5. For now, it’s just the point.
point=mt5.symbol_info(symbol).point # Smallest unit of price
Point is the smallest unit determined for each currency pair (trading target). For XMTrading’s GOLD, we’ll get point = 0.01.
Loop Processing
Once the initial setup is done, we’ll move on to loop processing.
Infinite Loop (while 1:)
while 1:
This is one way to achieve an infinite loop in Python. Unlike MQL4’s void OnTick(), which loops every time a tick is received, this loop runs infinitely regardless of ticks.
In the Nanpin Martingale bot, it is necessary to quickly place entry orders and close orders as soon as the price changes even slightly, so most of the main logic will be written within this loop.
From here on, we’ll describe the content that goes inside “while 1:”.
Obtaining the Latest Price (tick) Information
First, we’ll obtain the latest price (tick) information.
symbol_tick=mt5.symbol_info_tick(symbol) # Get tick information for
With this, you can now obtain the Ask and Bid values using symbol_tick.ask and symbol_tick.bid.
Checking Positions
Next, we will process the position checking.
Specifically, we will obtain the number of buy and sell positions, the unrealized profit/loss of the held positions, and the lot size and price of the latest position to be used for the next order.
# Checking Positions
buy_position = 0 # Initialize the number of buy position
sell_position = 0 # Initialize the number of sell positions
buy_profit = 0 # Initialize buy_profit
sell_profit = 0 # Initialize sell_profit
current_buy_lot = 0 # Initialize the lot size of the latest buy position
current_sell_lot = 0 # Initialize the lot size of the latest sell position
positions=mt5.positions_get(group='*'+symbol+'*') # Obtain position information
for i in range(len(positions)): # Check all positions
order_type = positions[i][5] # Get whether it is buy or sell
profit = positions[i][15] # Get the unrealized profit/loss of the position
if order_type == 0:
buy_position += 1
buy_profit += profit
current_buy_lot = positions[i][9]
current_buy_price = positions[i][10]
if order_type == 1:
sell_position += 1
sell_profit += profit
current_sell_lot = positions[i][9]
current_sell_price = positions[i][10]
buy_position, sell_position
buy_position represents the number of buy positions. It is used to determine how many buy positions are held. The same applies to sell_position.
buy_profit, sell_profit
buy_profit represents the total unrealized profit/loss of buy positions. If multiple buy positions are held, this figure will be the sum of the unrealized profit/loss of multiple positions. It is used to determine whether to close an order or not. The same applies to sell_profit.
current_buy_lot, current_sell_lot
current_buy_lot represents the lot size of the latest buy position. For example, if you have three buy positions, it means the lot size of the third buy position. The same applies to current_sell_lot.
current_buy_price, current_sell_price
current_buy_price represents the acquisition price of the latest buy position. For example, if you have three buy positions, it means the acquisition price of the third buy position. The same applies to current_sell_price.
Process Overview
To briefly explain this process, it checks all positions in order, and
if it is a buy position,
- Increase buy_position by one.
- Add the profit of the position to buy_profit.
- Record the lot size in current_buy_lot (by repeatedly overwriting, the information of the last buy position remains in the end).
- Record the acquisition price in current_buy_price (by repeatedly overwriting, the information of the last buy position remains in the end).
if it is a sell position,
- Increase sell_position by one.
- Add the profit of the position to sell_profit.
- Record the lot size in current_sell_lot (by repeatedly overwriting, the information of the last sell position remains in the end).
- Record the acquisition price in current_sell_price (by repeatedly overwriting, the information of the last sell position remains in the end)
This is the kind of processing being performed.
New entry orders
Next, let’s discuss new entry orders. The entry condition is simply “if there is no position.”
# New buy entry
if buy_position == 0:
request = {
'symbol': symbol, # Currency pair (trading target)
'action': mt5.TRADE_ACTION_DEAL, # Market order
'type': mt5.ORDER_TYPE_BUY, # Market buy order
'volume': first_lot, # Lot size
'price': symbol_tick.ask, # Order price
'deviation': slippage, # Slippage
'comment': 'first_buy', # Order comment
'magic': magic_number, # Magic number
'type_time': mt5.ORDER_TIME_GTC, # Order expiration
'type_filling': mt5.ORDER_FILLING_IOC, # Order type
}
result = mt5.order_send(request)
# New sell entry
if sell_position == 0:
request = {
'symbol': symbol, # Currency pair (trading target)
'action': mt5.TRADE_ACTION_DEAL, # Market order
'type': mt5.ORDER_TYPE_SELL, # Market sell order
'volume': first_lot, # Lot size
'price': symbol_tick.bid, # Order price
'deviation': slippage, # Slippage
'comment': 'first_sell', # Order comment
'magic': magic_number, # Magic number
'type_time': mt5.ORDER_TIME_GTC, # Order expiration
'type_filling': mt5.ORDER_FILLING_IOC, # Order type
}
result = mt5.order_send(request)
################################################################
In this case, we use a function called order_send. Create something like order information called “request structure” and send it to MT5 with “mt5.order_send(request)”.
The idea is that if there is no buy or sell position, immediately execute a market order at the current price (even with a market order, you specify the price to place the order). The lot size is set to first_lot (initial lot) which was set in the Input at the beginning.
Order comments do not need to be set, but in this case, we will set them as “first_buy” and “first_sell”.
If you want to check the details of “order expiration (type_time)” and “order type (type_filling)”, etc., please check the order_send function. However, for the purpose of this article, there should be no problem with the settings as they are.
Additional Entry (Grid) Orders
Next, let’s discuss the essential averaging order settings.
There’s nothing particularly difficult about it; the differences from the regular entry orders are:
- Orders are only placed if the averaging range condition is met.
- The lot size is set at 1.5 times the previous lot size.
That’s all.
# additional buy entry
if buy_position > 0 and symbol_tick.ask < current_buy_price - nanpin_range * point:
request = {
'symbol': symbol, # Currency pair (trading target)
'action': mt5.TRADE_ACTION_DEAL, # Market order
'type': mt5.ORDER_TYPE_BUY, # Market buy order
'volume': round(current_buy_lot * 1.5+0.001,2), # Lot size
'price': symbol_tick.ask, # Order price
'deviation': slippage, # Slippage
'comment': 'nanpin_buy', # Order comment
'magic': magic_number, # Magic number
'type_time': mt5.ORDER_TIME_GTC, # Order expiration
'type_filling': mt5.ORDER_FILLING_IOC, # Order type
}
result = mt5.order_send(request)
# additional sell entry
if sell_position > 0 and symbol_tick.bid > current_sell_price + nanpin_range * point:
request = {
'symbol': symbol, # Currency pair (trading target)
'action': mt5.TRADE_ACTION_DEAL, # Market order
'type': mt5.ORDER_TYPE_SELL, # Market sell order
'volume': round(current_sell_lot * 1.5+0.001,2), # Lot size
'price': symbol_tick.bid, # Order price
'deviation': slippage, # Slippage
'comment': 'nanpin_sell', # Order comment
'magic': magic_number, # Magic number
'type_time': mt5.ORDER_TIME_GTC, # Order expiration
'type_filling': mt5.ORDER_FILLING_IOC, # Order type
}
result = mt5.order_send(request)
Checking if the current price has reached the nanpin range
if buy_position > 0 and symbol_tick.ask < current_buy_price - nanpin_range * point:
current_buy_price is the acquisition price of the latest position obtained in the position confirmation.
nanpin_range is the averaging down range of 200 set in the Input. point is 0.01, which is obtained in various information.
In other words, this if statement, for example, if the price is 1740.21, becomes the conditional statement:
Position close order
Finally, we have the position close order.
We check the close conditions and, if they are met, we submit close orders for each position in sequence.
# buy close
if buy_position > 0 and buy_profit > profit_target * buy_position:
for i in range(len(positions)):
ticket=positions[i][0]
order_type = positions[i][5]
lot = positions[i][9]
if order_type == 0:
request = {
'symbol': symbol, # Currency pair (trading target)
'action': mt5.TRADE_ACTION_DEAL, # Market order
'type': mt5.ORDER_TYPE_SELL, # Market sell order
'volume': lot, # Lot size
'price': symbol_tick.bid, # Order price
'deviation': slippage, # Slippage
'type_time': mt5.ORDER_TIME_GTC, # Order expiration
'type_filling': mt5.ORDER_FILLING_IOC, # Order type
'position':ticket # Ticket number
}
result = mt5.order_send(request)
# sell close
if sell_position > 0 and sell_profit > profit_target * sell_position:
for i in range(len(positions)):
ticket=positions[i][0]
order_type = positions[i][5]
lot = positions[i][9]
if order_type == 1:
request = {
'symbol': symbol, # Currency pair (trading target)
'action': mt5.TRADE_ACTION_DEAL, # Market order
'type': mt5.ORDER_TYPE_BUY, # Market buy order
'volume': lot, # Lot size
'price': symbol_tick.ask, # Order price
'deviation': slippage, # Slippage
'type_time': mt5.ORDER_TIME_GTC, # Order expiration
'type_filling': mt5.ORDER_FILLING_IOC, # Order type
'position':ticket # Ticket number
}
result = mt5.order_send(request)
Position Close Conditions
if buy_position > 0 and buy_profit > profit_target * buy_position:
The conditions for placing a close order for buy positions are met when the following two conditions are satisfied simultaneously:
- Holding 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 expressed in the formula above. Specifically, for example, if the number of buy positions is 3, and the profit target (profit_target) is set at 1.00 USD, a close order for buy positions will be placed when the total unrealized profit of buy positions exceeds 3.00 USD, which is three times the 1.00 USD profit target.
Function to Close Positions
Just like with entry orders, the order_send function is used here as well. The “request structure (request)” is supplemented with the ticket number (the number that identifies the position) and the opposite trade order (a sell order for a buy position or a buy order for a sell position) is sent.
Conclusion
With the combination of the elements we have seen so far, the basic averaging down Martingale bot is complete.
How did you find it? I hope you’ve come to understand that it has a simpler structure than you might have thought.


Comments