Asynchronous OrderSend() function in Python

 

The MetaTrader 5 Python package offers the order_send() function, analogous to MQL5's OrderSend() .

This is a synchronous function; the caller has to wait for the MT5 server response before proceeding.

When a trading strategy necessitates multiple orders at varying prices or symbols, using the synchronous order_send() consecutively can cause a cumulative wait time.

This delay scales linearly with the number of orders, and can become significant with high network latency.

In MQL5, the asynchronous OrderSendAsync() method can mitigate this issue.

Unfortunately, the MetaTrader 5 Python package lacks such an asynchronous alternative for order_send() .

I'm aiming to develop a Python function mirroring the behavior of MQL5's OrderSendAsync() . My approach involves wrapping each order_send() call in a separate thread for concurrent execution.

Here's the code I've attempted:

import MetaTrader5 as mt5
import threading
import time
import numpy as np

# ------------------------------------------
#  ASYNCHRONOUS ORDER SEND FUNCTION
# ------------------------------------------

def order_send_async(
        symbol,
        trade_action,
        order_type,
        volume,
        type_filling=mt5.ORDER_FILLING_FOK
):

    request = {
        "symbol": symbol,
        "action": trade_action,
        "type": order_type,
        "volume": volume,
        "type_filling": type_filling
    }

    result = mt5.order_send(request)

    if result is None:
        print("Function order_send() returned None. The error code is ", mt5.last_error(), ".")
    elif result.retcode != mt5.TRADE_RETCODE_DONE:
        print("The function order_send() returned error ", result.retcode, ".")
        print(result)

# ------------------------------------------
#  MAIN SCRIPT
# ------------------------------------------

if mt5.initialize():
    print("The bot is connected to the MT5 server", mt5.account_info().server, ".")
else:
    print("ERROR: could not initialize the MT5 API. Error code =", mt5.last_error())
    quit()

ask = mt5.symbol_info("BTCUSDm").ask

orders = [
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.01),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.02),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.03),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.04),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.05),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.06),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.07),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.08),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.09),
    ("BTCUSDm", mt5.TRADE_ACTION_DEAL, mt5.ORDER_TYPE_BUY, 0.10),
]

print("SEQUENTIAL:")

t = np.zeros(len(orders))
t[0] = time.time()
for i, order in enumerate(orders):
    symbol, trade_action, order_type, volume = order
    order_send_async(symbol, trade_action, order_type, volume)
    t[i] = time.time()
for i in range(1, len(orders)):
    print("%d - %7.3f ms" % (i, 1000*(t[i]-t[0])))

print("CONCURRENT:")

threads = {}
t = np.zeros(len(orders))
t[0] = time.time()
for i, order in enumerate(orders):
    symbol, trade_action, order_type, volume = order
    threads[i] = threading.Thread(
        target=order_send_async,
        args=(symbol, trade_action, order_type, volume)
    )
    threads[i].start()
    t[i] = time.time()
for i in range(1, len(orders)):
    print("%d - %7.3f ms" % (i, 1000*(t[i]-t[0])))

mt5.shutdown()

Here's the result produced by the above script:

The bot is connected to the MT5 server Exness-MT5Trial11 .
SEQUENTIAL:
1 - 162.345 ms
2 - 351.698 ms
3 - 516.618 ms
4 - 685.829 ms
5 - 848.648 ms
6 - 1049.329 ms
7 - 1203.087 ms
8 - 1372.418 ms
9 - 1535.314 ms
CONCURRENT:
1 - 168.866 ms
2 - 347.301 ms
3 - 516.599 ms
4 - 670.146 ms
5 - 833.039 ms
6 - 1002.688 ms
7 - 1172.052 ms
8 - 1335.050 ms
9 - 1525.683 ms

The provided time measurements represent the elapsed time from the start of sending the first order to the completion of the k-th order, for k ranging from 1 to 9. Server ping time was around 120 ms during the experiment.

Based on the results, the elapsed time appears nearly identical for both sequential and concurrent implementations. This implies that the order_send() function might not be executing concurrently, as each subsequent call only seems to initiate after the completion of its predecessor.

Have I made an oversight in my approach?

Why doesn't the order_send() function seem to execute concurrently?

Does anyone have insights, solutions, or suggestions on effectively implementing concurrency for sending orders to the MT5 server using Python?

Thank you in advance.

 

In my understanding, Python has GIL and thus your threads are not really running in parallel, i.e. each thread has to run one by one and this is equivalent to send order in the main thread one by one. This should be the reason why time spent of both ways are roughly the same.


Maybe multiprocessing library can help you overcome this GIL as stated in the document:

multiprocessing is a package that supports spawning processes using an API similar to the  threading module. The  multiprocessing package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads. Due to this, the  multiprocessing module allows the programmer to fully leverage multiple processors on a given machine. It runs on both POSIX and Windows.
multiprocessing — Process-based parallelism
multiprocessing — Process-based parallelism
  • docs.python.org
Source code: Lib/multiprocessing/ Availability: not Emscripten, not WASI. This module does not work or is not available on WebAssembly platforms wasm32-emscripten and wasm32-wasi. See WebAssembly p...