了解使用MQL5下单·进阶篇
📈

了解使用MQL5下单·进阶篇

(2/3)· 从术语混淆到两种下单API实战,避开手写交易系统的常见坑

实战向 第 2/3 篇
把订单、交易、仓位三个词混着用,是手写EA时最隐蔽的bug源。不少人直接调用下单函数却从没理清挂单和成交的区别,回测和实盘表现差出一截。外汇贵金属杠杆高,逻辑没闭环前别急着上真金。

「挂单改价与撤单的发送结构」

在 MT5 里改挂单的止盈和有效期,靠的是同一份 MqlTradeRequest 复用:先把 tp 设成 1.07150,type_time 切到 ORDER_TIME_SPECIFIED,再给 expiration 填 D'2023.09.01 00.00',随后 OrderSend 一次就能把原挂单参数刷掉。 想直接删挂单也不复杂,把 action 改成 TRADE_ACTION_REMOVE,order 填原 ticket,再调一次 OrderSend 即可。两次调用共用一个 request 和 result 变量,顺序错了会动到不该动的单。 MqlTradeResult 是 broker 回执的落地结构,retcode 告诉你成没成,deal 和 order 分别是成交与挂单的 ticket,volume、price、bid、ask 都是经纪商确认后的真实值。下面这段逐行拆一下关键字段与判断。 返回后别只看 Print,实战里应优先判 retcode == TRADE_RETCODE_DONE 或 TRADE_RETCODE_PLACED 才认作成功;其余码直接打错误号,外汇和贵金属杠杆高,发送失败不处理可能让挂单残留在不利价位。

MQL5 / C++
request.tp = class="num">1.07150;                                                                     class=class="str">"cmt">//new take profit
request.type_time = ORDER_TIME_SPECIFIED;                                                    class=class="str">"cmt">//to set an expiration time
request.expiration = D&class="macro">#x27;class="num">2023.09.class="num">01 class="num">00.00&class="macro">#x27;;                                                    class=class="str">"cmt">//new expiration time - class="type">class="kw">datetime constant
OrderSend(request, result);                                                                  class=class="str">"cmt">//calling the OrderSend function
request.action = TRADE_ACTION_REMOVE;                                                        class=class="str">"cmt">//pending order remove
request.order = ticket;                                                                      class=class="str">"cmt">//ticket variable that holds the pending order ticket to remove
OrderSend(request, result);                                                                  class=class="str">"cmt">//calling the OrderSend function
class="kw">struct class="type">MqlTradeResult
  {
   class="type">uint     retcode;         class=class="str">"cmt">// Operation class="kw">return code
   class="type">class="kw">ulong    deal;            class=class="str">"cmt">// Deal ticket, if it is performed
   class="type">class="kw">ulong    order;           class=class="str">"cmt">// Order ticket, if it is placed
   class="type">class="kw">double   volume;          class=class="str">"cmt">// Deal volume, confirmed by broker
   class="type">class="kw">double   price;           class=class="str">"cmt">// Deal price, confirmed by broker
   class="type">class="kw">double   bid;             class=class="str">"cmt">// Current Bid price
   class="type">class="kw">double   ask;             class=class="str">"cmt">// Current Ask price
   class="type">class="kw">string   comment;         class=class="str">"cmt">// Broker comment to operation(by class="kw">default it is filled by description of trade server class="kw">return code)
   class="type">uint     request_id;      class=class="str">"cmt">// Request ID set by the terminal during the dispatch 
   class="type">int      retcode_external; class=class="str">"cmt">// Return code of an external trading system
  };
class="type">MqlTradeResult result;
   if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED)
    {
     Print("Trade Placed Successfully");
    }
   else
    {
     Print("Trade Not Placed, Error ", result.retcode);
    }

◍ 用 OrderSend 手搓 MA 交叉下单

