Creating an algotrader/trading bot with Python – Part 2

As an Amazon Associate I earn from qualifying purchases.

6 Min Read

Analyzing the Market Data

In my previous post, I showed you how to set up the trading interval and also how to pull the latest data for that interval. In this post, I will continue the process creating an algotrader/trading bot with Python. I will show you how analyze the data and open a trade (if it fits your trading plan). You will need to install another library called ta-lib. TA-Lib is widely used by trading software developers requiring to perform technical analysis of financial market data. An installation guide for this can be found here.

Once this is installed, lets start coding! Following on from the last post, you should have code similar to this:

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

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(stopDistance):
            sl = price + (stop_distance * point)
        if(tpDistance):
            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_positon_by_symbol(symbol):
    open_positions = positions_get(symbol)
    open_positions['ticket'].apply(lambda x: close_position(x))
    
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
        print(pair_data[pair])
    return pair_data
    
def run_trader(time_frame):
    connect(39672374)
    get_data(time_frame)
    
def live_trading():
    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)

if __name__ == '__main__':
    live_trading()

If you are struggling with the current design, I highly recommend going back to this post to get an overview and visualization of what you are trying to build.

Let’s start by importing talib:

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

Next, you should create a new method and name it check_trades. For this method, make time_frame and pair_data arguments

def check_trades(time_frame, pair_data):

You will want to iterate over this dictionary as you will be calculating some new values!

def check_trades(time_frame, pair_data):
    for pair, data in pair_data.items():

The next part is to implement your strategy, for me, I will be creating a simple strategy as follows. (It goes without saying this is probably not a great strategy but useful for this tutorial):

BUY when price > 50EMA and price < 10SMA

Using Ta-lib can can calculate the 50EMA and 10SMA easily. For SMA you can use the ta.SMA(close, period) method and for EMA you can use ta.EMA(close, period) method. Provide every close price from the data you collected earlier – this can be done easily by referencing the close column in the data data frame. You will add the newly calculated SMA and EMA to new columns in the data frame:

def check_trades(time_frame, pair_data):
    for pair, data in pair_data.items():
        data['SMA'] = ta.SMA(data['close'], 10)
        data['EMA'] = ta.EMA(data['close'], 50)

Here is a visualization of what the data frame looks like after adding SMA and EMA:

[1071 rows x 10 columns]
                    time     open     high      low    close  tick_volume  spread  real_volume       SMA       EMA
0    2020-12-31 22:30:00  1.27277  1.27360  1.27262  1.27351          511      19            0       NaN       NaN
1    2020-12-31 22:45:00  1.27352  1.27370  1.27164  1.27220         1050      18            0       NaN       NaN
2    2020-12-31 23:00:00  1.27221  1.27221  1.27221  1.27221            1      24            0       NaN       NaN
3    2021-01-04 00:00:00  1.27198  1.27301  1.27196  1.27215           75      28            0       NaN       NaN
4    2021-01-04 00:15:00  1.27210  1.27336  1.27204  1.27324          127      12            0       NaN       NaN
...                  ...      ...      ...      ...      ...          ...     ...          ...       ...       ...
1066 2021-01-19 01:45:00  1.27491  1.27517  1.27489  1.27491          122       5            0  1.274976  1.275782
1067 2021-01-19 02:00:00  1.27491  1.27491  1.27420  1.27427          406       0            0  1.274930  1.275723
1068 2021-01-19 02:15:00  1.27427  1.27447  1.27406  1.27443          364       1            0  1.274849  1.275672
1069 2021-01-19 02:30:00  1.27444  1.27444  1.27339  1.27371          503       0            0  1.274701  1.275595
1070 2021-01-19 02:45:00  1.27370  1.27382  1.27353  1.27367          363       0            0  1.274572  1.275519

As you can see above – the SMA and EMA values are in the data frame. Don’t worry about the first few values being NaN – this is normal. If you are interested in how SMA is calculated, a good resource can be found here.

Reacting to the latest data

Now that we calculated everything we need for our strategy, it’s time to focus on the latest data. As mentioned before, generally speaking an algotrader/trading bot should react to new data. You will need to check the last reported tick data for this strategy. The only condition we have for opening a trade, is if the close price for the last record is above 50EMA and below 10SMA.

Start by getting the last row:

def check_trades(time_frame, pair_data):
    for pair, data in pair_data.items():
        data['SMA'] = ta.SMA(data['close'], 10)
        data['EMA'] = ta.EMA(data['close'], 50)
        last_row = data.tail(1)

After this, you should specify the conditions of your strategy:

        for index, last in last_row.iterrows():
            if(last['close'] > last['EMA'] and last['close'] < last['SMA']):

