MT4编程入门:MACD示例EA代码逐段解析
在MT4安装完成后,系统通常会提供几个示例程序,这些程序对于初学者学习EA开发非常有帮助。下面我们就来深入解读MACD Sample这个示例EA,看看它是如何被构建的。
首先是注释部分。在MQL语言中,所有以双斜杠//开头的内容都是注释行,系统不会执行这些内容,它们仅用于解释代码的含义。在代码中添加详细注释有助于我们清晰理解代码逻辑,这是一个很好的编程习惯,我们鼓励大家多写注释。很多人觉得注释无关紧要,写代码时常常忽略,但即使是自己编写的程序,如果不加注释,过几天再回头看可能就忘记原本意图了,还得逐行分析代码,所以注释的作用不容小觑。
接下来是变量定义部分。我们发现有些变量前加了extern关键字,这意味着这些变量会被用作EA的运行参数。举个例子,如果我打算编写一个EA,并在使用过程中想调整止盈和止损值,如果程序里固定写死了这些数值,日后修改就必须改动代码,既麻烦又不灵活。但若把这些信息设为EA参数,就能在EA运行过程中随时调整了。
extern double TakeProfit = 50;
extern double Lots = 0.1;
extern double TrailingStop = 30;
extern double MACDOpenLevel=3;
extern double MACDCloseLevel=2;
extern double MATrendPeriod=26;
上面定义了6个变量,它们都作为参数使用。在设定变量名称时,尽量使用用户容易理解的词汇,因为在EA的参数设置界面,这些变量名就是参数名称。值得注意的是,EA中的变量名称也可以使用中文。
int start()
start()函数是EA运行的核心。MQL语言规定了一些默认函数,其中EA第一次运行时也会调用init()函数,在这个函数中我们可以放入一些需要初始化的信息。而start()函数承载着EA的核心代码,每次当一个新的TICK(也就是新报价)到来时,系统会自动调用start()函数。deinit()函数则在EA关闭时被调用,用于放置程序停止后需要执行的“善后”代码。start()函数是必须存在的,而其他两个函数如果不需要则可以省略。
if(Bars<100)
{
Print("bars less than 100");
return(0);
}
以上代码的含义是:如果当前图表中的K线数量少于100根,则在日志信息中输出提示内容,并结束start()函数的执行。return表示返回,如果在程序运行中判断出存在错误,导致后续代码无法继续执行,我们就可以调用return()函数来退出start()函数。
if(TakeProfit<10)
{
Print("TakeProfit less than 10");
return(0); // check TakeProfit
}
这段代码的意思是,如果参数中的TakeProfit变量小于10,也会输出提示信息并终止执行。TakeProfit从字面上看是止盈的意思,有些平台会限制下单时的止盈点数不得低于某个数值,如果小于该数值则在下单时可能会报错。为了避免这种错误,我们会限制参数中止盈的设定范围。
其实这里也可以调用MarketInfo()函数来获取当前平台允许的止盈止损最小值,从而根据平台的不同自动计算最小止盈点数。详细说明可以参考文档中对MarketInfo()函数的描述。
MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
MaPrevious=iMA(NULL,0,MATrendPeCWG Marketsriod,0,MODE_EMA,PRICE_CLOSE,1);
上述语句是调用MQL内置指标函数的示例。在MQL语言中,对于常见的指标如均线、MACD、KD等,MQL已经提供了现成的函数,我们只需直接调用即可获取指标数值(参见文档中技术指标部分)。
就上述代码而言,MacdCurrent的值是参数为12、26、9的MACD主线在当前K线上的数值,MacdPrevious则是上一根K线的数值;SignalCurrent和SignalPrevious则分别代表同样参数下信号线的当前值和前一根值。
后面两个语句是调用均线指标函数,这里的均线周期参数使用了EA的参数变量MATrendPeriod。这是一种良好的编写习惯,把指标参数放到EA参数里,就可以在运行过程中灵活调整,方便我们修改策略。MaCurrent和MaPrevious分别得到26均线在当前K线和前一根K线上的值。
total=OrdersTotal();
if(total<1)
上面的代码用于判断当前是否有未平仓的订单。它调用了OrdersTotal()函数,该函数能够计算当前账户中尚未平仓的订单和挂单的总数。如果这个数字小于1,说明当前没有任何订单。这种判断方式是一种简单的检查方法,如果这个EA在运行过程中用户手工也进行了下单,那么EA之后可能不会自动下单。若要更精确地计算该EA所下的订单数量,还需要借助其他方法,这些技巧会在后续文章中介绍。
if(AccountFreeMargin()<(1000*Lots))
{
Print("We have no money. Free Margin = ", AccountFreeMargin());
return(0);
}
这段代码是检查当前的可用保证金是否低于1000。如果资金太少可能不够用,因此会输出当前剩余的保证金金额并退出执行。
if(MacdCurrent<0 && MacdCurrentSignalCurrent && MacdPrevious
{
ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,"macd sample",16384,0,Green);
if(ticket0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) Print("BUY order opened : ",OrderOpenPrice());
}
else Print("Error opening BUY order : ",GetLastError());
return(0);
}
上面这段是开立多单的部分。开仓条件如下:当前MACD主线位于0轴下方,MACD出现“金叉”信号,并且MACD主线不在0轴附近(这个范围由EA参数设定),同时当前均线呈现上升趋势。
这里最精彩的地方在于如何判断MACD“金叉”,以及如何确认MACD值不在0轴附近和均线是上升还是下降。
“金叉”是EA中常用的判断条件,这里我们通过比较数值大小就能轻松实现。首先获取MACD两根线的当前值和上一根K线的MACD值,如果上一根K线的MACD主线大于信号线,而当前MACD主线小于信号线,那么这两根线就发生了一次“交叉”,因此我们可以认为MACD形成了“金叉”。从这里可以看出,用计算机解决我们人类认知的问题,通常依赖于具体数值的计算,因此计算机比较“刻板”,如果两根线纠缠在一起,用程序判断就不太准确,这也是目前计算机程序的一个局限。
对于0轴附近的判断,这里使用了一点简单的数学知识,不必担心。将MACD值取绝对值,然后判断是否大于指定值。由于MACD可能为负值,取绝对值后直接判断是否大于设定值即可,这样简化了判断的条件。
均线上升或下降的判断与“金叉”类似,获取当前均线值和前一根K线的均线值,如果前一根均线值小于当前值,那么说明均线在上升。
if(MacdCurrent0&&MacdCurrent
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point,"macd sample",16384,0,Red);
if(ticket0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) Print("SELL order opened : ",OrderOpenPrice());
}
else
Print("Error opening SELL order : ",GetLastError());
return(0);
这段代码是空单的进场条件,正好与多单相反。需要说明的是,这两个开单代码中都存在下单失败的可能性,因为当OrderSend()函数执行后会返回一个大于0的整数订单号,利用这一点我们就能方便地判断下单是否成功。
下面的代码是平仓和移动止损部分,这段代码相对难懂,但非常重要,因为在编写EA时这些操作经常碰到。让我们一步步拆解,理解其含义。
for(cnt=0; cnt
对于当前存在的订单,我们要判断是否满足平仓条件,所以第一步是遍历所有未平仓订单,逐一检查它们是否符合平仓条件。
此代码使用循环语句从第一单开始逐单循环。值得注意的是,所有订单按照下单先后顺序存放,第一张单子的编号是0而不是1,这是编程中通常采用的方式。我们在编写程序时务必记住,循环下标从0开始。
OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
上面是选择订单操作。当循环到某个订单时,必须调用OrderSelect()函数来锁定该订单,这样后续操作才能正常进行。这里最容易出错的地方在于函数的第二个参数。查文档会发现它有两个选项:SELECT_BY_POS和SELECT_BY_TICKET。第一种方式是根据订单位置进行选择,本示例中采用这种方式;第二种方式是根据订单号来选择,因为我们并不知道所有订单的订单号,所以只能使用第一种方式。如前所述,订单按照下单顺序存放,所以第一个单子是0,第二个是1,最后一个单子是总单数减一。
if(OrderType()<=OP_SELL && // check for opened position
OrderSymbol()==Symbol()) // check for symbol
上面的代码段用了一个小技巧。它首先调用OrderType()函数获取所选订单是持有多单还是空单,但查一下这个函数的定义会发现,多单的值是0,空单的值是1,那么如果OrderType()函数小于等于空单的值,就相当于在判断当前订单是否是非挂单类型。
第二个条件是判断当前订单的货币对是否与当前图表相同,这个判断是为了防止在处理订单过程中误操作了其他不是由EA所下的订单。
if(OrderType()==OP_BUY) // long position is opened
{
// should it be closed?
if(MacdCurrent0 && MacdCurrent
{
OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
return(0); // exit
}
这是多单的平仓部分,实际上就是移除了均线条件的空单进场信号。平仓操作中要特别注意,在平仓完成后必须终止遍历订单的循环,因为平仓会打乱所有订单的顺序,可能导致误操作其他订单。
举个例子,假设当前有三个未平仓订单,按顺序编号为0、1、2,如果第二个订单平仓后,第三个订单的序号会提前,这样在下一次循环执行到OrderSelect()时会因为找不到该编号而引发错误。
if(TrailingStop0)
{
if(Bid-OrderOpenPrice()Point*TrailingStop)
{
if(OrderStopLoss()
{
OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
returCWG Marketsn(0);
}
}
}
这段代码是多单的移动止损部分。当参数TrailingStop大于0时,EA会开启移动止损功能(默认值是30,意味着默认情况下启用移动止损)。以默认参数30点为例,当订单盈利超过30点,且订单当前止损价与当前价格差超过30点时,将订单止损修改为当前价格以下30点的位置。
在上述程序中,我们多次看到作者使用Point变量来计算点数。这个变量是MT4运行环境自动设定的,属于MQL语言中的预定义变量(关于预定义变量可参考相关文档)。Point表示当前货币对价格的最小变动单位,比如欧元对美元的报价形式为X.XXXX,其Point值为0.0001。当我们希望设定价格波动30点时,只需用30乘以Point来计算该货币对的30点实际数值。不过,Point常量在很多平台中可能无法精确发挥作用,这是因为许多平台已改成小数点后5位,导致Point值变为0.00001,这样直接乘以点数得到的结果是实际点位的十分之一,可能会引发EA运行中的致命逻辑错误。因此,在5位小数平台上,需要对相关点数值乘以10进行修正。关于Point的更完善处理方法,将在后续文章中继续讨论。
以上就是对MT4示例EA的全面解读。这个程序虽然相对复杂,但确实是一个优秀的范例,其中涉及了编写EA过程中经常使用的多种功能。对于刚接触EA编程的人来说,学习这个例子会很有帮助。我们也可以修改这个程序的开仓和平仓部分,直接将其改造为自己的交易逻辑。
