MQL5 技術指標:handle、CopyBuffer,用均線交叉做第一個訊號

均線、ATR、RSI 這些技術指標,MT5 不是直接給你一個數字:要先跟它「登記」一個指標拿到 handle,再用 CopyBuffer() 把數值抓進陣列。這篇用兩條 EMA 加一個 ATR 走完這條路,順便做出第一個交易訊號:均線交叉。EA 只印訊息、不下單。

範例是 EA 不是腳本,因為訊號要一根一根 K 線判斷,得靠 OnTick。整份程式在我的 MT5 上編譯過、用策略測試器跑過 2025 年 1 月的 EURUSD H1,文中日誌是實際輸出。可以先下載 MaCrossSignal.mq5 跟著跑。

先把範例跑起來

  1. 把 MaCrossSignal.mq5 放到 MQL5\Experts\,在 MetaEditor 打開、按 F7,0 errors。
  2. 掛到 EURUSD H1 圖表上。左上角會顯示上一根的快線、慢線、ATR;均線交叉的那根 K 線收完,「專家」頁會印一行。盤中要等到交叉可能要幾個小時。
  3. 不想等就用策略測試器:Ctrl+R,EA 選 MaCrossSignal,EURUSD,H1,日期 2025.01.02 到 2025.01.31,模式「每個報價」,按「開始」,切到「日誌」頁。這隻 EA 一根 K 線只算一次,換別的模式訊號也一樣,只差印出的時間。

1. 參數與 handle

//=== 1. 參數與 handle ===============================================
input int            i_FastPeriod = 10;       // 快線期數
input int            i_SlowPeriod = 30;       // 慢線期數
input ENUM_MA_METHOD i_MaMethod   = MODE_EMA; // 均線種類
input int            i_AtrPeriod  = 14;       // ATR 期數

int      g_FastHandle  = INVALID_HANDLE;  // 指標 handle 就是一個整數
int      g_SlowHandle  = INVALID_HANDLE;
int      g_AtrHandle   = INVALID_HANDLE;
datetime g_LastBarTime = 0;               // 上次處理過的 K 線時間
int      g_Signals     = 0;               // 訊號計數

iMA()iATR() 這類函數回傳的不是數值,是一個整數,叫 handle。可以把它想成掛號單:MT5 收到之後在背景幫你算這個指標,你之後拿號碼來要數值。跟 MT4 差最多的就是這裡,MT4 的 iMA(..., shift) 是直接回傳那一根的均線值。

handle 放在全域變數,初始值 INVALID_HANDLE(其實就是 -1),建立失敗時才分得出來。g_LastBarTime 記上次處理過的 K 線時間,第 4 段會用到。ENUM_MA_METHOD 是 MT5 內建的列舉,直接拿來當 input 就是 SMA、EMA 等四種均線的下拉選單。

2. OnInit:建立指標,一次就好

//=== 2. OnInit:建立指標,一次就好 ==================================
int OnInit()
{
   g_FastHandle = iMA(_Symbol, _Period, i_FastPeriod, 0,
                      i_MaMethod, PRICE_CLOSE);
   g_SlowHandle = iMA(_Symbol, _Period, i_SlowPeriod, 0,
                      i_MaMethod, PRICE_CLOSE);
   g_AtrHandle  = iATR(_Symbol, _Period, i_AtrPeriod);
   if(g_FastHandle == INVALID_HANDLE ||
      g_SlowHandle == INVALID_HANDLE ||
      g_AtrHandle  == INVALID_HANDLE)
   {
      Print("指標建立失敗,錯誤碼 ", GetLastError());
      return INIT_FAILED;
   }
   Print("指標建立完成:", EnumToString(i_MaMethod), " ",
         i_FastPeriod, "/", i_SlowPeriod, ",ATR ", i_AtrPeriod);
   return INIT_SUCCEEDED;
}

iMA() 六個參數:商品、週期、期數、位移(幾乎都填 0)、均線種類、用哪個價算(PRICE_CLOSE 收盤價)。iATR() 只要商品、週期、期數。建立失敗(通常是商品名稱打錯之類的參數問題)會回傳 INVALID_HANDLE,這裡直接 return INIT_FAILED 讓 EA 不要掛上去。

