☰
MT4 MQL4编程入门:变量函数数组与指标实战避坑指南
2026/10/23 17:24:37 网站建设 项目流程

简介:本资源是一份面向外汇交易者与编程初学者的MQL4编程入门教程,以MT4平台为载体,系统讲解从零开始编写交易策略、自定义指标与EA自动交易程序所需的核心概念。内容涵盖自定义变量、extern附加变量、自定义函数及数组等基础语法,并结合实例展示如何定义变量类型、编写带返回值的函数以及操作数组元素,为后续编写完整自动交易脚本打下扎实基础。包内共1个doc文档,整体约197KB,适合初次接触MQL4、希望理解MT4编程逻辑并尝试构建简单交易工具的读者。已有389人学习使用,文档结构清晰,按主题分节讲解并配有代码示例,便于按需查阅与上手练习。通过学习本教程,读者可掌握MQL4的基本语法规则与程序运行思路,逐步具备独立编写简单指标和EA的能力,从而提升交易自动化水平。

1. MT4的MQL4编程入门:这套教程能让你少走半个月弯路

很多人第一次打开MT4自带的代码编辑器,面对满屏报错和陌生语法,第一反应是找现成EA复制粘贴,结果一编译全是undeclared identifier。我第一次学MQL4也这么翻过车。后来拿到一份讲MT4的MQL4编程入门的连载教程,才把自定义变量、函数、数组、行情取值、判断循环、指标画线这些概念串起来。

这份资料的难得之处在于每个知识点都配一段能跑的小例子,适合完全没写过代码的新手,也适合写过其他语言但搞不清IndicatorCounted()和数组索引顺序的老手。接下来我按“先懂原理、再看代码、最后避坑”的顺序,把这份资源拆成一份可复现的实战笔记,保证每一段代码你都能直接粘到编辑器里编译。

2. 变量、函数与数组:四类类型、三种结构和作用域边界

如果只记住一句话,那就是MQL4里所有数据都要“先创建,后使用”。这句话看着简单,但很多翻车都从这里开始。我见过有人把变量当数学符号直接写,结果编译时找不到定义;也见过把extern变量和全局变量混用,改参数改到怀疑人生。这一章先把最基础的三个构造块讲清楚:变量怎么选型、函数怎么定义、数组怎么存取。

2.1 变量类型与extern附加变量:int、double、string、bool怎么选

MQL4的基本类型一共四种,选型原则其实很直白:

  • int:整数,用来计数、标记状态。比如mark=1表示已报警,这类“状态字典”用int最合适。
  • double:双精度小数,所有价格、指标值、点值都用它。注意double计算有浮点精度问题,判断相等时不要直接用==。
  • string:字符串,拼消息、拼接文件名用。
  • bool:逻辑型,只存true或false,也可以写1或0。适合开关类变量。

定义语句的格式是“类型 变量名;”,创建时可以赋初值,也可以不赋初值。变量名可以用中文,这点对新手很友好:bool 是否报警 = true;比bool alarmEnabled;读起来直观得多。

int mark; // 声明一个整型变量,未赋值 double 最新价格 = Close[0]; // 声明双精度并赋初值 string 提示语 = "价格到达"; // 字符串变量 bool 允许报警 = true; // 逻辑变量,true/1 为真 extern int 周期 = 9; // 附加变量:外部可改的整型参数

这里有个容易被忽略的点:类型定义词只在“创建”时使用。变量创建之后,后续用到它就只写名字,前面不能再加int、double。比如上面声明了mark,后面只能写mark = 1;,写int mark = 1;就是重复定义,编译必错。

extern前缀是附加变量的核心。加了extern的变量,编译后会出现在MT4图表的“参数”选项卡里,用户在界面上就能改,不需要重新编译。MACD(12,26,9)里的12、26、9,本质就是三个extern整型变量的默认值。所以你做指标时,凡是以后可能要调的周期、倍数、阈值,都应该设计成extern变量,而不是写死在代码里。我一般习惯把所有外部可调参数集中放在代码头部,方便别人只看前面这段就知道这个指标有哪些旋钮。

2.2 自定义函数:函数头、返回值与局部变量的生存周期

函数的作用很简单:把一段重复用到的计算过程封装起来,给它一个名字,以后调用名字就行。MQL4里的自定义函数和数学里的函数本质是一回事。比如数学函数f(x,y)=3x+2y,翻译成代码就是:

int ft(int x, int y) // 函数头:返回值类型 int,参数 x 和 y 都是 int { int temp; // 函数体内定义的局部变量 temp = 3 * x + 2 * y; return(temp); // return 结束函数并返回结果 }

