Creating an algotrader/trading bot with Python – Part 1

As an Amazon Associate I earn from qualifying purchases.

5 Min Read

Creating an algotrader/trading bot with Python & the MT5 platform should generally have 3 main pieces of functionality, – opening a trade, closing a trade and receiving and analyzing new data. This post will take you through the last step. Because of the complexity, it will be split into multiple parts. Let’s get started with the first part!

Specifying the Trading Interval

When building an algotrader, generally speaking it should react to new data. This means that your trader will only run at set intervals. Whether that is 1 minute, 3 minutes, 1 hour etc. For this tutorial, you will need to install the schedule and pytz libraries.

schedule – This can be installed here or by using the following command:

pip install schedule

pytz – This can be installed here or by using the following command:

pip install pytz

If you have followed along from the previous posts, your code will look something like below.

import MetaTrader5 as mt5
from datetime import datetime
import pandas as pd

def connect(account):
    account = int(account)
    mt5.initialize()
    authorized=mt5.login(account)

    if authorized:
        print("Connected: Connecting to MT5 Client")
    else:
        print("Failed to connect at account #{}, error code: {}"
              .format(account, mt5.last_error()))

def open_position(pair, order_type, size, tp_distance=None, stop_distance=None):
    symbol_info = mt5.symbol_info(pair)
    if symbol_info is None:
        print(pair, "not found")
        return

    if not symbol_info.visible:
        print(pair, "is not visible, trying to switch on")
        if not mt5.symbol_select(pair, True):
            print("symbol_select({}}) failed, exit",pair)
            return
    print(pair, "found!")

    point = symbol_info.point
    
    if(order_type == "BUY"):
        order = mt5.ORDER_TYPE_BUY
        price = mt5.symbol_info_tick(pair).ask
        if(stop_distance):
            sl = price - (stop_distance * point)
        if(tp_distance):
            tp = price + (tp_distance * point)
            
    if(order_type == "SELL"):
        order = mt5.ORDER_TYPE_SELL
        price = mt5.symbol_info_tick(pair).bid
        if(stop_distance):
            sl = price + (stop_distance * point)
        if(tp_distance):
            tp = price - (tp_distance * point)

    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": pair,
        "volume": float(size),
        "type": order,
        "price": price,
        "sl": sl,
        "tp": tp,
        "magic": 234000,
        "comment": "",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }

    result = mt5.order_send(request)

    if result.retcode != mt5.TRADE_RETCODE_DONE:
        print("Failed to send order :(")
    else:
        print ("Order successfully placed!")

def positions_get(symbol=None):
    if(symbol is None):
	    res = mt5.positions_get()
    else:
        res = mt5.positions_get(symbol=symbol)

    if(res is not None and res != ()):
        df = pd.DataFrame(list(res),columns=res[0]._asdict().keys())
        df['time'] = pd.to_datetime(df['time'], unit='s')
        return df
    
    return pd.DataFrame()

def close_position(deal_id):
    open_positions = positions_get()
    open_positions = open_positions[open_positions['ticket'] == deal_id]
    order_type  = open_positions["type"][0]
    symbol = open_positions['symbol'][0]
    volume = open_positions['volume'][0]

    if(order_type == mt5.ORDER_TYPE_BUY):
        order_type = mt5.ORDER_TYPE_SELL
        price = mt5.symbol_info_tick(symbol).bid
    else:
        order_type = mt5.ORDER_TYPE_BUY
        price = mt5.symbol_info_tick(symbol).ask
	
    close_request={
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": symbol,
        "volume": float(volume),
        "type": order_type,
        "position": deal_id,
        "price": price,
        "magic": 234000,
        "comment": "Close trade",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }

    result = mt5.order_send(close_request)
    
    if result.retcode != mt5.TRADE_RETCODE_DONE:
        print("Failed to close order :(")
    else:
        print ("Order successfully closed!")

def close_positons_by_symbol(symbol):
    open_positions = positions_get(symbol)
    open_positions['ticket'].apply(lambda x: close_position(x))

Start by modifying the code to import timedelta, time, schedule and pytz:

import MetaTrader5 as mt5
from datetime import datetime, timedelta
import pandas as pd
import time
import schedule
import pytz

At every interval, your algotrader should do 2 things which is receive new data and then analyze & open/close trades accordingly. Before this though, you need an entry point to the python application. This is defined in the following code:

if __name__ == '__main__':
    live_trading()

So, you are calling a method called live_trading but it doesn’t exist. Let’s fix that! Start by defining the method signature:

def live_trading():

