一步一步解讀MT4的例子EA 關(guān)于什么是EA請(qǐng)看此文章(連接到http://www./html/c350/2010-02/1239235.htm) 在MT4安裝后,默認(rèn)會(huì)給用戶(hù)提供幾個(gè)例子程序,這些程序?qū)τ谛聦W(xué)習(xí)EA開(kāi)發(fā)過(guò)程中有很大的幫助,下面我們就來(lái)對(duì)MACD Sample這個(gè)例子EA來(lái)做個(gè)解讀,來(lái)看看別人是如何開(kāi)發(fā)一個(gè)EA的。 首先是注釋?zhuān)贛QL語(yǔ)言中所有//的這一行就是注釋行,系統(tǒng)本身不會(huì)去執(zhí)行它,它只是用來(lái)解釋當(dāng)前代碼的含義的,我們?cè)诖a中加入這些注釋為了是讓我們能夠清楚的閱讀代碼的含義,在程序中加入詳細(xì)的注釋是一個(gè)很好的編程習(xí)慣,我們鼓勵(lì)大家多加入注釋。很多人覺(jué)得注釋這東西因?yàn)闆](méi)用在寫(xiě)代碼中覺(jué)得很麻煩而忽略它,但是即使是自己編寫(xiě)的程序如果不加注釋過(guò)幾天就會(huì)忘記意思,還要再一行一行地看這些代碼,所以注釋是非常重要的。 下面這些就是定義變量,我們發(fā)現(xiàn)前面加上了extern關(guān)鍵字,如果在定義變量的前面有這個(gè)詞說(shuō)明這個(gè)變量將會(huì)被作為EA運(yùn)行的參數(shù),舉個(gè)例子,比如我要編寫(xiě)一個(gè)EA,在使用過(guò)程中我想改變它的止盈和止損值,如果我程序都是事先編好的止盈止損,那么以后要改動(dòng)它必須要改代碼,這樣不僅麻煩也不靈活,如果我把這些信息作為EA運(yùn)行參數(shù),那么在EA運(yùn)行中就可以隨時(shí)調(diào)整它了。 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個(gè)變量并且都是當(dāng)作參數(shù),這里設(shè)定變量名稱(chēng)的時(shí)候盡量使用用戶(hù)可以理解的詞匯,在EA的參數(shù)設(shè)置里這些變量名就是參數(shù)的名字。值得注意的是EA里變量名稱(chēng)是可以用中文的。 int start() start()函數(shù)是EA運(yùn)行的核心,MQL語(yǔ)言規(guī)定了幾個(gè)默認(rèn)函數(shù),其中EA第一次運(yùn)行時(shí)會(huì)調(diào)用init()函數(shù),在這個(gè)函數(shù)里我們可以放入一些需要初始化的信息,start()函數(shù)中放我們EA的核心代碼,每次一個(gè)TICK(換句話說(shuō)就是新報(bào)價(jià))來(lái)到后,系統(tǒng)會(huì)自動(dòng)調(diào)用start()函數(shù)。deinit()函數(shù)是當(dāng)EA關(guān)閉的時(shí)候調(diào)用的,這里放一些我們程序停止后需要“善后”的代碼。start()函數(shù)是必須要有的,其它兩個(gè)函數(shù)如果不需要可以不用寫(xiě)。 if(Bars<100) { Print("bars less than 100"); return(0); } 上面代碼的意思是如果當(dāng)前圖表中的k線少于100根將會(huì)在日志信息里輸出提示信息并且結(jié)束start()函數(shù)的執(zhí)行。return的意思是返回,如果在程序中判斷出有錯(cuò)誤,下面的代碼無(wú)法繼續(xù)執(zhí)行了,我們調(diào)用return()函數(shù)讓他退出start()函數(shù)的執(zhí)行。 if(TakeProfit<10) { Print("TakeProfit less than 10"); return(0); // check TakeProfit } 上面的代碼意思是如果參數(shù)里的TakeProfit變量小于10也提示一條信息并結(jié)束執(zhí)行,TakeProfit從字面的意思中我們可以知道是止盈的意思,有些平臺(tái)會(huì)限制下單時(shí)的止盈點(diǎn)數(shù)不得小于某個(gè)點(diǎn),如果小于某值會(huì)在下單時(shí)報(bào)錯(cuò),為了避免這種錯(cuò)誤我們會(huì)限制參數(shù)中止盈的設(shè)定。 其實(shí)這里可以調(diào)用MarketInfo()函數(shù)得到我們當(dāng)前平臺(tái)中允許的止盈止損最小值從而根據(jù)平臺(tái)的不同自動(dòng)計(jì)算出最小的止盈點(diǎn)數(shù),詳細(xì)情況請(qǐng)參閱文檔MarketInfo()函數(shù)的描述。 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); 以上語(yǔ)句是調(diào)用了MQL的一些內(nèi)置指標(biāo)函數(shù),在MQL語(yǔ)言中,對(duì)于常用的指標(biāo)如均線,MACD,KD等MQL已經(jīng)提供給我們現(xiàn)成的函數(shù),我們只要調(diào)用他即可得到指標(biāo)的值(相見(jiàn)文檔技術(shù)指標(biāo)部分:http://docs./cn/indicators)。 就上面的代碼,MacdCurrent的值是參數(shù)為12,26,9的MACD主線當(dāng)前K線的值,MacdPrevious則是MacdCurrent前一根K線的值,SignalCurrent和SignalPrevious則是相同參數(shù)信號(hào)線的當(dāng)前值和前一根值。 后兩個(gè)是調(diào)用均線指標(biāo)函數(shù),這里的均線周期參數(shù)則是使用了EA的參數(shù)變量MATrendPeriod,這樣寫(xiě)是個(gè)好習(xí)慣,把調(diào)用指標(biāo)的參數(shù)放到EA參數(shù)里,這樣可以隨時(shí)在運(yùn)行中調(diào)整這些參數(shù)方便我們改變策略。MaCurrent和MaPrevious是得到26均線的當(dāng)前K線值和前一根的值。 total=OrdersTotal(); if(total<1) 上面的代碼就是判斷我們當(dāng)前是否有單子在做,他調(diào)用了OrdersTotal()函數(shù),它可以計(jì)算當(dāng)前賬戶(hù)中一共還沒(méi)有平倉(cāng)的單子和掛單的個(gè)數(shù),如果它小于1說(shuō)明當(dāng)前沒(méi)有任何單子,這種判斷方法只是一種簡(jiǎn)單的判斷法,如果這個(gè)EA在運(yùn)行過(guò)程中人為也去下單則EA永遠(yuǎn)不會(huì)下單了,如果要更加精確的計(jì)算這個(gè)EA所下的單子數(shù)量還需要利用其他方法,這些技巧我們會(huì)在以后的文章中介紹。 if(AccountFreeMargin()<(1000*Lots)) { Print("We have no money. Free Margin = ", AccountFreeMargin()); return(0); } 上面的代碼是計(jì)算當(dāng)前的剩余保證金是否小于1000,如果太少錢(qián)會(huì)不夠用,所以會(huì)輸出下當(dāng)前的保證金還剩多少并退出。 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(0); } 上面這段就是多單開(kāi)倉(cāng)部分了,條件是這樣:如果當(dāng)前MACD主線在0軸以下,MACD“金叉”,MACD的主線不在0軸附近(這塊是EA的參數(shù)來(lái)指定0軸附近多少點(diǎn))并且還要當(dāng)前的均線是上升的。 這里最精彩的部分在于如何判斷MACD“金叉”,如何判斷MACD值不在0軸附近和均線目前是上升的還是下降的。 “金叉”的判斷是EA里用的比較多的,這里我們用了判斷大小的方法就能很容易的計(jì)算它,首先得到MACD兩根線當(dāng)前的值和上一根K線的MACD值,如果上一根K線的MACD主線大于信號(hào)線并且當(dāng)前的MACD主線小于信號(hào)線那么就相當(dāng)于這兩根線做了一個(gè)“交叉”,因此我們可以認(rèn)為MACD“金叉”了。從這里我們也能看出來(lái)用計(jì)算機(jī)的方法來(lái)解決我們?nèi)祟?lèi)所認(rèn)知的問(wèn)題靠的都是這種具體數(shù)值的計(jì)算,所以計(jì)算機(jī)還是比較“死板”的,如果兩根線“扭”在了一起那么用計(jì)算機(jī)程序很難判斷出來(lái),這些就是目前計(jì)算機(jī)程序的缺點(diǎn)。 0軸附近這種判斷方法這里利用了一點(diǎn)數(shù)學(xué)方面的知識(shí),不過(guò)不用擔(dān)心都是很簡(jiǎn)單的算法。把MACD值做絕對(duì)值運(yùn)算然后判斷是否大于指定的值,因?yàn)镸ACD會(huì)是負(fù)值做絕對(duì)值運(yùn)算后直接判斷是否大于設(shè)定的值就行了,這塊相當(dāng)于是簡(jiǎn)化了判斷語(yǔ)句的條件。
均線的上升和下降判斷和“金叉”的算法差不多,得到當(dāng)前均線值和前一根線的均線值,如果前一根均線值小于當(dāng)前值那么就說(shuō)明均線是上升的。 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()); return(0); 這段代碼就是空單的進(jìn)場(chǎng)條件,和上面的正相反。值得說(shuō)明是這兩個(gè)下單代碼中會(huì)遇到下單失敗的情況,因?yàn)楫?dāng)用OrderSend()函數(shù)下單后會(huì)返回一個(gè)大于0的整數(shù)訂單號(hào)數(shù)值,利用這一點(diǎn)就可以很容易的知道下單是否成功了。 下面的代碼是平倉(cāng)和移動(dòng)止損部分,這段代碼比較難懂,但是卻是非常重要的部分,因?yàn)樵诰帉?xiě)EA中這些操作會(huì)經(jīng)常遇到,讓我們來(lái)一點(diǎn)一點(diǎn)的拆解開(kāi)來(lái)理解下它們的含義。 for(cnt=0;cnt<total;cnt++) 當(dāng)前存在的訂單中我們要判斷是否到達(dá)平倉(cāng)的條件,所以第一步我們首先要對(duì)所有在下的單子進(jìn)行一次遍歷,一個(gè)一個(gè)的去判斷它們是否達(dá)到平倉(cāng)條件。 此代碼中利用了一個(gè)循環(huán)語(yǔ)句從第一單開(kāi)始一單一單的循環(huán),這里值得注意的是所有單子都是按照下單的先后順序存放的,第一張單子的編號(hào)是0而不是1,這是編程語(yǔ)言中一般都采取的方法,我們?cè)诰帉?xiě)程序的時(shí)候一定要注意它的值要從0開(kāi)始。 OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES); 上面是選擇訂單操作,當(dāng)循環(huán)一次訂單后,必須調(diào)用OrderSelect()函數(shù)來(lái)鎖定這一訂單,這樣下面的操作才可以正常運(yùn)行。這里最容易出錯(cuò)的地方是函數(shù)的第二個(gè)參數(shù)如果查一下文檔我們會(huì)發(fā)現(xiàn)它有兩個(gè)選項(xiàng):SELECT_BY_POS和SELECT_BY_TICKET。第一種方式是根據(jù)訂單的位置進(jìn)行選定操作,這個(gè)例子中就是使用了這種方式,第二種方式是根據(jù)訂單號(hào)來(lái)進(jìn)行選定操作,因?yàn)槲覀儾⒉恢浪袉巫拥挠唵翁?hào)是多少所以我們只能使用第一種方式來(lái)選擇訂單,剛才說(shuō)過(guò)訂單是按照下單的先后順序來(lái)存放的,因此如果是第一個(gè)單子那么就是0,如果是第二個(gè)單子就是1,最后一個(gè)單子是總單子數(shù)減一。 if(OrderType()<=OP_SELL && // check for opened position OrderSymbol()==Symbol()) // check for symbol 上面的代碼段又運(yùn)用了一個(gè)小技巧,它首先調(diào)用了OrderType()函數(shù)來(lái)得到所選定的訂單是多單還是空單,但是我們查下這個(gè)函數(shù)的定義(http://docs./cn/constants/trading)發(fā)現(xiàn)多單的值是0,空單的值是1,那么如果OrderType()函數(shù)小于等于空單的值那么相當(dāng)于在判斷當(dāng)前訂單是否是非掛單。 第二個(gè)條件是判斷當(dāng)前單子的貨幣對(duì)是否和當(dāng)前圖表相同,這個(gè)判斷是為了防止我們處理訂單過(guò)程中誤操作了其他不是EA所下的單子。 if(OrderType()==OP_BUY) // long position is opened { // should it be closed? if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && MacdCurrent>(MACDCloseLevel*Point)) { OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position return(0); // exit } 多單的平倉(cāng)部分代碼,這里其實(shí)就是去掉均線條件的空單下單信號(hào),平倉(cāng)操作中一定要注意在平倉(cāng)完成后必須終止這個(gè)遍歷訂單的循環(huán),因?yàn)槠絺}(cāng)后會(huì)打亂所有單子的順序,造成誤操作其他訂單。 我們?cè)谶@里舉個(gè)例子就能明白為什么要這么做:比如當(dāng)前有三個(gè)單子沒(méi)有平倉(cāng),按照順序排列序號(hào)是0、1、2,如果第二個(gè)單子平倉(cāng)后第三個(gè)單子序號(hào)就會(huì)提前,這樣當(dāng)下一輪循環(huán)執(zhí)行到OrderSelect()函數(shù)后會(huì)因?yàn)闆](méi)有這個(gè)編號(hào)而出現(xiàn)錯(cuò)誤。 if(TrailingStop>0) { if(Bid-OrderOpenPrice()>Point*TrailingStop) { if(OrderStopLoss()<Bid-Point*TrailingStop) { OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green); return(0); } } } 這段代碼就是多單的移動(dòng)止損部分,當(dāng)參數(shù)TrailingStop大于0的時(shí)候EA就開(kāi)啟了移動(dòng)止損功能(默認(rèn)設(shè)定是30,也就是說(shuō)默認(rèn)情況下是開(kāi)啟移動(dòng)止損的),我們就用我們這個(gè)例子EA的默認(rèn)參數(shù)30點(diǎn)來(lái)說(shuō)明,當(dāng)單子的盈利大于30點(diǎn)并且單子的止損點(diǎn)和當(dāng)前價(jià)位相差30點(diǎn)以上時(shí),修改訂單的止損到當(dāng)前價(jià)格以下30點(diǎn)位置。 我們?cè)谏厦娴某绦蚶飳掖伟l(fā)現(xiàn)作者使用Point變量來(lái)計(jì)算點(diǎn)位,這個(gè)變量是MT4運(yùn)行環(huán)境中自動(dòng)設(shè)定的值,它在MQL語(yǔ)言中叫做預(yù)定義變量(關(guān)于預(yù)定義變量可以參考這里:http://docs./cn/predefined/variables),Point告訴我們當(dāng)前貨幣對(duì)的價(jià)格最小點(diǎn)值是多少,舉個(gè)例子:歐元對(duì)美元的價(jià)格總是X.XXXX這種形式,它的Point值就是0.0001,當(dāng)我們想設(shè)定當(dāng)價(jià)格大于30點(diǎn)這種情況時(shí),我們只要用30乘以Point就可以計(jì)算這個(gè)貨幣對(duì)的實(shí)際30點(diǎn)值。不過(guò)Point常量在很多平臺(tái)中不能正確的來(lái)實(shí)現(xiàn)它本身的功能了,原因是很多平臺(tái)已經(jīng)改為小數(shù)點(diǎn)后5位,這樣Point值變成了0.00001,我們直接用他來(lái)乘以點(diǎn)位得到是卻是實(shí)際點(diǎn)位的十分之一,這樣會(huì)在EA的運(yùn)行中出現(xiàn)致命的邏輯錯(cuò)誤。因此如果是5位的平臺(tái),需要在那些點(diǎn)位的值上乘以10來(lái)修正這個(gè)問(wèn)題。關(guān)于Point的完美解決方法我們將在后續(xù)的文章中繼續(xù)討論。 以上就是MT4例子EA的解讀,這個(gè)程序雖然比較復(fù)雜但是它卻是一個(gè)很好的例子,里面涉及到了我們?cè)趯?xiě)EA程序過(guò)程中常用到的一些功能,對(duì)于初學(xué)EA程序的人來(lái)說(shuō)幫助很大,我們也可以修改這個(gè)程序的開(kāi)倉(cāng)、平倉(cāng)部分直接變成我們自己的邏輯。 |
|