今天我们来聊聊如何使用专家顾问(EA)来下载MetaTrader 5中某个符号的所有历史数据。这段代码会扫描您经纪商的市场观察列表,并提取出可以下载所有可用ticks的符号。
这个工具可以帮助您下载所有符号的历史数据,以便进行回测,或者从这些ticks创建自定义图表。
请注意,终端会将ticks缓存到数据文件夹中,所以确保您的硬盘空间足够。
为了方便下载,我们首先需要一个下载管理器。
以下是 CDownloadManager 结构,它包含我们需要保留的所有信息:
struct CDownloadManager {
bool m_started, m_finished;
string m_symbols[], m_current;
int m_index;
} 它包含了以下信息:
- 下载状态(已开始/已完成)
- 待扫描的符号列表
- 当前符号
- 正在扫描的符号索引
我们还需要读写硬盘,因为我们在处理符号时,需要创建两个快速的函数来从二进制文件中读写字符串。
保存字符串到文件的函数:
void writeStringToFile(int f, string thestring) {
// 保存符号字符串
char sysave[];
int charstotal = StringToCharArray(thestring, sysave, 0, StringLen(thestring), CP_ACP);
FileWriteInteger(f, charstotal, INT_VALUE);
for (int i = 0; i < charstotal; i++) {
FileWriteInteger(f, sysave[i], CHAR_VALUE);
}
} 这个函数接收:
- 文件句柄 f,一个以写入和二进制标志打开的文件
- 要写入文件的字符串
它会写入字符串的字符长度,并将每个字符存储到文件中。
从文件读取字符串的函数:
string readStringFromFile(int f) {
string result = "";
// 加载符号字符串
char syload[];
int charstotal = (int)FileReadInteger(f, INT_VALUE);
if (charstotal > 0) {
ArrayResize(syload, charstotal, 0);
for (int i = 0; i < charstotal; i++) {
syload[i] = (char)FileReadInteger(f, CHAR_VALUE);
}
result = CharArrayToString(syload, 0, charstotal, CP_ACP);
}
return result;
} 这个函数接收:
- 文件句柄 f,一个以二进制方式打开的文件
它会读取预期字符的长度,然后逐个读取字符到字符数组中,最后将字符数组转换为字符串返回。
接下来,我们需要一种方法来初始化下载管理器,并从市场观察中填充它:
void grab_symbols() {
// 仅从市场观察中获取符号
int s = SymbolsTotal(true);
ArrayResize(m_symbols, s, 0);
for (int i = 0; i < ArraySize(m_symbols); i++) {
m_symbols[i] = SymbolName(i, true);
}
} 这个过程非常简单:
- 询问市场观察中有多少个符号(活动的)
- 调整 m_symbols 数组来接收这些符号
- 循环获取所有符号的名称
我们还需要一个管理下载符号数据的函数:
void manage(string folder, string filename) {
// 启动或导航到下一个符号
if (ArraySize(m_symbols) > 0) {
// 如果没有开始
if (!m_started) {
m_started = true;
m_current = m_symbols[0];
m_index = 1;
save(folder, filename);
if (_Symbol != m_current) {
ChartSetSymbolPeriod(ChartID(), m_current, _Period);
}
} else {
m_index++;
if (m_index <= ArraySize(m_symbols)) {
m_current = m_symbols[m_index - 1];
save(folder, filename);
if (_Symbol != m_current) {
ChartSetSymbolPeriod(ChartID(), m_current, _Period);
}
} else {
m_finished = true;
FileDelete(folder + "\" + filename);
Print("Finished");
ExpertRemove();
}
}
} else {
Print("请先获取符号");
}
} 至于系统是如何工作的:
- 打开一个图表,需要至少一个图表,并设置一个定时器。
- 定时器执行后,取消定时器。
- 检查这是新的下载还是持续的下载。
- 如果是新的下载,则通过获取所有符号设置它。
- 如果是持续的下载,则下载当前符号的数据。
在定时器上执行下载的代码部分如下:
void OnTimer() {
// 如果同步
if (SymbolIsSynchronized(_Symbol) && TerminalInfoInteger(TERMINAL_CONNECTED) == 1) {
EventKillTimer();
// 在这里加载系统
if (MANAGER.load(MANAGER_FOLDER, MANAGER_STATUS_FILE)) {
// 系统已加载,正在处理符号
Comment("系统已加载,正在处理 " + MANAGER.m_current);
// ticks 加载
// 首先找到经纪商中可用的最旧 tick
int attempts = 0;
int ping = -1;
datetime cursor = flatten(TimeTradeServer());
long cursorMSC = ((long)cursor) * 1000;
long jump = 2592000000; // 60*60*24*30*1000;
MqlTick receiver[];
long oldest = LONG_MAX;
Comment("请稍候");
while (attempts < 5) {
ping = CopyTicks(_Symbol, receiver, COPY_TICKS_ALL, cursorMSC, 1);
if (ping == 1) {
if (receiver[0].time_msc == oldest) {
attempts++;
} else {
attempts = 0;
}
if (receiver[0].time_msc < oldest) {
oldest = receiver[0].time_msc;
}
cursorMSC -= jump;
if (limitDate && receiver[0].time <= oldestLimit) {
break;
}
} else {
attempts++;
}
Sleep(44);
Comment("最旧的 Tick : " + TimeToString((datetime)(oldest / 1000), TIME_DATE | TIME_MINUTES | TIME_SECONDS) + "
Cursor(" + TimeToString((datetime)(cursorMSC / 1000), TIME_DATE | TIME_MINUTES | TIME_SECONDS) + ")
Attempts(" + IntegerToString(attempts) + ")
请等待响应...");
}
// 在此时,我们已经获取到最旧的 tick
// 开始从最旧的请求 ticks 到最新的
if (oldest != LONG_MAX) {
ArrayFree(receiver);
datetime newest_tick = 0;
// 接收存储在 symbol_time 中的最后一个 tick 时间
datetime most_recent_candle = (datetime)SymbolInfoInteger(_Symbol, SYMBOL_TIME);
while (newest_tick < most_recent_candle) {
// 请求从最旧时间开始的新批次,限制 ticks
int pulled = CopyTicks(_Symbol, receiver, COPY_TICKS_ALL, oldest, tick_packets);
if (pulled > 0) {
// 如果我们拉取了新批次,更新我们的下载时间
newest_tick = receiver[pulled - 1].time;
oldest = receiver[pulled - 1].time_msc;
ArrayFree(receiver);
}
// 超时服务器请求,如果想要,可以更改
Sleep(44);
Comment("拉取到 " + TimeToString(newest_tick, TIME_DATE | TIME_MINUTES | TIME_SECONDS) + " 到目前为止");
}
} else {
Alert("请关闭终端
前往 ticks 文件夹
并删除空文件夹");
ExpertRemove();
}
// 更新管理器并继续
MANAGER.manage(MANAGER_FOLDER, MANAGER_STATUS_FILE);
} else {
// 抓取市场观察符号以开始下载
Comment("获取市场观察并开始");
MANAGER.grab_symbols();
MANAGER.manage(MANAGER_FOLDER, MANAGER_STATUS_FILE);
}
}