In the code snippet above, I am saying, if close > EMA and close < SMA then open a trade. The last step in this section is to open a trade if the conditions you have specified meet your trading strategy. You can use the open_position method created in a previous post.

        for index, last in last_row.iterrows():
            if(last['close'] > last['EMA'] and last['close'] < last['SMA']):
                open_position(pair, "BUY", 1, 300, 100)

It’s time to add this method to the run_trader method. I have added a print statement which prints out the exact time the run_trader method runs. This is to help you determine if your trader is running at the correct time:

def run_trader(time_frame):
    print("Running trader at", datetime.now())
    connect(39672374)
    pair_data = get_data(time_frame)
    check_trades(time_frame, pair_data)


Testing the code

Now, it is time to test our code! When I ran the script, I got the output below. It is important to point out that no trades were opened by the trader because they did not meet my strategy.

>>> 
Running trader at 2021-01-19 09:00:00.619073
Connected: Connecting to MT5 Client
Running trader at 2021-01-19 09:15:00.707413
Connected: Connecting to MT5 Client

Let’s take a moment and review what you created so far, first, you created a method called live_trading which is the entry point to your application. This contains information for the schedule of your trader. At every chosen interval, whether its 1 Min, 5 Min, 4 Hour’s etc you are getting the latest data with get_data. To analyze this data and act accordingly, you created a method called check_trades which will calculate any indicators needed for your strategy and also, if a trade should be opened or not based on your strategy.

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 Friday 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 2”

  1. Avatar

    I have tried your code (I only included print(result)), but I received the following error:
    Running trader at 2021-02-08 11:25:00.728949
    Connected: Connecting to MT5 Client
    time open high … tick_volume spread real_volume
    0 2020-12-31 22:30:00 1.22151 1.22189 … 189 8 0
    1 2020-12-31 22:45:00 1.22154 1.22177 … 592 8 0
    2 2020-12-31 23:00:00 1.22156 1.22156 … 2 11 0
    3 2021-01-04 00:00:00 1.22395 1.22396 … 103 28 0
    4 2021-01-04 00:15:00 1.22316 1.22389 … 146 15 0
    … … … … … … … …
    2397 2021-02-05 22:30:00 1.20459 1.20468 … 260 0 0
    2398 2021-02-05 22:45:00 1.20460 1.20497 … 554 0 0
    2399 2021-02-05 23:00:00 1.20488 1.20498 … 383 0 0
    2400 2021-02-05 23:15:00 1.20470 1.20485 … 161 0 0
    2401 2021-02-05 23:30:00 1.20464 1.20481 … 190 0 0

    [2402 rows x 8 columns]
    time open high … tick_volume spread real_volume
    0 2020-12-31 22:30:00 1.27277 1.27360 … 511 19 0
    1 2020-12-31 22:45:00 1.27352 1.27370 … 1050 18 0
    2 2020-12-31 23:00:00 1.27221 1.27221 … 1 24 0
    3 2021-01-04 00:00:00 1.27198 1.27301 … 75 28 0
    4 2021-01-04 00:15:00 1.27210 1.27336 … 127 12 0
    … … … … … … … …
    2397 2021-02-05 22:30:00 1.27656 1.27674 … 745 0 0
    2398 2021-02-05 22:45:00 1.27656 1.27679 … 793 0 0
    2399 2021-02-05 23:00:00 1.27653 1.27683 … 545 0 0
    2400 2021-02-05 23:15:00 1.27678 1.27687 … 174 0 0
    2401 2021-02-05 23:30:00 1.27638 1.27668 … 266 0 0

    [2402 rows x 8 columns]
    EURUSD found!
    OrderSendResult(retcode=10019, deal=0, order=0, volume=0.0, price=0.0, bid=0.0, ask=0.0, comment=’No money’, request_id=0, retcode_external=0, request=TradeRequest(action=1, magic=234000, order=0, symbol=’EURUSD’, volume=1.0, price=1.20382, stoplimit=0.0, sl=1.1998199999999999, tp=1.21182, deviation=0, type=0, type_filling=1, type_time=0, expiration=0, comment=”, position=0, position_by=0))
    Failed to send order 🙁
    USDCAD found!
    OrderSendResult(retcode=10019, deal=0, order=0, volume=0.0, price=0.0, bid=0.0, ask=0.0, comment=’No money’, request_id=0, retcode_external=0, request=TradeRequest(action=1, magic=234000, order=0, symbol=’USDCAD’, volume=1.0, price=1.27618, stoplimit=0.0, sl=1.27218, tp=1.28418, deviation=0, type=0, type_filling=1, type_time=0, expiration=0, comment=”, position=0, position_by=0))
    Failed to send order 🙁

    Thank you for your help!

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