12 min read

7 Indispensable Trading Functions For Your MetaTrader 5 Python Trading Bot

Your Python trading bot needs seven critical functions to unleash the power of MetaTrader 5. This chapter shows you how.
Text: Build Your Own Trading Bot, 7 Indispensable Functions.
7 Indispensable Trading Functions Your Meta Trader 5 Trading Bot Needs. Part of the Book “Build Your Own Trading Bot”.

Build Your Own Algorithmic Trading Bot with Python Book

When it comes to algorithmic trading, no matter what exchange you’re using, there are 7 functions you’ll need to implement. These functions form the basis of your trading interactions, enabling your python trading bot to execute and manage trades based on your algorithmic analysis.

It’s critical to get these functions right. When done effectively, your trading bot operates smoothly and quickly. Signals generated from your analysis are acted on immediately, allowing you to focus on developing new, more profitable strategies. However, if you get these functions wrong, you’ll quickly find yourself unable to take advantage of opportunities.

In this chapter, I’ll show you how to build and integrate each of the 7 indispensable functions into your python trading bot.


Initialize Symbols

Any interaction with trading symbols on MetaTrader 5 (MT5) requires the symbols to be initialized. If the symbols are not initialized, you’ll find that you get return values of nothing, which can cause a cascading series of errors through your trading bot.

Therefore, the first indispensable trading function for your python trading bot is to initialize the symbol(s) your trading bot will be using. Because MetaTrader 5 provides different symbols depending on which broker you’re using, I’ll include a step to confirm that the symbol exists before initializing it.

The function performs the following steps:

  1. Retrieves a list of available symbols on your MT5
  2. Checks the provided symbol(s) are available
  3. If available, initialize
  4. If not, return an error

Note. Custom error handling and logging have not yet been implemented in the book, so the errors and notifications will be generic.

How to Initialize Symbols On MetaTrader 5 with Python

Place the function below in the file mt5_interaction covered in the previous chapter:

def initialize_symbols(symbol_array): 
    """ 
    Function to initialize a symbol on MT5. Note that different brokers have different symbols.  
    To read more: https://trading-data-analysis.pro/everything-you-need-to-connect-your-python-trading-bot-to-metatrader-5-de0d8fb80053 
    :param symbol_array: List of symbols to be initialized 
    :return: True if all symbols enabled 
    """ 
    # Get a list of all symbols supported in MT5 
    all_symbols = MetaTrader5.symbols_get() 
    # Create a list to store all the symbols 
    symbol_names = [] 
    # Add the retrieved symbols to the list 
    for symbol in all_symbols: 
        symbol_names.append(symbol.name) 
 
    # Check each provided symbol in symbol_array to ensure it exists 
    for provided_symbol in symbol_array: 
        if provided_symbol in symbol_names: 
            # If it exists, enable 
            if MetaTrader5.symbol_select(provided_symbol, True): 
                # Print outcome to user. Custom Logging Not yet enabled 
                print(f"Symbol {provided_symbol} enabled") 
            else: 
                # Print the outcome to screen. Custom Logging/Error Handling not yet created 
                print(f"Error creating symbol {provided_symbol}. Symbol not enabled.") 
                # Return a generic value error. Custom Error Handling not yet created. 
                return ValueError 
        else: 
            # Print the outcome to screen. Custom Logging/Error Handling not yet created 
            print(f"Symbol {provided_symbol} does not exist in this MT5 implementation. Symbol not enabled.") 
            # Return a generic syntax error. Custom Error Handling not yet enabled 
            return SyntaxError 
    # Return true if all symbols enabled 
    return True

Place Order

Programmatically placing an order on MT5 allows your Trading Bot to be incredibly fast and efficient. It also removes the last-second “Should I really make this trade” nerves that many traders experience.

There are two approaches to placing an order with Python. I’ll explain both and then provide the code for a function which allows you to use whichever method you need.

Note. For this chapter, I’ll be covering the following order types as they fit the most use cases and risk profiles for retail traders:

  1. BUY -> Buy at the current market price with the volume specified
  2. SELL -> Sell at the current market price market with the volume specified
  3. BUY_STOP -> Buy once the market price reaches a certain price limit
  4. SELL_STOP -> Sell once the market price reaches a certain price limit
  5. All orders will include a STOP_LOSS and TAKE PROFIT
  6. All orders will be valid until the time specified, allowing you to control when and how your orders are invalidated (and getting around MetaTraders 10-minute minimum expiry period)

