Creating a strategy for your algorithmic trading bot – Part 2

As an Amazon Associate I earn from qualifying purchases.

4 Min Read

Today, we will be continue creating a strategy for your algorithmic trading bot.

By now, you should have 3 new files created from the last post. strategy.py that loads in your strategy, myStrategy.json that contains your strategy and a constants.py file that stores all the moving average functions for ta-lib. Today, we will be exploring how to dynamically apply this strategy to our trader. Let’s get started!

Loading the json strategy from the trader

Let’s go back to the main code add imports for the new python files that you have created. Add strategy and constants and sys to your list of imports:

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

After adding the imports, modify the get_data method to include a new argument called strategy:

def get_data(time_frame, strategy):

On the next line where pairs are defined, replace this with strategy['pairs']. This means that all of our trading pairs will come from the strategy. This is important as you probably will want to trade different pairs with different strategies:

def get_data(time_frame, strategy):
    pairs = strategy['pairs']

Add the same strategy parameter to the check_trades method as follows:

def check_trades(time_frame, pair_data, strategy):

With this done, delete the 2 lines that are calculating SMA and EMA. We will be replacing this with something more dynamic:

        data['SMA'] = ta.SMA(data['close'], 10)
        data['EMA'] = ta.EMA(data['close'], 50)

First of all, let’s get our defined moving averages from the strategy dictionary. Under the method signature for check_trades add the following:

    moving_averages = strategy['movingAverages']

Here is a reminder of what the moving averages were defined as from the last post:

	"movingAverages": {
        "SMA": {
            "val": 10,
            "aboveBelow": "below"
        },
        "EMA": {
            "val": 50,
            "aboveBelow": "above"
        }
    }

Now, you will want to get the corresponding moving average function based on your strategy dictionary. This is done by using a for loop, looping over the keys in movingAverages and matching them with the movingAverageFunctions dictionary created in constants

        for m in moving_averages :
            ma_func = constants.movingAveragesFunctions[m]

You will also need to extract the value from the current moving average as this is used by the moving average function:

            val = moving_averages [m]['val']

The last step is to run the function with the close price and the val and add it to the data frame. You will add the moving averages to the dataframe by the key from the moving_averages dictionary:

            data[m] = maFunc(close, val)

The beginning of your check_trades method should look like the following:

def check_trades(time_frame, pair_data, strategy):
    moving_averages = strategy['movingAverages']
    for pair, data in pair_data.items():
        for m in moving_averages:
            ma_func = constants.movingAveragesFunctions[m]
            val = moving_averages [m]['val']
            data[m] = ma_func(data['close'], val)

This code will get all the moving averages you want to calculate specified in the strategy, calculate them using the stored functions in constants and then add the results to the data dataframe with key as the column name.

Implementing the strategy stop loss and take profit

Now that we have re-implemented our strategy for entering trades dynamically let’s see how we can add our stop loss and take profits dynamically.

Find the following line in your code:

open_position(pair, "BUY", 1, 300, 100)

300 is the current take profit and 100 is the current stop loss. You need to replace these values with the values from your strategy dictionary and make them a float:

open_position(pair, "BUY", 1, float (strategy['takeProfit']), float(strategy['stopLoss']))

Dynamically loading the strategy

The last step for creating a strategy for your algorithmic trading bot, is to load the strategy dynamically – You will be passing the strategy file as an argument to the script. But first, let’s implement some code that will take this argument and set it to a variable named current_strategy. You will be using sys.argv[1] to capture the input parameter.

Find the main entry point to your script. If you have been following this series, this is at the bottom of your trader.py file:

if __name__ == '__main__':
    live_trading()

Start by using sys.argv[1] to capture the strategy file name and assign it to a variable named current_strategy then add some print statements to the beginning of the program to let you know what strategy is currently being run with the trader:

if __name__ == '__main__':
    current_strategy = sys.argv[1]
    print("Trading bot started with strategy: ", current_strategy)

