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

從指標到 EA

EA 的骨架:新 K 線閘門、部位檢查、magic number,以及一次乾淨的進場。

中級閱讀時間約 10 分鐘初學 → 進階第 10 / 17 堂
在 MetaEditor 實際跑一次
一個最小可行 EA:新 K 線閘門、magic number、一次進場

存成 MQL5/Experts/Lesson10FirstEA.mq5。僅限模擬帳戶——它會在第一根新 K 線開出一張真實買單。在 Experts 分頁看 retcode。

//+------------------------------------------------------------------+
//| Lesson 10 - First EA: new-bar gate, magic number, one entry      |
//| WARNING: sends real market orders. Use a DEMO account.           |
//+------------------------------------------------------------------+
#property copyright "Strategist Academy"
#property version   "1.00"
#property strict

#include <Trade/Trade.mqh>

input double Lots        = 0.10;
input int    StopPoints  = 200;
input ulong  MagicNumber = 10101010;

CTrade   trade;
datetime lastBarTime = 0;

int OnInit()
{
   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(10);
   trade.SetTypeFillingBySymbol(_Symbol);

   Print("Lesson 10 EA ready on ", _Symbol, " / ", EnumToString((ENUM_TIMEFRAMES)_Period));
   return(INIT_SUCCEEDED);
}

void OnTick()
{
   // 1) Only act on the first tick of a new bar.
   if(!IsNewBar())
      return;

   // 2) Never stack positions - check first.
   if(HasPosition(_Symbol, MagicNumber))
   {
      Print("Already in a position on ", _Symbol, " - standing down.");
      return;
   }

   // 3) Send the order and always log the retcode.
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double sl  = NormalizeDouble(ask - StopPoints * _Point, _Digits);

   bool ok = trade.Buy(Lots, _Symbol, 0.0, sl, 0.0, "lesson10");

   Print("Buy request ", ok ? "accepted" : "FAILED",
         "  retcode=", trade.ResultRetcode(),
         "  (", trade.ResultRetcodeDescription(), ")");

   if(ok)
      Print("Deal: ", trade.ResultDeal(), "  volume: ", trade.ResultVolume(),
            "  price: ", DoubleToString(trade.ResultPrice(), _Digits));
}

//+------------------------------------------------------------------+
//| Has the current bar's open time changed since we last looked?    |
//+------------------------------------------------------------------+
bool IsNewBar()
{
   datetime t = iTime(_Symbol, _Period, 0);
   if(t == lastBarTime)
      return false;
   lastBarTime = t;
   return true;
}

//+------------------------------------------------------------------+
//| Is there a position on this symbol carrying our magic number?    |
//+------------------------------------------------------------------+
bool HasPosition(const string sym, const ulong magic)
{
   if(!PositionSelect(sym))
      return false;
   return ((ulong)PositionGetInteger(POSITION_MAGIC) == magic);
}
如何執行
  1. 1. 從 MT5 開啟 MetaEditor(按 F4), 選擇這支程式該放的資料夾——ExpertsIndicators Scripts
  2. 2. 開新檔案、把這段程式貼上去,然後按 F7 編譯。Errors 分頁顯示的錯誤 都修掉。
  3. 3. 回到 MT5,把它拖到圖表上——如果是 EA,就打開 Strategy Tester

當你能交易時,什麼改變了

一個指標只畫圖。一個 EA 會行動,而行動意味著三個新的責任:決定何時行動、避免行動兩次,以及只為自己的部位負責。

新 K 線閘門

OnTick 在每次價格更新時觸發。如果你的邏輯是以 K 線為基礎——而多數順勢邏輯都是——你必須忽略每根新 K 線第一個 tick 以外的所有 tick。記住目前這根 K 線的開盤時間;如果它沒變,就回傳。少了這個閘門,一個 EA 可以在某個條件剛成立的一分鐘之內開出幾十個部位。

magic number

magic number 只是一個你貼在每一張單上的整數。它是讓一個 EA 分辨「自己的部位」與「你的部位」,以及同一個帳戶上其他 EA 部位的唯一可靠方法。在關閉或修改任何東西之前先檢查它。挑一個有辨識度的;不要重複用 0。

先檢查有沒有已存在的部位

在每一次進場之前,先問自己是不是已經在場內。用 PositionSelect(symbol) 然後驗證 POSITION_MAGIC。在避險帳戶上,同一個商品可以有多個部位,所以你可能需要迭代 PositionsTotal() 並逐一比對 ticket——這裡的簡化版假設一個商品只有一個部位,而那也正好是淨額 CFD 帳戶強制的行為。

把 retcode 記下來

每一次交易呼叫都會回傳一個結果碼。把它印出來。「它沒交易」不是一個診斷;retcode 10019(沒錢)與 10016(無效停損)是兩個完全不同的問題,修法也完全不同。

只在模擬帳戶上執行這個

這堂課的 EA 會送出一張真實的市價單。那就是重點——但在你讀完第三部分並理解風險那堂課之前,請在模擬帳戶上跑它。

你剛完成了什麼

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