Generate code in 3 minutes
Enter your idea and AI generates runnable code.
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:
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);
}
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);
}
Locate the Common Folder:
Common.Files. This is where the SharedTrades.csv will be created. Both terminals can access this specific path.Install Sender:
TradeSender.mq4, paste the first code block, and compile.TradeSender onto one chart (e.g., EURUSD).Install Receiver:
TradeReceiver.mq4, paste the second code block, and compile.TradeReceiver onto one chart.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.\Terminal\Common\Files) rather than the isolated instance folder. This allows two different MT4 installations to talk to each other.OrderComment field of the Slave's order. This allows the Slave to know exactly which trade corresponds to which, preventing duplicate entries.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).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.