img

7 Indispensable Trading Functions For Your MetaTrader 5 Python Trading Bot

img
valuezone 10 December 2022

7 Indispensable Trading Functions For Your MetaTrader 5 Python Trading Bot

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.

About The Book

This article is one of the chapters in my living book “Build Your Own Algorithmic Trading Bot with Python”. The book covers everything you need to build your own trading bot, including detecting indicators, developing strategies, and automating trading. The link above is to the Medium version of the book where you can read the chapters as I publish them.

Not financial advice. This article and the code provided are for use at your own risk. It isn’t financial advice, nor is it designed to make claims about profitability.

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.

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
view rawmt5_interaction.py hosted with ❤ by GitHub