这段代码要注意三点。第一,函数头里的int ft(int x, int y),最前面的int是返回值类型,表示这个函数算完会返回一个整数给调用它的地方;x和y是参数列表,参数之间用逗号分隔,每个参数前都必须带类型定义词。第二,大括号内是函数体,顺序执行;return可以出现在任意位置,只要执行到return,函数就立刻结束,并把括号里的值带回去。第三,函数里声明的temp、x、y都是局部变量,只在本函数内存在,函数一结束它们就自动注销,外部看不见也改不了。

调用方式同样简单:只要函数名正确、参数个数一致、类型匹配,直接用ft(2,1)就能得到结果。比如:

int result = ft(4, 5); // 把4传给x,把5传给y,result 得到 23

如果函数运行完后不需要返回值,比如只是弹个报警、发个邮件,那函数类型写成void,return后面不带值。MQL4里系统默认函数init()、start()都会强制要求返回值,所以必须写return(0),而不是把整个函数声明成void。自定义函数是否返回,取决于调用方后续要不要用这个计算结果。我自己的习惯是:只要输出会被后面用到就返回double或int,纯粹是“执行动作”就返回void,少写return还能少几个绊脚石。

2.3 数组:声明、赋值与“一次只能用一个元素”的规则

数组是一组同类型变量的集合,适合存K线序列、指标序列这类批量数据。声明方式有两种,要么直接指定长度并批量赋初值,要么先声明一个空数组,后面和指标缓冲区绑定后再填充:

int aa[4] = {4, 3, 2, 1}; // 声明长度为4的整型数组,aa[0]=4,aa[3]=1 double buf[]; // 先不指定长度,后面绑定指标缓冲 int cc[8]; // 指定8个元素,不赋初值

数组的编号从0开始,这是新手最容易踩的地方。aa[0]是第一个元素,aa[1]是第二个,不是你想当然的“第一个是aa[1]”。前面这个例子,aa[0]的值为4,aa[1]的值为3,aa[2]的值为2,aa[3]的值为1。

数组赋值有一个强制规则:除了声明时能整体赋初值,其他时候必须一个一个元素赋值。比如想改aa数组第二个元素,只能写aa[1] = 10;,不能写aa = 10;。这个规则放到MQL4指标里就是:每次只能修一根K线对应的缓冲值,但配合循环就能一次填满整条指标线。

double dd[3]; dd[2] = 1; // 只改第三个元素,前两个保持未定义状态

未赋值的数组元素默认是0还是随机值,MQL4规范里不保证,依赖未初始化值就是埋雷。我见过有人声明double bb[5];后直接拿bb[3]去做比较,结果行情一变数值就跳,排查半天才发现是忘了赋初值。所以数组声明后,要么立刻批量赋初值,要么在循环里先全部填一遍,别留空位。

3. 行情数据怎么取:从Open/Ask到iMA/iCustom的调用清单

第二把变量函数数组讲完,接下来是MT4编程最实用的部分:怎么把K线价格、实时报价、均线值取出来。很多教程把这部分跳过去直接讲EA,结果新手连Close[0]都不知道是当前价。这一章把数据取值的几种方式全列出来,并告诉你每个函数参数该怎么填。

3.1 预定义数组与预定义变量:K线数据和实时报价怎么读

MT4已经把当前图表上每根K线的开高低收时间量都存到了预定义数组里,直接按序号访问即可:Open[]、High[]、Low[]、Close[]、Time[]、Volume[]。这些数组的元素都是double类型(Time是datetime),序号从右往左数:右边最新的那根K线编号为0,第二根编号为1,第三根编号为2。

double 最新收盘 = Close[0]; // 当前最新一根K线的收盘价 double 前一根最高 = High[1]; // 右边第二根K线的最高价 double 当前开盘 = Open[0]; // 当前K线开盘价

注意这里的数组用的是方括号,函数调用用的是圆括号,别混。另一个高频预定义变量是Ask和Bid,分别代表当前买入价和卖出价,都是double类型,实时变化。如果你想监控点差,用Ask - Bid直接算就行。

还有三个和品种属性相关的预定义变量,写报警和显示时经常用到:

int 柱数 = Bars; // 当前图表的K线总数 int 小数位 = Digits; // 当前品种价格小数位数,非日元对通常4,日元对通常2 double 点值 = Point; // 最小价格变动,非日元对0.0001,日元对0.01

