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

你的第一個指標:一條移動平均線

指標 buffer、OnCalculate,以及為什麼每一個指標最前面幾根 K 線都是空的。

初學閱讀時間約 9 分鐘初學 → 進階第 04 / 17 堂
在 MetaEditor 實際跑一次
手刻一條 SMA,讓你親眼看到 buffer 怎麼運作

存成 MQL5/Indicators/Lesson4SMA.mq5,編譯,然後掛到圖表上。在對話框裡改 input 週期——這就是你在第三部分會重複使用的同一個指標。

//+------------------------------------------------------------------+
//| Lesson 4 - First indicator: simple moving average                |
//+------------------------------------------------------------------+
#property copyright "Strategist Academy"
#property version   "1.00"
#property description "Hand-rolled SMA so you can see how buffers work."

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_width1  2

input int SMA_Period = 20;   // Period

double SMABuffer[];

int OnInit()
{
   if(SMA_Period < 2)
   {
      Print("SMA_Period must be at least 2");
      return(INIT_FAILED);
   }

   SetIndexBuffer(0, SMABuffer, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, SMA_Period - 1);
   PlotIndexSetString(0, PLOT_LABEL, "SMA(" + IntegerToString(SMA_Period) + ")");
   IndicatorSetString(INDICATOR_SHORTNAME, "Lesson 4 SMA(" + IntegerToString(SMA_Period) + ")");

   // NOTE: we do NOT call ArraySetAsSeries() here, so index 0 is the OLDEST bar.
   // This is MQL5's default. Mixing conventions is the classic beginner bug.
   return(INIT_SUCCEEDED);
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < SMA_Period)
      return(0);   // not enough bars yet - nothing to draw

   int start;
   if(prev_calculated == 0)
      start = SMA_Period - 1;          // first bar that CAN be calculated
   else
      start = prev_calculated - 1;     // only recompute the newest bars

   if(start < SMA_Period - 1)
      start = SMA_Period - 1;

   for(int i = start; i < rates_total && !IsStopped(); i++)
   {
      double sum = 0.0;
      for(int k = 0; k < SMA_Period; k++)
         sum += close[i - k];          // i is oldest-first: look backwards

      SMABuffer[i] = sum / SMA_Period;
   }

   return(rates_total);
}
如何執行
  1. 1. 從 MT5 開啟 MetaEditor(按 F4), 選擇這支程式該放的資料夾——ExpertsIndicators Scripts
  2. 2. 開新檔案、把這段程式貼上去,然後按 F7 編譯。Errors 分頁顯示的錯誤 都修掉。
  3. 3. 回到 MT5,把它拖到圖表上——如果是 EA,就打開 Strategy Tester

Buffer 就是你去填滿的陣列

一個 MQL5 指標不會回傳一個值。你宣告一個或多個 buffer(就是單純的 double 陣列),告訴終端機哪些是要畫出來的資料,然後在 OnCalculate 裡面填滿它們。

  • SetIndexBuffer(0, Buffer, INDICATOR_DATA)——把你的陣列綁到繪圖索引 0。
  • PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, n)——隱藏最前面 n 根 K 線,讓你永遠不會畫出一條只算了一半的平均線。
  • #property indicator_* 那幾行則是在最前面宣告 buffer、繪圖、顏色與樣式。

索引:0 是最舊的那根 K 線

除非你呼叫 ArraySetAsSeries(arr, true),MQL5 的時間序列陣列是用0 = 圖表上最舊的那根 K 線來索引的。這與 Pine 的預設直覺相反,而且是「我的指標怎麼是反的」這個 bug 的第一大來源。這堂課我們刻意維持預設(最舊在前)的順序——先學會一種慣例,再去混用兩種。

prev_calculated 是你的最佳化

OnCalculate 會收到 prev_calculated:你上一次已經處理了幾根 K 線。第一次呼叫時它是 0,你要全部重算。之後你只需要重算最新的那幾根。忽略它仍然能跑,但會在每一個 tick 上浪費數千根 K 線的運算。

永遠要防資料不夠

如果圖表上的 K 線比你的週期少,立刻回傳 0。讀超過時間序列陣列的末端是未定義行為,也是在小時間週期或剛開啟的商品上典型的崩潰原因。

你剛完成了什麼

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