MetaTrader does allow for a number of other types of orders, and I may write about them in a future chapter.

Directly Place Order

The first option is to directly place the order. There is no testing of the trade, no “Let’s see if this is a valid option”. You simply receive a signal and place a trade, accepting whatever risk that may entail.

This provides a marginal benefit in terms of time. It removes the time taken to test an order, receive a ‘Yes this ok’ return then place the order. Such a time-saving can be useful if you’re placing time-sensitive trades.

Test Balance Then Place Order

The second option is to check your balance first. With this option, an order is sent to your MT5, balance checked, and only if a positive return is received, the order is placed.

This approach is generally recommended by more professional traders. It can avoid the risk of there being insufficient funds.

How to Place an Order on MetaTrader 5 with Python

Here’s the function. If you wish to use the direct ordering method, simply set direct=True.

def place_order(order_type, symbol, volume, stop_loss, take_profit, comment, direct=False, price=0): 
    """ 
    Function to place a trade on MetaTrader 5 with option to check balance first 
    :param order_type: String from options: SELL_STOP, BUY_STOP, SELL, BUY 
    :param symbol: String 
    :param volume: String or Float 
    :param stop_loss: String or Float 
    :param take_profit: String of Float 
    :param comment: String 
    :param direct: Bool, defaults to False 
    :param price: String or Float, optional 
    :return: Trade outcome or syntax error 
    """ 
    # Convert volume, price, stop_loss, and take_profit to float 
    volume = float(volume) 
    stop_loss = float(stop_loss) 
    take_profit = float(take_profit) 
 
    # Set up the place order request 
    request = { 
        "symbol": symbol, 
        "volume": volume, 
        "sl": round(stop_loss, 3), 
        "tp": round(take_profit, 3), 
        "type_time": MetaTrader5.ORDER_TIME_GTC, 
        "comment": comment 
    } 
 
    # Create the order type based upon provided values. This can be expanded for different order types as needed. 
    if order_type == "SELL_STOP": 
        request['type'] = MetaTrader5.ORDER_TYPE_SELL_STOP 
        request['action'] = MetaTrader5.TRADE_ACTION_PENDING 
        request['price'] = round(price, 3) 
        request['type_filling'] = MetaTrader5.ORDER_FILLING_RETURN 
    elif order_type == "BUY_STOP": 
        request['type'] = MetaTrader5.ORDER_TYPE_BUY_STOP 
        request['price'] = round(price, 3) 
        request['action'] = MetaTrader5.TRADE_ACTION_PENDING 
        request['type_filling'] = MetaTrader5.ORDER_FILLING_RETURN 
    elif order_type == "SELL": 
        request['type'] = MetaTrader5.ORDER_TYPE_SELL 
        request['action'] = MetaTrader5.TRADE_ACTION_DEAL 
        request['type_filling'] = MetaTrader5.ORDER_FILLING_IOC 
    elif order_type == "BUY": 
        request['type'] = MetaTrader5.ORDER_TYPE_BUY 
        request['action'] = MetaTrader5.TRADE_ACTION_DEAL 
        request['type_filling'] = MetaTrader5.ORDER_FILLING_IOC 
    else: 
        print("Choose a valid order type from SELL_STOP, BUY_STOP, SELL, BUY") 
        return SyntaxError  # Generic error handling only 
 
    if direct is True: 
        # Send the order to MT5 
        order_result = MetaTrader5.order_send(request) 
        # Notify based on return outcomes 
        if order_result[0] == 10009: 
            # Print result 
            print(f"Order for {symbol} successful") 
        elif order_result[0] == 10027: 
            # Turn off autotrading 
            print(f"Turn off Algo Trading on MT5 Terminal") 
        else: 
            # Print result 
            print(f"Error placing order. ErrorCode {order_result[0]}, Error Details: {order_result}") 
        return order_result 
    else: 
        # Check the order 
        result = MetaTrader5.order_check(request) 
        print(result) 
        if result[0] == 0: 
            print("Balance Check Successful") 
            # If order check is successful, place the order. Little bit of recursion for fun. 
            order_outcome = place_order( 
                order_type=order_type, 
                symbol=symbol, 
                volume=volume, 
                price=price, 
                stop_loss=stop_loss, 
                take_profit=take_profit, 
                comment=comment, 
                direct=True 
            ) 
        else: 
            print(f"Order unsucessful. Details: {result}") 
            return False
