🚀 AI One-Click Generation of mql4 Strategy Code

How to Code a Local Trade Copier in Mql4 to Duplicate Trades Between Accounts

mql4 | Published on: 2025/12/9
The following content is generated by EasyQuant .

Problem Description

How can I write a program that automatically copies trades from one trading account to another on my computer?

Solution

To create a trade copier system in MQL4 that works between two accounts on the same computer, you need to establish a communication channel between two separate MetaTrader 4 terminals.

Since MT4 terminals are isolated (sandboxed), the most reliable method without using complex DLLs is to use the Common Data Folder (FILE_COMMON).

Here is the architecture of the solution:

  1. Sender EA (Master): Runs on the source account. It continuously writes the list of open trades to a CSV file in the shared data folder.
  2. Receiver EA (Slave): Runs on the destination account. It reads that CSV file, compares it with its own open trades, and executes entries or exits to match the Master.

Part 1: The Sender EA (Master)

Compile this code as TradeSender.mq4 and attach it to one single chart (e.g., EURUSD M1) on the Master terminal.

//+------------------------------------------------------------------+
//|                                                  TradeSender.mq4 |
//|                                     Copyright 2023, Quantitative |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//--- Input Parameters
input string FileName = "SharedTrades.csv"; // File name in Common/Files
input int    RefreshRate = 1;               // Refresh rate in seconds

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Create a timer to write data periodically
   EventSetTimer(RefreshRate);
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   // Clear the file on exit to prevent stuck trades
   int handle = FileOpen(FileName, FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_ANSI, ',');
   if(handle != INVALID_HANDLE) FileClose(handle);
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
   WriteTradesToFile();
}

//+------------------------------------------------------------------+
//| Writes current open trades to CSV                                |
//+------------------------------------------------------------------+
void WriteTradesToFile()
{
   // Open file in the COMMON folder shared by all terminals
   // FILE_SHARE_READ allows the receiver to read while we write
   int handle = FileOpen(FileName, FILE_WRITE|FILE_CSV|FILE_COMMON|FILE_ANSI|FILE_SHARE_READ, ',');
   
   if(handle == INVALID_HANDLE)
   {
      Print("Error opening file for writing: ", GetLastError());
      return;
   }

   // Iterate through all open orders
   for(int i = 0; i < OrdersTotal(); i++)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         // We only copy market orders (OP_BUY and OP_SELL)
         if(OrderType() <= OP_SELL)
         {
            // Format: Ticket, Symbol, Type, Lots, OpenPrice, SL, TP
            FileWrite(handle,
                      OrderTicket(),
                      OrderSymbol(),
                      OrderType(),
                      OrderLots(),
                      OrderOpenPrice(),
                      OrderStopLoss(),
                      OrderTakeProfit()
                     );
         }
      }
   }
   
   FileClose(handle);
}

Part 2: The Receiver EA (Slave)

Compile this code as TradeReceiver.mq4 and attach it to one single chart on the Slave terminal.

Important: Ensure "Allow Live Trading" is checked in the EA properties.

//+------------------------------------------------------------------+
//|                                                TradeReceiver.mq4 |
//|                                     Copyright 2023, Quantitative |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//--- Input Parameters
input string FileName      = "SharedTrades.csv"; // Must match Sender
input double LotMultiplier = 1.0;                // Lot size multiplier
input int    Slippage      = 3;                  // Max slippage in pips
input int    MagicNumber   = 123456;             // Magic number for Slave trades

// Structure to hold Master trade data
struct MasterTrade
{
   int    ticket;
   string symbol;
   int    type;
   double lots;
   double price;
   double sl;
   double tp;
};

MasterTrade masterTrades[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   EventSetTimer(1); // Check for updates every second
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
   ReadMasterFile();
   SynchronizeTrades();
}

//+------------------------------------------------------------------+
//| Reads the CSV file from the Master                               |
//+------------------------------------------------------------------+
void ReadMasterFile()
{
   int handle = FileOpen(FileName, FILE_READ|FILE_CSV|FILE_COMMON|FILE_ANSI|FILE_SHARE_WRITE, ',');
   
   ArrayResize(masterTrades, 0); // Clear array
   
   if(handle == INVALID_HANDLE) return; // File might be locked by writer, try next tick

   while(!FileIsEnding(handle))
   {
      // Check if line is empty
      if(FileIsLineEnding(handle)) 
      {
         FileReadString(handle); // Skip line ending
         continue;
      }

      int size = ArraySize(masterTrades);
      ArrayResize(masterTrades, size + 1);
      
      // Read data in order: Ticket, Symbol, Type, Lots, Price, SL, TP
      masterTrades[size].ticket = (int)FileReadNumber(handle);
      masterTrades[size].symbol = FileReadString(handle);
      masterTrades[size].type   = (int)FileReadNumber(handle);
      masterTrades[size].lots   = FileReadNumber(handle);
      masterTrades[size].price  = FileReadNumber(handle);
      masterTrades[size].sl     = FileReadNumber(handle);
      masterTrades[size].tp     = FileReadNumber(handle);
   }
   
   FileClose(handle);
}

