Unlimited MT5's Trades Copiers using two Python scripts

 

My mother language is Arabic, I will try to make this article as simple as possible, away from technical jargons.


This article is about using two Python scripts (master_client001.py and slave_server.py) to copy trades locally (at the same PC or Laptop) between two MT5 platforms (Master and Slave accounts).

master_client001.py

import MetaTrader5 as mt5

import socket

import json

import time

import logging

from datetime import datetime, timedelta



DEAL_TYPE_CLOSE_BY = 9

MASTER_ID = "master_001"

SYMBOL_FILTER = "XAUUSD"

SERVER_ADDRESS = ("127.0.0.1", 9999)

TERMINAL_PATH = r"C:\\Program Files\\Vantage International MT5\\terminal64.exe"



# Setup logging

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[logging.StreamHandler()])



# Maintain socket connection

def connect_socket():

    while True:

        try:

            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

            s.connect(SERVER_ADDRESS)

            logging.info("Connected to slave server.")

            return s

        except Exception as e:

            logging.warning(f"Socket connection failed: {e}. Retrying...")

            time.sleep(5)



# Reinitialize MT5 if needed

def ensure_mt5():

    if not mt5.initialize(TERMINAL_PATH):

        logging.error("MT5 initialization failed.")

        return False

    return True



s = connect_socket()

ensure_mt5()

active_tickets = {}

sent_close_by = set()

last_heartbeat = 0



def send_event(event):

    global s

    try:

        s.sendall((json.dumps(event) + "\n").encode())

    except Exception as e:

        logging.error(f"Send failed: {e}. Reconnecting socket.")

        s.close()

        s = connect_socket()



while True:

    try:

        if not mt5.terminal_info() or not mt5.version():

            ensure_mt5()



        # Heartbeat

        if time.time() - last_heartbeat > 10:

            send_event({"type": "heartbeat", "master_id": MASTER_ID})

            last_heartbeat = time.time()



        # Get open positions

        current = {}

        trades = mt5.positions_get()

        for trade in trades or []:

            if trade.symbol != SYMBOL_FILTER or trade.type not in (mt5.ORDER_TYPE_BUY, mt5.ORDER_TYPE_SELL):

                continue



            ticket = trade.ticket

            current[ticket] = trade.volume



            if ticket not in active_tickets:

                # New trade

                send_event({

                    "type": "entry",

                    "master_id": MASTER_ID,

                    "ticket": ticket,

                    "symbol": trade.symbol,

                    "volume": trade.volume,

                    "type_order": "buy" if trade.type == mt5.ORDER_TYPE_BUY else "sell",

                    "price": trade.price_open

                })

            elif trade.volume < active_tickets[ticket]:

                # Partial close

                send_event({

                    "type": "partial_close",

                    "master_id": MASTER_ID,

                    "ticket": ticket,

                    "symbol": trade.symbol,

                    "remaining_volume": trade.volume

                })



        # Detect exits

        closed = [t for t in active_tickets if t not in current]

        for ticket in closed:

            send_event({

                "type": "exit",

                "master_id": MASTER_ID,

                "ticket": ticket,

                "symbol": SYMBOL_FILTER

            })



        active_tickets = current.copy()



        # Detect CloseBy from history

        now = datetime.now()

        start_time = now - timedelta(minutes=3)

        deals = mt5.history_deals_get(start_time, now)

        for deal in deals or []:

            if deal.type == DEAL_TYPE_CLOSE_BY and deal.symbol == SYMBOL_FILTER and deal.ticket not in sent_close_by:

                send_event({

                    "type": "close_by",

                    "master_id": MASTER_ID,

                    "symbol": deal.symbol,

                    "ticket1": deal.position_id,

                    "ticket2": deal.position_by_id

                })

                sent_close_by.add(deal.ticket)



    except Exception as e:

        logging.error(f"Loop error: {e}")

        ensure_mt5()



    time.sleep(1)

slave_server.py

import socket

import threading

import json

import MetaTrader5 as mt5

import logging

from utils.file_storage import load_ticket_map, save_ticket_map



ALLOWED_SYMBOL = "XAUUSD"