Note. If you’re wondering about the return codes, you can check all of them out on the webpage Trade Server Return Codes (also linked in resources section). These will be revisited when custom error handling is introduced.

Cancel Order

Canceling an order programmatically is an indispensable trading bot function. It allows your trading bot to respond rapidly and efficiently to a changing market. Including it in your MetaTrader trading bot also allows you to ensure that your trades are managed by your algorithm.

Canceling an order simply requires you to know your order number. I’ll cover how to get a list of your open orders later in this chapter.

How to Cancel an Order on MetaTrader 5 with Python

Here’s the code to cancel an order on MetaTrader 5 with Python

def cancel_order(order_number): 
    """ 
    Function to cancel an order 
    :param order_number: Int 
    :return:  
    """ 
    # Create the request 
    request = { 
        "action": MetaTrader5.TRADE_ACTION_REMOVE, 
        "order": order_number, 
        "comment": "Order Removed" 
    } 
    # Send order to MT5 
    order_result = MetaTrader5.order_send(request) 
    return order_result

Modify Position

Modifying a position programmatically gives you some powerful options for your Python Trading Bot. Here’s a couple (and why I’ve included it in the list of indispensable trading functions):

  1. Update your trade positions if market conditions change
  2. Implement your own powerful trailing stop / trailing profit (I’ll show you how to do this in a future chapter)

To modify an open position, you’ll need the following:

  1. Order Number
  2. Symbol
  3. New Stop Loss
  4. New Take Profit
Note. This code will only return a Boolean value for the trade. Because we haven’t yet implemented custom error handling, this is to prevent your code from breaking down altogether if an error is encountered.

How to Modify a Position on MetaTrader 5 with Python

Here’s the code to modify a position on MetaTrader 5 using Python:

# Function to modify an open position 
def modify_position(order_number, symbol, new_stop_loss, new_take_profit): 
    """ 
    Function to modify a position 
    :param order_number: Int 
    :param symbol: String 
    :param new_stop_loss: Float  
    :param new_take_profit: Float 
    :return: Boolean 
    """ 
    # Create the request 
    request = { 
        "action": MetaTrader5.TRADE_ACTION_SLTP, 
        "symbol": symbol, 
        "sl": new_stop_loss, 
        "tp": new_take_profit, 
        "position": order_number 
    } 
    # Send order to MT5 
    order_result = MetaTrader5.order_send(request) 
    if order_result[0] == 10009: 
        return True 
    else: 
        return False

Close Position

Closing is a critical function of an effective trading bot. It enables the designer to ensure there are emergency provisions in place to limit losses.

Other uses for this function can include:

  1. Partial reductions in position sizes to lock in profits
  2. Rapidly closing positions which have been opened in error

How to Close a Position in MetaTrader 5 With Python

Here’s the code:

def close_position(order_number, symbol, volume, order_type, price, comment): 
    """ 
    Function to close an open position from MetaTrader 5 
    :param order_number: int 
    :return: Boolean 
    """ 
    # Create the request 
    request = { 
        'action': MetaTrader5.TRADE_ACTION_DEAL, 
        'symbol': symbol, 
        'volume': volume, 
        'position': order_number, 
        'price': price, 
        'type_time': MetaTrader5.ORDER_TIME_GTC, 
        'type_filling': MetaTrader5.ORDER_FILLING_IOC, 
        'comment': comment 
    } 
 
    if order_type == "SELL": 
        request['type'] = MetaTrader5.ORDER_TYPE_SELL 
    elif order_type == "BUY": 
        request['type'] = MetaTrader5.ORDER_TYPE_BUY 
    else: 
        return Exception 
 
    # Place the order 
    result = MetaTrader5.order_send(request) 
    if result[0] == 10009: 
        print("Order Closed") 
        return True 
    else: 
        print(result) 
        return False

