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 !!!!!

 
Ynal Al Khalil #:

WEIRD !!!!!

no one commented on this post !!!!!

Please note that bumping is not allowed. If your topic has low activity, it simply means that no one is interested.
 
Does your script have the ability to copy trades in such a way that the slave terminals do not need to be turned on?
 
Darkfutur #:
Does your script have the ability to copy trades in such a way that the slave terminals do not need to be turned on?

Whaaattttt?????

are you serious ???

it is impossible !!!!!

and before you run the Master account(s), you have to run the Slave account first !!!!

 
i like your work , thanks for sharing