返回《給 CFD 交易者的 MetaTrader 5 與 MQL5》
第四部分

透過 handle 使用內建指標

別再手刻均線了。建立 handle、用 CopyBuffer 讀它、在結束時釋放。

中級閱讀時間約 11 分鐘初學 → 進階第 14 / 17 堂
在 MetaEditor 實際跑一次
透過 handle 讀取 iMA 與 iATR

存成 MQL5/Experts/Lesson14Handles.mq5。它在 OnInit 建立 handle、在 OnTick 讀取、在 OnDeinit 釋放——正確的生命週期。

//+------------------------------------------------------------------+
//| Lesson 14 - Indicator handles: iMA + iATR                        |
//| Read-only: this EA prints values, it does not trade.             |
//+------------------------------------------------------------------+
#property copyright "Strategist Academy"
#property version   "1.00"
#property strict

input int MAPeriod  = 20;
input int ATRPeriod = 14;

int maHandle  = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
datetime lastBarTime = 0;

int OnInit()
{
   maHandle  = iMA(_Symbol, _Period, MAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   atrHandle = iATR(_Symbol, _Period, ATRPeriod);

   if(maHandle == INVALID_HANDLE || atrHandle == INVALID_HANDLE)
   {
      Print("Failed to create indicator handles. Error: ", GetLastError());
      return(INIT_FAILED);
   }

   Print("Handles created. MA=", maHandle, "  ATR=", atrHandle);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   // Always release - a leaked handle per backtest pass will stall the tester.
   if(maHandle  != INVALID_HANDLE) IndicatorRelease(maHandle);
   if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
}

//+------------------------------------------------------------------+
//| Pull one value out of an indicator handle                        |
//+------------------------------------------------------------------+
bool ReadValue(const int handle, const int buffer, const int shift, double &out)
{
   double buf[];
   ArraySetAsSeries(buf, true);            // 0 = newest

   int need = shift + 3;
   int got  = CopyBuffer(handle, buffer, 0, need, buf);

   if(got < need)
      return false;

   double v = buf[shift];
   if(v == EMPTY_VALUE || v == 0.0)
      return false;

   out = v;
   return true;
}

void OnTick()
{
   datetime t = iTime(_Symbol, _Period, 0);
   if(t == lastBarTime) return;
   lastBarTime = t;

   // shift 1 = last CLOSED bar. shift 0 is still forming and will repaint.
   double ma  = 0.0;
   double atr = 0.0;

   if(!ReadValue(maHandle, 0, 1, ma))
   {
      Print("Could not read MA yet - not enough bars.");
      return;
   }
   if(!ReadValue(atrHandle, 0, 1, atr))
   {
      Print("Could not read ATR yet - not enough bars.");
      return;
   }

   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   Print("bid=", DoubleToString(bid, _Digits),
         "  EMA(", MAPeriod, ")=", DoubleToString(ma, _Digits),
         "  ATR(", ATRPeriod, ")=", DoubleToString(atr, _Digits),
         "  ATR in points=", DoubleToString(atr / _Point, 1),
         "  price is ", (bid > ma ? "ABOVE" : "below"), " the MA");
}
如何執行
  1. 1. 從 MT5 開啟 MetaEditor(按 F4), 選擇這支程式該放的資料夾——ExpertsIndicators Scripts
  2. 2. 開新檔案、把這段程式貼上去,然後按 F7 編譯。Errors 分頁顯示的錯誤 都修掉。
  3. 3. 回到 MT5,把它拖到圖表上——如果是 EA,就打開 Strategy Tester

是 handle,不是值

MQL5 的技術指標是你建立一次、然後去查詢的物件。iMAiATRiRSI 這些函式回傳的是一個 int handle——不是陣列,也不是值。你再用 CopyBuffer 把值取出來。

三步模式

  • 在 OnInit 裡建立——iMA(_Symbol, _Period, 20, 0, MODE_EMA, PRICE_CLOSE)。檢查 INVALID_HANDLE,失敗就回傳 INIT_FAILED
  • 用 CopyBuffer 讀——複製進一個 double 陣列,先設好序列順序,並檢查回傳的筆數。
  • 在 OnDeinit 裡釋放——IndicatorRelease(handle)。沒有商量餘地。

為什麼不全部手刻?

第 4 堂課要你手算一條 SMA,是為了讓你理解 buffer。正式上線時請用內建的:它們更快、正確處理套用價格,而且已經除錯過了。只有在內建做不到你要的事時才自己寫。

handle 洩漏

如果你在 OnTick 裡建立一個 handle 而不是在 OnInit,你會每個 tick 建立一個,終端機會慢到停住。建立一次、重複使用多次、釋放一次。這是初學者 EA 最常見的效能 bug。

K 線不夠,就拿到空值

剛建立完的指標還沒計算。太早複製,你會拿到一個空陣列或 EMPTY_VALUE。要嘛等到有足夠的 K 線,要嘛檢查 CopyBuffer 實際回傳了什麼——永遠不要假設它把你的陣列填滿了。

用 shift 1,不是 shift 0

第 0 根是正在形成的 K 線。它的指標值會一直動,直到那根 K 線收盤。任何在第 0 根上算出來的訊號都會在事後重繪,並在回測裡看起來比實際上線時更好。做決策請用 shift 1,除非你特別想要 K 線內的行為。

你剛完成了什麼

給 CFD 交易者的 MetaTrader 5 與 MQL5》的第 14 / 17 堂課。跑完範例或讀完這段之後, 把它勾起來,然後進下一堂課。