这里有个容易绕弯的点:Digits和Point含义相反。记住一个能推导另一个:Digits是小数点后几位,Point是那个“1”在哪个位置。EURUSD的Digits是4,Point就是0.0001;USDJPY的Digits是2,Point就是0.01。

3.2 通用指标函数:iOpen、iClose、iMA的七个参数

预定义数组只能取当前图表、当前周期的数据。只要你想跨品种、跨周期,就得用i系列函数。最基础的四个是:

double iOpen(symbol, timeframe, shift); double iClose(symbol, timeframe, shift); double iHigh(symbol, timeframe, shift); double iLow(symbol, timeframe, shift);

它们都有三个参数:品种名、周期、位置。品种名传字符串,想用当前图表就写NULL;周期用常量,当前周期写0或PERIOD_CURRENT,1分钟写PERIOD_M1,5分钟PERIOD_M5,一直到月线PERIOD_MN1;shift就是序号,和Open[]的规则一样,0是最新K线。

以下周期常量是写跨周期取数时的常用对照:

周期常量含义
PERIOD_M11分钟
PERIOD_M55分钟
PERIOD_M1515分钟
PERIOD_M3030分钟
PERIOD_H11小时
PERIOD_H44小时
PERIOD_D1日线
PERIOD_W1周线
PERIOD_MN1月线
double 欧小时最新收盘 = iClose("EURUSD", PERIOD_H1, 0); double 棒日线前一根高 = iHigh("GBPJPY", PERIOD_D1, 1);

用NULL和0可以写出高度通用的代码:iHigh(NULL, 0, 0)表示“当前品种、当前周期、最新K线的最高价”,这个函数放在哪个图表上,取的就是哪个图表的数。写指标时尽量用这种写法,方便直接拖到任何品种上跑。

指标取值里最常用的是移动平均线,iMA有7个参数,顺序和含义一个都不能错:

double 当前MA20 = iMA(NULL, 0, 20, 0, MODE_SMA, PRICE_CLOSE, 0);

参数依次是:品种名、周期、均线周期(20就是20根K线的均值)、均线偏移(一般填0)、平均模式(MODE_SMA简单平均、MODE_EMA指数平均、MODE_SMMA平滑、MODE_LWMA线性加权)、应用价格(PRICE_CLOSE、PRICE_OPEN、PRICE_HIGH、PRICE_LOW、PRICE_MEDIAN、PRICE_TYPICAL、PRICE_WEIGHTED)、位置(shift)。这套参数结构是MT4的通用规范,MACD、RSI、布林带等指标的取值函数参数顺序也类似,只是参数个数和含义不同,学会看编程手册的签名就不怕了。

3.3 跨指标取数:iMAOnArray和iCustom的用法边界

iMAOnArray用来对“自己定义的数组”求移动平均。比如你先用循环把一段自定义计算的结果存进数组,然后再对这个数组做平滑,就要用它。参数的顺序是:数组名、数组参与计算的元素个数、均线周期、偏移、平均模式、位置。

double 数组均值 = iMAOnArray(buf, Bars, 14, 0, MODE_EMA, 0);

注意这里的“总数”参数,一般填Bars或你实际填充的元素个数,如果填得比数组实际长度大,会越界。另外iMAOnArray默认假定上一个值已经按移动平均算好,所以直接传原始数据数组时要小心第一个值的行为,最好提前在循环里给数组初始化。

iCustom用于读取自定义指标的值,参数结构是:品种、周期、指标文件名称、依次排列的自定义指标输入参数、指标线编号、位置。假设你写了一个叫mymacd的指标,带有12、26、9三个extern参数,并且有3条指标线,那么取第3条线最新值的写法是:

double 第三线值 = iCustom(NULL, 0, "mymacd", 12, 26, 9, 2, 0);

最后两个数字里,2指的是指标线编号(第1条是0,第2条是1,第3条是2),0是位置。这个函数的坑在于:参数个数必须和自定义指标的extern参数完全一致,多了或少了编译不报错,但运行取回的值是0或错误值。我排查过好几次,最后都是数参数个数数错了。

4. 判断与循环:把报警逻辑和批量计算写成代码

有了数据,接下来就是“根据条件做事”和“批量处理K线”。这一章从if语句讲起,一路写到while和for循环,最后给一个能直接放到指标里用的价格穿越报警例子。

4.1 if与if-else:条件判断的写法与“一条语句可以不要大括号”的坑

if语句的逻辑很直白:小括号里条件成立,就执行大括号里的语句,否则跳过。MQL4里的条件支持==、!=、>、<、>=、<=,多个条件用&&(并且)或||(或者)连接。

