使用手册
功能在哪 · 是什么 · 怎么调出来一、MQL4 是什么
MQL4(MetaQuotes Language 4)是 MT4 内置的策略编程语言,类 C 语法,专门用于写四类程序:
| 程序类型 | 作用 | 入口事件 |
|---|---|---|
| EA(Expert Advisor) | 自动交易程序,长期挂在图表上运行 | OnTick / OnTimer |
| 自定义指标(Custom Indicator) | 画自己的指标线 | OnCalculate |
| 脚本(Script) | 一次性执行的工具(跑完自动退出) | OnStart |
| 库(Library) | 可复用代码模块,本身不处理事件 | 无 |
MT4 build 600 之后的"新版 MQL4"在语法上向 MQL5 大幅靠拢(结构体、类、严格编译),社区生态也是全球外汇 EA 最大的。本教程基于新版 MQL4,所有示例加 #property strict 严格模式。
白话注解:EA 就是挂在图表上的机器人:行情来一个报价(tick)它就醒一次,看看条件满足没有,满足就下单。你睡觉它盯盘——前提是 MT4 终端开着。
二、账户与市场信息函数
1、账户信息(Account\* 族)
| 函数 | 含义 |
|---|---|
AccountBalance() | 账户余额(不含浮动盈亏) |
AccountEquity() | 净值(余额 + 浮动盈亏) |
AccountProfit() | 当前浮动盈亏 |
AccountMargin() / AccountFreeMargin() | 已用 / 可用保证金 |
AccountFreeMarginCheck(symbol,cmd,volume) | 预检:下单这么多手后剩余保证金,<0 会报 134 |
IsDemo() | 是否模拟账户 |
2、市场信息(MarketInfo)
| 参数 | 含义 |
|---|---|
| MODE_STOPLEVEL | 最小止损/挂单距离(点)——SL/TP 与挂单价离市价太近会报 130 |
| MODE_MINLOT / MODE_MAXLOT / MODE_LOTSTEP | 最小 / 最大手数 / 手数步长(0.01 步长给 0.115 手会报 131) |
| MODE_SPREAD | 当前点差(点) |
| MODE_TICKVALUE / MODE_TICKSIZE | 一个 tick 的价值 / 大小(算每点盈亏金额用) |
3、环境检查函数族(Is\*)
IsTradeAllowed()(交易允许)、IsConnected()(与服务器连接)、IsTesting()(回测中)、IsDemo()(模拟账户)、IsTradeContextBusy()(交易线程忙)。下单前组合检查是实盘 EA 的基本功。
白话注解:STOPLEVEL 是经纪商画的"安全距离":止损止盈挂太近会被拒。而 IsTradeContextBusy 是"交易线程正忙"——MQL4 交易操作排一条队,忙时下单直接失败,等一下再试即可。
操作教程
一步一步 · 跟着做完一件事三、开发环境 MetaEditor
1、打开方式
- MT4 菜单 Tools → MetaQuotes Language Editor,或直接按 F4;
- 或在 Navigator(导航器)里右键 Expert Advisors → Create,用向导生成骨架文件。
MetaEditor 是一个类 VS 的 IDE:左侧 Navigator(文件树 + 函数列表),中间代码区,下方 Error/Output 提示窗口。
2、文件目录结构
MT4 数据文件夹下的 MQL4/ 目录(MT4 里点 File → Open Data Folder 直接打开):
MQL4/
├── Experts/ ← EA 源码(.mq4)与编译产物(.ex4)
├── Indicators/ ← 自定义指标
├── Scripts/ ← 脚本
├── Include/ ← 头文件(.mqh)
├── Libraries/ ← 库文件
├── Files/ ← 程序读写文件的沙箱目录
├── Presets/ ← 参数预设(.set)文件
└── Logs/ ← 日志
白话注解:文件放错位置是最常见的"新手谜题"——EA 放进 Indicators 目录它就不出现在 Expert Advisors 列表里。记住:谁的家谁回,编译器不会帮你搬家。注意 Files/ 是程序读写文件的"沙箱",FileOpen 只能在这一片活动,写别的路径要另想办法。
四、程序骨架与三个核心事件函数
//+------------------------------------------------------------------+
//| MyFirstEA.mq4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Your Name"
#property link ""
#property version "1.00"
#property strict
//+------------------------------------------------------------------+
//| 初始化:EA 加载到图表时执行一次 |
//+------------------------------------------------------------------+
int OnInit()
{
Print("EA 已启动");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 去初始化:EA 从图表移除时执行一次 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("EA 已停止, 原因码=", reason);
}
//+------------------------------------------------------------------+
//| 核心函数:每收到一个新 tick 执行一次 |
//+------------------------------------------------------------------+
void OnTick()
{
// 你的交易逻辑写在这里
}
1、OnInit 的返回码(官方 ENUM_INIT_RETCODE)
| 返回码 | 含义 |
|---|---|
| INIT_SUCCEEDED | 初始化成功,EA 可以继续(等同返回 0) |
| INIT_FAILED | 初始化失败,无继续意义(如必需的指标创建失败;等同返回非 0) |
| INIT_PARAMETERS_INCORRECT | 输入参数组合不正确——优化时该组参数将被跳过,结果行标红 |
官方注意:只有用 #property strict 编译的程序,终端才会分析 OnInit 的返回结果;非严格模式下返回值被忽略。OnInit 建议始终写 return(INIT_SUCCEEDED); 或明确的失败码。
2、官方冷知识
- EA 挂上图表后,终端最多给 5 秒的启动延迟做数据同步,之后带着"现有数据"启动——断线时 EA 可能晚启动,不是卡死。
- OnDeinit 的
reason参数是去初始化原因码:换品种/换周期、改参数、重新编译、手动移除等都会先触发 Deinit 再重新 Init。
白话注解:OnInit 是"开机自检",检查表里有一项不过就 return(INIT_FAILED) 拒绝开机,比带着坏数据硬跑强。OnDeinit 的 reason 码能告诉你 EA 为什么被卸了——日志里打个 reason,排查"EA 怎么自己停了"一目了然。
五、数据类型与变量(input vs extern)
1、基本类型
| 类型 | 说明 | 正确示例 |
|---|---|---|
int | 整数 | int magic = 12345; |
double | 浮点数(价格、手数、盈亏) | double lots = 0.1; |
string | 字符串 | string sym = "EURUSD"; |
bool | 布尔 | bool isLong = true; |
datetime | 日期时间(秒级时间戳) | datetime t = TimeCurrent(); |
color | 颜色 | color c = clrRed; |
纠错:旧版教程示例 int lots = 0.1; 是错的——手数是小数,必须 double。把 0.1 塞进 int 结果是 0,下单时手数为 0 直接报 131 错误(invalid trade volume)。
2、input 与 extern 的区别
input int FastMAPeriod = 10; // 快线周期(面板可见,运行中不可修改)
input double Lots = 0.1; // 下单手数
extern int Slippage = 3; // 滑点容忍(点,运行中代码可修改)
// 全局变量(程序内部用)
double maFastCurrent;
double maFastPrevious;
| 关键字 | 面板可调 | 代码运行中可改值 | 典型用途 |
|---|---|---|---|
input | 是 | 否(常量语义) | 不该被程序自己改的参数 |
extern | 是 | 是 | 运行中需要按状态微调的参数 |
两种都会出现在 EA 属性面板的 Inputs 标签里。行尾的 // 注释 会成为面板里的参数名,写清楚点比写完自己都忘了强。
白话注解:input 是"顾客点的菜"——端上桌就不能改;extern 是"厨房自己还能调味"。拿不准就用 input,防止程序运行中把自己参数改乱。
六、运算符与控制结构
// if 判断
if(maFastCurrent > maSlowCurrent && maFastPrevious <= maSlowPrevious)
{
// 金叉
}
// for 循环(订单遍历惯用倒序,平仓后索引不乱)
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
Print(i);
}
// while 循环
int count = 0;
while(count < 5)
{
count++;
}
// switch
switch(OrderType())
{
case OP_BUY: Print("多单"); break;
case OP_SELL: Print("空单"); break;
default: Print("挂单"); break;
}
- 逻辑运算
&&(与)||(或)!(非);build 600 起采用 C 语言标准的短路求值——左条件已定结果时右条件不再计算。 - 循环里
break跳出整个循环,continue跳过本轮。
白话注解:遍历订单用倒序(从大到小)是 MQL4 的祖传手艺:平掉一笔订单后,后面订单的索引会整体前移,倒序遍历才不会漏单或跳单。正序遍历边平仓边 i++,几乎必然出事。
七、行情数据访问
1、预定义变量(仅 EA/脚本环境)
| 变量 | 含义 |
|---|---|
Ask / Bid | 当前卖价 / 买价(开多用 Ask,平多/开空用 Bid) |
Point | 一个最小变动价位的大小 |
Digits | 当前品种的小数位数(5 位报价=5) |
Bars | 当前图表 K 线数量 |
Time[] Open[] High[] Low[] Close[] Volume[] | 预定义时间序列数组,索引 0 = 当前(未走完的)K 线,越老越大 |
4 位 / 5 位报价:同样是 EURUSD,5 位报价经纪商的 Point=0.00001,4 位报价的 Point=0.0001。写 StopLossPoints * Point 这类距离计算时,先确认你的账户是几位报价,否则止损差 10 倍。通用做法是用 Digits 判断(Digits==5 或 Digits==3 时给距离乘 10)。
2、时间序列与指标访问函数(官方 Series 页全表收录)
| 函数 | 作用 |
|---|---|
iMA(symbol,timeframe,period,ma_shift,ma_method,applied_price,shift) | 取均线值 |
iRSI(...) / iATR(...) / iMACD(...) 等 | 取内置指标值 |
iOpen / iHigh / iLow / iClose / iTime / iVolume | 按品种+周期+索引取任意 K 线数据 |
iHighest / iLowest | 区间内最高/最低值的位置 |
iBarShift | 由时间反查 K 线索引 |
CopyRates / CopyClose / CopyOpen / CopyHigh / CopyLow / CopyTime / CopyTickVolume | 批量复制历史数据到数组(MQL4 build 600+ 支持) |
RefreshRates() | 刷新预定义变量(Ask/Bid 等)与序列数组 |
两个官方要点:
- 时间序列索引 0 是未走完的当根 K 线;shift=1 才是上一根已收线的值。指标比较用"当根 vs 上一根"(shift 0 和 1)时,盘中当根值会一直变——要么接受,要么统一用已收盘的 shift 1 和 2。
- 跨品种/跨周期取数可能触发 4066(ERR_HISTORY_WILL_UPDATED,历史数据更新中),官方说明此时需稍后重新请求。
double fast0 = iMA(Symbol(), 0, 10, 0, MODE_EMA, PRICE_CLOSE, 0); // 当根
double fast1 = iMA(Symbol(), 0, 10, 0, MODE_EMA, PRICE_CLOSE, 1); // 上一根
double prevDayClose = iClose(Symbol(), PERIOD_D1, 1); // 昨日收盘
白话注解:iMA 第二个参数是 timeframe,写 0 表示跟图表周期一致——回测时改周期不用改代码,这是最常用的写法。RefreshRates 是长循环里的续命药:循环里折腾半天后 Ask/Bid 还是旧的,刷一下再下单,能少踩一次 138(requote)。
八、第一个完整 EA:双均线自动交易
下面是一个结构完整、可编译、可回测的 EA 示例。它只演示 MQL4 怎么写,不代表这个策略能赚钱。
//+------------------------------------------------------------------+
//| DualMA_EA.mq4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Your Name"
#property link ""
#property version "1.00"
#property strict
//=== 可调参数 ===
input int FastMAPeriod = 10; // 快线周期
input int SlowMAPeriod = 30; // 慢线周期
input double Lots = 0.1; // 下单手数
input int Slippage = 3; // 滑点容忍(点)
input int StopLossPoints = 50; // 止损(点)
input int TakeProfitPoints = 100; // 止盈(点)
//=== 全局变量 ===
int ticket; // 当前持仓订单号
double point;
//+------------------------------------------------------------------+
//| 初始化 |
//+------------------------------------------------------------------+
int OnInit()
{
point = Point;
Print("双均线 EA 启动, 快线=", FastMAPeriod, " 慢线=", SlowMAPeriod);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 主循环 |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. 取当前根与上一根的快慢均线值
double fastMA0 = iMA(Symbol(), 0, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0);
double fastMA1 = iMA(Symbol(), 0, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double slowMA0 = iMA(Symbol(), 0, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0);
double slowMA1 = iMA(Symbol(), 0, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
// 2. 遍历订单,检查本 EA(Magic=12345)在本品种上是否已有持仓
bool hasPosition = false;
int posType = -1; // 0=多 1=空
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == 12345)
{
hasPosition = true;
posType = OrderType();
ticket = OrderTicket();
break;
}
}
}
// 3. 判断金叉死叉
bool crossUp = (fastMA0 > slowMA0) && (fastMA1 <= slowMA1);
bool crossDown = (fastMA0 < slowMA0) && (fastMA1 >= slowMA1);
// 4. 金叉:平空 + 开多
if(crossUp)
{
if(hasPosition && posType == OP_SELL)
OrderClose(ticket, OrderLots(), Bid, Slippage, Red);
if(!hasPosition || posType == OP_SELL)
{
double sl = Ask - StopLossPoints * point;
double tp = Ask + TakeProfitPoints * point;
ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage,
sl, tp, "DualMA", 12345, 0, Blue);
Print("开多单 ticket=", ticket);
}
}
// 5. 死叉:平多 + 开空
if(crossDown)
{
if(hasPosition && posType == OP_BUY)
OrderClose(ticket, OrderLots(), Bid, Slippage, Red);
if(!hasPosition || posType == OP_BUY)
{
double sl = Bid + StopLossPoints * point;
double tp = Bid - TakeProfitPoints * point;
ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage,
sl, tp, "DualMA", 12345, 0, Orange);
Print("开空单 ticket=", ticket);
}
}
}
教学示例与实盘的差距,自己心里要有数:这份代码没有检查 OrderSend 返回值是否 -1、没有 NormalizeDouble、没有 STOPLEVEL 预检、没有重试逻辑——这些都是第 2 课《订单管理进阶》的内容。别拿它直接上实盘。
九、代码逐段解释与 Magic Number
1、主循环五步拆解
iMA()取当根(shift 0)与上一根(shift 1)的快慢均线值,共 4 个数;- 倒序遍历订单,用"品种 + Magic Number"双重过滤找自己的持仓;
- 金叉 = 快线当前在慢线上方且上一根不在上方(反之死叉);
- 金叉时:有空单先 OrderClose,再 OrderSend 开多;
- 死叉对称处理。
2、关键函数速查
| 函数 | 参数 | 作用 |
|---|---|---|
iMA() | 品种,时间框架,均线周期,偏移,均线类型,价格类型,索引 | 取均线值 |
OrdersTotal() | 无 | 当前市场持仓+挂单总数 |
OrderSelect() | 索引或票号,选择方式,交易池 | 选中订单后才能读其属性 |
OrderSend() | 品种,方向,手数,价格,滑点,止损,止盈,注释,Magic,过期时间,箭头颜色 | 下单,成功返回 ticket,失败返回 -1 |
OrderClose() | 订单号,手数,平仓价,滑点,箭头颜色 | 平仓 |
3、Magic Number 是什么
每个 EA 下单时打一个"标签"(这里是 12345),作用:
- 多个 EA 同时跑不会互相干扰——各自只认自己的 Magic;
- EA 只管理自己开的单,不会误平你手动下的仓(手动单 Magic=0)。
白话注解:Magic Number 就是"这是我的孩子"的认亲信物。不加 Magic 过滤的 EA 是灾难:它会把你账户里所有单子都当成自己的来平。同一策略跑多个品种时也建议每个图表实例用不同 Magic,方便对账。
十、编译与加载
1、编译
- MetaEditor 打开
DualMA_EA.mq4; - 按 F7(或工具栏 Compile);
- 下方 Errors 窗口显示 0 errors, 0 warnings 最理想(warning 也别无视);
- 编译生成
.ex4,MT4 只加载 .ex4。
2、加载到图表
- 切回 MT4,Navigator → Expert Advisors → 找到
DualMA_EA(找不到先刷新右键菜单); - 拖到品种图表上,弹出属性窗口:Common 标签勾选 Allow live trading(允许实时交易);Inputs 标签调参数;
- 确认 MT4 工具栏的 AutoTrading(自动交易)总开关是亮的绿色;
- 图表右上角出现笑脸 = EA 正常运行;出现哭脸或 = 没跑起来。
白话注解:能交易需要"三重门"同时打开:终端总开关 AutoTrading、EA 属性里的 Allow live trading、(有的经纪商还要求账户本身允许程序化)。哭脸只说明"当前不允许交易",不一定是 EA 写错了。
十一、EA 跑不起来的排查清单
| 现象 | 原因 |
|---|---|
| 图表右上角哭脸 | 工具栏 AutoTrading 总开关没开,或 EA 属性没勾 Allow live trading |
| 笑脸但不下单 | 信号条件从未满足;或下单被拒(看 Experts 日志的错误码,见第十八章) |
| Navigator 里找不到 EA | 文件没编译成功(无 .ex4),或放错了目录 |
| 改了代码没生效 | 没重新编译,或 MT4 里挂的还是旧 .ex4(重新编译后 EA 会自动重载) |
| Terminal → Experts 报错 | 运行时错误:手数非法、止损太近(130)、余额不足(134)等 |
| Terminal → Journal 报错 | 账户/连接层面问题:断线、登录失效等 |
白话注解:排查顺序永远是:笑脸有没有 → Experts 日志 → Journal 日志。Experts 是"你的 EA 说了什么",Journal 是"终端和服务器说了什么",两层日志分开看。
十二、日志与调试
1、三种输出
| 函数 | 输出位置 | 用途 |
|---|---|---|
Print(...) | Terminal → Experts 标签 | 标准日志,支持逗号拼接多参数 |
Comment(...) | 图表左上角 | 实时状态面板(持仓、盈亏等),OnDeinit 里 Comment("") 清掉 |
Alert(...) | 弹窗 + 声音 | 需要人工注意的事件,别滥用 |
2、调试手段
GetLastError():取最近一次错误码(配合 ResetLastError() 先清零再调用,见第十八章);- MetaEditor 支持断点调试(Debug 模式下在图表上启动 EA,可逐行单步、查看变量);
DebugBreak():代码内硬断点(函数列表官方收录);- 日志双语对照:自己的 Print 都带上下文(品种、ticket、价格),否则三个月后你自己都看不懂日志。
int t = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, sl, tp, "DualMA", 12345, 0, Blue);
if(t < 0)
Print("开多失败 ticket=", t, " err=", GetLastError(), " Ask=", DoubleToString(Ask, Digits));
白话注解:所有交易函数的返回值都必须检查——OrderSend 失败返回 -1,不检查你就以为"没信号",其实是"下单被拒"。err 码加上 Ask 现价一起打出来,一眼定位是滑点问题还是止损距离问题。
十三、事件函数全集与事件队列规则
1、官方事件函数表(docs.mql4.com Program Running 页)
| 函数 | 签名 | 适用 |
|---|---|---|
OnInit | int OnInit()(或 void) | EA、指标、脚本 |
OnDeinit | void OnDeinit(const int reason) | EA、指标、脚本 |
OnTick | void OnTick() | 仅 EA |
OnTimer | void OnTimer() | EA、指标(脚本不可用) |
OnTester | double OnTester() | 仅 EA(测试器自定义评估值) |
OnChartEvent | void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) | EA、指标 |
OnStart | void OnStart() | 仅脚本 |
OnCalculate | _rates_total 等 9 参 | 仅指标 |
警告:OnTrade() 是 MQL5 的交易事件,MQL4 的事件函数表里没有它。MQL4 里要感知成交变化,只能自己在 OnTick/OnTimer 里遍历订单比对。网上把 OnTrade 写在"MQL4 教程"里的,都是 MQL5 内容串了台。
2、官方事件队列规则
- NewTick 事件:队列里已有未处理的 NewTick 或正在处理时,新 tick 不入队——处理太慢的 EA 会自动丢 tick,不会堆积;
- Timer、ChartEvent 同理各排各的队;
- 每个程序(及图表)有独立事件队列,按到达顺序处理;
- 官方明确不建议用死循环处理事件(脚本例外)。
3、各程序类型的函数禁用表(官方)
| 程序类型 | 禁止调用 |
|---|---|
| 指标 | OrderSend、SendFTP、Sleep、ExpertRemove、MessageBox(交易与阻塞类全禁) |
| EA、脚本 | SetIndexBuffer、IndicatorSetDouble/Integer 等"指标专用"函数 |
| 库 | 不处理任何事件 |
白话注解:指标住在主界面线程里,一个指标死循环会让整个终端卡死——所以官方干脆把 Sleep、下单全给禁了。这也解释了"为什么我的指标里不能下单":不是 bug,是设计。
十四、订单体系与 OrderSend 全参数
1、六种订单类型(OP_ 常量)
| 常量 | 值 | 含义 |
|---|---|---|
| OP_BUY | 0 | 市价买 |
| OP_SELL | 1 | 市价卖 |
| OP_BUYLIMIT | 2 | 买入限价(下方挂单) |
| OP_SELLLIMIT | 3 | 卖出限价(上方挂单) |
| OP_BUYSTOP | 4 | 买入止损(上方挂单) |
| OP_SELLSTOP | 5 | 卖出止损(下方挂单) |
2、OrderSend 完整签名
int ticket = OrderSend(
symbol, // 品种
cmd, // OP_ 六选一
volume, // 手数
price, // 委托价(市价单用 Ask/Bid,挂单用挂单价)
slippage, // 滑点容忍(点,市价单有效)
stoploss, // 止损价(绝对价格,不是距离!)
takeprofit, // 止盈价(绝对价格)
comment, // 订单注释
magic, // Magic Number
expiration, // 挂单过期时间(0=不过期,仅挂单有效)
arrow_color // 图表箭头颜色
);
// 成功返回 ticket(正数),失败返回 -1
- SL/TP 是绝对价格(如 1.0850),不是"亏 50 点"这种距离——新手第一大坑;
- SL/TP 距离必须满足经纪商 STOPLEVEL 限制,否则报 130,见第十七、十八章;
- expiration 只对挂单有意义;市价单写 0;部分经纪商禁止挂单过期(报 147)。
白话注解:止损参数填的是"价格线画在哪",不是"离进场多远"。要"亏 50 点止损",得自己算 Ask - 50*Point 再传进去——算错方向或忘记 NormalizeDouble,就是 130 错误伺候。
十五、订单遍历与 OrderSelect
// 遍历当前持仓+挂单
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol() != Symbol() || OrderMagicNumber() != 12345) continue;
Print("ticket=", OrderTicket(), " type=", OrderType(),
" lots=", OrderLots(), " open=", OrderOpenPrice(),
" sl=", OrderStopLoss(), " tp=", OrderTakeProfit(),
" profit=", OrderProfit());
}
// 按 ticket 选中(平仓/改单前必做)
if(OrderSelect(ticket, SELECT_BY_TICKET))
{
// 注意:历史单也会被选中,用 OrderCloseTime() 判断是否已平
if(OrderCloseTime() == 0)
{
// 还在持仓/挂单中,可以操作
}
}
| 要点 | 说明 |
|---|---|
| SELECT_BY_POS / SELECT_BY_TICKET | 按位置索引 / 按订单号选中 |
| MODE_TRADES | 当前持仓+挂单池(OrdersTotal() 计数范围) |
| MODE_HISTORY | 历史池,配合 OrdersHistoryTotal() 遍历已平仓单 |
| OrderCloseTime()==0 | 未平仓;非 0 是平仓时间(SELECT_BY_TICKET 可能选中历史单,必查) |
白话注解:OrderSelect 是 MQL4 的"选中才能读"机制——先选中,OrderSymbol/OrderLots 这些属性函数才有意义。按 ticket 选中时连已平仓的历史单都能选中,所以判断"这单还活着吗"要看 OrderCloseTime() 是不是 0。
十六、OnTimer 定时器
int OnInit()
{
EventSetTimer(5); // 每 5 秒触发一次 OnTimer
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
EventKillTimer(); // 卸载时必须销毁定时器
}
void OnTimer()
{
if(!IsTradeAllowed()) return;
// 无 tick 时段的自救逻辑:检查持仓、移动止损、状态巡检
}
| 函数 | 说明 |
|---|---|
EventSetTimer(秒) | 启动秒级定时器(再次调用会重设周期) |
EventSetMillisecondTimer(毫秒) | 亚秒级高精度定时器 |
EventKillTimer() | 销毁定时器(OnDeinit 里必须调用) |
- OnTimer 只对 EA 和指标有效,脚本用不了;
- 官方规则:Timer 事件在队列中或处理中时,新 Timer 事件不入队;
- 市场没报价(冷清时段)OnTick 不触发,但 OnTimer 照常触发——用它做"无 tick 自救"(断线检测、状态上报、悬挂单巡检)。
白话注解:OnTick 是"有行情才醒",OnTimer 是"闹钟到点就醒"。周末停盘、清淡时段,OnTick 一小时不来一次,但你的 EA 还得巡检持仓状态——这时候 OnTimer 就是唯一的心跳。
十七、OnChartEvent 图表事件
void OnChartEvent(const int id, const long &lparam,
const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_CLICK)
Print("鼠标点击: x=", lparam, " y=", dparam);
if(id == CHARTEVENT_KEYDOWN)
Print("按键虚拟码: ", lparam);
if(id == CHARTEVENT_OBJECT_CLICK)
Print("点击了对象: ", sparam); // 按钮做交易面板的入口
}
- 事件类型常量:CHARTEVENT_CLICK(点击)、CHARTEVENT_KEYDOWN(按键)、CHARTEVENT_OBJECT_CLICK / OBJECT_CREATE / OBJECT_DELETE / OBJECT_DRAG(对象类)、CHARTEVENT_CHART_CHANGE(图表切换)等;
EventChartCustom()可生成自定义事件,实现程序内部/程序间的消息传递;- 典型用途:图形化交易面板(按钮下单、拖动改止损)、热键控制 EA 开关。
白话注解:想让 EA 有个"一键清仓"按钮,路径就是:ObjectCreate 画个按钮对象 → OnChartEvent 监听 CHARTEVENT_OBJECT_CLICK → 判断 sparam 是不是你的按钮名 → 执行平仓。MQL4 的图形面板全是这么攒出来的。
十八、数组与 CopyRates 数据复制
1、MqlRates 结构与 CopyRates
MqlRates rates[];
ArraySetAsSeries(rates, true); // 索引0=最新K线
int copied = CopyRates(Symbol(), 0, 0, 100, rates);
if(copied < 0)
{
Print("复制失败 err=", GetLastError()); // 常见 4066:历史更新中
return;
}
for(int i = 0; i < copied; i++)
Print(rates[i].time, " O=", rates[i].open,
" H=", rates[i].high, " L=", rates[i].low, " C=", rates[i].close);
- 三种重载:按 起始位置+数量、按 起始时间+数量、按 时间区间;
- 失败返回 -1:请求区间超出服务器数据范围、超过图表最大 K 线数(TERMINAL_MAXBARS)、数据尚未下载都会返回 -1;
- 返回值 > 0 时是实际复制的元素个数,循环边界用它,别自己假设 100 根就一定拿满 100 根;
- 同族的 CopyOpen/High/Low/Close/Time/TickVolume 按字段复制到 double/datetime/long 数组。
2、ArraySetAsSeries 的真相(官方注)
该函数只改索引方向,不搬数据——数组元素物理存储顺序不变,series=true 只是把"读下标的方式"反过来。官方建议:频繁在 OnTick 里复制的场景用静态数组省去重新分配内存的开销。
白话注解:MQL4 的老 Time[]/Close[] 数组天生就是"0 号最新",而 CopyRates 塞进来的数组默认"0 号最旧"。ArraySetAsSeries(arr, true) 就是把它们统一成老习惯——只想换读法,不想动数据。
十九、字符串与时间函数
1、新旧函数名并存(build 600+ 特点)
| 旧名(兼容保留) | 新名(推荐) |
|---|---|
| DoubleToStr(x, d) | DoubleToString(x, d) |
| StrToDouble(s) | StringToDouble(s) |
| StrToInteger(s) | StringToInteger(s) |
| TimeToStr(t, flags) | TimeToString(t, flags) |
两套都在官方函数列表里,同一程序里别混用,风格统一。
2、常用时间函数
| 函数 | 含义 |
|---|---|
TimeCurrent() | 服务器时间(最后已知报价时间) |
TimeLocal() | 本机时间 |
Time[0] / iTime(...) | K 线开盘时间 |
Day() / DayOfWeek() / DayOfYear() | 按服务器时间的日 / 星期(0=周日)/ 年积日 |
TimeHour(t) / TimeMinute(t) | 从时间戳里拆时 / 分 |
// 过滤开仓时间窗口示例(仅交易欧美时段)
int h = TimeHour(TimeCurrent());
if(h >= 7 && h < 20)
{
// 该时段才允许开新仓
}
白话注解:做时间过滤一定用服务器时间 TimeCurrent——它和你手表的时间不是一回事(差时区),而 K 线就是按服务器时间切的。DayOfWeek 0 是周日,周五下午收盘前减仓这种逻辑全靠它。
二十、终端全局变量与文件读写
1、终端全局变量(GlobalVariable 族)
GlobalVariableSet("MyEA_State", 1); // 写
double s = GlobalVariableGet("MyEA_State"); // 读
GlobalVariableDel("MyEA_State"); // 删
- 这是终端级的共享变量:同一 MT4 里的多个 EA/脚本/指标都能读写,跨图表传状态就靠它;
- 和程序内的全局变量是两码事:程序内的只在内存里,EA 卸载就没了;终端全局变量持久保存(带过期时间的变体也有);
- 典型用途:两个 EA 互斥(同一时间只允许一个开仓)、EA 与配套指标传状态。
2、文件读写(File 族)
int fh = FileOpen("mydata.csv", FILE_WRITE|FILE_CSV, ',');
if(fh != INVALID_HANDLE)
{
FileWrite(fh, "time", "ticket", "profit");
FileClose(fh);
}
- 读写范围限定在
MQL4/Files/沙箱(官方函数表含 FileOpen/FileClose/FileWrite/FileRead\*/FileIsExist/FileDelete 等完整一族); - 模式常量:FILE_WRITE / FILE_READ / FILE_APPEND / FILE_CSV / FILE_TXT / FILE_BIN;
- 典型用途:导出交易记录做分析、EA 重启后恢复状态。
白话注解:GlobalVariable 是"EA 之间的便签条",File 是"EA 的日记本"。EA 崩溃重启后想恢复"我今天已经开了几单"这类状态,要么写终端全局变量,要么写文件——只靠程序内存,重启全清零。
二十一、本课小结
- MQL4 四类程序:EA/指标/脚本/库,入口事件各不同;EA 主循环是 OnTick
- OnInit 返回 INIT_SUCCEEDED / INIT_FAILED / INIT_PARAMETERS_INCORRECT,#property strict 下才被分析
- 手数用 double;input 不可运行中修改、extern 可以
- Ask/Bid/Point/Digits 是预定义变量;时间序列索引 0 = 未走完的当根 K 线;4/5 位报价 Point 差 10 倍
- OrderSend 的 SL/TP 是绝对价格;订单遍历用倒序;一切交易函数返回值必须检查
- Magic Number 是 EA 的认亲信物;SELECT_BY_TICKET 选中历史单要看 OrderCloseTime()==0
- 事件函数全集里没有 OnTrade(MQL5 专属);OnTimer 做"无 tick 自救",OnDeinit 必须 EventKillTimer
- 错误码 128/142/143 = 结果未知先查成交;130 = 止损违规;可重试的是 135/136/138
- 回测三档模型:粗筛 Open prices only,精测 Every tick;回测 ≠ 实盘,业绩不代表未来收益
下一课预告:EA02 订单管理进阶——追踪止损、部分平仓、多持仓管理、错误码处理(已在库中)。
本站固定免责声明:本教程仅讲解软件编程与操作方法,不构成投资建议或策略推荐。回测业绩不代表未来收益,EA自动交易风险大,请谨慎决策。
核心知识点
为什么会这样 · 规则是什么二十二、回测:策略测试器
1、打开与设置
- 菜单 View → Strategy Tester,或按 Ctrl+R;
- 底部出现测试器面板,按下表设置:
| 选项 | 填什么 |
|---|---|
| Expert | 选 DualMA_EA |
| Symbol / Period | 回测品种(如 EURUSD)与周期(如 H1) |
| Model | Every tick(最精确最慢)/ Control points / Open prices only(最快最粗) |
| Date / Use date | 回测时间段 |
| Visual mode | 勾上可看图表逐根回放 |
2、三档模型怎么选
- Open prices only:只在 K 线开盘价成交——只适合"信号只在 K 线走完产生、且用市价单"的粗筛,速度飞快;
- Control points:最近 K 线细、远处用控制点插值——中等精度;
- Every tick:基于每一笔 tick 模拟——最接近实盘,也最慢。含盘中止损止盈(SL/TP 在 K 线内触发)的 EA 必须用它,否则回测结果失真。
面板里还可勾选优化(Optimization)并启用遗传算法对 input 参数做批量寻优。
白话注解:先用 Open prices only 快速筛掉明显不行的参数,再用 Every tick 精测幸存者——顺序别反,精测全参数组合能跑到天荒地老。模型档位越低回测越"好看",因为它看不见 K 线内的波动。
二十三、回测报告解读
跑完后看两个标签:Graph(资金曲线)和 Report(指标)。重点字段:
| 指标 | 含义 | 怎么看 |
|---|---|---|
| Total Net Profit | 总净利润 | 必须为正,但只看它必被坑 |
| Gross Profit / Gross Loss | 总盈利 / 总亏损 | 两者之比即 Profit Factor |
| Profit Factor | 盈利因子(总盈÷总亏) | <1 直接淘汰;略大于 1 扣除点差滑点后可能归零 |
| Maximal Drawdown | 最大回撤 | 决定你实盘拿不拿得住 |
| Total Trades | 总交易笔数 | 几十笔的样本没有统计意义 |
回测 ≠ 实盘:回测好的策略实盘可能亏钱——过拟合、滑点、点差放大、流动性不足、报价延迟都吃利润。回测是淘汰工具,不是赚钱保证书。
白话注解:不要只看净利润。赚 10 万但中间回撤 8 万的策略,你大概率在谷底手动关掉它——对你来说它就是亏 8 万。资金曲线"陡不陡"比终点"高不高"更值得看。
二十四、错误处理与常见错误码
ResetLastError(); // 先清零,防止读到旧错误
int t = OrderSend(...);
if(t < 0)
{
int err = GetLastError();
Print("下单失败 err=", err);
}
官方错误码常量表中交易类常用项(完整表见 docs.mql4.com 的错误码页):
| 码 | 含义 |
|---|---|
| 128 | 交易超时——结果未知,必须先查订单状态再决定是否重发 |
| 129 | 无效价格(价格离市价过远/过期) |
| 130 | 无效止损止盈(违反 STOPLEVEL 或没 NormalizeDouble) |
| 131 | 无效手数(低于 MINLOT / 不符 LOTSTEP) |
| 132 | 市场关闭 |
| 133 | 交易被禁用 |
| 134 | 余额/保证金不足 |
| 135 | 价格已变动(可刷新后重试) |
| 136 | 离价(off quotes) |
| 137 | 经纪商忙 |
| 138 | 重新报价(requote)——刷新价格后可重试 |
| 139 | 订单被锁定 |
| 141 | 请求过于频繁 |
| 146 | 交易上下文忙(IsTradeContextBusy() 检测)——稍候重试 |
| 147 | 经纪商拒绝使用 expiration(挂单过期时间被禁) |
| 148 | 持仓/挂单数量超上限 |
| 142 / 143 | 请求送达但连接中断类——结果未知,先查成交再重试 |
| 4066 | 请求的历史数据正在更新(ERR_HISTORY_WILL_UPDATED),稍后重新请求 |
白话注解:128、142、143 这三个码是"石沉大海"型——请求发出去了,成没成交不知道。这时直接重发可能开出双倍仓位,正确动作是先遍历订单查这笔单到底成没成。可安全重试的是 135/136/138 这类"明确被拒"的码。
二十五、常见问题全集 FAQ
1、Q1:input 和 extern 到底用哪个?
默认用 input(防程序运行中自己改参数);确实需要运行中改(如自适应滑点)用 extern。两者都出现在属性面板且都能参与优化。
2、Q2:OrderSend 返回 -1 怎么办?
先 GetLastError() 拿错误码再对症下药:130 查 STOPLEVEL 和 NormalizeDouble,131 查手数规范,134 查保证金,138 刷新价格重试,128/142/143 结果未知先查成交。逐条对照第十八章。
3、Q3:止损总是报 130?
三大原因:SL/TP 当成了"点数距离"而不是绝对价格;距离小于经纪商 STOPLEVEL;价格没 NormalizeDouble 到 Digits 位。三样全查。
4、Q4:为什么 OnTick 有时不触发?
市场没有新报价(冷清时段/休市)自然不来 tick;另外官方规则:上一 tick 还没处理完时,新 tick 不入队。对时效敏感的逻辑配 OnTimer(第十九章)。
5、Q5:MQL4 能用 OnTrade 吗?
不能。OnTrade 是 MQL5 的事件。MQL4 感知成交变化要自己在 OnTick/OnTimer 里遍历订单,比对 ticket 和持仓变化。
6、Q6:回测选哪个 Model?
粗筛用 Open prices only,精测用 Every tick。EA 用了 K 线内触发的 SL/TP 或盘中逻辑,必须 Every tick,否则回测结果不可信。样本(Total Trades)太少的结果直接无视。
7、Q7:EA 重编译后图表上的会自动更新吗?
会——重新编译触发 Deinit→重新加载。但改了 input 默认值不会覆盖图表上已保存的参数设置;参数改变本身也会触发一次 Deinit/Init 循环。
8、Q8:能拿别人的 EA 直接跑实盘吗?
先模拟盘跑够长时间,看懂它的 Magic、手数、止损逻辑再用。来路不明的 EA 是资金盘与盗号的高发载体(要求远程"托管账户"、承诺稳赚收益的更要远离)。只讲避坑,不荐任何 EA。
风险声明:本手册仅用于软件操作教学,不构成任何投资建议、开户建议或交易推荐。境外券商/平台涉及跨境资金与外汇管理问题,境内居民参与不受国内法律保护。保证金、期货、期权交易风险极高,可能损失超过本金的资金。请根据自身情况谨慎决策。
EA01-MQL4开发环境与第一个EA25 章 · 39 节仅供学习参考
二十六、学习路线
- 先学基础语法
- 写第一个简单指标
- 写第一个自动交易EA
- 回测优化
- 模拟盘验证
- 小资金实盘
二十七、快速上手
1、第一步:装环境
安装对应语言开发环境。
2、第二步:跑示例
先运行官方示例代码。
3、第三步:改参数
修改参数看效果。
4、第四步:写策略
从简单的均线策略开始写。
5、第五步:回测
用历史数据回测看效果。
💬 用户评价 (0)
立即登录