From strategy.py you can now load the json file using the load_strategy method defined earlier:

if __name__ == '__main__':
    current_strategy = sys.argv[1]
    print("Trading bot started with strategy: ", current_strategy)
    current_strategy = strategy.load_strategy(current_strategy)

Now pass this into the live_trading method:

if __name__ == '__main__':
    current_strategy = sys.argv[1]
    print("Trading bot started with strategy: ", current_strategy)
    current_strategy = strategy.load_strategy(current_strategy)
    live_trading(current_strategy)

Add an argument called strategy to the live_trading method and in the schedule code, pass this argument after mt5.TIMEFRAME_M15.

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

Finally, add the same strategy argument to run_trader and pass this argument into the get_data and check_trades calls:

Testing the code

Let’s try running our updated code passing in the strategy created from the previous post. As you can see below the trader ran with the strategy ‘strategy’ and also managed to find 2 trades and open them!

> python trader.py strategy
Trading bot started with strategy: strategy
Connected: Connecting to MT5 Client
EURUSD found!
Order successfully placed!
GBPUSD found!
Order successfully placed!

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 how you can dynamically calculate your position size of a trade based on risk tolerance and account size! 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.

3 thoughts on “Creating a strategy for your algorithmic trading bot – Part 2”

  1. Avatar

    I’m having an issue when I try to run it. After it “gets the data” and proceeds to check the data, it does nothing and loops back. It doesn’t open any trades.

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

    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, strategy):
    print(“Getting data now…”)
    pairs = strategy[‘pairs’]
    pair_data = dict()
    for pair in pairs:
    utc_from = datetime(2021, 1, 1, tzinfo=pytz.timezone(‘Europe/London’))
    date_to = datetime.now().astimezone(pytz.timezone(‘Europe/London’))
    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 check_trades(time_frame, pair_data, strategy):
    print(“Checking trades now…”)
    moving_averages = strategy[‘movingAverages’]
    for pair, data in pair_data.items():
    for m in moving_averages:
    ma_func = constants.movingAveragesFunctions[m]
    val = moving_averages [m][‘val’]
    data[m] = ma_func(data[‘close’], val)

    last_row = data.tail(1)
    open_positions = positions_get(pair)
    #open_positions = positions_get()
    #current_dt = datetime.now().astimezone(pytz.timezone(‘Europe/London’))
    #for index, position in open_positions.iterrows():
    # Check to see if the trade has exceeded the time limit
    #trade_open_dt = position[‘time’].replace(tzinfo = pytz.timezone(‘Europe/London’))
    #deal_id = position[‘ticket’]
    #if(current_dt – trade_open_dt >= timedelta(hours = 2)):

    for index, last in last_row.iterrows():
    #Exit strategy
    if(last[‘close’] last[‘SMA’]):
    close_positon_by_symbol(pair)

    #Entry strategy
    #if(last[‘close’] > last[‘EMA’] and last[‘close’] < last['SMA']):
    open_position(pair, "BUY", 1, float (strategy['takeProfit']), float(strategy['stopLoss']))

    def run_trader(time_frame, strategy):
    print("Running trader at", datetime.now())
    connect(12345678)
    pair_data = get_data(time_frame, strategy)
    #open_position("USDCAD", "BUY", 1, 300, 100)
    check_trades(time_frame, pair_data, strategy)

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

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

    if __name__ == '__main__':
    #print(sys.argv)
    #current_strategy = sys.argv[1]
    print("C:\Python\Python39\Scripts\strategies\MyStrategy.json")
    current_strategy = "MyStrategy"
    print("Trading bot started with strategy: ", current_strategy)
    current_strategy = strategy.load_strategy(current_strategy)
    live_trading(current_strategy)

    1. Conor O'Hanlon

      The aboveBelow parameter is just specifying if the price should be above or below the moving average specified to open/close a trade

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