Problem Description
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:
- 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.
- 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
-
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 theSharedTrades.csvwill be created. Both terminals can access this specific path.
-
Install Sender:
- Open the Master Terminal.
- Open MetaEditor, create
TradeSender.mq4, paste the first code block, and compile. - Drag
TradeSenderonto one chart (e.g., EURUSD). - Ensure "Allow DLL imports" is NOT required, but standard file permissions are automatic.
-
Install Receiver:
- Open the Slave Terminal.
- Open MetaEditor, create
TradeReceiver.mq4, paste the second code block, and compile. - Drag
TradeReceiveronto one chart. - Crucial: Go to the "Common" tab in the EA settings and check "Allow live trading".
- Ensure the
Symbolnames 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
OrderCommentfield of the Slave's order. This allows the Slave to know exactly which trade corresponds to which, preventing duplicate entries. - Lot Normalization: The
NormalizeLotsfunction ensures that if the calculation results in0.123lots but the broker only supports0.01steps, it rounds correctly to0.12to avoidOrderSendError 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.