How to Create an FX Automated Trading Tool using Python: Copy and Paste Friendly

MT5

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.

When it comes to automated trading in Forex, I believe that EAs (Expert Advisors) built with languages such as MQL4 or MQL5 for use in MT4 or MT5 are the most popular. However, in MT5, an official Python API is provided, making it relatively easy to access MT5 and perform automated trading using Python. With Python, it’s also easy to implement machine learning. There is potential for growth, such as the development of models combining cost-averaging and machine learning.

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:

          “If the current price (Ask) is below 1740.21 – 200 × 0.01 = 1738.21”

          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

              Copied title and URL