//+------------------------------------------------------------------+
//| Synchronizes Slave trades with Master data                       |
//+------------------------------------------------------------------+
void SynchronizeTrades()
{
   // 1. Check for Exits (Close trades that are no longer in Master file)
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         // Only manage trades opened by this EA
         if(OrderMagicNumber() == MagicNumber)
         {
            // We store Master Ticket in the Order Comment for tracking
            int masterTicketID = (int)StringToInteger(OrderComment());
            
            bool existsInMaster = false;
            for(int j = 0; j < ArraySize(masterTrades); j++)
            {
               if(masterTrades[j].ticket == masterTicketID)
               {
                  existsInMaster = true;
                  break;
               }
            }
            
            // If master trade is gone, close slave trade
            if(!existsInMaster)
            {
               RefreshRates();
               double closePrice = (OrderType() == OP_BUY) ? SymbolInfoDouble(OrderSymbol(), SYMBOL_BID) : SymbolInfoDouble(OrderSymbol(), SYMBOL_ASK);
               bool res = OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrRed);
               if(!res) Print("Failed to close order: ", GetLastError());
            }
         }
      }
   }

   // 2. Check for Entries (Open trades that are new in Master file)
   for(int i = 0; i < ArraySize(masterTrades); i++)
   {
      if(masterTrades[i].ticket == 0) continue; // Skip empty reads

      bool alreadyCopied = false;
      
      // Check if we already have this trade open
      for(int j = 0; j < OrdersTotal(); j++)
      {
         if(OrderSelect(j, SELECT_BY_POS, MODE_TRADES))
         {
            if(OrderMagicNumber() == MagicNumber)
            {
               if((int)StringToInteger(OrderComment()) == masterTrades[i].ticket)
               {
                  alreadyCopied = true;
                  // Optional: Add logic here to modify SL/TP if Master changed them
                  break;
               }
            }
         }
      }
      
      // If not copied, open the trade
      if(!alreadyCopied)
      {
         // Normalize Lot Size
         double vol = NormalizeLots(masterTrades[i].symbol, masterTrades[i].lots * LotMultiplier);
         
         // Get current price
         double price = 0;
         if(masterTrades[i].type == OP_BUY) price = SymbolInfoDouble(masterTrades[i].symbol, SYMBOL_ASK);
         if(masterTrades[i].type == OP_SELL) price = SymbolInfoDouble(masterTrades[i].symbol, SYMBOL_BID);
         
         if(price == 0) 
         {
            Print("Symbol not found or no price: ", masterTrades[i].symbol);
            continue;
         }

         // Send Order
         // Note: We put Master Ticket in the comment field to track it
         int ticket = OrderSend(masterTrades[i].symbol, 
                                masterTrades[i].type, 
                                vol, 
                                price, 
                                Slippage, 
                                masterTrades[i].sl, 
                                masterTrades[i].tp, 
                                IntegerToString(masterTrades[i].ticket), // Comment = Master Ticket
                                MagicNumber, 
                                0, 
                                clrGreen);
                                
         if(ticket < 0)
         {
            Print("OrderSend failed for ", masterTrades[i].symbol, " Error: ", GetLastError());
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Helper to normalize lots based on broker requirements            |
//+------------------------------------------------------------------+
double NormalizeLots(string symbol, double lots)
{
   double minLot  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   double stepLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   
   if(stepLot == 0) return(lots);
   
   double normalized = MathFloor(lots / stepLot) * stepLot;
   
   if(normalized < minLot) normalized = minLot;
   if(normalized > maxLot) normalized = maxLot;
   
   return(normalized);
}

How to Setup and Run

  1. Locate the Common Folder:

    • Open MetaTrader 4.
    • Go to File -> Open Data Folder.
    • Navigate Up one level (out of the specific terminal ID folder).
    • Open the folder named Common.
    • Open Files. This is where the SharedTrades.csv will be created. Both terminals can access this specific path.
  2. Install Sender:

    • Open the Master Terminal.
    • Open MetaEditor, create TradeSender.mq4, paste the first code block, and compile.
    • Drag TradeSender onto one chart (e.g., EURUSD).
    • Ensure "Allow DLL imports" is NOT required, but standard file permissions are automatic.
  3. Install Receiver:

    • Open the Slave Terminal.
    • Open MetaEditor, create TradeReceiver.mq4, paste the second code block, and compile.
    • Drag TradeReceiver onto one chart.
    • Crucial: Go to the "Common" tab in the EA settings and check "Allow live trading".
    • Ensure the Symbol names match exactly between brokers (e.g., "EURUSD" vs "EURUSD"). If your broker uses suffixes (like "EURUSD.pro"), the code will need modification to handle string substrings.

Key Features of this Implementation

  • FILE_COMMON Flag: This is the critical component. It forces MT4 to write to the shared Windows AppData folder (\Terminal\Common\Files) rather than the isolated instance folder. This allows two different MT4 installations to talk to each other.
  • Order Mapping: The Receiver stores the Master's Ticket Number in the OrderComment field of the Slave's order. This allows the Slave to know exactly which trade corresponds to which, preventing duplicate entries.
  • Lot Normalization: The NormalizeLots function ensures that if the calculation results in 0.123 lots but the broker only supports 0.01 steps, it rounds correctly to 0.12 to avoid OrderSend Error 131 (Invalid Trade Volume).
  • Synchronization:
    • If a trade appears in the CSV, the Slave opens it.
    • If a trade disappears from the CSV (Master closed it), the Slave closes it.

Q&A

Q: Will this work if the Master and Slave have different account currencies?
A: Yes, but the lot size calculation is simple multiplication (LotMultiplier). It does not automatically calculate risk based on account balance currency conversion. You should adjust LotMultiplier manually based on the balance difference.

Q: What happens if the file is locked?
A: File operations can fail if both EAs try to access the file at the exact same millisecond. The code handles INVALID_HANDLE by simply returning and trying again on the next timer tick (1 second later).

Q: Does this copy Pending Orders (Limit/Stop)?
A: No. The code specifically checks if(OrderType() <= OP_SELL). This copies only active Market Buy and Market Sell orders. Copying pending orders is more complex because if a pending order triggers on the Master, it becomes a market order, potentially causing double entry logic issues on the Slave if not handled carefully. It is safer to copy the trade only once it triggers and becomes a live position on the Master.