Get Open Orders

Retrieving a list of open orders allows you to keep track of your current commitments. This can be an important function of risk management, ensuring your trading bot doesn’t overcommit.

How To Retrieve a List of Open Orders from MetaTrader 5 with Python

Here’s the code:

def get_open_orders(): 
    """ 
    Function to retrieve a list of open orders from MetaTrader 5 
    :return: List of open orders 
    """ 
    orders = MetaTrader5.orders_get() 
    order_array = [] 
    for order in orders: 
        order_array.append(order[0]) 
    return order_array

Get Open Positions

Retrieving a list of open positions allows your python trading bot to quickly calculate your current trade exposure. This can be useful for a variety of functions such as:

  1. Dynamically adjusting your trailing stops to manage risk
  2. Calculating a changing balance of funds for your next trade

I’ll cover these approaches and more in future chapters, however, for now it’s important to be able to programmatically retrieve your current open positions.

How to Retrieve a List of Open Positions from MetaTrader 5 with Python

Here’s the code:

def get_open_positions(): 
    """ 
    Function to retrieve a list of open orders from MetaTrader 5 
    :return: list of positions 
    """ 
    # Get position objects 
    positions = MetaTrader5.positions_get() 
    # Return position objects 
    return positions

Make It So

I like to ensure that every chapter has a way to show some progress.

For this chapter, have a practice at opening and then closing your own trades using your test account.

To do this, return to your main.py page and add make your __main__ look like this:

# Press the green button in the gutter to run the script. 
if __name__ == '__main__': 
    # Import project settings 
    project_settings = get_project_settings(import_filepath=import_filepath) 
    # Check Exchanges 
    check_exchanges(project_settings=project_settings) 
    # Place an order 
    mt5_interaction.place_order( 
        order_type="BUY", 
        symbol="BTCUSD.a", # This is the raw symbol for BTCUSD on IC Markets. Your exchange may be different. 
        volume=0.1, 
        stop_loss=17130.00, # Update this value to match when you trade. Hopefully it's closer to 100,000 
        take_profit=17200.00, # Update this value to match when you trade. 
        comment="Test Trade" 
    )

This will place a trade for you at whatever the value is for you today. Like I said, hopefully it’s closer to $100,000 USD by then. Lol.

Now, go to your MetaTrader (which should have opened when you ran your project) and navigate to the trade window. In this trade window, you should see a column called ‘Ticket’.

Retrieve this number and then update your __main__ to look like this:

# Press the green button in the gutter to run the script. 
if __name__ == '__main__': 
    # Import project settings 
    project_settings = get_project_settings(import_filepath=import_filepath) 
    # Check Exchanges 
    check_exchanges(project_settings=project_settings) 
    # Place an order 
    """ 
    mt5_interaction.place_order( 
        order_type="BUY", 
        symbol="BTCUSD.a", 
        volume=0.1, 
        stop_loss=17130.00, 
        take_profit=17200.00, 
        comment="Test Trade" 
    ) 
    """ 
    mt5_interaction.close_position( 
        order_number=12345678, # Replace with your order number  
        symbol="BTCUSD.a", # Must match your place order symbol 
        volume=0.1, # Volume must be <= purchase volume 
        order_type="SELL", # Must be the opposite of the purchase 
        price=17135.10, # Must be <= to current price 
        comment="Test Trade" 
    )

This will close the order you just placed.

Of course, it would be far easier to do the opening and closing programmatically, and that’s what we’ll be covering later on in the book.


Say Hi!

I love hearing from my readers, so feel free to reach out. It means a ton to me when you clap for my articles or drop a friendly comment — it helps me know that my content is helping.

Resources

  1. Project GitHub
  2. MetaTrader 5 website
  3. MetaTrader Python Documentation
  4. MetaTrader Python Package (PyPi)
  5. Meta Query Langage
  6. Build Your Own Algorithmic Trading Bot with Python Introduction
  7. Everything You Need to Connect to MetaTrader 5 with Pyton
  8. MetaTrader 5 Trade Server Return Codes

Full Code

import MetaTrader5