指標只在 OnInit 建一次。寫在 OnTick 裡,MT5 會認出是同一個指標、回同一個 handle,不會多算一份,但每個 tick 都白跑一次登記,是初學最常見的錯。

3. OnDeinit:釋放指標

//=== 3. OnDeinit:釋放指標 ==========================================
void OnDeinit(const int reason)
{
   IndicatorRelease(g_FastHandle);
   IndicatorRelease(g_SlowHandle);
   IndicatorRelease(g_AtrHandle);
   Comment("");
   Print("EA 結束,共 ", g_Signals, " 個訊號");
}

EA 被拿掉的時候用 IndicatorRelease() 把 handle 還給 MT5。不寫也不會怎樣,EA 卸載時 MT5 會自己收,但有借有還的習慣要養,之後在程式裡換參數重建指標時才不會越積越多。

4. 只在新 K 線算一次

//=== 4. 新 K 線判斷 =================================================
bool IsNewBar()
{
   datetime t = iTime(_Symbol, _Period, 0);  // 目前這根的開盤時間
   if(t == g_LastBarTime)
      return false;
   g_LastBarTime = t;
   return true;
}

已收盤的 K 線,指標值不會再變,沒必要每個 tick 都算。iTime(_Symbol, _Period, 0) 是目前這根的開盤時間,跟上次記的一樣就是同一根,直接跳過;不一樣就是新的一根開始了,記下來、往下算。這個函數之後每隻 EA 都會有。

副作用是訊號會在「下一根的第一個 tick」才出現。日誌裡第一筆黃金交叉說的是 01.03(週五)23:00 那根,測試器印出這行的時間卻是 01.06(週一)00:05:週五收完之後要等週一第一筆報價進來,OnTick 才會被叫到。這個時間欄文末的日誌拿掉了,自己跑一次就看得到。

5. OnTick:讀指標值、判斷訊號

//=== 5. OnTick:讀指標值、判斷訊號 ==================================
void OnTick()
{
   if(!IsNewBar())
      return;                       // 一根 K 線只算一次

   double fast[], slow[], atr[];
   ArraySetAsSeries(fast, true);    // [0] 進行中、[1] 上一根已收盤
   ArraySetAsSeries(slow, true);
   ArraySetAsSeries(atr,  true);
   if(CopyBuffer(g_FastHandle, 0, 0, 3, fast) < 3 ||
      CopyBuffer(g_SlowHandle, 0, 0, 3, slow) < 3 ||
      CopyBuffer(g_AtrHandle,  0, 0, 3, atr)  < 3)
   {
      Print("指標資料還沒準備好,錯誤碼 ", GetLastError());
      g_LastBarTime = 0;            // 下一個 tick 再試這根
      return;
   }

   // 訊號只看已收盤的 [1] 與 [2],不看還在跳的 [0]
   bool goldenCross = fast[2] <= slow[2] && fast[1] > slow[1];
   bool deadCross   = fast[2] >= slow[2] && fast[1] < slow[1];
   int  atrPoints   = (int)MathRound(atr[1] / _Point);

   Comment("快線 ", DoubleToString(fast[1], _Digits),
           "  慢線 ", DoubleToString(slow[1], _Digits),
           "\nATR ", atrPoints, " 點");

   if(goldenCross || deadCross)
   {
      g_Signals++;
      Print(goldenCross ? "黃金交叉" : "死亡交叉",
            "  ", TimeToString(iTime(_Symbol, _Period, 1)),
            "  快線 ", DoubleToString(fast[1], _Digits),
            "  慢線 ", DoubleToString(slow[1], _Digits),
            "  ATR ", atrPoints, " 點");
   }
}

CopyBuffer(handle, 緩衝區編號, 起點, 幾根, 陣列)。均線跟 ATR 只有一個緩衝區,編號 0;起點 0 是目前這根,抓 3 根就是 [0]、[1]、[2]。回傳值是實際抓到幾根,剛掛上圖表、歷史還沒載完的時候會拿不齊,一定要檢查。拿不齊就把 g_LastBarTime 清成 0,下一個 tick 再試同一根,不然這根就被跳過了。

