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

下單與管理訂單

手刻 MqlTradeRequest:成交模式、滑價、retcode,以及關掉你開的倉。

中級閱讀時間約 12 分鐘初學 → 進階第 11 / 17 堂
在 MetaEditor 實際跑一次
手刻 OrderSend,帶一條成交模式降級鏈

存成 MQL5/Experts/Lesson11RawOrder.mq5。僅限模擬。它手動組出請求、依序嘗試 FOK 再 IOC 再 RETURN,並印出完整結果——也就是 CTrade 藏起來的那套邏輯。

//+------------------------------------------------------------------+
//| Lesson 11 - Raw OrderSend, filling modes, retcodes               |
//| WARNING: sends real market orders. Use a DEMO account.           |
//+------------------------------------------------------------------+
#property copyright "Strategist Academy"
#property version   "1.00"
#property strict

input double Lots        = 0.10;
input int    StopPoints  = 200;
input int    Deviation   = 10;
input ulong  MagicNumber = 11111111;

datetime lastBarTime = 0;

int OnInit()
{
   Print("Filling mode supported by ", _Symbol, ": ",
         EnumToString((ENUM_ORDER_TYPE_FILLING)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE)));
   return(INIT_SUCCEEDED);
}

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

   if(PositionSelect(_Symbol))
   {
      Print("Position already open - skipping.");
      return;
   }

   OpenBuy(Lots, StopPoints);
}

//+------------------------------------------------------------------+
//| Market buy built by hand, with a filling-mode fallback chain     |
//+------------------------------------------------------------------+
void OpenBuy(const double lots, const double stopPoints)
{
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double sl  = NormalizeDouble(ask - stopPoints * _Point, _Digits);

   MqlTradeRequest req = {};
   MqlTradeResult  res = {};

   req.action    = TRADE_ACTION_DEAL;
   req.symbol    = _Symbol;
   req.volume    = NormalizeDouble(lots, 2);
   req.type      = ORDER_TYPE_BUY;
   req.price     = ask;
   req.sl        = sl;
   req.deviation = Deviation;
   req.magic     = MagicNumber;
   req.comment   = "lesson11";

   // Ask the symbol first; fall back through the rest if the server refuses.
   long supported = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);

   ENUM_ORDER_TYPE_FILLING modes[3];
   if((supported & SYMBOL_FILLING_FOK) != 0)
      { modes[0] = ORDER_FILLING_FOK;    modes[1] = ORDER_FILLING_IOC;    modes[2] = ORDER_FILLING_RETURN; }
   else if((supported & SYMBOL_FILLING_IOC) != 0)
      { modes[0] = ORDER_FILLING_IOC;    modes[1] = ORDER_FILLING_RETURN; modes[2] = ORDER_FILLING_FOK;    }
   else
      { modes[0] = ORDER_FILLING_RETURN; modes[1] = ORDER_FILLING_FOK;    modes[2] = ORDER_FILLING_IOC;    }

   for(int i = 0; i < 3; i++)
   {
      req.type_filling = modes[i];
      ResetLastError();

      if(!OrderSend(req, res))
      {
         Print("OrderSend failed with ", EnumToString(modes[i]),
               ". GetLastError=", GetLastError());
         continue;
      }

      Print("Sent with ", EnumToString(modes[i]),
            " -> retcode=", res.retcode,
            "  deal=", res.deal,
            "  volume=", DoubleToString(res.volume, 2),
            "  price=", DoubleToString(res.price, _Digits));

      if(res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED)
      {
         Print("SUCCESS: order accepted.");
         return;
      }

      // Retcode-specific hints - do not blindly retry these.
      if(res.retcode == TRADE_RETCODE_INVALID_STOPS)
      {
         Print("Invalid stops: your SL/TP is too close. Broker minimum is ",
               SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL), " points.");
         return;
      }
      if(res.retcode == TRADE_RETCODE_NO_MONEY)
      {
         Print("Not enough margin. Free margin: ",
               DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2));
         return;
      }
      Print("Rejected with retcode ", res.retcode, " - check the retcode table.");
      return;
   }
}
如何執行
  1. 1. 從 MT5 開啟 MetaEditor(按 F4), 選擇這支程式該放的資料夾——ExpertsIndicators Scripts
  2. 2. 開新檔案、把這段程式貼上去,然後按 F7 編譯。Errors 分頁顯示的錯誤 都修掉。
  3. 3. 回到 MT5,把它拖到圖表上——如果是 EA,就打開 Strategy Tester

在 CTrade 底下的原始請求

CTrade 很方便,但你應該看一次裡面的機械。每一張單都是一個 MqlTradeRequest struct,傳給 OrderSend,然後填入一個 MqlTradeResult

重要的欄位

  • action——TRADE_ACTION_DEAL(市價)、TRADE_ACTION_PENDINGTRADE_ACTION_SLTPTRADE_ACTION_MODIFYTRADE_ACTION_REMOVE
  • type——ORDER_TYPE_BUYORDER_TYPE_SELL,加上掛單的各種變體。
  • volume——正規化到商品的級距。
  • price——買單用 ask、賣單用 bid。市價單你可以傳 0,讓伺服器去填。
  • sltp——絕對價格,正規化到小數位數。
  • deviation——滑價容許度,單位是 point
  • type_filling——ORDER_FILLING_FOKORDER_FILLING_IOCORDER_FILLING_RETURN
  • magiccomment——你的識別碼,以及一段給紀錄看的註解。

成交模式是經典的拒絕原因

送錯 type_filling,你就會拿到 unsupported filling mode——而且通常正好發生在你最想交易的那家券商上。不要硬編它。去查 SYMBOL_FILLING_MODE,或讓 CTrade::SetTypeFillingBySymbol() 做這件事。這堂課展示手動的降級鏈,讓你看見那個 helper 在做什麼。

retcode 就是你的錯誤訊息

TRADE_RETCODE_DONE 代表成交了。TRADE_RETCODE_PLACED 代表掛單被接受。其他任何東西都是一個具體、有文件的失敗:重新報價、無效停損、沒錢、市場關閉、交易被停用。把代碼印出來然後查表——不要只是盲目重試,因為重試一個「停損太近」造成的拒絕,會永遠失敗。

關閉與修改

掛單用 TRADE_ACTION_REMOVE,已開部位用 CTrade::PositionClose。在避險帳戶上你關閉一個特定的 ticket;在淨額帳戶上你關閉該商品的單一淨部位。

你剛完成了什麼

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