# Function to start Meta Trader 5 (MT5)
def start_mt5(username, password, server, path):
    """
    Initializes and logs into MT5
    :param username: 8 digit integer
    :param password: string
    :param server: string
    :param path: string
    :return: True if successful, Error if not
    """
    # Ensure that all variables are the correct type
    uname = int(username)  # Username must be an int
    pword = str(password)  # Password must be a string
    trading_server = str(server)  # Server must be a string
    filepath = str(path)  # Filepath must be a string

    # Attempt to start MT5
    if MetaTrader5.initialize(login=uname, password=pword, server=trading_server, path=filepath):
        # Login to MT5
        if MetaTrader5.login(login=uname, password=pword, server=trading_server):
            return True
        else:
            print("Login Fail")
            quit()
            return PermissionError
    else:
        print("MT5 Initialization Failed")
        quit()
        return ConnectionAbortedError


# Function to initialize a symbol on MT5
def initialize_symbols(symbol_array):
    """
    Function to initialize a symbol on MT5. Note that different brokers have different symbols.
    To read more: https://trading-data-analysis.pro/everything-you-need-to-connect-your-python-trading-bot-to-metatrader-5-de0d8fb80053
    :param symbol_array: List of symbols to be initialized
    :return: True if all symbols enabled
    """
    # Get a list of all symbols supported in MT5
    all_symbols = MetaTrader5.symbols_get()
    # Create a list to store all the symbols
    symbol_names = []
    # Add the retrieved symbols to the list
    for symbol in all_symbols:
        symbol_names.append(symbol.name)

    # Check each provided symbol in symbol_array to ensure it exists
    for provided_symbol in symbol_array:
        if provided_symbol in symbol_names:
            # If it exists, enable
            if MetaTrader5.symbol_select(provided_symbol, True):
                # Print outcome to user. Custom Logging Not yet enabled
                print(f"Symbol {provided_symbol} enabled")
            else:
                # Print the outcome to screen. Custom Logging/Error Handling not yet created
                print(f"Error creating symbol {provided_symbol}. Symbol not enabled.")
                # Return a generic value error. Custom Error Handling not yet created.
                return ValueError
        else:
            # Print the outcome to screen. Custom Logging/Error Handling not yet created
            print(f"Symbol {provided_symbol} does not exist in this MT5 implementation. Symbol not enabled.")
            # Return a generic syntax error. Custom Error Handling not yet enabled
            return SyntaxError
    # Return true if all symbols enabled
    return True


# Function to place a trade on MT5
def place_order(order_type, symbol, volume, stop_loss, take_profit, comment, direct=False, price=0):
    """
    Function to place a trade on MetaTrader 5 with option to check balance first
    :param order_type: String from options: SELL_STOP, BUY_STOP, SELL, BUY
    :param symbol: String
    :param volume: String or Float
    :param stop_loss: String or Float
    :param take_profit: String of Float
    :param comment: String
    :param direct: Bool, defaults to False
    :param price: String or Float, optional
    :return: Trade outcome or syntax error
    """
    # Convert volume, price, stop_loss, and take_profit to float
    volume = float(volume)
    stop_loss = float(stop_loss)
    take_profit = float(take_profit)

    # Set up the place order request
    request = {
        "symbol": symbol,
        "volume": volume,
        "sl": round(stop_loss, 3),
        "tp": round(take_profit, 3),
        "type_time": MetaTrader5.ORDER_TIME_GTC,
        "comment": comment
    }

    # Create the order type based upon provided values. This can be expanded for different order types as needed.
    if order_type == "SELL_STOP":
        request['type'] = MetaTrader5.ORDER_TYPE_SELL_STOP
        request['action'] = MetaTrader5.TRADE_ACTION_PENDING
        request['price'] = round(price, 3)
        request['type_filling'] = MetaTrader5.ORDER_FILLING_RETURN
    elif order_type == "BUY_STOP":
        request['type'] = MetaTrader5.ORDER_TYPE_BUY_STOP
        request['price'] = round(price, 3)
        request['action'] = MetaTrader5.TRADE_ACTION_PENDING
        request['type_filling'] = MetaTrader5.ORDER_FILLING_RETURN
    elif order_type == "SELL":
        request['type'] = MetaTrader5.ORDER_TYPE_SELL
        request['action'] = MetaTrader5.TRADE_ACTION_DEAL
        request['type_filling'] = MetaTrader5.ORDER_FILLING_IOC
    elif order_type == "BUY":
        request['type'] = MetaTrader5.ORDER_TYPE_BUY
        request['action'] = MetaTrader5.TRADE_ACTION_DEAL
        request['type_filling'] = MetaTrader5.ORDER_FILLING_IOC
    else:
        print("Choose a valid order type from SELL_STOP, BUY_STOP, SELL, BUY")
        return SyntaxError  # Generic error handling only

    if direct is True:
        # Send the order to MT5
        order_result = MetaTrader5.order_send(request)
        # Notify based on return outcomes
        if order_result[0] == 10009:
            # Print result
            print(f"Order for {symbol} successful")
        elif order_result[0] == 10027:
            # Turn off autotrading
            print(f"Turn off Algo Trading on MT5 Terminal")
        else:
            # Print result
            print(f"Error placing order. ErrorCode {order_result[0]}, Error Details: {order_result}")
        return order_result
    else:
        # Check the order
        result = MetaTrader5.order_check(request)
        print(result)
        if result[0] == 0:
            print("Balance Check Successful")
            # If order check is successful, place the order. Little bit of recursion for fun.
            order_outcome = place_order(
                order_type=order_type,
                symbol=symbol,
                volume=volume,
                price=price,
                stop_loss=stop_loss,
                take_profit=take_profit,
                comment=comment,
                direct=True
            )
        else:
            print(f"Order unsucessful. Details: {result}")
            return False

