原文位址:MQL語言 EA代碼注解 作者:交易之外 在MT4安裝後,預設會給使用者提供幾個例子程式,這些程式對于新學習EA開發過程中有很大的幫助,下面我們就來對MACD Sample這個例子EA來做個解讀,來看看别人是如何開發一個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,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
以上語句是調用了MQL的一些内置名額函數,在MQL語言中,對于常用的名額如均線,MACD,KD等MQL已經提供給我們現成的函數,我們隻要調用他即可得到名額的值。
就上面的代碼,MacdCurrent的值是參數為12,26,9的MACD主線目前K線的值,MacdPrevious則是MacdCurrent前一根K線的值,SignalCurrent和SignalPrevious則是相同參數信号線的目前值和前一根值。
後兩個是調用均線名額函數,這裡的均線周期參數則是使用了EA的參數變量MATrendPeriod,這樣寫是個好習慣,把調用名額的參數放到EA參數裡,這樣可以随時在運作中調整這些參數友善我們改變政策。MaCurrent和MaPrevious是得到26均線的目前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 && 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);
}
上面這段就是多單開倉部分了,條件是這樣:如果目前MACD主線在0軸以下,MACD“金叉”,MACD的主線不在0軸附近(這塊是EA的參數來指定0軸附近多少點)并且還要目前的均線是上升的。
這裡最精彩的部分在于如何判斷MACD“金叉”,如何判斷MACD值不在0軸附近和均線目前是上升的還是下降的。
“金叉”的判斷是EA裡用的比較多的,這裡我們用了判斷大小的方法就能很容易的計算它,首先得到MACD兩根線目前的值和上一根K線的MACD值,如果上一根K線的MACD主線大于信号線并且目前的MACD主線小于信号線那麼就相當于這兩根線做了一個“交叉”,是以我們可以認為MACD“金叉”了。從這裡我們也能看出來用計算機的方法來解決我們人類所認知的問題靠的都是這種具體數值的計算,是以計算機還是比較“死闆”的,如果兩根線“扭”在了一起那麼用計算機程式很難判斷出來,這些就是目前計算機程式的缺點。
0軸附近這種判斷方法這裡利用了一點數學方面的知識,不過不用擔心都是很簡單的算法。把MACD值做絕對值運算然後判斷是否大于指定的值,因為MACD會是負值做絕對值運算後直接判斷是否大于設定的值就行了,這塊相當于是簡化了判斷語句的條件。
均線的上升和下降判斷和“金叉”的算法差不多,得到目前均線值和前一根線的均線值,如果前一根均線值小于目前值那麼就說明均線是上升的。
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);
這段代碼就是空單的進場條件,和上面的正相反。值得說明是這兩個下單代碼中會遇到下單失敗的情況,因為當用OrderSend()函數下單後會傳回一個大于0的整數訂單号數值,利用這一點就可以很容易的知道下單是否成功了。
下面的代碼是平倉和移動止損部分,這段代碼比較難懂,但是卻是非常重要的部分,因為在編寫EA中這些操作會經常遇到,讓我們來一點一點的拆解開來了解下它們的含義。
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,最後一個單子是總單子數減一。
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(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && MacdCurrent>(MACDCloseLevel*Point))
{
OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
return(0); // exit
}
多單的平倉部分代碼,這裡其實就是去掉均線條件的空單下單信号,平倉操作中一定要注意在平倉完成後必須終止這個周遊訂單的循環,因為平倉後會打亂所有單子的順序,造成誤操作其他訂單。
我們在這裡舉個例子就能明白為什麼要這麼做:比如目前有三個單子沒有平倉,按照順序排列序号是0、1、2,如果第二個單子平倉後第三個單子序号就會提前,這樣當下一輪循環執行到OrderSelect()函數後會因為沒有這個編号而出現錯誤。
if(TrailingStop>0)
{
if(Bid-OrderOpenPrice()>Point*TrailingStop)
{
if(OrderStopLoss()<Bid-Point*TrailingStop)
{
OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
return(0);
}
}
}
這段代碼就是多單的移動止損部分,當參數TrailingStop大于0的時候EA就開啟了移動止損功能(預設設定是30,也就是說預設情況下是開啟移動止損的),我們就用我們這個例子EA的預設參數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程式的人來說幫助很大,我們也可以修改這個程式的開倉、平倉部分直接變成我們自己的邏輯。