Problem Description
Solution
To programmatically create and write data to a custom offline chart in MetaTrader 4, you must generate a binary file with the .hst extension in the terminal's history folder. This file must adhere strictly to the MT4 History File Format (Version 401).
The Strategy
- Define Data Structures: Create
structsthat match the binary layout of the MT4 HST header (148 bytes) and the bar record (60 bytes). - File Operations: Use
FileOpenHistoryto create the file directly in the correct directory. - Binary Writing: Use
FileWriteStructto write the header once, followed by the bar data in a loop. - Access: Once created, the chart is accessed via File -> Open Offline in the MT4 terminal.
MQL4 Implementation
Here is a complete script. You can run this on any chart (e.g., EURUSD M1), and it will generate an offline file (e.g., !EURUSD_OFFLINEM10.hst) containing the current chart's data.
//+------------------------------------------------------------------+
//| CreateOfflineChart.mq4 |
//| Copyright 2023, QuantitativeAI |
//| https://mql4.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, QuantitativeAI"
#property link "https://mql4.com"
#property version "1.00"
#property strict
#property script_show_inputs
//--- Input Parameters
input string CustomSymbolName = "!EURUSD_OFFLINE"; // Name of the offline symbol
input int CustomPeriod = 10; // Period (e.g., 10 minutes)
//--- MT4 History Header Structure (148 bytes)
struct MqlHistoryHeader
{
int version; // Database version (401)
uchar copyright[64]; // Copyright info
uchar symbol[12]; // Symbol name
int period; // Timeframe
int digits; // Digits
int timesign; // Time of file creation
int last_sync; // Time of last synchronization
int unused[13]; // Reserved for future use
};
//--- MT4 Bar Record Structure (60 bytes)
struct MqlHistoryRate
{
long time; // Bar open time
double open; // Open price
double high; // High price
double low; // Low price
double close; // Close price
long tick_volume; // Tick volume
int spread; // Spread
long real_volume; // Real volume
};
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
// 1. Construct the file name
// Format must be: Symbol + Period + ".hst"
string fileName = CustomSymbolName + IntegerToString(CustomPeriod) + ".hst";
Print("Attempting to create offline chart: ", fileName);
// 2. Open the file in the History folder
// FILE_BIN: Binary mode
// FILE_WRITE: Write mode
// FILE_SHARE_WRITE/READ: Allows the offline chart to update while open
int handle = FileOpenHistory(fileName, FILE_BIN | FILE_WRITE | FILE_SHARE_WRITE | FILE_SHARE_READ);
if(handle == INVALID_HANDLE)
{
Print("Error opening file. Code: ", GetLastError());
return;
}
// 3. Prepare and Write the Header
MqlHistoryHeader header;
ZeroMemory(header);
header.version = 401;
StringToCharArray("MetaQuotes", header.copyright);
StringToCharArray(CustomSymbolName, header.symbol);
header.period = CustomPeriod;
header.digits = Digits;
header.timesign = (int)TimeLocal();
header.last_sync = 0;
// Write the header structure to the file
if(FileWriteStruct(handle, header) <= 0)
{
Print("Error writing header. Code: ", GetLastError());
FileClose(handle);
return;
}
// 4. Prepare and Write Bar Data
// We copy data from the current chart to the offline chart
int barsCount = Bars;
int writtenCount = 0;
// MT4 history files store oldest data first.
// The Bars array is indexed [0] = newest.
// So we loop from Bars-1 down to 0.
for(int i = barsCount - 1; i >= 0; i--)
{
MqlHistoryRate rate;
rate.time = (long)Time[i];
rate.open = Open[i];
rate.high = High[i];
rate.low = Low[i];
rate.close = Close[i];
rate.tick_volume = (long)Volume[i];
rate.spread = 0; // Optional: Record specific spread if needed
rate.real_volume = 0; // Usually 0 for Forex
if(FileWriteStruct(handle, rate) <= 0)
{
Print("Error writing bar at index ", i);
break;
}
writtenCount++;
}
// 5. Finalize
FileFlush(handle);
FileClose(handle);
Print("Successfully wrote ", writtenCount, " bars to ", fileName);
Print("Go to File -> Open Offline to view the chart.");
}
//+------------------------------------------------------------------+
Key Implementation Details
FileOpenHistory: This function is critical. UnlikeFileOpen(which opens in theMQL4/Filessandbox),FileOpenHistoryopens files directly in thehistory/<server_name>folder, which is where MT4 looks for chart data.- Struct Alignment: The
MqlHistoryHeaderandMqlHistoryRatestructures are defined exactly to match the C++ structures used internally by MT4. Usinguchararrays for strings ensures the header is exactly 148 bytes. - Data Ordering: MT4
.hstfiles require data to be written chronologically (Oldest -> Newest). Since MQL4 arrays (Time[],Open[]) are usually indexed as Series (0 is Newest), the loop iterates backwards:for(int i = barsCount - 1; i >= 0; i--). - Shared Access: The flags
FILE_SHARE_WRITE | FILE_SHARE_READare essential if you plan to update this file continuously (e.g., from an Expert Advisor) while the offline chart is open and being viewed.
How to Use
- Compile the script.
- Drag it onto any live chart (e.g., EURUSD).
- Check the "Experts" tab for the success message.
- In MT4, go to File -> Open Offline.
- Find
!EURUSD_OFFLINE, M10in the list and click Open.
Q&A
Q: Can I update the offline chart in real-time?
A: Yes. Instead of a script, implement this logic in an Expert Advisor (EA). In OnInit, write the history. In OnTick, seek to the end of the file (FileSeek(handle, 0, SEEK_END)), write the new bar data, and call FileFlush. To make the offline chart refresh visually, you may need to send a window command message to the offline chart window.
Q: Why use uchar arrays for strings in the header?
A: The .hst file format requires fixed-byte fields (64 bytes for copyright, 12 bytes for symbol). Standard MQL4 string types are dynamic pointers. Using uchar arrays ensures the binary structure is written with the exact byte size required by the file format specification.
Q: What happens if I use a standard timeframe ID (e.g., 60 for H1)?
A: You can overwrite standard history data if the filename matches an existing symbol (e.g., EURUSD60.hst). However, the terminal might overwrite your custom data during synchronization with the broker server. It is safer to use a custom symbol name or a non-standard timeframe (e.g., M10, M2, Renko) to prevent conflicts.