TERMINAL_PATH = r"C:\\Program Files\\FTMO Global Markets MT5 Terminal\\terminal64.exe"

ticket_map = load_ticket_map()



def get_magic_number(master_id: str) -> int:

    return 10000 + abs(hash(master_id)) % 8999



def ensure_mt5():

    if not mt5.initialize(TERMINAL_PATH):

        logging.error("MT5 initialization failed.")

        return False

    return True



ensure_mt5()



logging.basicConfig(

    level=logging.INFO,

    format="%(asctime)s [%(levelname)s] %(message)s",

    handlers=[

        logging.FileHandler("logs/slave.log"),

        logging.StreamHandler()

    ]

)



def adjust_volume(symbol, raw_volume):

    info = mt5.symbol_info(symbol)

    if not info or not mt5.symbol_select(symbol, True):

        logging.warning(f"Symbol {symbol} issue.")

        return None

    step = info.volume_step

    adjusted = round(round(raw_volume / step) * step, 2)

    return max(min(adjusted, info.volume_max), info.volume_min)



def close_partial(pos, reduce_by):

    close_type = mt5.ORDER_TYPE_SELL if pos.type == 0 else mt5.ORDER_TYPE_BUY

    tick = mt5.symbol_info_tick(pos.symbol)

    price = tick.bid if pos.type == 0 else tick.ask

    request = {

        "action": mt5.TRADE_ACTION_DEAL,

        "symbol": pos.symbol,

        "volume": reduce_by,

        "type": close_type,

        "position": pos.ticket,

        "price": price,

        "deviation": 10,

        "magic": get_magic_number("slave"),

        "comment": "partial_close"

    }

    result = mt5.order_send(request)

    logging.info(f"Partial close result: {result._asdict()}")



def close_position(pos):

    return close_partial(pos, pos.volume)



def process_trade(event):

    if not ensure_mt5():

        return

    symbol = event.get("symbol")

    if symbol != ALLOWED_SYMBOL:

        logging.info(f"Ignored symbol: {symbol}")

        return



    master_key = f"{event['master_id']}_{event['ticket']}"

    magic = get_magic_number(event["master_id"])



    if event["type"] == "entry":

        volume = adjust_volume(symbol, event["volume"])

        if not volume:

            return

        tick = mt5.symbol_info_tick(symbol)

        order_type = mt5.ORDER_TYPE_BUY if event["type_order"] == "buy" else mt5.ORDER_TYPE_SELL

        price = tick.ask if order_type == mt5.ORDER_TYPE_BUY else tick.bid

        result = mt5.order_send({

            "action": mt5.TRADE_ACTION_DEAL,

            "symbol": symbol,

            "volume": volume,

            "type": order_type,

            "price": price,

            "deviation": 10,

            "magic": magic,

            "comment": f"copy_{event['master_id']}"

        })

        logging.info(f"Entry result: {result._asdict()}")

        if result.retcode == mt5.TRADE_RETCODE_DONE:

            ticket_map[master_key] = result.order

            save_ticket_map(ticket_map)



    elif event["type"] == "exit":

        slave_ticket = ticket_map.get(master_key)

        pos = mt5.positions_get(ticket=slave_ticket)

        if pos:

            close_position(pos[0])

        ticket_map.pop(master_key, None)

        save_ticket_map(ticket_map)



    elif event["type"] == "partial_close":

        slave_ticket = ticket_map.get(master_key)

        pos = mt5.positions_get(ticket=slave_ticket)

        if pos:

            original = pos[0].volume

            remaining = adjust_volume(symbol, event["remaining_volume"])

            diff = round(original - remaining, 2)

            if diff > 0:

                close_partial(pos[0], diff)



    elif event["type"] == "close_by":

        key1 = f"{event['master_id']}_{event['ticket1']}"

        key2 = f"{event['master_id']}_{event['ticket2']}"

        pos1 = mt5.positions_get(ticket=ticket_map.get(key1))

        pos2 = mt5.positions_get(ticket=ticket_map.get(key2))

        if pos1:

            close_position(pos1[0])

            ticket_map.pop(key1, None)

        if pos2:

            close_position(pos2[0])

            ticket_map.pop(key2, None)

        save_ticket_map(ticket_map)



    elif event["type"] == "heartbeat":

        logging.info(f"Heartbeat from {event['master_id']}")



