了解使用MQL5下单·进阶篇
(2/3)· 从术语混淆到两种下单API实战,避开手写交易系统的常见坑
「挂单改价与撤单的发送结构」
在 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 才认作成功;其余码直接打错误号,外汇和贵金属杠杆高,发送失败不处理可能让挂单残留在不利价位。
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 算式。
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 策略测试器用真实点差回测该穿越逻辑。
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 判断成败。
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。外汇与贵金属杠杆高,用类封装降低出错率不等于降低风险,参数手误仍可能瞬间放大回撤。
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 );