简介面向外汇交易者与编程初学者的MT4 MQL4编程入门资料系统梳理MetaTrader 4平台内置编程语言的基础用法帮助读者从零开始建立编写自定义指标与Expert AdvisorEA的能力。内容以文档形式呈现围绕自定义变量、附加变量、自定义函数、自定义数组等核心概念展开配有代码示例与中文变量命名说明可直接对照MetaTrader 4环境练习。资料共1个文件类型为doc压缩包约197KB小巧精炼适合快速阅读与随时查阅。页面显示已有389人学习浏览适合初步接触MQL4、希望理解EA构建逻辑的交易者与程序化交易爱好者。通过学习本资料可掌握变量类型定义、extern参数接口、函数编写方式以及数组索引规则并在此基础上结合MT4预定义数组读取市场数据为后续开发个性化交易策略打下基础。内容结构紧凑从基础语法到实际调用逐步递进能帮助读者降低入门门槛更快进入自动化交易实践。1. 学 MQL4 之前先确认它替你解决的是哪一类盯盘问题做外汇或者黄金交易盯盘久了大概率会碰到这种场景等一个关键价位眼睛不敢离开图表结果起身接杯水行情就冲过去了或者深夜被一根假突破骗进止损刚好被打掉。MT4 里的 MQL4 编程就是把这些“必须盯着才能做”的动作自动化。它允许你写自定义指标把开高低收、均线、MACD 按自己的想法加工成一条线也允许你写 EA把入场、止损、出场交给程序执行甚至做成定时邮件提醒把行情直接推到手机上。这套入门教程正是从零开始讲 MQL4 的变量、数组、函数、指标结构最后落成一个完整实例。想验证策略、不想一直盯盘的人以及刚接触编程的量化爱好者都适合顺着章节边抄边改比自己硬啃帮助文件快得多。2. 变量、类型与 extern 参数把交易条件翻译成机器指令MQL4 程序无论是指标还是 EA本质上都在处理一组数和一堆状态。价格是 double均线周期是 int要不要报警是 bool。先把类型这件事弄清楚后面写逻辑才不会处处别扭。2.1 四种基本类型int、double、string、bool 怎么选MQL4 的自定义变量在使用前必须先创建也就是定义变量类型和名称。定义时可以同时赋初值也可以只定义不赋值变量名允许使用中文。int mark; // 整型变量变量名为 mark bool 是否报警 true; // 逻辑型变量赋初值 true这里有几条边界需要记住int 是整数包含负整数均线周期、订单数量、循环计数这类整数用 int。double 是双精度小数简单理解就是带小数位的数。价格、指标计算值都归它管K 线数据 Open、High、Low、Close 全是 double。string 是字符串存文本信息指标名称、注释、自定义消息都用它。bool 是逻辑型取值只有 true 和 false也可以写成 1 和 01 是真0 是假用于开关和状态标记。类型定义词只在创建变量时使用。单击变量已经定义过后后面再给它赋值或取值直接写变量名不能再带 int、double 这些词。这个细节经常有新手在 start() 里重复写 int编译时报“重新定义”错误。变量命名也值得多说一句。MQL4 允许中文命名老教程里大量使用中文变量比如“快线”“慢线”“是否报警”可读性确实好。但我自己的习惯是全局参数和 extern 变量用中文或带语义的英文局部临时变量用简短的 i、j、temp。别用 a、b、c 这类单字母做全局变量隔两周回看代码会非常痛苦。2.2 extern 附加变量把参数面板变成你的控制台extern 是 MQL4 里一个特别实用的关键字。它定义的变量和普通全局变量一样可以在程序中使用但多了一个能力程序加载到图表后用户不需要打开源码直接在 MT4 的输入参数界面里修改它的值。extern int 周期 9; // 整型附加变量默认值 9允许外部修改MACD(12,26,9) 大家都很熟这里(12、26、9) 实际上就是三个附加变量的默认值。指标加载后你会在参数列表里看到“周期”“快线”“慢线”等条目修改后点击确认指标就会用新参数重新计算。把哪些变量设计成 extern直接关系到指标好不好用。一般我会把周期、均线长度、报警阈值、邮件发送间隔这类“每次加载都想调”的参数做成 extern。程序内部临时用的计数器或中间结果就不要用 extern否则参数面板会被一堆无关变量塞满使用的人反而找不到重点。extern 变量也是全局变量但它对外暴露普通全局变量只在程序内部可见这是两者最大的区别。2.3 数组定义、赋值和下标从 0 开始的规矩数组是一系列同类型变量的集合可以同时存储多个数值。MQL4 数组的下标从 0 开始这一点和大多数编程语言一致但在交易语境里容易和 K 线编号混在一起需要单独说几遍。int aa[4] {4, 3, 2, 1}; // 整型数组4 个元素aa[0] 的值是 4aa[1] 的值是 3aa[2] 的值是 2aa[3] 的值是 1。创建时可以赋初值也可以不赋int bb[]; // 创建数组 bb不设定元素个数 int cc[8]; // 创建数组 cc8 个元素不赋初值 int dd[3] {9, 8, 7}; // 创建数组 dd3 个元素并赋初值 int ee[9] {0,1,2,3}; // 创建数组 ee9 个元素只给前 4 个赋初值初学阶段最容易忽略的是数组创建之后赋值必须一个一个来。批量赋值的写法只有创建时这一次机会。dd[2] 1; // 把 dd 数组第 3 个元素下标 2从 7 改成 1引用数组时也一次只能引用一个元素。如果你想对整列数组做操作需要配合循环逐个处理。这正是后面 for 和 while 循环的核心场景。另外MQL4 的数组越界检查并不总是及时报错有时编译能过运行时数据错乱排查起来很费劲所以定义数组长度时宁可留出余量也不要卡着数量写。3. 行情数据读取预定义数组和指标函数的正确打开方式写交易程序第一步永远是拿价格。MQL4 把 K 线数据做成了预定义数组把实时报价做成了预定义变量又提供了一组以 i 开头的函数做跨周期、跨品种取数。这三块内容就是行情数据的全部入口。3.1 预定义数组Open、High、Low、Close 的下标规则图表上每一根 K 线都有开盘价、最高价、最低价、收盘价、成交量和时间MQL4 用六个预定义数组直接暴露给开发者Open[] // 开盘价double 型 High[] // 最高价double 型 Low[] // 最低价double 型 Close[] // 收盘价double 型 Volume[] // 成交量double 型 Time[] // K 线时间datetime 型这几个数组最关键的规则是编号方向右边第一根 K 线也就是最新一根编号为 0第二根编号 1第三根编号 2。从右向左数。图表上越往右的 K 线下标越小这和我们阅读图表的习惯刚好相反。Open[0]; // 最新一根 K 线的开盘价 High[1]; // 右侧第二根 K 线的最高价 Close[2]; // 右侧第三根 K 线的收盘价 Close[i]; // 右侧第 i1 根 K 线的收盘价注意这是方括号数组下标。有初学者写成 Open(0)那就是函数调用语法在 MQL4 里直接用 Open(0) 编译都会报错。至于为什么不直接给个全是 1 的写法原因在于循环处理历史数据时下标 i 本身就是循环变量从 0 到 N 正好覆盖所有 K 线不需要额外转换。判断价格上穿某个价位时最经典也最抗噪的写法是拿相邻两根 K 线的最高价做对比if (High[1] 1.0000 High[0] 1.0000) { // 最新 K 线最高价已经站上 1.0000 }这个写法比用 Close 判断更抗噪因为最高价覆盖了这根 K 线试探过的最远位置。反过来下穿判断则用最低价if (Low[1] 1.0000 Low[0] 1.0000) { // 最新 K 线最低价已经跌破 1.0000 }只要理解 Right-to-Left 下标方向这两段代码是整套教程里复用率最高的基础逻辑。3.2 实时行情与交易环境Ask、Bid、Digits、PointK 线数组是历史Ask 和 Bid 才是当下。Ask 是买入价Bid 是卖出价类型都是 double实时变动。挂单、移动止损、判断真实报价时EA 直接用这两个变量。double curAsk Ask; // 当前买入价 double curBid Bid; // 当前卖出价注意MT4 图表上显示的价格通常是 Bid你在图表上看到的那条收盘价线是 Close[]不是 Ask。如果做的是收盘价策略就用 Close[]如果是即时成交价触发才需要考虑 Ask/Bid 的区别。预定义变量里还有三个值得养成习惯使用的变量类型说明Barsint当前图表 K 线总数Digitsint当前品种小数位无日元币对 4有日元币对 2Pointdouble最小价格单位无日元币对 0.0001有日元币对 0.01Digits 和 Point 经常用于显示和计算。显示价格时如果写死小数位数换到日元对价格就会缺位或多位看着像玄学错误。正确的姿势是用 DoubleToStr(Close[0], Digits)让程序自己根据当前品种决定保留几位小数。3.3 指标函数与跨周期取数iMA、iCustom 参数拆解预定义数组只能拿当前图表的数据。想跨周期、跨品种取数必须用 i 开头的一组函数。它们的基本格式一致前两个参数是品种和周期中间是各自特有参数最后一位是指定位移。iOpen(symbol, timeframe, shift) // 开盘价 iClose(symbol, timeframe, shift) // 收盘价 iHigh(symbol, timeframe, shift) // 最高价 iLow(symbol, timeframe, shift) // 最低价 iVolume(symbol, timeframe, shift) // 成交量 iTime(symbol, timeframe, shift) // K 线时间timeframe 用内置常量PERIOD_M1、PERIOD_M5、PERIOD_M15、PERIOD_M30、PERIOD_H1、PERIOD_H4、PERIOD_D1、PERIOD_W1、PERIOD_MN1。不要自己去换算成分钟数可读性差且容易出错。shift 的规则和预定义数组下标一致0 是最新 K 线。示例iClose(EURUSD, PERIOD_H4, 2) // 欧元兑美元 4 小时图右侧第 3 根 K 线的收盘价 iHigh(NULL, 0, i) // 当前品种、当前周期、右侧第 i1 根 K 线的最高价第一个参数写 NULL表示让函数自动取当前图表品种timeframe 写 0表示当前图表周期。这种写法在自定义指标里特别常见因为指标加载在哪个品种上就跑哪个品种不需要写死。移动平均线函数 iMA 是 7 个参数这样拆解iMA(symbol, timeframe, period, ma_shift, ma_method, applied_price, shift)period 是均线周期10 日均线的 period 就是 10。ma_shift 是均线整体左右平移的偏移量通常写 0。ma_method 是平均模式MODE_SMA 是简单移动平均MODE_EMA 是指数移动平均MODE_SMMA 是平滑移动平均MODE_LWMA 是线性加权移动平均。applied_price 是取哪种价格PRICE_CLOSE、PRICE_OPEN、PRICE_HIGH、PRICE_LOW、PRICE_MEDIAN 这些都是。最后一个 shift 是位置。iMA(NULL, 0, 20, 0, MODE_EMA, PRICE_CLOSE, 2)这行代码的意思当前品种当前周期下以收盘价计算 20 周期指数移动平均线取右侧第 3 根 K 线所在位置的值。在实际策略里这个函数常用于快线和慢线的差值计算也就是 MACD 白线的核心算法。自定义指标的值要用 iCustom 读取语法是iCustom(symbol, timeframe, 指标名称, 参数1, 参数2, ..., 指标线编号, shift)指标线编号从 0 开始。如果自定义指标只有一根线编号写 0多根线则依次为 0、1、2。示例iCustom(NULL, 0, mymacd, 12, 26, 9, 2, 0)意思是当前图表上调用自定义指标 mymacd给它传入 12、26、9 三个参数取第 3 条指标线在最新位置的值。这里有一个隐蔽的坑如果 mymacd 不存在或者参数个数不一致iCustom 不会报错而是静默返回 0。排查这种问题时先确认指标文件已经编译再核对参数个数和类型最后在数据窗口里查看指标当前值是否正常。4. if 判断与循环取数从报警条件到批量赋值策略逻辑离不开判断和循环。把“什么条件下做什么”用 if 写清楚把“一批数据怎么处理”用循环写清楚MQL4 的基本语法就通了。4.1 if/else 报警逻辑条件、嵌套与分号陷阱if 语句的规则很直观条件成立就执行大括号里的语句不成立就跳过。if (a 1) { b c 1; }MQL4 里比较用两个等号赋值用一个等号。有初学 EA 的人把 if(Close[0] Open[0]) 写成 if(Close[0] Open[0])编译能过但意思是把 Open[0] 的值赋给 Close[0]然后判断这个值是否为真逻辑完全跑偏。遇到这种“明明代码没报错但行为不对”的情况优先检查比较处是不是写成了单等号。else 配合 if构成两路分支if (条件) { // 条件成立执行这里 } else { // 条件不成立执行这里 }报警指标里最常见的需求是防止同样的条件反复报警。典型写法是配合一个状态变量 markint mark 0; if (High[1] 1.0000 High[0] 1.0000 mark ! 1) { Alert(Symbol(), 价格向上触及 1.0000); mark 1; // 标记上穿已经报过警 } if (Low[1] 1.0000 Low[0] 1.0000 mark ! 2) { Alert(Symbol(), 价格向下触及 1.0000); mark 2; // 标记下穿已经报过警 }mark 初值是 0表示“从未报警”此时上穿和下穿都可以触发。上穿报警后 mark 变成 1那么后面再上穿mark ! 1 的判断就为假不再报警但下穿仍然可以触发。反过来也一样。这个去重逻辑在实际运行中非常有用不加的后果是价格在一个位置反复穿越时报警弹窗把 MT4 刷到卡顿。分号规则同样值得注意。只有语句结束才加分号条件、大括号、小括号后面都不加分号。单行语句可以省略大括号但为了后续扩展代码不踩坑我一般都会写成带大括号的格式即使内部只有一行语句。4.2 while 与 for 循环给数组批量赋值的两种姿势K 线图动辄上万根每一根都有开高低收和若干指标值。逐根手写不现实循环就是专门解决批量处理问题的。while 循环的规则条件成立就一直执行大括号里的语句直到条件不成立。extern int 快线 12; extern int 慢线 26; double buffer[]; int i 0; while (i 1000) { buffer[i] iMA(NULL, 0, 快线, 0, MODE_EMA, PRICE_CLOSE, i) - iMA(NULL, 0, 慢线, 0, MODE_EMA, PRICE_CLOSE, i); i; }注意事项集中在三点。第一buffer 数组与指标线绑定由 SetIndexBuffer 分配空间直接赋值没问题但如果是在普通 EA 脚本里用同样的数组必须先调用 ArrayResize(buffer, 1000) 分配内存。第二循环体里的 i 不能漏漏掉就是死循环MT4 会假死只能强制重启终端。第三循环运行 1000 次后 i 变成 1000条件 i1000 不成立退出循环。此时 buffer[0] 到 buffer[999] 都存入了计算值。for 循环与 while 逻辑完全一致只是把三个关键步骤集中在一起extern int 快线 12; extern int 慢线 26; double buffer[]; for (int i 0; i 1000; i) { buffer[i] iMA(NULL, 0, 快线, 0, MODE_EMA, PRICE_CLOSE, i) - iMA(NULL, 0, 慢线, 0, MODE_EMA, PRICE_CLOSE, i); }拆开看 for 的三个部分int i 0 是初始变量定义整个循环只执行一次i 1000 是循环条件每次循环开始前判断i 是每次循环体执行完后的步进。相比 whilefor 把变量初始化、条件判断、步进写在同一行不容易漏掉 i因此日常开发里 for 用得更频繁。4.3 自定义函数封装可复用的计算逻辑自定义函数和数学函数本质是一回事。数学里 ft(x, y) 3x 2y写成 MQL4 是int ft(int x, int y) { int temp; temp 3 * x 2 * y; return (temp); }头部第一行说明三件事int 是函数返回值的类型ft 是函数名x 和 y 是参数。因为变量使用前必须先定义所以参数也要带类型定义词 int。函数体有三句定义局部变量 temp计算 3x2y 存入 temp然后 return 结束函数并把 temp 的值返回给调用点。这段代码也可以简写成int ft(int x, int y) { return (3 * x 2 * y); }函数类型必须与返回值匹配。返回整数用 int返回小数用 double返回字符串用 string。还有一种类型是 void表示函数运行结束不需要返回任何值void ShowAlarmMessage() { Print(自定义提示消息); }函数内部定义的变量是局部变量只在函数运行时存在函数结束自动注销。上例中的 x、y、temp 三个变量在外部完全不可见。如果希望多个函数共享数据就要把变量定义到全局区也就是文件头部、函数外面的位置。调用自定义函数只需要保证函数名正确、参数个数一致、参数类型一致。ft(4, 5) 会按顺序把 4 传给 x、5 传给 y计算后返回 22。在写自定义指标时建议把均线差值、突破判断、资金管理这类可复用的逻辑都封装成函数后续在多个 EA 之间迁移会轻松很多。5. 避坑排查新手最容易翻车的 5 个典型问题把基本语法学会之后真正耗时的是排错。MQL4 的编译器只检查语法错误不检查逻辑错误。以下 5 个坑是入门教程里最容易遇到的“现象、原因、解决”三连。5.1 数组越界数据错乱还是程序崩溃现象指标加载时日志提示数组越界或者指标线上某几根 K 线位置突然出现巨大偏移看起来像线断了一截。原因数组长度不够。索引从 0 开始长度为 N 的数组最大合法下标是 N-1。写循环时条件如果是 i limit而 limit 恰好等于数组长度最后一次循环就访问了一个不存在的元素。指标缓冲区这类由系统托管的数组越界访问不一定立刻报错而是可能读到内存里其它位置的值造成数据错乱。解决循环条件统一写成 i 个数不要用 。动态数组使用前用 ArrayResize 分配空间。自定义数组的大小不要卡着 K 线数量写多留几十个元素作为余量。排错时先看日志里数组越界提示来自哪个函数再查那个函数里对数组长度的控制。5.2 K 线编号搞反0 是右边最新一根不是最老一根现象指标结果和手工对照完全对不上手算这里应该已经突破程序却没有任何动作。原因初学者容易把编号 0 当成图最左边的第一根 K 线也就是最老的数据。实际上 MT4 预定义数组的编号是从右往左数的Open[0] 是当前最新一根 K 线的开盘价Open[1] 才是它左边的那一根。解决把图表坐标的“右侧是现在左侧是历史”这个方向记死在脑子里。取当前 K 线的上一根下标是 1再上一根是 2。跨周期函数 iClose(EURUSD, PERIOD_H4, 2) 同样是这个规则2 代表当前这根 K 线左侧第二根而不是从最早数过来第二根。5.3 SetIndexBuffer 没绑好指标线一直不显示现象指标编译通过加载到图表上却没有任何线条也没有报错。原因定义了一个 double 数组也在 start() 里给它赋了值但 init() 里没有调用 SetIndexBuffer 把这个数组和指标线绑定。系统不知道这个数组的数据应该画在哪条线上。另一种情况是绑定了但绑定顺序和声明不一致比如声明了 2 条指标线却在 init() 里先绑定第二条、后绑定第一条。解决检查文件头部的 #property indicator_buffers 数量与 init() 里 SetIndexBuffer 的调用次数和顺序是否一致。检查 #property indicator_color1、indicator_color2 与线条编号是否对应。加载后如果还是不显示把指标拖到另一个周期再拖回来强制刷新一次。5.4 double 和 int 混用精度丢失是最安静的坑现象价格和均线差偶尔差 0.0001或者计算出的结果让人摸不着头脑比如 5 除 2 得到 2 而不是 2.5。原因double 与 int 混合运算时MQL4 会做隐式类型转换但把小数赋给 int 变量时小数部分直接截断不是四舍五入。更隐蔽的是两个 int 相除结果仍然是 int5/2 返回 2。交易计算里这么一截止损位就可能偏移几个点。解决涉及价格的中间量一律用 double。比较或赋值前用 NormalizeDouble(value, Digits) 统一精度。int 类型只用于计数、周期参数、循环变量不要参与价格计算。尤其是止损止盈价位一旦被 int 截断实盘轻仓可能看不出问题重仓时差别就大了。5.5 参数改了没反应先查 IndicatorCounted 的逻辑现象修改 extern 变量后指标在图表上的数值没有变化看起来像参数修改没有生效。原因start() 里常见这样一行int limit Bars - IndicatorCounted();这个写法的本意是跳过已经计算过的 K 线只算新增的 K 线提升性能。但修改 extern 参数后“已计算”的标记不会自动失效系统认为这些 K 线已经算过了直接跳过于是旧值继续保留。解决参数修改后重新编译一次或者切换时间周期强制刷新。开发调试阶段建议先全量计算也就是不依赖 IndicatorCounted()每次 start() 都从 0 开始遍历全部 K 线。逻辑稳定后再改回增量计算否则很容易出现“改了参数以为生效实际用的是老数据”的乌龙。6. 落地验证定时邮件行情通知的完整实现与调参这一段的代码是整套教程里最实用的一段。思路是把 MT4 当作邮件客户端把行情变化通过 E-mail 发到自己的邮箱再利用邮箱的短信提醒功能推送到手机相当于让手机变成一台简易行情接收机。老教程里的这个思路到现在依然可用而且代码足够短适合验证你对变量、函数、判断的理解。#property indicator_chart_window extern int 间隔分钟 10; // 外部可调参数默认每 10 分钟一次 datetime NextTime 0; // 记录下次发送时间 int start() { if (TimeLocal() NextTime) { SendMail(Symbol() DoubleToStr(Close[0], Digits), ); NextTime TimeLocal() 间隔分钟 * 60; } return (0); }代码逻辑只有一层判断加两条语句。TimeLocal() 返回从 1970 年 1 月 1 日零时起算的秒数。NextTime 一开始是 0TimeLocal() 必然大于 0所以指标加载后会立即发送一次邮件。发送完成后NextTime 被设置为当前时间加上间隔分钟乘以 60也就是下一次触发的时间点。之后只有当电脑时间超过 NextTime才会再次发送。SendMail 的第一个参数是邮件正文。Symbol() 返回当前商品名称Close[0] 是最新收盘价DoubleToStr(Close[0], Digits) 按当前货币对的小数位格式化价格。现在手机邮箱基本都支持新邮件推送这封邮件到达后报价就会直接出现在锁屏通知里。加载验证步骤把代码保存为 .mq4 文件放入 MQL4 目录的 Indicators 文件夹在 MT4 导航窗口右键编译。确认编译日志无报错后把指标拖到图表在参数框里应能看到“间隔分钟”且默认值为 10。然后在工具、选项、电子邮件里配置发件 SMTP 和收件地址先点“测试”按钮。第一次测试常常不成功日志页会显示错误重启 MT4 后再测试一般就正常了邮件发送成功后日志会显示发送成功信息。后续想扩展可以尝试三件事。一是把固定间隔改成突破触发在 if 条件里加上均线交叉或者价位穿越判断满足条件才发邮件。二是发送内容里加上 Ask 和 Bid让手机通知直接显示可执行的买卖报价。三是把参数“间隔分钟”的范围调大通常设置为 5 到 30 分钟之间设置太短会频繁收到邮件反而失去提醒作用。这套入门材料最值得动手的地方就是从这个短小的定时通知开始先跑通语法再逐步把交易判断加进去。我自己以前写指标习惯先把整体框架搭好再验证结果第一次运行一堆小问题来回改。从那以后不论写什么指标我都强制自己在最小数据集上先跑一遍确认语法、确认输出格式、确认参数可调再逐步叠加逻辑。这个方法希望能在你学 MQL4 的路上少走些弯路也祝你写出的第一个指标能顺利画出第一根线。本文还有配套的精品资源点击获取