def client_thread(conn):

    buffer = ""

    while True:

        try:

            data = conn.recv(4096)

            if not data:

                break

            buffer += data.decode()

            while "\n" in buffer:

                line, buffer = buffer.split("\n", 1)

                try:

                    event = json.loads(line.strip())

                    if "symbol" not in event and event.get("type") != "heartbeat":

                        continue

                    process_trade(event)

                except Exception as e:

                    logging.warning(f"Invalid event: {e}")

        except Exception as e:

            logging.error(f"Socket error: {e}")

            break

    conn.close()



def main():

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    s.bind(("0.0.0.0", 9999))

    s.listen(10)

    logging.info("Slave server listening on port 9999")

    while True:

        conn, addr = s.accept()

        logging.info(f"Connection from {addr}")

        threading.Thread(target=client_thread, args=(conn,), daemon=True).start()



if __name__ == "__main__":

    main()

You need Python installed on your PC (Laptop), with (Include Path) enabled.

and I suggest to read this >>>>> Installing Python and the MetaTrader5 package

I use (Visual Studio Code) to edit the Python Scripts and test them on my Laptop with Windows 10.


by modifying these two .py files, you can add as many Masters accounts as you want(you must have many MT5 platforms from many different brokers installed on your PC), the same with the Slave accounts.


make a new folder on D: drive and name it as (mt5_trade_copier), open it and paste master_client001.py and 

slave_server.py

make 3 new folders with names:

config

logs

utils

open the utils folder, and make a 3 .py files and one new folder __pycache__

the 3 .py files are the following:

file_storage.py

socket_utils.py

trade_utils.py


file_storage.py

# utils/file_storage.py

import json

import os



TICKET_MAP_PATH = "ticket_map.json"



def load_ticket_map():

    if os.path.exists(TICKET_MAP_PATH):

        with open(TICKET_MAP_PATH, "r") as f:

            return json.load(f)

    return {}



def save_ticket_map(data):

    with open(TICKET_MAP_PATH, "w") as f:

        json.dump(data, f, indent=4)




socket_utils.py

# utils/socket_utils.py

import socket

import time



def connect_socket(host: str, port: int) -> socket.socket:

    while True:

        try:

            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

            s.connect((host, port))

            print(f"Connected to {host}:{port}")

            return s

        except Exception as e:

            print(f"Socket connect failed: {e}. Retrying in 5s...")

            time.sleep(5)



def send_json(sock: socket.socket, data: dict):

    try:

        sock.sendall((json.dumps(data) + "\n").encode())

    except Exception as e:

        print("Send failed:", e)


trade_utils.py

# utils/trade_utils.py

import MetaTrader5 as mt5



def get_price(symbol: str, order_type: str) -> float:

    tick = mt5.symbol_info_tick(symbol)

    return tick.ask if order_type == "buy" else tick.bid



def get_opposite_order_type(order_type: str) -> int:

    return mt5.ORDER_TYPE_SELL if order_type == "buy" else mt5.ORDER_TYPE_BUY


My trading Style needs to copy four things only >>>>>>

1. the entry of the trade

2. the exit of the trade

3. the partial close of a trade

4. the (CloseBy) of two opposite trades

about the first two's >>>>>>>

it doesn't copy TP or SL of the trades, for me, it doesn't make sense to copy TP and SL, 

let's say I have two trading accounts, the Master is Exness and the Slave is Pepperstone, Pepperstone has tighter spread, the price will close any copied trade with TP or SL on Pepperstone before it is closed on Exness !!!!

if you have an expertise in Python programming, you can modify the code to support copying the TP and SL, but I don't recommend this.

it makes no sense to copy the TP and SL of any trades.

all I need is to copy the entry and the exit.


about the (copying the CloseBy of two opposite trades):

I am addicted on using CloseBy function while I am trading, 

but I had a problem with Commercial trades copiers>>>>>>>>> they don't support copying this function

I lost many Real trading accounts (and failed two FTMO challenges)because of this glitch !!!!