# Function to cancel an order
def cancel_order(order_number):
    """
    Function to cancel an order
    :param order_number: Int
    :return:
    """
    # Create the request
    request = {
        "action": MetaTrader5.TRADE_ACTION_REMOVE,
        "order": order_number,
        "comment": "Order Removed"
    }
    # Send order to MT5
    order_result = MetaTrader5.order_send(request)
    return order_result


# Function to modify an open position
def modify_position(order_number, symbol, new_stop_loss, new_take_profit):
    """
    Function to modify a position
    :param order_number: Int
    :param symbol: String
    :param new_stop_loss: Float
    :param new_take_profit: Float
    :return: Boolean
    """
    # Create the request
    request = {
        "action": MetaTrader5.TRADE_ACTION_SLTP,
        "symbol": symbol,
        "sl": new_stop_loss,
        "tp": new_take_profit,
        "position": order_number
    }
    # Send order to MT5
    order_result = MetaTrader5.order_send(request)
    if order_result[0] == 10009:
        return True
    else:
        return False


# Function to retrieve all open orders from MT5
def get_open_orders():
    """
    Function to retrieve a list of open orders from MetaTrader 5
    :return: List of open orders
    """
    orders = MetaTrader5.orders_get()
    order_array = []
    for order in orders:
        order_array.append(order[0])
    return order_array


# Function to retrieve all open positions
def get_open_positions():
    """
    Function to retrieve a list of open orders from MetaTrader 5
    :return: list of positions
    """
    # Get position objects
    positions = MetaTrader5.positions_get()
    # Return position objects
    return positions


# Function to close an open position
def close_position(order_number, symbol, volume, order_type, price, comment):
    """
    Function to close an open position from MetaTrader 5
    :param order_number: int
    :return: Boolean
    """
    # Create the request
    request = {
        'action': MetaTrader5.TRADE_ACTION_DEAL,
        'symbol': symbol,
        'volume': volume,
        'position': order_number,
        'price': price,
        'type_time': MetaTrader5.ORDER_TIME_GTC,
        'type_filling': MetaTrader5.ORDER_FILLING_IOC,
        'comment': comment
    }

    if order_type == "SELL":
        request['type'] = MetaTrader5.ORDER_TYPE_SELL
    elif order_type == "BUY":
        request['type'] = MetaTrader5.ORDER_TYPE_BUY
    else:
        return Exception

    # Place the order
    result = MetaTrader5.order_send(request)
    if result[0] == 10009:
        print("Order Closed")
        return True
    else:
        print(result)
        return False