三個陣列都先 ArraySetAsSeries(..., true),[0] 才是最新那根、[1] 上一根、[2] 再前一根,跟 CopyClose 一樣。忘了設,順序就反過來,交叉判斷整個顛倒,而且不會報錯。

訊號只用 [1] 和 [2],不用 [0]。[0] 這根還在跑,均線值每個 tick 都在變,這一秒交叉、下一秒又沒了。用已收盤的 [1] 代價是慢一根,但訊號出來就不會消失,之後回測要準也靠這一條。

黃金交叉的寫法是「前一根快線還在慢線下面(或相等)、這根收盤翻到上面」,兩個條件都要,只有翻過去的那一根會印,不會每根都印。死亡交叉反過來。日誌裡 01.16 05:00 那筆快線慢線都印 1.02929 卻算死亡交叉,因為 DoubleToString() 只顯示到第 5 位,實際差在第 6 位以後。均線值是 double,這種一線之隔很常見。

ATR(Average True Range)是最近 14 根的平均波動幅度,除以 _Point 換成點數比較好讀,日誌裡這幾筆落在 80 到 220 點之間。現在只是印出來,之後算停損距離會用它:停損跟著波動走,比固定點數合理。

執行結果

2025 年 1 月,14 個訊號(測試器日誌的時間欄省略,只列訊息):

指標建立完成:MODE_EMA 10/30,ATR 14
黃金交叉  2025.01.03 23:00  快線 1.02981  慢線 1.02972  ATR 144 點
死亡交叉  2025.01.07 20:00  快線 1.03812  慢線 1.03816  ATR 195 點
黃金交叉  2025.01.14 02:00  快線 1.02346  慢線 1.02330  ATR 190 點
死亡交叉  2025.01.16 05:00  快線 1.02929  慢線 1.02929  ATR 142 點
黃金交叉  2025.01.16 19:00  快線 1.02915  慢線 1.02909  ATR 166 點
死亡交叉  2025.01.17 09:00  快線 1.02948  慢線 1.02950  ATR 80 點
黃金交叉  2025.01.17 10:00  快線 1.02952  慢線 1.02951  ATR 84 點
死亡交叉  2025.01.17 18:00  快線 1.02951  慢線 1.02955  ATR 152 點
黃金交叉  2025.01.20 06:00  快線 1.02915  慢線 1.02907  ATR 130 點
死亡交叉  2025.01.23 03:00  快線 1.04123  慢線 1.04125  ATR 106 點
黃金交叉  2025.01.23 19:00  快線 1.04129  慢線 1.04105  ATR 170 點
死亡交叉  2025.01.28 01:00  快線 1.04819  慢線 1.04823  ATR 173 點
黃金交叉  2025.01.30 17:00  快線 1.04220  慢線 1.04206  ATR 176 點
死亡交叉  2025.01.30 22:00  快線 1.04200  慢線 1.04203  ATR 217 點
EA 結束,共 14 個訊號

光看這份就知道均線交叉的老問題:01.17 一天翻了三次,01.30 黃金交叉五小時後又死亡交叉。盤整時兩條均線黏在一起反覆穿越,所以交叉很少單獨拿來交易,通常會再加濾網。這篇只做訊號,先不管好不好賺。

換別的指標

換指標只是換函數跟緩衝區編號,讀法一樣。有好幾條線的指標,每條線是一個緩衝區:

函數緩衝區編號
iMAiATRiRSIiCCI0
iBands(布林通道)0 中軌、1 上軌、2 下軌
iMACD0 主線、1 訊號線
iStochastic0 主線 %K、1 訊號線 %D
iADX0 ADX、1 +DI、2 -DI

記不住就在 MetaEditor 把游標放在函數名上按 F1,說明裡有列。

用 handle 建的指標不會畫在圖表上(測試器的視覺化模式除外)。想核對數值,就自己從導航拖一個同參數的 Moving Average 到圖上,開數據窗口(Ctrl+D)比對。

本篇檔案:MaCrossSignal.mq5,放到 MQL5\Experts\ 底下,在 MetaEditor 打開按 F7 就能用。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料