Problem Description
Solution
The most efficient way to save and retrieve custom data structures in MQL4 is using Binary Files combined with the FileWriteStruct() and FileReadStruct() functions.
Why this is the most efficient method:
- Speed: It dumps the memory block of the structure directly to the disk without converting data to text (strings), avoiding costly parsing operations.
- Precision: Floating-point numbers (
double) are saved exactly as they are represented in memory, preventing precision loss associated with CSV/Text files (e.g., truncation of decimals). - Simplicity: You can read/write complex structures in a single line of code.
Important Limitation
To use FileWriteStruct, your structure cannot contain:
- Dynamic arrays.
- Dynamic strings (use fixed-length
ucharorchararrays instead). - Class objects with virtual functions.
MQL4 Implementation
Here is a complete, runnable script demonstrating how to define a structure, populate it with data, save it to a binary file, and read it back.
//+------------------------------------------------------------------+
//| StructBinaryIO_Example.mq4 |
//| Copyright 2023, Quantitative AI Assistant |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative AI Assistant"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#property script_show_inputs
//--- Input parameters
input string FileName = "TradeData.bin"; // File name to save in MQL4/Files
//+------------------------------------------------------------------+
//| Define the Custom Structure (POD - Plain Old Data) |
//+------------------------------------------------------------------+
struct MyTradeData
{
datetime time; // 8 bytes
double price; // 8 bytes
double lots; // 8 bytes
int ticket; // 4 bytes
int type; // 4 bytes
char symbol[12]; // Fixed length string for binary safety
char comment[64]; // Fixed length string for binary safety
};
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
// --- 1. PREPARE DATA ---
MyTradeData dataToWrite[];
ArrayResize(dataToWrite, 3);
// Fill with sample data
for(int i=0; i<3; i++)
{
dataToWrite[i].time = TimeCurrent();
dataToWrite[i].price = Ask;
dataToWrite[i].lots = 0.1 * (i+1);
dataToWrite[i].ticket = 1000 + i;
dataToWrite[i].type = OP_BUY;
// String conversion to fixed char array
string sym = Symbol();
string comm = "Trade #" + IntegerToString(i);
StringToCharArray(sym, dataToWrite[i].symbol);
StringToCharArray(comm, dataToWrite[i].comment);
}
Print("--- Starting Write Operation ---");
// --- 2. SAVE TO FILE ---
if(SaveStructsToFile(FileName, dataToWrite))
{
Print("Successfully saved ", ArraySize(dataToWrite), " records.");
}
// --- 3. READ FROM FILE ---
Print("--- Starting Read Operation ---");
MyTradeData dataRead[];
if(LoadStructsFromFile(FileName, dataRead))
{
Print("Successfully loaded ", ArraySize(dataRead), " records.");
// Verify data
for(int i=0; i<ArraySize(dataRead); i++)
{
PrintFormat("Record %d: Ticket=%d, Symbol=%s, Price=%.5f, Comment=%s",
i,
dataRead[i].ticket,
CharArrayToString(dataRead[i].symbol),
dataRead[i].price,
CharArrayToString(dataRead[i].comment)
);
}
}
}
//+------------------------------------------------------------------+
//| Function to Save Array of Structures to Binary File |
//+------------------------------------------------------------------+
bool SaveStructsToFile(string filename, MyTradeData &data[])
{
// Reset error state
ResetLastError();
// Open file for Writing in Binary mode
// FILE_COMMON can be added to flags if you want to share between terminals
int handle = FileOpen(filename, FILE_WRITE | FILE_BIN);
if(handle == INVALID_HANDLE)
{
Print("Error opening file for writing: ", GetLastError());
return(false);
}
int count = ArraySize(data);
// Write the data
// Note: We can loop through the array, or write individually.
// FileWriteStruct writes one structure at a time.
uint written = 0;
for(int i=0; i<count; i++)
{
written += FileWriteStruct(handle, data[i]);
}
FileClose(handle);
// Check if bytes written matches expected size
// Size of one struct * number of items
if(written != sizeof(MyTradeData) * count)
{
Print("Warning: Data size mismatch during write.");
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Function to Read Array of Structures from Binary File |
//+------------------------------------------------------------------+
bool LoadStructsFromFile(string filename, MyTradeData &result[])
{
ResetLastError();
// Open file for Reading in Binary mode
int handle = FileOpen(filename, FILE_READ | FILE_BIN);
if(handle == INVALID_HANDLE)
{
if(GetLastError() == ERR_FILE_NOT_EXIST) // Error 5004
Print("File does not exist: ", filename);
else
Print("Error opening file for reading: ", GetLastError());
return(false);
}
// Clear result array
ArrayFree(result);
// Read loop
int i = 0;
while(!FileIsEnding(handle))
{
// Resize array to fit new item
ArrayResize(result, i + 1);
// Read structure directly into the array index
uint bytesRead = FileReadStruct(handle, result[i]);
// Validation: Ensure we actually read a full struct
if(bytesRead != sizeof(MyTradeData))
{
// If 0 bytes read (EOF), resize back and break
if(bytesRead == 0)
{
ArrayResize(result, i);
break;
}
Print("Error: Corrupted data or incomplete structure read.");
FileClose(handle);
return(false);
}
i++;
}
FileClose(handle);
return(true);
}
Key Implementation Details
StringToCharArray: Since we cannot use dynamicstringtypes in a binary structure, we definechar symbol[12]. We useStringToCharArrayto convert MQL4 strings into this fixed-byte format before saving, andCharArrayToStringwhen reading back for display.FILE_BINFlag: This is critical inFileOpen. It tells the system not to treat the data as text lines (looking for line breaks), but as raw bytes.sizeof(MyTradeData): This operator returns the exact byte size of the structure. This is useful for validating that the data read/written matches the expected structure size.FileIsEnding: Used in the read loop to determine when the file pointer has reached the end of the file.
Q&A: MQL4 Data Structures & File I/O
Q: Can I save an array of structures in one go without a loop?
A: FileWriteArray can be used for arrays of simple types or structures. However, FileWriteStruct is often safer for explicit control over structure alignment. If using FileWriteArray with structures, ensure the structure contains absolutely no dynamic objects.
Q: What happens if I change the structure definition and try to read an old file?
A: The read will likely fail or produce garbage data. Binary files rely on exact byte alignment. If you add a field to the struct, the size changes, and the old file's byte mapping will no longer align with the new struct definition. You should implement a version header in your file if you plan to update the structure later.
Q: Where are these files saved?
A: By default, they are saved in the MQL4/Files directory of your terminal's data folder. You can access this via File -> Open Data Folder. If you use the FILE_COMMON flag, they are saved in the common data path shared by all terminals installed on the computer.