MQL5 中的交易操作 - 很简单·进阶篇
(2/3)·从手写 OrderSend 到封装类调用,少踩坑的多单空单挂单实战拆解
◍ 用 CTrade 把下单从结构填表变成一行调用
MQL5 底层只靠 OrderSend() 与 OrderSendAsync() 两个函数发交易请求,二者都接收一个含十多个字段的 MqlTradeRequest 结构。其中 5 个字段必须取预定义枚举的正确值,填错或留空会直接拦在本地、请求根本不往服务器发。对外汇与贵金属这类高波动品种来说,手写这套结构极易因字段遗漏导致下单静默失败。 CTrade 类把上述细节封装掉了。以市价单为例,Buy(0.1) 不传品种时默认用当前图表品种,多货币策略则必须显式写 Buy(0.1,"GBPUSD")。限价单 BuyLimit 开盘价须低于时价、SellLimit 高于时价;止损单 BuyStop 反之。回测时若参数写反,CTrade 通常会自动打印错误,不用自己接日志。 初始化阶段就该把魔术数字与滑点钉死:SetExpertMagicNumber(123456) 与 SetDeviationInPoints(10) 这类调用一次即可,后续每笔单不再重复传。异步模式 SetAsyncMode(true) 适合不想阻塞 OnTick 的机器人,但订单回报要靠 ResultRetcode() 自行轮询。 持仓改 SL/TP 用 PositionModify(),挂单改参用 OrderModify()——后者必须带原订单 ticket,且新开盘价相对时价方向不能写反。OrderOpen() 是万能挂单入口,参数比专用方法多,习惯裸写结构的老手可能更顺手。
class="macro">#include<Trade\Trade.mqh> class=class="str">"cmt">//--- 执行交易操作的对象 CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA交易初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 设置MagicNumber来识别你的订单 class="type">int MagicNumber=class="num">123456; trade.SetExpertMagicNumber(MagicNumber); class=class="str">"cmt">//--- 当买/卖时,设置可用滑点 class="type">int deviation=class="num">10; trade.SetDeviationInPoints(deviation); class=class="str">"cmt">//--- 订单填充模式,要使用服务器允许的模式 trade.SetTypeFilling(ORDER_FILLING_RETURN); class=class="str">"cmt">//--- 日志模式:最好不要声明这个方法,类会自动选择最佳模式。 trade.LogLevel(class="num">1); class=class="str">"cmt">//--- 哪个函数用来交易:true - OrderSendAsync(), false - OrderSend() trade.SetAsyncMode(true); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//--- class="num">1. 在当前交易品种上进行买入的例子 if(!trade.Buy(class="num">0.1)) { class=class="str">"cmt">//--- 报错信息 Print("Buy() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("Buy() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">2. 在指定交易品种上进行买入的例子 if(!trade.Buy(class="num">0.1,"GBPUSD")) { class=class="str">"cmt">//--- 报错信息 Print("Buy() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("Buy() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">3. 指定交易品种和指定获利止损的买入例子 class="type">class="kw">double volume=class="num">0.1; class=class="str">"cmt">// 指定交易量 class="type">class="kw">string symbol="GBPUSD"; class=class="str">"cmt">//指定交易操作的交易品种
市价买入与限价挂单的参数拼装
在 MT5 用 CTrade 类下单前,先要把品种的小数位、点值、实时报价抓出来,否则止损止盈会被服务器拒掉。下面这段把 GBPUSD 的 digits 和 point 取出来后,用 bid±1000*point 算出裸止损获利,再 NormalizeDouble 到合规精度——外汇和贵金属杠杆高,价格跳空可能让止损失效,参数一定要按品种规范来。 [CODE]int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // 小数位数 double point=SymbolInfoDouble(symbol,SYMBOL_POINT); // 点值 double bid=SymbolInfoDouble(symbol,SYMBOL_BID); // 平买入持仓的当前价格 double SL=bid-1000*point; // 未标准化的止损值 SL=NormalizeDouble(SL,digits); // 标准化止损 double TP=bid+1000*point; // 未标准化的获利值 TP=NormalizeDouble(TP,digits); // 标准化获利 //--- 接收买入持仓的当前开仓价 double open_price=SymbolInfoDouble(symbol,SYMBOL_ASK); string comment=StringFormat("Buy %s %G lots at %s, SL=%s TP=%s", symbol,volume, DoubleToString(open_price,digits), DoubleToString(SL,digits), DoubleToString(TP,digits)); if(!trade.Buy(volume,symbol,open_price,SL,TP,comment)) { //--- 报错信息 Print("Buy() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("Buy() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } //--- 1. 下买入限价挂单的例子 string symbol="GBPUSD"; // 指定下单的交易品种 int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // 小数位数 double point=SymbolInfoDouble(symbol,SYMBOL_POINT); // 点值 double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); // 当前买入价 double price=1000*point; // 未标准化的开盘价 price=NormalizeDouble(price,digits); // 标准化开盘价 //--- 一切准备就绪,将买入限价挂单发送到服务器 if(!trade.BuyLimit(0.1,price)) {[/CODE] 逐行看:SymbolInfoInteger 拿 SYMBOL_DIGITS 是避免 5 位平台写出 1.234567 这种非法价格;SL/TP 用 ±1000*point 意味着距市价 1000 点,GBPUSD 点值若 0.0001 就是 0.0100 的跨度,回测里这种宽止损倾向降低被噪音扫掉的概率。BuyLimit 只传了 0.1 手和 price,说明其余字段用了类内默认值,实盘前建议显式填 SL/TP 注释。 把 symbol 写死成 "GBPUSD" 后,digits 和 point 重新取一遍,说明每个订单前都要按品种刷新精度。开 MT5 按 F4 贴这段代码,改 symbol 和 1000 系数,能直接验证不同品种的标准化结果。
class="type">int digits=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">// 小数位数 class="type">class="kw">double point=SymbolInfoDouble(symbol,SYMBOL_POINT); class=class="str">"cmt">// 点值 class="type">class="kw">double bid=SymbolInfoDouble(symbol,SYMBOL_BID); class=class="str">"cmt">// 平买入持仓的当前价格 class="type">class="kw">double SL=bid-class="num">1000*point; class=class="str">"cmt">// 未标准化的止损值 SL=NormalizeDouble(SL,digits); class=class="str">"cmt">// 标准化止损 class="type">class="kw">double TP=bid+class="num">1000*point; class=class="str">"cmt">// 未标准化的获利值 TP=NormalizeDouble(TP,digits); class=class="str">"cmt">// 标准化获利 class=class="str">"cmt">//--- 接收买入持仓的当前开仓价 class="type">class="kw">double open_price=SymbolInfoDouble(symbol,SYMBOL_ASK); class="type">class="kw">string comment=StringFormat("Buy %s %G lots at %s, SL=%s TP=%s", symbol,volume, DoubleToString(open_price,digits), DoubleToString(SL,digits), DoubleToString(TP,digits)); if(!trade.Buy(volume,symbol,open_price,SL,TP,comment)) { class=class="str">"cmt">//--- 报错信息 Print("Buy() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("Buy() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">1. 下买入限价挂单的例子 class="type">class="kw">string symbol="GBPUSD"; class=class="str">"cmt">// 指定下单的交易品种 class="type">int digits=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">// 小数位数 class="type">class="kw">double point=SymbolInfoDouble(symbol,SYMBOL_POINT); class=class="str">"cmt">// 点值 class="type">class="kw">double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); class=class="str">"cmt">// 当前买入价 class="type">class="kw">double price=class="num">1000*point; class=class="str">"cmt">// 未标准化的开盘价 price=NormalizeDouble(price,digits); class=class="str">"cmt">// 标准化开盘价 class=class="str">"cmt">//--- 一切准备就绪,将买入限价挂单发送到服务器 if(!trade.BuyLimit(class="num">0.1,price)) {
「带完整参数的 BuyLimit 挂单落地」
在 MT5 里用 CTrade 类下买入限价单,光写 trade.BuyLimit() 不够,价格、止损、获利都必须按品种精度归一化,否则服务器会直接拒单。下面这段例子把 GBPUSD 的挂单参数一次性算齐:0.1 手、挂单价取 1000 倍 point、止损 300 点、获利 500 点、过期时间设为服务端时间加一根 D1 周期秒数。 注意 price=1000*point 只是演示用的未标准化价,必须用 NormalizeDouble(price,digits) 收口;SL 和 TP 同理,不然和 SymbolInfoInteger 取到的 SYMBOL_DIGITS 对不上,返码会报错而非成交。 回测或实盘里若返回非零 Retcode,优先 Print 出 ResultRetcodeDescription() 看原文描述,比查文档快。外汇与贵金属杠杆高,挂单未成交前也可能因跳空直接触发,实盘使用前先在策略测试器跑一遍。
class="type">class="kw">double volume=class="num">0.1; class="type">class="kw">string symbol="GBPUSD"; class=class="str">"cmt">// 指定下单的交易品种 class="type">int digits=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">// 小数位数 class="type">class="kw">double point=SymbolInfoDouble(symbol,SYMBOL_POINT); class=class="str">"cmt">// 点值 class="type">class="kw">double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); class=class="str">"cmt">// 当前买入价 class="type">class="kw">double price=class="num">1000*point; class=class="str">"cmt">// 未标准化的开盘价 price=NormalizeDouble(price,digits); class=class="str">"cmt">// 标准化开盘价 class="type">int SL_pips=class="num">300; class=class="str">"cmt">// 止损点数 class="type">int TP_pips=class="num">500; class=class="str">"cmt">// 获利点数 class="type">class="kw">double SL=price-SL_pips*point; class=class="str">"cmt">// 未标准化的止损值 SL=NormalizeDouble(SL,digits); class=class="str">"cmt">// 标准化止损 class="type">class="kw">double TP=price+TP_pips*point; class=class="str">"cmt">// 未标准化的获利值 TP=NormalizeDouble(TP,digits); class=class="str">"cmt">// 标准化获利 class="type">class="kw">datetime expiration=TimeTradeServer()+PeriodSeconds(PERIOD_D1); class="type">class="kw">string comment=StringFormat("Buy Limit %s %G lots at %s, SL=%s TP=%s", symbol,volume, DoubleToString(price,digits), DoubleToString(SL,digits), DoubleToString(TP,digits)); if(!trade.BuyLimit(volume,price,symbol,SL,TP,ORDER_TIME_GTC,expiration,comment)) { Print("BuyLimit() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("BuyLimit() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); }
◍ 用 BuyStop 挂单把止损止盈一次写死
在 CTrade 封装里,BuyStop 有两种用法:只传 volume 和 price 的简化版,以及把 SL、TP、偏离、过期时间全塞进去的完备版。下面这段直接以 USDJPY 为例,先取品种小数位和 point,再把挂单价定为离市价 1000 个 point 的位置,最后 NormalizeDouble 到该品种精度——外汇与贵金属杠杆高,挂单价格不规范会直接被服务器拒单。 简化调用里 volume=0.1、price 由 1000*point 算出并标准化;若 trade.BuyStop 返回 false,就打印 ResultRetcode 和 Description,成功则回显状态码。想验证就开 MT5 把 symbol 改成自己盯的币种,跑完看 Experts 日志里的 Return code 是不是 0。 完备版多了 SL_pips=300、TP_pips=500 两个变量,意味着止损 300 点、获利 500 点随挂单一起提交。回测或实盘里这类参数最好按 ATR 倍数动态算,硬编码 300/500 只适合震荡市,趋势行情可能被扫损。
Print("BuyLimit() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("BuyLimit() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">1. 下买入停止挂单的例子 class="type">class="kw">string symbol="USDJPY"; class=class="str">"cmt">// 指定下单的交易品种 class="type">int digits=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">// 小数位数 class="type">class="kw">double point=SymbolInfoDouble(symbol,SYMBOL_POINT); class=class="str">"cmt">// 点值 class="type">class="kw">double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); class=class="str">"cmt">// 当前买入价 class="type">class="kw">double price=class="num">1000*point; class=class="str">"cmt">// 未标准化的开盘价 price=NormalizeDouble(price,digits); class=class="str">"cmt">// 标准化开盘价 class=class="str">"cmt">//--- 一切准备就绪,将买入停止挂单发送到服务器 if(!trade.BuyStop(class="num">0.1,price)) { class=class="str">"cmt">//--- 报错信息 Print("BuyStop() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("BuyStop() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">2. 下指定所有参数的买入停止挂单的例子 class="type">class="kw">double volume=class="num">0.1; class="type">class="kw">string symbol="USDJPY"; class=class="str">"cmt">// 指定下单的交易品种 class="type">int digits=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">// 小数位数 class="type">class="kw">double point=SymbolInfoDouble(symbol,SYMBOL_POINT); class=class="str">"cmt">// 点值 class="type">class="kw">double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); class=class="str">"cmt">// 当前买入价 class="type">class="kw">double price=class="num">1000*point; class=class="str">"cmt">// 未标准化的开盘价 price=NormalizeDouble(price,digits); class=class="str">"cmt">// 标准化开盘价 class="type">int SL_pips=class="num">300; class=class="str">"cmt">// 止损点数 class="type">int TP_pips=class="num">500; class=class="str">"cmt">// 获利点数
挂单与市价单的下单收口逻辑
把止损获利算出来之后,直接丢给服务器前一定要过一遍 NormalizeDouble。MT5 里 SYMBOL_DIGITS 对 XAUUSD 通常是 2、对 EURUSD 是 5,若不标准化,price-1000*point 可能算出 1900.123456 这种脏值,下单请求会被拒。 下面这段先挂 Buy Stop:SL 取卖价减去 SL_pips 个点、TP 取加上 TP_pips 个点,都 NormalizeDouble 到 digits 位;expiration 用 TimeTradeServer()+PeriodSeconds(PERIOD_D1) 留一个次日失效。注释串用 StringFormat 拼好品种、手数、价格、SL、TP,最后 trade.BuyStop 发单,失败就 Print 回 retcode 和描述。 [CODE] double SL=price-SL_pips*point; // 未标准化的止损值 SL=NormalizeDouble(SL,digits); // 标准化止损 double TP=price+TP_pips*point; // 未标准化的获利值 TP=NormalizeDouble(TP,digits); // 标准化获利 datetime expiration=TimeTradeServer()+PeriodSeconds(PERIOD_D1); string comment=StringFormat("Buy Stop %s %G lots at %s, SL=%s TP=%s", symbol,volume, DoubleToString(price,digits), DoubleToString(SL,digits), DoubleToString(TP,digits)); //--- 一切准备就绪,将买入停止挂单发送到服务器 if(!trade.BuyStop(volume,price,symbol,SL,TP,ORDER_TIME_GTC,expiration,comment)) { //--- 报错信息 Print("BuyStop() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("BuyStop() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } [/CODE] 市价单更直白:digits 和 point 从 SymbolInfoInteger / SymbolInfoDouble 拿,price 取 SYMBOL_ASK;SL、TP 写死 1000 点距做演示,NormalizeDouble 收尾后 trade.PositionOpen 吃 0.1 手。外汇和贵金属杠杆高,1000 点距在黄金上约 10 美元每标准手,实盘请按自己风控改。 [CODE] //--- 小数位数 int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); //--- 点值 double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); //--- 接收买入价格 double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); //--- 计算并标准化止损和获利 double SL=NormalizeDouble(price-1000*point,digits); double TP=NormalizeDouble(price+1000*point,digits); //--- 填充备注 string comment="Buy "+_Symbol+" 0.1 at "+DoubleToString(price,digits); //--- 一切就绪,尝试建立买入持仓 if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,0.1,price,SL,TP,comment)) { //--- 报错信息 Print("PositionOpen() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("PositionOpen() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } [/CODE] 开 MT5 新建 EA 把两段分别跑一下,看 Experts 日志里 retcode=0 是不是都打出来;若返回 4753 之类,基本就是 digits 没对齐或点距超品种限制。
class="type">class="kw">double SL=price-SL_pips*point; class=class="str">"cmt">// 未标准化的止损值 SL=NormalizeDouble(SL,digits); class=class="str">"cmt">// 标准化止损 class="type">class="kw">double TP=price+TP_pips*point; class=class="str">"cmt">// 未标准化的获利值 TP=NormalizeDouble(TP,digits); class=class="str">"cmt">// 标准化获利 class="type">class="kw">datetime expiration=TimeTradeServer()+PeriodSeconds(PERIOD_D1); class="type">class="kw">string comment=StringFormat("Buy Stop %s %G lots at %s, SL=%s TP=%s", symbol,volume, DoubleToString(price,digits), DoubleToString(SL,digits), DoubleToString(TP,digits)); class=class="str">"cmt">//--- 一切准备就绪,将买入停止挂单发送到服务器 if(!trade.BuyStop(volume,price,symbol,SL,TP,ORDER_TIME_GTC,expiration,comment)) { class=class="str">"cmt">//--- 报错信息 Print("BuyStop() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("BuyStop() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- 小数位数 class="type">int digits=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); class=class="str">"cmt">//--- 点值 class="type">class="kw">double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); class=class="str">"cmt">//--- 接收买入价格 class="type">class="kw">double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); class=class="str">"cmt">//--- 计算并标准化止损和获利 class="type">class="kw">double SL=NormalizeDouble(price-class="num">1000*point,digits); class="type">class="kw">double TP=NormalizeDouble(price+class="num">1000*point,digits); class=class="str">"cmt">//--- 填充备注 class="type">class="kw">string comment="Buy "+_Symbol+" class="num">0.1 at "+DoubleToString(price,digits); class=class="str">"cmt">//--- 一切就绪,尝试建立买入持仓 if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,class="num">0.1,price,SL,TP,comment)) { class=class="str">"cmt">//--- 报错信息 Print("PositionOpen() method failed. Return code=",trade.ResultRetcode(), ". Code description: ",trade.ResultRetcodeDescription()); } else { Print("PositionOpen() method executed successfully. Return code=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); }