|
一、MACD Sample 解读
//+------------------------------------------------------------------+
//| MACD Sample.mq4 |
//| Copyright 2005-2014, MetaQuotes Software Corp. |
//| http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "2005-2014, MetaQuotes Software Corp."
#property link "http://www.mql4.com"
input double TakeProfit =50; // 盈利目标点数
input double Lots =0.1; // 每单入场的手数
input double TrailingStop =30; // 追踪止损的点数
input double MACDOpenLevel =3; // MACD开仓的参考位置
input double MACDCloseLevel =2; // MACD平仓的参考位置
input int MATrendPeriod =26; // 交易条件中使用的MA均线的周期数
程序最上面input开始的这些数据都是外部变量,也就是在使用者调用的时候可以修改的部分。
这个EA是个常见的技术指标条件入场、条件出场、同时又进行移动止损功能的设置,很适合初学者研究。
先总结这个程序的交易策略,以方便大家对号入座,尽快理解。
多头开仓条件:MACD位于0轴下方 并且 小于指定的参数MACDOpenLevel*Point 同时 MACD信号线上穿基准线(金叉)
并且 MA趋势向上。
多头平仓条件:MACD位于0轴上方 并且 大于指定的参数MACDCloseLevel*Point 同时 MACD信号线下穿基准线(死叉)。
空头开仓条件:MACD位于0轴上方 并且 大于指定的参数MACDOpenLevel*Point 同时 MACD信号线下穿基准线(死叉)
并且 MA趋势向下。
空头平仓条件:MACD位于0轴下方 并且 大于指定的参数MACDCloseLevel*Point 同时 MACD信号线上穿基准线(金叉)。
有了以上的初步了解,下面开始进行EA程序基本结构的分析:
1、OnTick()函数是最重要的执行部分,每来一个价格,此函数都自动执行一次,所以主要的逻辑结构都在这个函数里面。
2、程序的基本流程都是按照以下步骤进行,我们先牢牢记住这个结构,然后再对号入座去理解程序。
先判断当前自身的仓位状态,因为OnTick函数式循环运行的,所以中间的每个步骤都会使用OnTick函数,因此,当函数开始的时候我们首先要通过MT4的仓位操作函数获得当前的仓位状态,并进一步根据状态进行不同分支的计算。
void OnTick(void)
{
double MacdCurrent,MacdPrevious;
double SignalCurrent,SignalPrevious;
double MaCurrent,MaPrevious;
int cnt,ticket,total;
//---
// initial data checks
// it is important to make sure that the expert works with a normal
// chart and the user did not make any mistakes setting external
// variables (Lots, StopLoss, TakeProfit,
// TrailingStop) in our case, we check TakeProfit
// on a chart of less than 100 bars
//---
if(Bars<100)
{
Print("bars less than 100");
return;
}
if(TakeProfit<10)
{
Print("TakeProfit less than 10");
return;
}
程序开始的以下两个部分不重要 简单说一下:
if(Bars<100)
{
Print("bars less than 100");
return;
}
上面代码的意思是如果当前图表中的k线少于100根将会在日志信息里输出提示信息并且结束OnTick()函数的执行。return的意思是返回,如果在程序中判断出有错误,下面的代码就不再继续执行了,我们调用return函数让它退出OnTick()函数的执行。 不过这种情况一般不会出现,所以我们自己写程序的时候可以不写这部分。
if(TakeProfit<10)
{
Print("TakeProfit less than 10");
return;
}
上面的代码意思是如果参数里的TakeProfit移动止损点数的设定如果小于10点,也提示一条信息并结束执行。TakeProfit从字面的意思中我们可以知道是止盈的意思,有些平台会限制下单时的止盈点数不得小于某个点,如果小于某值会在下单时报错,为了避免这种错误我们会限制参数中止盈的设定。
其实这里可以调用MarketInfo()函数得到我们当前平台中允许的止盈止损最小值从而根据平台的不同自动计算出最小的止盈点数,详细情况请参阅文档MarketInfo()函数的描述。加入这段代码是为了防止乱设数值,引起后面计算的错误。这部分,如果程序只是我们自己使用,估计不会犯这种低级错误,所以写程序的时候也可以忽略不写。
下面这段:
//--- to simplify the coding and speed up access data are put into internal variables
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,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
这部分是变量赋值部分,等于提前计算出为后面用到的当前MACD数值以及MA数值,这样提前写出来在后面直接使用赋值后的变量就很清楚了,这是一个很好的编程习惯。
以上语句调用了MQL的一些内置指标函数,在MQL语言中,对于常用的指标如MA,MACD,KD等MQL4已经提供给我们现成的函数,我们只要调用这些内置的函数即可得到指标的值。上面的代码,MacdCurrent的值是参数为12,26,9的MACD主线当前的值,MacdPrevious则是MacdCurrent前一根K线MACD主线的值,SignalCurrent和SignalPrevious则是相同参数MACD信号线的当前值和前一根值。 后两个是调用均线指标函数,这里的均线周期参数则是使用了EA的参数变量MATrendPeriod,这样写是个好习惯,把调用指标的参数放到EA参数里,这样可以随时在运行中调整这些参数方便我们改变策略。MaCurrent和MaPrevious是得到26期均线的当前K线值和前一根的值。
再下面开始最主要的程序逻辑部分,首先遇到的就是我们上面说过的通过仓位函数获得当前状态的部分。
total=OrdersTotal();
if(total<1)
{
//--- no opened orders identified
上面的代码判断我们当前是否有单子在做,它调用了OrdersTotal()函数,该函数可以计算当前账户中一共还没有平仓的单子和挂单的个数,如果它小于1,则说明是空仓状态,那么接下来就进行多头和空头的入场条件判断,如果满足条件则进行入场。
if(AccountFreeMargin()<(1000*Lots))
{
Print("We have no money. Free Margin = ",AccountFreeMargin());
return;
}
上面的代码是计算当前的可用预付款是否足够下单,如果不够就输出当前可用预付款还剩多少,然后直接退出,不进行后续入场条件判断。
//--- check for long position (BUY) possibility
if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious)
{
ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,"macd sample",16384,0,Green);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("BUY order opened : ",OrderOpenPrice());
}
else
Print("Error opening BUY order : ",GetLastError());
return;
}
上面这段就是多单开仓部分了,条件是这样:如果当前MACD主线在0轴以下,MACD“金叉”,MACD的主线不在0轴附近(这块是EA的参数来指定0轴附近多少点)并且还要当前的均线是上升的。 这里最精彩的部分在于如何判断MACD“金叉”,如何判断MACD值不在0轴附近和均线目前是上升的还是下降的。 “金叉”的判断是EA里用的比较多的,这里我们用了判断大小的方法就能很容易的计算它,首先得到MACD两根线当前的值和上一根K线的MACD值,如果上一根K线的MACD主线大于信号线并且当前的MACD主线小于信号线那么就相当于这两根线做了一个“交叉”,因此我们可以认为MACD“金叉”了。从这里我们也能看出来用计算机的方法来解决我们人类所认知的问题靠的都是这种具体数值的计算,所以计算机还是比较“死板”的,如果两根线“扭”在了一起那么用计算机程序很难判断出来,这些就是目前计算机程序的缺点。 0轴附近这种判断方法这里利用了一点数学方面的知识,不过不用担心都是很简单的算法。把MACD值做绝对值运算然后判断是否大于指定的值,因为MACD会是负值做绝对值运算后直接判断是否大于设定的值就行了,这块相当于是简化了判断语句的条件。 均线的上升和下降判断和“金叉”的算法差不多,得到当前均线值和前一根线的均线值,如果前一根均线值小于当前值那么就说明均线是上升的。
记得一定要判断进场是否成功,因为很多服务器由于滑点或者服务器价格变动而不能成交,所以,要在判断进场不成功后作出提示。ticket就是定单入场是否成功的标记。if(ticket>0) 大于0则说明进场成功。如果进场不成功,则输出不成功的系统原因。
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("BUY order opened : ",OrderOpenPrice());
}
else
Print("Error opening BUY order : ",GetLastError());
return;
}
return; 这里为什么使用了返回呢。因为一种情况是进场成功,那么直接返回等待下一个价格到来的时候再执行OnTick函数,另一种情况是进场不成功,则返回也是等待下一个价格到来的时候在此执行入场操作。
下面是空单进场的判断了,大家自己对照观看即可:
//--- check for short position (SELL) possibility
if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious)
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point,"macd sample",16384,0,Red);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("SELL order opened : ",OrderOpenPrice());
}
else
Print("Error opening SELL order : ",GetLastError());
}
//--- exit from the "no opened orders" block
return;
}
这段代码就是空单的进场条件,和上面多单进场的条件刚好相反。值得说明的是这两个下单代码中会遇到下单失败的情况,因为当用OrderSend()函数下单后会返回一个大于0的整数订单号数值,利用这一点就可以很容易的知道下单是否成功了。
开仓以后接下来的代码是平仓和移动止损部分,这段代码比较难懂,但是却是非常重要的部分,因为在编写EA中这些操作会经常遇到,让我们来一点一点的拆解开来解读它们的含义。
//--- it is important to enter the market correctly, but it is more important to exit it correctly...
for(cnt=0;cnt<total;cnt++)
当前存在的订单中我们要判断是否到达平仓的条件,所以第一步我们首先要对所有在下的单子进行一次遍历,一个一个的去判断它们是否达到平仓条件。 此代码中利用了一个循环语句从第一单开始一单一单的循环,这里值得注意的是所有单子都是按照下单的先后顺序存放的,第一张单子的编号是0而不是1,这是编程语言中普遍采用的方法,我们在编写程序的时候一定要注意它的值要从0开始。
OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
这段代码是选择订单操作,当循环一次订单后,必须调用OrderSelect()函数来锁定这一订单,这样下面的操作才可以正常运行。这里最容易出错的地方是函数的第二个参数。如果查一下文档我们会发现它有两个选项:SELECT_BY_POS和SELECT_BY_TICKET。第一种方式是根据订单的位置进行选定操作,这个例子中就是使用了这种方式,第二种方式是根据订单号来进行选定操作,因为我们并不知道所有单子的订单号是多少,所以我们只能使用第一种方式来选择订单。刚才说过订单是按照下单的先后顺序来存放的,因此如果是第一个单子那么就是0,如果是第二个单子就是1,最后一个单子是总单子数减1。
if(OrderType()<=OP_SELL && // check for opened position
OrderSymbol()==Symbol()) // check for symbol
上面的代码段又运用了一个小技巧,它首先调用了OrderType()函数来得到所选定的订单是多单还是空单,我们查下这个函数的定义可以发现多单的值是0,空单的值是1,那么如果OrderType()函数小于等于空单的值,就相当于是判断当前订单为市价成交单而不是挂单。 第二个条件是判断当前单子的货币对是否和当前图表相同,这个判断是为了防止我们处理订单过程中误操作了其他不是EA所下的单子。
{
//--- long position is opened
if(OrderType()==OP_BUY)
{
//--- should it be closed?
if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
MacdCurrent>(MACDCloseLevel*Point))
{
//--- close order and exit
if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet))
Print("OrderClose error ",GetLastError());
return;
}
多单的平仓部分代码,这里其实就是去掉均线条件的空单下单信号,平仓操作中一定要注意在平仓完成后必须终止这个遍历订单的循环,因为平仓后会打乱所有单子的顺序,造成误操作其他订单。
我们在这里举个例子就能明白为什么要这么做:比如当前有三个单子没有平仓,按照顺序排列序号是0、1、2,如果第二个单子平仓后第三个单子序号就会提前,这样当下一轮循环执行到OrderSelect()函数后会因为没有这个编号而出现错误。
//--- check for trailing stop
if(TrailingStop>0)
{
if(Bid-OrderOpenPrice()>Point*TrailingStop)
{
if(OrderStopLoss()<Bid-Point*TrailingStop)
{
//--- modify order and exit
if(!OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green))
Print("OrderModify error ",GetLastError());
return;
}
}
}
}
这段代码就是多单的移动止损部分。当参数TrailingStop大于0的时候EA就开启了移动止损功能(默认设定是30,也就是说默认情况下是开启移动止损的),我们就用我们这个例子EA的默认参数30点来说明,当单子的盈利大于30点并且单子的止损点和当前价位相差30点以上时,修改订单的止损到当前价格以下30点位置。
我们在上面的程序里屡次发现作者使用Point变量来计算点位,这个变量是MT4运行环境中自动设定的值,它在MQL语言中叫做预定义变量(关于预定义变量可以参考这里:http://docs.mql4.com/cn/predefined/variables),Point告诉我们当前货币对的价格最小点值是多少,举个例子:欧元对美元的价格总是X.XXXX这种形式,它的Point值就是0.0001,当我们想设定当价格大于30点这种情况时,我们只要用30乘以Point就可以计算这个货币对的实际30点值。不过Point常量在很多平台中不能正确的来实现它本身的功能了,原因是很多平台已经改为小数点后5位,这样Point值变成了0.00001,我们直接用它来乘以点位得到的却是实际点位的十分之一,这样会在EA的运行中出现致命的逻辑错误。因此,如果是小数点后5位的平台,需要在那些点位的值上乘以10来修正这个问题。
上面完整地说明了持仓单为多单的时候所进行的2项处理。后面空单持仓的代码大家对照观看即可。
else // go to short position
{
//--- should it be closed?
if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&
MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
{
//--- close order and exit
if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet))
Print("OrderClose error ",GetLastError());
return;
}
//--- check for trailing stop
if(TrailingStop>0)
{
if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
{
if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
{
//--- modify order and exit
if(!OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red))
Print("OrderModify error ",GetLastError());
return;
}
}
}
}
}
}
//---
}
//+------------------------------------------------------------------+
细心的读者会发现,上面针对空单执行移动止损的代码相比针对多单移动止损的代码多了|| (OrderStopLoss()==0)) 这一段,这是因为对空单执行第一次移动止损时,如果该空单没有设置初始止损价的话,那么按照上面类似多单移动止损设置的代码编写的话空单的移动止损就不会执行,因为OrderStopLoss()等于0,所以(OrderStopLoss()>(Ask+Point*TrailingStop)便不成立,因此,针对空单设置移动止损的代码需要增加|| (OrderStopLoss()==0)) 这一段,以防碰到空单没有设置初始止损时程序无法执行。
以上就是MT4自带EA:MACD Sample的解读,这个程序虽然比较复杂但是它却是一个很好的例子,里面涉及到了我们在写EA程序过程中经常用到的一些功能,对于初学EA程序的人来说帮助很大,我们也可以修改这个程序的开仓、平仓部分使用我们自己的逻辑把它变成我们自己的交易策略。
实际上大部分EA程序的结构都和上面差不多,并不复杂,希望通过这样的讲解,能让大家先对EA这种神秘的程序得到一个初步的框架。不要着急去想具体的语句,先把这种逻辑关系想的很清楚,后面就会越学越快!
二、基本策略
根据以上分析,可以看出该EA所使用的基本策略如下:
1、多头开仓:MACD处于0轴下方+MACD指标低位金叉+均线上升
空头开仓:MACD处于0轴上方+MACD指标高位死叉+均线下降
2、多头平仓:MACD处于0轴上方+MACD指标高位死叉
空头平仓:MACD处于0轴下方+MACD指标低位金叉
3、多头移动止损:订单止损价>卖价+移动止损*点,修改订单止损价
空头移动止损:订单止损价<买价 - 移动止损*点,修改订单止损价 |
|