想在 MT5 里跑一个最朴素的均线交叉系统,不靠 CTrade 封装类,直接调 OrderSend() 也行。下面这套逻辑只做一件事:H1 周期、50 期简单均线,当现价上穿均线开买、下穿开卖,纯市价单。 全局先留两个 int 坑位:simpleMA 接 iMA 句柄,barsTotal 接 iBars 返回的柱数,都在 OnInit 里填。iMA 参数这么写——_Symbol 锁当前品种、PERIOD_H1 锁一小时图、50 是均线周期、0 是偏移、MODE_SMA 是简单算法、PRICE_CLOSE 用收盘价算。 OnTick 里先备数组:MqlRates 存价量价差,double 存均线值;ArraySetAsSeries 都置 true 让索引从右往左。CopyRates 拉 3 根、CopyBuffer 拉均线缓冲 3 个,取 [1] 为最近收、[2] 为前一根,分别比收盘价和 SMA 值。 barsTotal 不等于实时 bars 就更新,避免重复触发。交叉判定用 prevClose<prevSMAVal && lastClose>SMAVal 开买,反过来开卖。请求结构体里 volume 写死 0.1、sl 距 ask 500 点、tp 距 ask 1000 点、deviation 50、填充方式 FOK——外汇贵金属杠杆高,这类硬编码止损止盈在跳空时可能不成交,上机前先开模拟盘验。 代码里能看到 request 只 Reset 了一次 ZeroMemory,买卖分支共用同个结构体,卖单分支没重写 price 和 sl/tp,直接发容易把买单价挂进去,复制去改的时候记得补 request.price = Bid 和对应的卖单 sl/tp 算式。

MQL5 / C++
class="type">int simpleMA;
class="type">int barsTotal;
   simpleMA = iMA(_Symbol, PERIOD_H1, class="num">50, class="num">0, MODE_SMA, PRICE_CLOSE);
   barsTotal=iBars(_Symbol,PERIOD_H1);
   class="type">MqlRates priceArray[];
   class="type">class="kw">double mySMAArray[];
   class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
ZeroMemory(request);
   ArraySetAsSeries(priceArray,true);
   ArraySetAsSeries(mySMAArray,true);
class="type">int Data=CopyRates(_Symbol,_Period,class="num">0,class="num">3,priceArray);
CopyBuffer(simpleMA,class="num">0,class="num">0,class="num">3,mySMAArray);
   class="type">class="kw">double lastClose=(priceArray[class="num">1].close);
   class="type">class="kw">double prevClose=(priceArray[class="num">2].close);
   class="type">class="kw">double SMAVal = NormalizeDouble(mySMAArray[class="num">1],_Digits);
   class="type">class="kw">double prevSMAVal = NormalizeDouble(mySMAArray[class="num">2],_Digits);
class="type">int bars=iBars(_Symbol,PERIOD_H1);
if(barsTotal != bars)
barsTotal=bars;
if(prevClose<prevSMAVal && lastClose>SMAVal)
            request.action = TRADE_ACTION_DEAL;
            request.type = ORDER_TYPE_BUY;
            request.symbol = _Symbol;
            request.volume = class="num">0.1;
            request.type_filling = ORDER_FILLING_FOK;
            request.price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
            request.sl = Ask-(class="num">500*_Point);
            request.tp = Ask+(class="num">1000*_Point);
            request.deviation = class="num">50;
            OrderSend(request, result);
if(prevClose>prevSMAVal && lastClose<SMAVal)
            request.type = ORDER_TYPE_SELL;
            request.symbol = _Symbol;
            request.volume = class="num">0.1;
            request.type_filling = ORDER_FILLING_FOK;

用 H1 均线交叉触发市价单

这段逻辑把挂单思路换成了市价穿透:在 H1 周期上加载一条 50 期收盘价简单均线,只在每根新 K 线成型时检查前一根与当前根的收线位置相对均线的穿越。 OnInit 里先拿句柄:simpleMA = iMA(_Symbol, PERIOD_H1, 50, 0, MODE_SMA, PRICE_CLOSE),barsTotal 记录初始 K 线数。OnTick 每次先用 CopyRates 取最近 3 根、CopyBuffer 取均线最近 3 值,并将数组设为时间倒序,方便用 [1] 表示上一根、[2] 表示再前一根。 触发条件写得很直白:若 prevClose<prevSMAVal 且 lastClose>SMAVal,判为向上穿越,发 BUY;反之 prevClose>prevSMAVal 且 lastClose<SMAVal 则向下穿越发 SELL。用 barsTotal != bars 卡住「新柱才判断一次」,避免同根 K 线内重复下单。 风控参数固定在代码里:volume=0.1,sl 距入场 500 点、tp 距入场 1000 点,deviation=50,type_filling 用 FOK。外汇与贵金属杠杆品种跳空频繁,500 点止损在黄金 M1 可能一瞬扫掉,实盘前务必在 MT5 策略测试器用真实点差回测该穿越逻辑。