At this point, you will want to define the exact intervals to get new data and, when you want to analyze the data to open/close trades. This is done by using the schedule library. For this example, I will be using mt5.TIMEFRAME_M15 to get data every 15 minutes. You will also need to add an infinite loop which will check the schedule for pending items every second:

    schedule.every().hour.at(":00").do(run_trader, mt5.TIMEFRAME_M15)
    schedule.every().hour.at(":15").do(run_trader, mt5.TIMEFRAME_M15)
    schedule.every().hour.at(":30").do(run_trader, mt5.TIMEFRAME_M15)
    schedule.every().hour.at(":45").do(run_trader, mt5.TIMEFRAME_M15)

    while True:
        schedule.run_pending()
        time.sleep(1)

The first parameter for the do(.. method is the method you want to call (you will be creating the run_trader method shortly), and the second, is the parameter for the run_trader method. With this out-of-the-way, it is time to implement the run_trader method.

Pulling the data

The next steps to creating an algotrader/trading bot with Python & MT5 is to start by defining the run_trader method with time_frame as an argument. In this method you will call connect and get_data passing time_frame as the argument for get_data.

def run_trader(time_frame):
    connect(39672374)
    get_data(time_frame)

Next, define the get_data method. Add a list of pairs you wish to get data for and an empty dictionary. For simplicity, I used EURUSD and USDCAD:

def get_data(time_frame):
    pairs = ['EURUSD', 'USDCAD']
    pair_data = dict()

Define a loop to iterate over the list:

def get_data(time_frame):
    pairs = ['EURUSD', 'USDCAD']
    pair_data = dict()
    for pair in pairs:

In an earlier post, I showed you how to get tick data from MT5. In this post, I mention a method called copy_rates_range from the MT5 API. You will use this method to get the data. But first, you must setup a few arguments for it.

You need to provide copy_rates_range with a symbol, timeframe, date from and date to. The date from can be any date in the past but your date to will be the current date & time. This is because your algotrader is trading in the live markets and you will always want the most up-to-date data.

Note: Every broker on MT5 operates on its local timezone. This is extremely important to understand. To compensate for this, you will use the pytz library that you installed earlier:

def get_data(time_frame):
    pairs = ['EURUSD', 'USDCAD']
    for pair in pairs:
        utc_from = datetime(2021, 1, 1)
        date_to = datetime.now().astimezone(pytz.timezone('Europe/Athens'))

In the code snippet above, you can see that I am calculating the date_to variable based on my local date & time but converting it to the Europe/Athens timezone as this is the timezone for my broker. Set incorrectly, there is a possibility of not getting the latest data from your broker. A full list of time zones for pytz.timezome can be found here.

If you’re wondering why you need to fetch a range of data at every interval and this is a great question! If you want to apply moving averages or other indicators to decide to open/close a trade, you will need this historical data. In a future blog, I will show you how to cache the data locally.

Similarly, like my post Getting Data from MT5 to Python, you will be using copy_rates_range function from the MT5 API. You will want drop the last row when you get the data. This is important as you do not want any partial data from the current period. Let’s say the current time is 12:45. You will want to collect data for 12:30 (if your chosen interval is 15 minutes).

        date_to = datetime(date_to.year, date_to.month, date_to.day, hour=date_to.hour, minute=date_to.minute)
        rates = mt5.copy_rates_range(pair, time_frame, utc_from, date_to)
        rates_frame = pd.DataFrame(rates)
        rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')
        rates_frame.drop(rates_frame.tail(1).index, inplace = True) 

The last thing to do, is to add each pair along with the tick data to the dictionary pair_data created earlier. You should also return this dictionary from the method as you will be using this later.

def get_data(time_frame):
    pairs = ['EURUSD', 'USDCAD']
    pair_data = dict()
    for pair in pairs:
        utc_from = datetime(2021, 1, 1, tzinfo=pytz.timezone('Europe/Athens'))
        date_to = datetime.now().astimezone(pytz.timezone('Europe/Athens'))
        date_to = datetime(date_to.year, date_to.month, date_to.day, hour=date_to.hour, minute=date_to.minute)
        rates = mt5.copy_rates_range(pair, time_frame, utc_from, date_to)
        rates_frame = pd.DataFrame(rates)
        rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')
        rates_frame.drop(rates_frame.tail(1).index, inplace = True)
        pair_data[pair] = rates_frame
    return pair_data

If you are interested in learning more about algo trading and trading systems, I highly recommend reading this book. I have taken some of my own trading ideas and strategies from this book. It also provided me a great insight into effective back testing. Check it out here.

That’s all for now! Check back on Wednesday to see the next part of this tutorial on creating an algotrader/trading bot with Python & MT5. As always, if you have any questions or comments please feel free to post them below. Additionally, if you run into any issues please let me know.

2 thoughts on “Creating an algotrader/trading bot with Python – Part 1”

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to my newsletter to keep up to date with my latest posts

Holler Box