您现在的位置: 比特财富网 >> 财经 >  >> 外匯
MT4自帶EA:MACD Sample詳解與實戰分析
外_匯_邦 WaiHuiBang.com 一、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開始的這些數據都是外部變量,也就是在使用者調用的時候可以修改的部分。www.emoneybtc.com

這個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、多頭移動止損:訂單止損價>賣價+移動止損*點,修改訂單止損價
     空頭移動止損:訂單止損價<買價 - 移動止損*點,修改訂單止損價 外_匯_邦 WaiHuiBang.com
  風險提示:比特財富網的各種信息資料僅供參考,不構成任何投資建議,不對任何交易提供任何擔保,亦不構成任何邀約,不作為任何法律文件,投資人據此進行投資交易而產生的後果請自行承擔,本網站不承擔任何責任,理財有風險,投資需謹慎。
比特財富網 版權所有 © www.emoneybtc.com