MQL5 / C++
class="type">int simpleMA;
class="type">int barsTotal;
class="type">int OnInit()
  {
   simpleMA = iMA(_Symbol, PERIOD_H1, class="num">50, class="num">0, MODE_SMA, PRICE_CLOSE);
   barsTotal=iBars(_Symbol,PERIOD_H1);
   class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnTick()
  {
   class="type">MqlRates priceArray[];
   class="type">class="kw">double mySMAArray[];
   class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   class="type">MqlTradeRequest request;
   class="type">MqlTradeResult result;
   ZeroMemory(request);
   ArraySetAsSeries(priceArray,true);
   ArraySetAsSeries(mySMAArray,true);
   class="type">int Data=CopyRates(_Symbol,_Period,class="num">0,class="num">3,priceArray);
   CopyBuffer(simpleMA,class="num">0,class="num">0,class="num">3,mySMAArray);
   class="type">class="kw">double lastClose=(priceArray[class="num">1].close);
   class="type">class="kw">double prevClose=(priceArray[class="num">2].close);
   class="type">class="kw">double SMAVal = NormalizeDouble(mySMAArray[class="num">1],_Digits);
   class="type">class="kw">double prevSMAVal = NormalizeDouble(mySMAArray[class="num">2],_Digits);
   class="type">int bars=iBars(_Symbol,PERIOD_H1);
   if(barsTotal != bars)
     {
      barsTotal=bars;
      if(prevClose<prevSMAVal && lastClose>SMAVal)
        {
         request.action = TRADE_ACTION_DEAL;
         request.type = ORDER_TYPE_BUY;
         request.symbol = _Symbol;
         request.volume = class="num">0.1;
         request.type_filling = ORDER_FILLING_FOK;
         request.price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         request.sl = Ask-(class="num">500*_Point);
         request.tp = Ask+(class="num">1000*_Point);
         request.deviation = class="num">50;
         OrderSend(request, result);
        }
      if(prevClose>prevSMAVal && lastClose<SMAVal)
        {

「用 FOK 模式挂一笔反向市价单」

下面这段 MT5 交易请求演示了如何在满足条件时直接以市价卖出,并同步带上止损与止盈。外汇与贵金属杠杆高,实盘前务必在策略测试器里跑通。 request.action = TRADE_ACTION_DEAL 表明这是一笔立即成交的市价单,而非挂单等待。request.type = ORDER_TYPE_SELL 指定方向为做空,request.symbol = _Symbol 则自动绑定当前图表品种,避免写死符号。 volume 设为 0.1 手,type_filling = ORDER_FILLING_FOK 意味着要么按请求量全部成交、要么不成交,不会被部分撮合。price 取 SYMBOL_BID 即时买价,deviation = 50 允许 50 点内滑点。 sl = Bid + 500*_Point 把止损放在现价上方 500 点,tp = Bid - 1000*_Point 止盈放下方 1000 点,风险回报比约 1:2。最后 OrderSend(request, result) 发出指令,result 结构里可读取 RETCODE 判断成败。

MQL5 / C++
   request.action = TRADE_ACTION_DEAL;
   request.type = ORDER_TYPE_SELL;
   request.symbol = _Symbol;
   request.volume = class="num">0.1;
   request.type_filling = ORDER_FILLING_FOK;
   request.price = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   request.sl = Bid+(class="num">500*_Point);
   request.tp = Bid-(class="num">1000*_Point);
   request.deviation = class="num">50;
   OrderSend(request, result);
   }
  }
 }

◍ 用 CTrade 对象替掉裸调 OrderSend

在 MT5 安装目录的 include\Trade 下能找到 Trade.mqh,EA 里一句 #include <Trade\Trade.mqh> 再声明 CTrade trade; 就能把整套下单、改仓、挂单逻辑收进一个对象,不用再手写 OrderSend 的参数包。 市场单直接用 trade.PositionOpen,参数依次是品种、买卖方向、手数、挂单价、SL、TP、备注。例子里用 _Symbol 配合 Ask 开 0.1 手多单,SL 放在 Ask 减 500 点、TP 放在 Ask 加 1000 点,这种写法比裸调 OrderSend 少一层结构体封装。 改已有仓位走 trade.PositionModify,可以按品种名也可以按 ticket 改。示例里把 EURUSD 的 SL 挪到 1.06950、TP 放到 1.07100,若传 ticket 变量则只动那一张单。 挂单交给 trade.OrderOpen,比市价单多了 limit_price、type_time、expiration。代码片段里下了一张 ORDER_TYPE_BUY_LIMIT,执行价 1.07000,过期类型 ORDER_TIME_SPECIFIED 且指定 2023.08.31 00.00 作废,这类限价单在震荡行情里可能比追市价更省滑点。 修改和删除挂单分别用 OrderModify 与 OrderDelete,前者重设执行价、SL/TP 和过期,后者只吃一个 ticket。外汇与贵金属杠杆高,用类封装降低出错率不等于降低风险,参数手误仍可能瞬间放大回撤。

MQL5 / C++
class="macro">#include <Trade\Trade.mqh>
CTrade trade;
trade.PositionOpen(
   _Symbol,               class=class="str">"cmt">//to be applied for the current symbol
   ORDER_TYPE_BUY,        class=class="str">"cmt">//to place buy order
   class="num">0.1,                   class=class="str">"cmt">//lot size or volume
   Ask,                   class=class="str">"cmt">//opening price of the order - current ask
   Ask-(class="num">500*_Point),      class=class="str">"cmt">//sl
   Ask+(class="num">1000*_Point),     class=class="str">"cmt">//tp
   NULL                   class=class="str">"cmt">//NULL
);
trade.PositionModify(
   EURUSD,                class=class="str">"cmt">//the symbol name
   class="num">1.06950,               class=class="str">"cmt">//the new sl
   class="num">1.07100,               class=class="str">"cmt">//the new tp
);
trade.PositionModify(
   ticket,                class=class="str">"cmt">//the ticket variable that holds the needed ticket number to modify
   class="num">1.06950,               class=class="str">"cmt">//the new sl
   class="num">1.07100,               class=class="str">"cmt">//the new tp
);
        trade.OrderOpen(
            "EURUSD",               class=class="str">"cmt">// symbol
            ORDER_TYPE_BUY_LIMIT,   class=class="str">"cmt">// order type
            class="num">0.1,                    class=class="str">"cmt">// order volume
            class="num">0,                      class=class="str">"cmt">// StopLimit price
            class="num">1.07000,                class=class="str">"cmt">// execution price
            class="num">1.06950,                class=class="str">"cmt">// Stop Loss price
            class="num">1.07100,                class=class="str">"cmt">// Take Profit price
            ORDER_TIME_SPECIFIED,   class=class="str">"cmt">// type by expiration
            D&class="macro">#x27;class="num">2023.08.class="num">31 class="num">00.00&class="macro">#x27;,    class=class="str">"cmt">// expiration
            ""                      class=class="str">"cmt">// comment
        );
交给小布盯盘看盘口
这些订单状态诊断小布盯盘的AIGC已内置,打开对应品种页即可看到挂单与成交的实时分布,你专注决策就好。

常见问题

市场订单走即时成交通道,挂单需指定触发价与有效期;OrderSend参数里type字段决定二者,写错类型会直接返回错误码。
CTrade封装了校验与重试,少写样板代码;但进阶场景仍需读返回结果做风控,不能无脑封装。
MT5中改仓本质是新订单请求,需按品种与ticket定位原仓再发修单指令,术语错位会导致改到别的位置。
可以,小布的品种页会聚合当前挂单与成交,异常缺失一眼可辨,省去自己轮询交易池的重复劳动。
Buy limit倾向回调接多,Sell stop倾向破位追空;具体触发概率看价格行为,没有必然胜率。