Algorithm Trading with MT5 and Python - Part 1

trading
Author

Paulo Urbano

Published

May 6, 2023

Connecting to MT5 using Python

TL;DR

Previous posts on Analysing Derivatives Data with MetaTrader and Python (part 1, part 2 and part 3) dealt with the correlation between order flow and price trends using cumulative delta metric. The manual step for downloading candle and tick data from MetraTrader 5 is a challenge in creating an automated, closed loop system to analyze tick data and take actions. In this post the use of MetaTrader Python module will be presented as an option to close this gap.

Getting data

To recreate the analysis made previously in an automated way, it is necessary to collected tick data from MT5 related to a specific time window. First, the MetaTrader Python module should be installed:

pip install MetaTrader5 

It is relevant to remember that the module was designed to work in a Windows machine, so the installation will not work on Linux, for example.

After installing, the following should run successfully:

import MetaTrader5 as mt5
import dateparser

def get_tick_data(
    ticker,
    start_date,
    end_date,
):
    tick_data = pd.DataFrame()

    if mt5.initialize():
        date_to = dateparser.parse(end_date)
        date_from = dateparser.parse(start_date)

        ticks = mt5.copy_ticks_range(ticker, 
                                    date_from, 
                                    date_to, 
                                    mt5.COPY_TICKS_TRADE)

        tick_data = pd.DataFrame(ticks)

        tick_data["datetime"] = pd.to_datetime(tick_data["time_msc"], unit="ms")

    else:
        print(f"MT5 error: \n{mt5.last_error()}")

    return tick_data

print(
    get_tick_data(
        "MESM23", 
        "2023-05-05 14:00:00 UTC+0000", 
        "2023-05-05 14:10:00 UTC+0000"
    )
)

The get_tick_data function will first initialize MetaTrader 5: if the application is not running, it will be opened and the default user account will be activated. If any error occurs on this step, a message will be given.

In the next step the start and end dates will be parsed from the string representation to datetime.datetime objects. The UTC+0000 at the end of the strings ensures that the dates are created with the UTC timezone, used by MT5 as default.

With the parsed dates, the function copy_ticks_range is called in order to retrieve all the tick data relative to the trades that took place in the time window between start_date and end_date. The raw data is transformed into a pandas.DataFrame and the time_msc column in raw data, which contains epoch timestamps of the trades, is used as the basis for a new column, datetime, with a Python datetime object representing the timestamp.

A sample output of copy_ticks_range is show in Figure 1 below.

Figure 1: Raw MT5 data

One difference between those data records and the tick data collected using MT5 export interface is the value range of flags. The sell trades were identified with flag 88, while now the value 344 is used. This changes the logic of calculating the delta and should be taken into consideration.

Creating charts

The return value of get_tick_data function has all the columns needed by the code used in the previous post and can be used to create similar chart and support the analysis process. The 10 minutes of data between 14:00 and 14:10 of May 5th 2023 are represented as OHLC and delta metric in Figure 2 below.

Figure 2: OHLC visualization

Continuous monitoring

Besides the collection and processing of tick data for offline analysis, on which historical data is used to generate charts and correlate order flow and price, it is interesting to use the MT5 Python module as a continuous monitoring tool, allowing for automated trading if specific conditions are true.

After the initial analysis made, a first simple strategy could be to issue a buy market order if the current 10-second rolling sum of the delta is above 500; and issue a sell market order if it is below -500.

In order to do that, it is first necessary to monitor the metric continuously, given the current time as input to the time window. A slight modification on the get_tick_data, as shown below, will return the most recent 3-minute tick data window.

import MetaTrader5 as mt5
import dateparser

def get_tick_data(
    ticker,
    start_date,
    end_date,
):
    tick_data = pd.DataFrame()

    if mt5.initialize():
        if start_date is None or end_date is None:
            date_to = datetime.datetime.now(datetime.timezone.utc)
            date_from = date_to - datetime.timedelta(minutes=3)
        else:
            date_to = dateparser.parse(end_date)
            date_from = dateparser.parse(start_date)

        ticks = mt5.copy_ticks_range(ticker, 
                                    date_from, 
                                    date_to, 
                                    mt5.COPY_TICKS_TRADE)

        tick_data = pd.DataFrame(ticks)

        tick_data["datetime"] = pd.to_datetime(tick_data["time_msc"], unit="ms")

    else:
        print(f"MT5 error: \n{mt5.last_error()}")

    return tick_data

print(
    get_tick_data(
        "MESM23", 
        None, 
        None
    )
)

By monitoring the most recent record in the data frame containing the delta values, it is possible to monitor the condition that will trigger a trading action. When that occurs, the following functions can be called:

def market_order(ticker, order_type, volume=1.0):
    open_positions = mt5.positions_get(symbol=ticker)
    if len(open_positions) == 0:
        request = {
            "action": mt5.TRADE_ACTION_DEAL,
            "symbol": ticker,
            "volume": volume,
            "type": order_type,
        }

        result = mt5.order_send(request)
        print(result)

The function verifies if there is already an open position before taking any action. This avoids sending multiple orders related to the same triggering condition. The request dictionary contains the bare minimum options to the order, the most relevant of which is the type, which in our example will be equal to either mt5.ORDER_TYPE_BUY or mt5.ORDER_TYPE_SELL for going long or short, respectively, on the ticker.

It is also necessary to enable algorithmic trading on MT5 interface, as shown in Figure 3 below.

Figure 3: Enabling algorithmic trading

The complete code used in this post can be found in this GitHub gist.

Conclusion

MetaTrader Python module can be regarded as a useful tool to automate the collection of tick data for offline analysis and also as the basis for an algorithmic trading system. A simple trading strategy was shown for informational purposes but an important step towards using it in a real context would be to evaluate how effectively it would have performed in previous market conditions. This process, called backtesting, will be the topic of an upcoming post.

Disclaimer

Futures and options trading has large potential rewards, but also large potential risk. You must be aware of the risks and be willing to accept them in order to invest in the futures and options markets. Do not trade with money you can not afford to lose. This post is neither a solicitation nor an offer to buy or sell futures or options. No representation is being made that any account will or is likely to achieve profits or losses similar to those discussed. The past performance of any trading system or methodology is not necessarily indicative of future results.

This post is provided for informational and educational purposes only. This material neither is, nor should be construed as an offer, solicitation, or recommendation to buy or sell any securities. Any investment decisions made by the reader through the use of this content is solely based on the reader independent analysis, taking into consideration their financial circumstances and risk tolerance. The author shall not be liable for any errors or for any actions taken in reliance thereon.

The references made to MT5 and AMP Global in this post do not represent a recommendation. I am not affiliated to neither platform, service nor their parent companies.