MQL4智能交易核心特性精讲
从保证金校验到历史数据与新K线判定的实战要点
一、为什么需要理解智能交易的特性
在MetaTrader 4交易系统中创建并测试智能交易(EA)时,许多新手直接照搬简单逻辑,结果在真实账户或回测中频繁出现下单失败、数据错位、重复开仓等问题。这是因为MQL4语言与交易终端的运行机制存在一些必须提前掌握的底层特性。只有理解这些特性,才能编写出在历史和实盘环境下都稳定运行的程序。本文基于官方技术文章,提炼出最容易被忽略、却决定EA成败的若干要点。
这些特性可以归纳为五大类:账户资源验证、历史数据访问方式、调试与错误获取、新K线(柱)的开始判定、以及文件输出。每一类都对应着具体的函数与编程习惯,下面逐一展开。
二、开仓前必须验证自由保证金
在发送订单之前,必须确认账户内存在足够的自由保证金(Free Margin)。如果自由保证金不足,OrderSend函数会返回0(即失败),且可能伴随错误代码。很多EA在回测中表现正常,实盘却无故不开仓,根源往往在此。MQL4提供AccountFreeMargin()函数实时返回当前可用保证金。
官方建议在测试时检验FreeMargin值不能少于1000,因为测试环境下一个标准手占用约1000保证金。当然实盘应根据杠杆与合约大小动态调整。简单做法是在EA开头或下单前加入判断,不足则直接退出当前tick。
if(AccountFreeMargin() < 1000) return(0); // 没有保证金 - 退出
三、利用预定义数组获取历史数据
MQL4为当前图表品种和周期预定义了六个数组:Time、Open、Low、High、Close、Volume。它们以索引方式提供历史数据。需要注意的是,由于历史数据逐步加载,索引是反向递减的:索引0表示当前未闭合的柱,索引1表示倒数一个已闭合时间周期,索引2表示倒数两个,以此类推。这种结构方便写类似“前一根K线收盘价小于前两根收盘价”的条件。
// 如果前一个蜡烛柱的Close少于 // 前者的Close if(Close[1] < Close[2]) return(0);
使用这些数组时,不要试图用正数索引向未来取值,那永远无效。同时,在EA初始化阶段或历史数据未下载完全时,数组长度可能不足,访问Close[2]前最好用Bars或iBars确认数据量。
- Time[0]为当前柱开盘时间,Time[1]为上一柱
- Volume[0]随当前柱成交 tick 增长,Volume[1]已固定
- 数组为全局预定义,无需声明即可在EA中任意函数使用
四、获取其他周期与品种的数据
有时EA需要参考其他时间周期甚至其他货币对的历史。例如用EURUSD的M1收盘辅助过滤信号。MQL4提供ArrayCopySeries函数,可将指定品种、指定周期的序列数据复制到一维数组中。调用时可省略部分默认参数,但目标数组必须事先声明为对应类型。
double eur_close_m1[];
int number_copied = ArrayCopySeries(eur_close_m1, MODE_CLOSE,
"EURUSD", PERIOD_M1);
该函数返回实际复制的元素数量。与预定义数组不同,自行声明的数组索引0也是最新数据,这与iClose等函数返回值的索引规则一致。需注意跨品种数据依赖于终端已缓存该品种历史,否则可能复制为空。
五、调试信息与错误捕获
编写EA离不开调试。MQL4提供多种输出信息的方法。Alert函数会弹出对话框显示指定内容,适合需要强提醒的变量;Comment函数在图表左上角持续显示,不阻断程序,常用换行符\n排版多行;Print则将信息写入终端日志,无界面干扰。
Alert("FreeMargin grows to ", AccountFreeMargin(), "!");
Comment("FreeMargin is ", AccountFreeMargin(), ".");
Print("FreeMargin is ", AccountFreeMargin(), ".");
交易类函数出错时,OrderSend常返回0(即tick数无效)。此时必须调用GetLastError获取详细错误代码。注意:GetLastError调用一次后会重置内部错误值为0,因此连续调用第二次将返回0,应在第一时间保存返回值。
int iTickNum = 0;
int iLastError = 0;
...
iTickNum = OrderSend(Symbol(), OP_BUY, g_Lots, Ask, 3, 0,
Ask + g_TakeProfit * g_Points);
if(iTickNum <= 0)
{
iLastError = GetLastError();
if(iLastError != ERR_NO_ERROR)
Alert("Some Message"); }
六、如何可靠判定新柱开始
很多策略只在每根K线开盘时计算一次信号。若误判“新柱”,会导致重复下单或漏算。文章给出三种方法,优劣不同。
方法一:比较Bars数量。用静态变量记录上次Bars值,若相等则说明还未新柱。但历史数据加载时Bars会突变,导致失效。可改为检测差值是否为1。
static int prevbars = 0; ... if(prevbars == Bars) return(0); prevbars = Bars; ...
方法二:基于Volume[0]判断。当前柱第一个tick进来时Volume[0]为1,若大于1说明已不是新柱。但在高频tick涌入时,由于终端单线程处理,部分tick可能被丢弃,Volume可能直接从1跳到3,使判定失败。改进是用静态变量存上次Volume。
if( Volume[0] > 1) return(0); ...
方法三:基于Time[0]开盘时间。用静态变量记录上次Time[0],若相等则未新柱。这是最稳妥的方法,任何数据环境都适用。
static datetime prevtime=0; ... if(prevtime == Time[0]) return(0); prevtime = Time[0]; ...
七、CSV文件读写范例
EA可将中间数据导出为CSV供分析。FileOpen以MODE_CSV结合分隔符打开;MODE_WRITE会清空原文件,MODE_READ配合FileSeek可追加。打开失败需检查句柄小于0并退出。
int h1;
h1 = FileOpen("my_data.csv", MODE_CSV | MODE_WRITE, ";");
if(h1 < 0)
{
Print("Unable to open file my_data.csv");
return(false);
}
FileWrite(h1, High[1], Low[1], Close[1], Volume[1]);
FileClose(h1);
若想保留原有内容并追加,使用MODE_WRITE|MODE_READ,打开后FileSeek到末尾再写。
int h1;
h1 = FileOpen("my_data.csv", MODE_CSV | MODE_WRITE | MODE_READ, ";");
if(h1 < 0)
{
Print("Unable to open file my_data.csv");
return(false);
}
FileSeek(h1, 0, SEEK_END);
FileWrite(h1, High[1], Low[1], Close[1], Volume[1]);
FileClose(h1);
八、与其他方案的对比与总结
MQL4的这些特性在MQL5中大部分有对应但写法不同(如历史数据用CopyClose)。但理解MQL4的数组索引与新柱判定逻辑,对学习任何版本都有益。相较于直接调用i系列函数,预定义数组效率更高;相较于弹窗Alert,Comment更适合持续监控。
总结:写EA不是拼指标,而是管理资源、数据与时序。把保证金校验、错误捕获、新柱判定这三道关卡写好,你的EA就超过了多数新手作品。建议把文中代码片段存为模板,在新项目中复用。