MQL4: Writing a Trade Copier for MetaTrader4
You have surely needed more than once to copy trades from one of your terminals to another. For example, if you need to open the same position on different accounts or with several brokers. Doing this work manually takes some time, which can be saved if you entrust this routine task to a copier. Moreover, you can write the copier yourself.
General Idea

The idea is as follows. All terminals installed on the computer have a directory shared among them. In the shared directory, the Provider creates a file with all open positions. The client terminal reads the data from the file and opens the required positions.
This approach was chosen because of its versatility and ease of implementation. But this is far from a universal solution, since some situations require speed and greater throughput, which files cannot provide. Nevertheless, for copying ordinary trading this will be quite enough.
The main problem of all copiers is state synchronization. First of all, we need to somehow link an order on the client with an order opened by the provider. To do this, we will use the provider's order ticket as the unique order identifier (magic number). At the same time, the order ticket may change if only part of the position was closed, and this case also needs to be handled.
struct Params
{
int account;
char symbol;
int ticket;
int magic;
int type;
double volume;
double sl;
double tp;
double equity;
};
So, the master writes all active positions to the shared file. The client then reads this data and creates its own copy of the file in the local directory. Thus, for our work we need to create functions for reading and writing, with the ability to access the shared and local directories.
In this implementation, the expert advisor enters an infinite loop until it gains access to the file. Shared access to the common file is not provided. This way we will protect ourselves from incomplete data that the client could mistakenly take for a trading signal.
void write(string name,Params &a[],bool local=false)
{
int h;
do
{
h=local ? FileOpen(name,FILE_WRITE|FILE_BIN) :
FileOpen(name,FILE_WRITE|FILE_BIN|FILE_COMMON);
if(GetLastError()!=0)
Sleep(DELAY);
}
while(h==INVALID_HANDLE);
FileWriteArray(h,a);
FileClose(h);
}
void read(string name,Params &a[],bool local=false)
{
int h;
do
{
h=local ? FileOpen(name,FILE_READ|FILE_BIN) :
FileOpen(name,FILE_READ|FILE_BIN|FILE_COMMON);
if(GetLastError()!=0)
Sleep(DELAY);
}
while(h==INVALID_HANDLE);
FileReadArray(h,a);
FileClose(h);
}
File names are created in the initialization function. The shared file name is the same for everyone, while the trading account number is used as the name of the local backup. Here we also call the backup method, which creates a local record with positions in the terminal folder (still empty for now).
void create()
{
account=AccountNumber();
backupName=IntegerToString(AccountNumber());
sharedName="shared";
backup();
}
In essence, the “backup” method creates a backup file through which we will then check changes in open positions.
void backup()
{
write(backupName,shared,true);
}
There is also an option for reading from a file, where the number of saved positions is returned.
int backup(Params &a[])
{
read(backupName,a,true);
return ArraySize(a);
}
There are similar methods for sharing positions. “pull” is for getting position data from the shared file, while “share” is for writing changes to the shared file. “share” supports several providers. To avoid a conflict between several master accounts trying to access one file, each provider is identified by the account number.
int pull(Params &a[])
{
read(sharedName,a);
return ArraySize(a);
}
void share()
{
Params orders[],temp[];
read(sharedName,orders); // read shared positions
int szO=ArraySize(orders);
int szP=ArraySize(positions);
int szTemp=ArrayResize(temp,0,szO+szP);
// copy all "foreign" orders into a temporary array
for(int i=0;i<szO;i++)
if(orders.account!=this.account)
{
szTemp=ArrayResize(temp,szTemp+1);
ArrayCopy(temp,orders,szTemp-1,i,1);
}
// now append the current positions in the account
ArrayResize(temp,szTemp+szP);
for(int i=szTemp;i<szTemp+szP;i++)
ArrayCopy(temp,positions,i,i-szTemp,1);
// save changes to the shared file
write(sharedName,temp);
}
The “opened” method returns the currently open positions in the account. At the beginning I said that when partially closed, MetaTrader changes the order ticket and writes the old one into the comment of the new one. Therefore, so that the client understands that this is not a new order, we remember the old ticket by writing it to the “magic” field.
for(int i=total-1;i>=0;i--)
{
if(OrderSelect(i,SELECT_BY_POS) && OrderType() <= 1)
{
sz=ArrayResize(positions,sz+1);
char sa[];
StringToCharArray(OrderSymbol(),sa);
ArrayCopy(positions.symbol,sa);
positions.ticket=OrderTicket();
c=OrderComment();
positions.magic=
StringToInteger(StringSubstr(c,StringFind(c,"#")+1));
positions.volume=OrderLots();
positions.sl=OrderStopLoss();
positions.tp=OrderTakeProfit();
positions.type=OrderType();
positions.equity=eq;
}
}
The main function of the copier, the direct copying of trades, is implemented through the “mergeAndTrade” method. See the full source code for the exact implementation. In short, all we do is compare the latest local backup with the shared file. If changes appear in the shared file, we modify current positions or open new ones.
For convenience, when all the main functions are called, the class instance returns a pointer to itself.
TradeCopier *func()
{
...
return GetPointer(this);
}
So the organization of the provider's and the copier's work is implemented in just one line of code, with the required functions called sequentially.
void OnTimer()
{
if(CopierType==SLAVE)
{
copier.mergeAndTrade();
}
else if(CopierType==MASTER)
{
copier.opened().share();
}
}
Finally, we create a small static trading class "Trade", which does nothing more than open and modify positions. Ideally, an implementation of a smart error handler is needed, in case of a strong mismatch in quotes. Also, the synchronization logic can be configured differently, in case of connection drops or a closed market. For example, open delayed signals only at a more favorable price.
Also, you can choose the method for copying volumes. A fixed volume is copied one-to-one, regardless of the deposit size. A dynamic volume is copied in proportion to the client's equity size. That is, if the provider has a position of 0.1 lots open with a deposit of 100 dollars, then on a client with a deposit of 200 dollars a trade with a volume of 0.2 lots will be opened.
If the lot is dynamic, we calculate the ratio of the current account equity to the master account, and multiply this value by the order volume. Also, do not forget to normalize the volume value.
double v=LotType==DYNAMIC
? normalizeLot(CharArrayToString(p.symbol),
eq/p.equity*p.volume)
: p.volume;
static double normalizeLot(string symbol,double lot)
{
double lotMin = SymbolInfoDouble(symbol,
SYMBOL_VOLUME_MIN);
double lotMax = SymbolInfoDouble(symbol,
SYMBOL_VOLUME_MAX);
double lotStep= SymbolInfoDouble(symbol,
SYMBOL_VOLUME_STEP);
double c=1.0/lotStep;
double l=floor(lot*c)/c;
if(l lotMax) l = lotMax;
return l;
}
To start copying, put the expert on the chart and select the copier type, Master or Slave. The number of both providers and clients is unlimited: you can copy trades from several accounts of any type to any live/demo accounts.
Conclusion

So, at this stage the following has been implemented:
- Transmission reliability: files are not used simultaneously by different processes, while each client has a snapshot of the current positions;
- Copying fixed and relative position volume, support for modifying stop levels and partial closing;
- The possibility of use on any number of MT4/MT5 terminals, with an unlimited number of Providers/Clients.
The disadvantages include:
- Read speed: not an ideal option for scalping, tick copying, and any other operations that require minimal delay;
- Disk load: the lower the delay, the more often the file will be overwritten.
Download the Copier Source Code
Respectfully, Dmitry aka Silentspec
Today we will learn how to write a simple trade copier for MT4.