try open on a Master account two opposite trades with different lot sizes, the commercial EA will copy the two trades to the Slave account, then apply (CloseBy) to the two trades on the Master account, the commercial EA will not copy this behavior exactly to the Slave account !!!!!

this problem was the main reason and motive for me to program these Python scripts, Necessity is the mother of Invention !!!!

these two Python scripts are lightweight, fast, and reliable and don't need a lot of the device's memeory to run, these scripts run from the Terminal window (CMD window, or the black window, whatever you call it) by calling them like this >>>>>>

python slave_server.py

python master_client001.py

D:\mt5_trade_copier>python slave_server.py
2025-12-24 19:25:53,257 [INFO] Slave server listening on port 9999

then you start to run your Master(s) by typing 

D:\mt5_trade_copier>python master_client001.py

in a new CMD window and wait, you'll get this message >>>>>>>>>> 

2025-12-24 19:28:35,365 [INFO] Connected to slave server.

these two Python scripts (master_client001.py and slave_server.py)work on Gold (XAUUSD) ONLY !!!

if you are good in Python programming, you can modify the code and add support for any Instrument (pair) you want.


You can modify master_client001.py file to add more Master accounts (master_client002.py, master_client003.py, master_client004.py, etc...) by changing >>>>>>>

line 

9 MASTER_ID = "master_001"

and line 

12 TERMINAL_PATH = r"C:\\Program Files\\Vantage International MT5\\terminal64.exe"


you change the name of the Master account which will appear in the comments of the opened trade, and you change the path of the Master account, 

notice here that Python accept this path with two (\\), not with one (\)

if you want to know what is the path of your MT5 platform, 

click on Windows logo and search for the MT5 platform's name you want it to be the master account, let's say AMarkets, click on (Open file location), then right click on the platform Icon and choose Properties, then Shortcut, then copy Target

paste the copied Target in place of line 12 and don't forget to change this (\) into this (\\)

the same with the slave_server.py

the two Python scripts (master_client001.py and slave_server.py) must listen to the same port, which is here 9999

you can add more Slave accounts by changing the port number of both Python scripts.

If you are expert in Python programming, you can modify the code to add more copying functions to the Scripts (like lot Multiplier, copying with fixed lot size, etc....).

one last thing, after you setup many Master accounts and a Slave account or more Slave account, you can make a .cmd file, contains all the paths of the platforms, by clicking it, you can run all the terminals by one click !!!
something like this >>>>>>>>>>

TIMEOUT 10

start /D "C:\Program Files\FxPro - MetaTrader 5\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\EightCap MetaTrader 5\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\PU Prime MT5 Terminal\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\Tickmill MT5 Terminal\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\FTMO Global Markets MT5 Terminal\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\ACY Securities MetaTrader 5\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\AMarkets - MetaTrader 5\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\MetaTrader 5 EXNESS\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\CPT Markets MT5 Terminal\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\CXM Trading MT5 Terminal\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\easyMarkets MetaTrader 5\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\FreshForex MT5 Terminal\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\MT5 Weltrade\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\Fusion Markets MetaTrader 5\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\FXGT MT5 Terminal\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\IFC Markets MT5\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files\Headway MT5 Terminal\" /MIN terminal64.exe

TIMEOUT 10

start /D "C:\Program Files (x86)\Hugo's Way MetaTrader 4 Terminal\" /MIN terminal.exe

TIMEOUT 60

exit

save that file as Start.cmd

or you can save it in the Startup folder of Windows, to auto run automatically, when you enter the Windows or your VPS.

click Windows Logo + R and type >>>>> shell:startup
a window will open, paste the file Start.cmd there.

that's it !!!!

and always, run (python slave_server.py) before any (python master_client00X.py)

this is all what you need to copy trades between MT5 plaforms using Python Scripts!

Trading is simple, but not easy!

I wish you a happy trading.

MQL5 Book: Installing Python and the MetaTrader5 package / Advanced language tools
MQL5 Book: Installing Python and the MetaTrader5 package / Advanced language tools
  • www.mql5.com
To study the materials in this chapter, Python must be installed on your computer. If you haven't installed it yet, download the latest version of...
 

WEIRD !!!!!

no one commented on this post !!!!!