if(High[1] < 1.0000 && High[0] >= 1.0000) { Alert(Symbol(), "价格向上触及 1.0000"); }

这里判断的是“上一根K线最高价低于1.0000,同时当前K线最高价大于等于1.0000”,也就是价格从下方穿越1.0000的瞬间。注意&&两边的小括号可以省略,但建议保留,避免优先级搞错。

如果需要在条件不成立时做另一件事,就用if...else:

if(条件) { // 条件成立时执行 } else { // 条件不成立时执行 }

MQL4允许“只有一条语句时省略大括号”,例如if(a==1) b=c+1;。这个语法能让代码简短,但新手很容易把后面的语句误当成if的一部分。比如:

if(a == 1) b = c + 1; d = 2; // 这行不在if里,无论条件如何都会执行

只有当缩进和括号都在时,d=2才受if控制。我的习惯是哪怕只有一行也加大括号,省得后续加第二行时光看缩进不报错,但逻辑已经变了。

还有两个高频翻车点:条件判断用==,赋值用=,一个指标里如果写成if(a = 1),a会被赋值为1,然后条件恒为真;语句结束符“;”只加在语句末尾,条件括号、大括号后面都不加。

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++; // 计数器自增,让i不断变大 }

这段代码计算两条指数均线的差,把结果存到buffer数组中。循环体内第一句是赋值,第二句i++;让i每次加1。如果没有第二句,i永远等于0,循环条件永远成立,程序就死循环卡死了。while循环的缺点是把初始、判断、步进三件事分散在三个地方,容易漏写步进。

for循环把三件事写到同一行,可读性更好:

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++是步进操作,每轮循环体结束后执行。执行顺序是“初始化→判断→循环体→步进→再判断”,和while完全等价。如果循环体只有一条语句,大括号可以省略,但同样建议保留。

4.3 实战示例:价格穿越1.0000的去重报警

下面这是一个能直接放进自定义指标start()函数里的报警片段。它的需求是:价格向上穿越1.0000时报警一次,向下穿越1.0000时报警一次,之后不再重复报警。

int mark = 0; // 报警标记,0=未报警,1=已上穿报警,2=已下穿报警 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就为假,以后不会再触发上穿报警,但下穿还没报过,仍然可以触发下穿。反过来也一样。这种用int变量做状态开关的方法在报警类指标里很常用,比用bool对只能记一个状态要灵活。

如果还想更高级,可以在报警里加上SendMail,把行情发送到邮箱,配合邮件短信提醒,就能实现行情异常时手机收通知的效果。这块我会在第6章讲指标模板时再补一段。

5. 常见问题与避坑:变量作用域、数组越界与指标画线失败

基础语法看着简单,一编译就错才是常态。这一章把我自己踩过、也帮别人排查过的高频问题按“现象→原因→解决”列出来。每条都是真实翻车记录,照着检查能省不少时间。

5.1 现象:编译报错 undeclared identifier

现象:代码里写了mark = 1;,编译提示'mark' - undeclared identifier。

原因:mark变量没有在当前位置之前定义,或者定义在别的函数里、出了函数就失效。常见于在start()函数里直接用变量,却把定义写到了另一个函数内部。

解决:把变量定义提到函数外面,作为全局变量;或者在使用前先声明局部变量。MQL4里全局变量位置一般在代码头部,extern变量也属于全局。记住“先创建后使用”,编译器按从上到下的顺序找定义。

5.2 现象:指标让终端卡死或退出

现象:指标加载后,MT4图表卡成白屏,日志反复刷新,最后只能强制杀掉。

原因:循环边界条件写错。典型错误是while(i < 1000)里忘了写i++;,或者数组越界:循环写了i < 10000,但数组只声明了1000个元素,访问buffer[9999]就越界访问了。MQL4对越界访问没有明显的异常提示,结果往往是读到一个垃圾值,或者直接把终端拖垮。

解决:检查循环计数器是否递增;检查数组声明长度是否覆盖循环最大下标;在start()里用Bars-IndicatorCounted()作为循环上限,而不是拍脑袋写1000。这个表达式表示“还有多少根K线没计算”,既能覆盖全图,又不会越界。

5.3 现象:SendMail第一次总是发不出去

现象:指标里写了SendMail,日志显示邮件已发送,但邮箱收不到;或者第一次测试报错,重启后又能收到。

原因:MT4的邮件功能依赖你在“工具→选项→电子邮件”里填好SMTP服务器、登录账号密码,以及收件人和发件人地址。第一次测试时SMTP握手可能还没建立成功,日志页会出现错误信息,重启客户端后才会正常。

解决:先在“选项”里点测试,第一次失败不要慌,重启MT4再测,看到日志出现“Mail: Test message has been sent”说明配置成功。之后指标里的SendMail才能正常发信。另外SendMail的标题和正文不能为空,至少给一个空字符串占位。

5.4 现象:指标线画不出来或颜色不对

现象:代码编译通过,但图表上没有出现指标线;或者出现一条线但颜色和设置不一致。

原因:最常见的遗漏是忘了在init()里调用SetIndexBuffer把数组绑定到指标线。数组只是普通变量集合,系统不知道它要画线。另一个原因是#property indicator_buffers声明的缓冲区数量小于实际绑定的数量,或者SetIndexBuffer和SetIndexStyle顺序颠倒,导致线条样式没有生效。

解决:凡是画线的自定义指标,必须在init()里为每个缓冲区做两件事:SetIndexBuffer(0, buf)绑定数组,SetIndexStyle(0, DRAW_LINE)指定线型。颜色用#property indicator_color1定义,线宽用indicator_width1。如果画箭头,还要用DRAW_ARROW并在SetIndexArrow里指定箭头代码。

5.5 现象:读到的历史和图表对不上

现象:在EURUSD 1小时图里,用Open[0]取到的是当前小时的开盘价,但用iClose("EURUSD", PERIOD_H1, 0)取到的却感觉不一样。

原因:预定义数组Open[]、Close[]等只代表“当前图表”的数据,而iClose可以指定周期和品种。如果两个函数的周期不同,数值当然不同。另一种可能是图表没刷新,历史K线数据在你加载指标那一刻已经存在,但最新K线还在变动,等收盘后数值才稳定。

解决:先确认两个取数函数使用的周期和品种一致。想要跨周期比对,统一用i系列函数,不要用预定义数组。如果发现最新值不稳定,可以在状态栏对应周期图表里按Home键手动刷新,或者把指标重新拖一次。

6. 把入门变成成品:用MACD白线模板做一个可调参数指标

到这一步,变量、数组、循环、画线的知识点都齐了,是时候拼一个完整指标。这里我拿MACD最核心的白线来做样板——去掉信号线和红绿柱,只保留两条均线的差值曲线。虽然功能少,但它包含了一个标准指标的全部骨架,后面无论画什么线都是在这个结构上做增补。

#property indicator_separate_window #property indicator_color1 White #property indicator_level1 0 extern int FMA = 12; extern int SMA = 26; double buf[]; int init() { SetIndexBuffer(0, buf); SetIndexStyle(0, DRAW_LINE); return(0); } int start() { int limit = Bars - IndicatorCounted(); for(int i = 0; i < limit; i++) { buf[i] = iMA(NULL,0,FMA,0,MODE_EMA,PRICE_CLOSE,i) - iMA(NULL,0,SMA,0,MODE_EMA,PRICE_CLOSE,i); } return(0); }

这段代码有几个值得背下来的细节。#property indicator_separate_window把指标放进副图,如果改成indicator_chart_window就变成主图叠加;indicator_color1 White指定第一条线颜色;indicator_level1 0在副图0轴画一条水平虚线,方便看正负。init()里的SetIndexBuffer(0, buf)将数组和第一条指标线绑定,DRAW_LINE表示画连续曲线。start()里的limit = Bars - IndicatorCounted()是性能优化的核心:IndicatorCounted()返回已经计算过的K线数,相减后只计算新出现的K线,不会每次把整张图几千根K线全重算一遍。

接下来想升级,可以在这个模板上加东西。比如把FMA和SMA改成快慢参数,用户就能在指标属性里调;再加一条信号线,用SetIndexBuffer(1, signalBuf)和SetIndexStyle(1, DRAW_LINE)就能画第二条;想加报警,在start()里判断“白线上穿0轴”这个条件,然后调Alert或SendMail。很多商业指标看起来复杂,扒开看其实都是这个骨架的重复扩展。

我从第一次跑通这个模板之后,养成了一个习惯:每个新指标都先复制这套最小结构,确认编译通过、指标出现一条线,再一步步加颜色、加缓冲、加逻辑。遇到问题也能确定问题出在新增部分,而不是基础结构。这个方法让我后面写EA省了很多排查时间。这份入门资料的完整版本我放在下载区了,里面除了我拆解的这些,还有更多报警和画线的实例,可以直接对照着编译。希望这份拆解也能帮到你,把MT4的MQL4编程入门这条路走得顺一点。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询