Most traders who run an Expert Advisor on a VPS eventually hit the same wall: you want to see what your EA did overnight, but you don't want to log into the VPS every morning to dig through logs or regenerate reports. You could email reports, but email attachments get messy, get blocked, or fill up an inbox fast. That's where the built-in FTP client in MetaTrader 5 comes in.
MT5 ships with an FTP client that isn't just for downloading updates — you can use it to push files out to your own server. Combined with a bit of MQL5 code, you can generate a trade report and upload it automatically after every trade, every day, or on a timer. This guide walks through the whole setup: enabling the client, configuring the connection, writing the MQL5 code to generate and upload files, and the troubleshooting you'll likely need.
What You'll Achieve
By the end, you'll have a working system where your MT5 terminal automatically uploads a .htm trade report and a compressed log file to your web server or FTP host. You'll be able to open a URL like https://yourdomain.com/reports/ and see today's report without touching the VPS. This is genuinely useful if you run multiple EAs, manage client accounts, or just want a clean audit trail.
I'll also show you the common pitfalls — because the MT5 FTP client has a few quirks that aren't documented well. You'll save yourself an afternoon of head-scratching.
Prerequisites
Before we start, make sure you have the following:
- MetaTrader 5 build 2000 or newer — older builds have a less reliable FTP implementation. Check via Help → About.
- An FTP server — either a web hosting account with FTP credentials, a VPS with an FTP server (like vsftpd on Linux), or a free service. You need a hostname, username, password, and ideally a port (default 21).
- A directory on the server where you have write permission. Create it beforehand, e.g.,
/public_html/reports/. - AutoTrading enabled — the FTP client works regardless, but if you're uploading trade reports, you want the EA running. Click the Algo Trading button in the toolbar (it should be green).
- Basic MQL5 knowledge — you should be comfortable editing a script or EA in MetaEditor. If you've never compiled anything, start with a simple script first.
Step-by-Step: Configure the MT5 FTP Client
The FTP client is tucked away in the Tools menu. Here's the exact path:
- Open MT5 and go to Tools → Options (or press Ctrl+O).
- Click the FTP tab. You'll see a list of FTP connections — it's empty by default.
- Click Add. A dialog appears with fields for server, port, username, password, and a "Use SSL" checkbox.
- Enter your server address (e.g.,
ftp.yourdomain.comor an IP). Leave the port as 21 unless your host uses a custom port (some use 22 for SFTP, but MT5 doesn't support SFTP — it's plain FTP or FTPS). - Enter your username and password. Check the Use SSL box if your server supports FTPS (implicit or explicit TLS). Most shared hosts do; I recommend enabling it if available.
- Click OK to save, then Check to test the connection. MT5 will attempt to connect and show a message like "Connection successful" or an error code.
That's the manual setup. But the FTP tab alone only lets you upload files manually — right-click a file in the Navigator and choose "Upload to FTP". To automate, you need MQL5.
Understanding the MQL5 FTP Functions
MQL5 gives you a set of functions to work with FTP directly from code. The key ones are:
FileOpen()withFILE_COMMONor local path — to create the file you want to upload.FileWrite()— to write content into that file.FileClose()— to flush and close the file.FileCopy()— to copy a local file to the FTP server.FileDelete()— to remove a local file after upload (optional).
The trick is that FileCopy() doesn't just copy within the local filesystem — if you pass a destination with an ftp:// prefix, it uploads to your configured FTP server. This is the core of the automation.
Writing the MQL5 Code: Generate and Upload a Report
Let's write a script that does three things: generates a trade history report in HTML, saves it locally, then uploads it via FTP. You can adapt this into an EA's OnDeinit() or a timer function.
Here's the full script. Create a new file in MetaEditor (File → New → Expert Advisor or Script), name it FTP_Report_Uploader, and paste this:
//+------------------------------------------------------------------+
//| FTP_Report_Uploader.mq5 |
//| (c) 2024, Your Name Here |
//+------------------------------------------------------------------+
#property copyright "Your Name"
#property version "1.00"
#property script_show_inputs
//--- Input parameters
input string InpServerPath = "/reports/"; // Remote FTP directory
input string InpLocalName = "TradeReport"; // Base file name (no extension)
input bool InpUploadLog = true; // Also upload the daily log
input int InpMaxDays = 1; // Report period in days (0 = all history)
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- Generate the report filename with date
string fileName = InpLocalName + "_" + TimeToString(TimeCurrent(), TIME_DATE) + ".htm";
fileName = StringReplace(fileName, ".", "_"); // Replace dots in date (MT5 uses dots)
fileName = StringReplace(fileName, ":", ""); // Remove colons if any
fileName = InpLocalName + "_" + TimeToString(TimeCurrent(), TIME_DATE) + ".htm";
//--- Generate the report using built-in function
string reportPath = "Reports\\" + fileName;
bool reportCreated = false;
//--- Collect trade history
HistorySelect(TimeCurrent() - InpMaxDays * 86400, TimeCurrent());
int totalDeals = HistoryDealsTotal();
//--- Create a simple HTML report manually
string html = BuildReportHTML(totalDeals);
//--- Save locally
int handle = FileOpen(reportPath, FILE_WRITE|FILE_TXT|FILE_COMMON, 0, CP_UTF8);
if(handle != INVALID_HANDLE)
{
FileWriteString(handle, html);
FileClose(handle);
reportCreated = true;
Print("Report saved locally: ", reportPath);
}
else
{
Print("Failed to create local file, error: ", GetLastError());
return;
}
//--- Upload to FTP
if(reportCreated)
{
string ftpPath = "ftp://" + InpServerPath + fileName;
bool uploaded = FileCopy("\\Files\\" + reportPath, 0, ftpPath, 0, FILE_COMMON);
if(uploaded)
Print("Upload successful: ", ftpPath);
else
Print("Upload failed, error: ", GetLastError());
}
//--- Optional: upload the daily log
if(InpUploadLog)
{
string logName = "MT5_" + TimeToString(TimeCurrent(), TIME_DATE) + ".log";
string localLog = "\\Logs\\" + logName;
string ftpLog = "ftp://" + InpServerPath + logName;
if(FileCopy(localLog, 0, ftpLog, 0, FILE_COMMON))
Print("Log uploaded: ", ftpLog);
else
Print("Log upload failed, error: ", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Build a simple HTML report from deal history |
//+------------------------------------------------------------------+
string BuildReportHTML(int totalDeals)
{
string html = "MT5 Trade Report";
html += "Trade Report - " + TimeToString(TimeCurrent()) + "
";
html += "Total deals in period: " + IntegerToString(totalDeals) + "
";
html += "";
//--- Loop through deals
for(int i = 0; i < totalDeals; i++)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket == 0) continue;
string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
long type = HistoryDealGetInteger(ticket, DEAL_TYPE);
double volume = HistoryDealGetDouble(ticket, DEAL_VOLUME);
double price = HistoryDealGetDouble(ticket, DEAL_PRICE);
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
string typeStr = (type == DEAL_TYPE_BUY) ? "Buy" : "Sell";
html += "";
}
html += "Ticket Symbol Type Volume Price Profit " + (string)ticket + " " + symbol + " " + typeStr + " " + DoubleToString(volume, 2) + " " + DoubleToString(price, 5) + " " + DoubleToString(profit, 2) + "
";
return html;
}
Before you run this, note a few things:
- The
FILE_COMMONflag is crucial — it places files in the Common folder (%APPDATA%\MetaQuotes\Terminal\Common\Files), which is shared across all terminals. This avoids permission issues with the sandboxed\Filesfolder. - The FTP destination string must start with
ftp://and then the directory path you set in the input. The server address itself comes from the FTP tab settings, not the code. - I used
StringReplacein a clumsy way — that's intentional to show you the date formatting issue.TimeToString()withTIME_DATEreturns something like2024.12.15, and dots in filenames can confuse some FTP servers. Better to format it cleanly:
string dateStr = StringFormat("%04d%02d%02d", Year(), Month(), Day());
string fileName = InpLocalName + "_" + dateStr + ".htm";Use that instead — it gives you TradeReport_20241215.htm, which sorts nicely and avoids dot issues.
Compiling and Running the Script
- Press F7 in MetaEditor to compile. Fix any errors — usually a missing semicolon or a typo in a function name.
- Go back to MT5. In the Navigator panel, find your script under Scripts. Drag it onto any chart.
- A dialog appears showing the input parameters. Set your remote directory (e.g.,
/reports/) and whether to upload the log. - Click OK. Watch the Experts tab in the Toolbox — you'll see print messages like "Report saved locally" and "Upload successful".
If you get error 5020 (file not found) or 5021 (file error), it's almost always a path issue. I'll cover that in troubleshooting below.
Making It Automatic: From Script to EA
A script runs once and exits. For true automation, you want this inside an EA or a timer. Here's the pattern I use:
//--- In EA's OnInit()
EventSetTimer(3600); // Check every hour
//--- In EA's OnTimer()
void OnTimer()
{
static datetime lastUpload = 0;
if(TimeCurrent() - lastUpload >= 86400) // Once per day
{
UploadReport(); // Your function from above
lastUpload = TimeCurrent();
}
}This uploads once every 24 hours. You can also trigger it from OnTradeTransaction() to upload after every closed deal, but that can hammer your FTP server if you trade frequently. I prefer a daily snapshot plus a manual trigger on demand.
One more tip: if you're running this on a VPS, make sure the terminal stays running. Use a watchdog script or the built-in Tools → Global Variables to track last upload time and restart if needed. The FTP client itself doesn't have a retry mechanism — if the server is down, the upload fails silently and you'll only see it in the log.
Tips and Best Practices from Experience
After using this for a while, here's what I've learned:
- Use passive FTP mode. MT5 handles this internally, but if your server is behind a firewall, active mode will fail. Configure your FTP server (e.g., vsftpd) to use passive mode with a port range, and open those ports in your firewall.
- Compress your logs. Uploading a raw
.logfile is fine, but a zip is smaller and preserves the file. MQL5 doesn't have a built-in zip function, but you can useFileOpenwithFILE_BINto read the log and write a compressed version if you have a library. For most cases, a plain text log is fine. - Don't upload every tick. It's tempting to upload after every trade, but it's unnecessary. Daily is enough for most analysis. If you need intraday, upload every few hours.
- Set a filename convention. Include the account number and date:
Report_123456_20241215.htm. This saves you from mixing up multiple accounts. - Test with a dummy file first. Before running the full script, create a simple text file and upload it manually via the FTP tab to confirm your server credentials work. This isolates connection issues from code issues.
- Use the
FILE_COMMONflag consistently. Mixing local and common paths is a common source of "file not found" errors. Decide on one and stick to it.
Common Mistakes and Troubleshooting
Here are the issues I've seen most often, with fixes:
| Error / Symptom | Likely Cause |
|---|






