为莫斯科交易所开发一个交易机器人从哪里开始呢?·综合运用
(3/3)· 前两篇铺垫语言与类库,本篇把市价限价、止损挂单与交易类串成可跑的自动化闭环
◍ 用 CTrade 把下单逻辑压成几行调用
MQL5 里发单底层就两个入口:OrderSend() 同步等回执,OrderSendAsync() 只丢请求不阻塞。两者首个参数都是 MqlTradeRequest 结构,栏位按交易类型填,填错或漏必填项会在终端校验阶段直接驳回,连服务器都碰不到。 真正写 EA 没必要手搓结构,CTrade 类把市价、挂单、改仓、删单都封装好了。以当前价进场只要给交易量,品种名省略时默认用图表品种;多品种 EA 则每次显式传 symbol。 幻数和滑点是在 CTrade 实例初始化时定的。下面这段 OnInit 里设了幻数 123456、滑点 10 点、填充模式 ORDER_FILLING_RETURN,并开了异步模式,回测或实盘直接套用就能跑。 莫斯科交易所系品种常用 ORDER_FILLING_RETURN:市价单部分成交后剩余挂单不撤,继续排队;BUY_STOP_LIMIT/SELL_STOP_LIMIT 触发后会生成对应 RETURN 模式的限价单。外汇与贵金属杠杆高、滑点跳空频繁,用错填充模式可能让部分成交单滞留,实盘前务必在策略测试器核对品种属性。 Buy() 调失败时用 ResultRetcode() 和 ResultRetcodeDescription() 打错误码,比盲猜高效。下面代码里两种 Buy 写法(默认品种 / 指定 Si-6.16)都带了成功失败分支,复制进 MT5 能立刻看到日志输出。
class="macro">#include<Trade\Trade.mqh> class=class="str">"cmt">//--- 进行交易操作的对象 CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 设置用于标识EA订单的幻数 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(), class="kw">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">1)) { class=class="str">"cmt">//--- 报告失败 Print("Buy() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("The Buy() method has been successfully performed. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">2. 一个买入指定交易品种的示例 if(!trade.Buy(class="num">1,"Si-class="num">6.16")) { class=class="str">"cmt">//--- 错误报告 Print("Buy() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("The Buy() method has been successfully performed. 返回代码=",trade.ResultRetcode(),
市价买入与挂单的前置参数拼装
在 MT5 里用 CTrade 类下买单前,得先把交易品种的小数位和点数抓出来,否则止损获利算出来会是脏值。下面这段以 Si-6.16(俄交所白银期货连续)为例,volume 写死 1 手,实际跑的时候建议从仓位管理模块传参,别硬编。 digits 用 SymbolInfoInteger 拿 SYMBOL_DIGITS,point 用 SymbolInfoDouble 拿 SYMBOL_POINT;这两行不写,后面 NormalizeDouble 就没基准。注意外汇贵金属高风险,Si 这类合约杠杆和跳空都凶,100 点止损在白银上可能只是噪音。 SL 和 TP 先按 bid±100*point 粗算,再各自 NormalizeDouble 到 digits 位;不规范化的话,服务器可能拒单或自动取整。open_price 取 SYMBOL_ASK,因为买入建仓吃的是卖一价。 注释里还留了个 BuyLimit 挂单的入口,symbol 和 digits、point 的取法跟市价单一模一样,只是后面挂单价要低于 ask,这一节没给全,下一段接着拆。
class="type">class="kw">double volume=class="num">1; class=class="str">"cmt">// 指定交易操作的交易量 class="type">class="kw">string symbol="Si-class="num">6.16"; 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 bid=SymbolInfoDouble(symbol,SYMBOL_BID); class=class="str">"cmt">// 当前的买入平仓价格 class="type">class="kw">double SL=bid-class="num">100*point; class=class="str">"cmt">// 未规范化的止损值 SL=NormalizeDouble(SL,digits); class=class="str">"cmt">// 规范化止损 class="type">class="kw">double TP=bid+class="num">100*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("买入 %s %G 手, 价位 %s, 止损=%s 获利=%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() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("The Buy() method has been successfully performed. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">1. 设置 BuyLimit 挂单的实例 class="type">class="kw">string symbol="Si-class="num">6.16"; 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">// 点数
「限价买单的价格规范化与全参数挂单」
在 MT5 里用 BuyLimit 挂单,最容易被忽略的一步是价格规范化。直接拿 ask 减去 100 个点得到的 price,如果不过 NormalizeDouble 处理,可能因小数位不匹配被服务器拒单,尤其对外汇和贵金属这类高风险品种,报价精度错一位就全废。 下面这段是最小化发单逻辑:取 SYMBOL_ASK,减 100*point 得到挂单价,规范化后调用 trade.BuyLimit(1, price),失败就打印 ResultRetcode 和描述,成功也回打代码便于跟踪。 double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); // 当前买入价格 double price=ask-100*point; // 未规范化的建仓价格 price=NormalizeDouble(price,digits); // 规范化建仓价格 //--- 一切准备工作就绪,向服务器发出限价买入挂单 if(!trade.BuyLimit(1,price)) { //--- 错误报告 Print("BuyLimit() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("The BuyLimit() method has been successfully performed. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } 实际策略里往往要带 SL/TP 和过期时间。第二段示例用 Si-6.16 合约,digits 从 SYMBOL_DIGITS 取,SL 和 TP 各设 100 点,都用 NormalizeDouble 收口;expiration 设为服务器时间加一个 D1 周期,避免挂单永久漂浮。 double volume=1; string symbol="Si-6.16"; // 指定订单的交易品种 int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // 小数点位数 double point=SymbolInfoDouble(symbol,SYMBOL_POINT); // 点数 double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); // 当前买入价格 double price=ask-100*point; // 未规范化的建仓价格 price=NormalizeDouble(price,digits); // 规范化建仓价格 int SL_pips=100; // 止损点数 int TP_pips=100; // 获利点数 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); 开 MT5 把 symbol 换成你盯的 XAUUSD 或欧美对,改掉那 100 点偏移,跑一遍就能看到返回码 10009(成功)或具体报错,比看文档直观。
class="type">class="kw">double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); class=class="str">"cmt">// 当前买入价格 class="type">class="kw">double price=ask-class="num">100*point; class=class="str">"cmt">// 未规范化的建仓价格 price=NormalizeDouble(price,digits); class=class="str">"cmt">// 规范化建仓价格 class=class="str">"cmt">//--- 一切准备工作就绪,向服务器发出限价买入挂单 if(!trade.BuyLimit(class="num">1,price)) { class=class="str">"cmt">//--- 错误报告 Print("BuyLimit() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("The BuyLimit() method has been successfully performed. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">2. 使用全部参数设置 BuyLimit 挂单的示例 class="type">class="kw">double volume=class="num">1; class="type">class="kw">string symbol="Si-class="num">6.16"; 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=ask-class="num">100*point; class=class="str">"cmt">// 未规范化的建仓价格 price=NormalizeDouble(price,digits); class=class="str">"cmt">// 规范化建仓价格 class="type">int SL_pips=class="num">100; class=class="str">"cmt">// 止损点数 class="type">int TP_pips=class="num">100; 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);
◍ 限价与止损买入挂单的发送写法
在 MT5 里用 CTrade 类发挂单,先拼好 comment 字符串再调 BuyLimit,能让日志直接读出品种、手数、挂单价和止损获利。下面这段把 RTS-6.16 的限价买入参数格式化后送出,失败时用 ResultRetcodeDescription 打明原因,比光看返回码直观。 string comment=StringFormat("限价买入 %s %G 手,价位 %s, 止损=%s 获利=%s", symbol,volume, DoubleToString(price,digits), DoubleToString(SL,digits), DoubleToString(TP,digits)); //--- 一切准备工作就绪,向服务器发送限价买入挂单 if(!trade.BuyLimit(volume,price,symbol,SL,TP,ORDER_TIME_DAY,expiration,comment)) { //--- 错误报告 Print("BuyLimit() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("BuyLimit() 方法运行成功. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } 止损买入和限价买入只差一个触发逻辑:BuyStop 挂在 ask 上方 100 点,必须先用 NormalizeDouble 按品种 digits 规范化价格,否则可能因精度不符被服务器拒单。RTS-6.16 这类合约的小数位和 point 值要现查,不能写死。 //--- 1. 设置止损买入挂单的示例 string symbol="RTS-6.16"; // 指定订单的交易品种 int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); // 小数位数 double point=SymbolInfoDouble(symbol,SYMBOL_POINT); // 点数 double ask=SymbolInfoDouble(symbol,SYMBOL_ASK); // 当前买入价格 double price=ask+100*point; // 未规范化的建仓价格 price=NormalizeDouble(price,digits); // 规范化建仓价格 //--- 一切准备工作就绪,把止损买入挂单发送给服务器 if(!trade.BuyStop(1,price)) { //--- 错误报告 Print("BuyStop() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("BuyStop() 方法运行成功. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } 带全套参数的 BuyStop 调用要把 volume、SL、TP、有效期都传齐,demo 里 volume 写 1 手仅作演示。外汇和贵金属挂单同样适用这套结构,但点差和到期时间受经纪商限制,实际发送前建议先打印 digits 和 point 核对,贵金属杠杆高、滑点大,挂单成交价可能偏离预期。
class="type">class="kw">string comment=StringFormat("限价买入 %s %G 手,价位 %s, 止损=%s 获利=%s", symbol,volume, DoubleToString(price,digits), DoubleToString(SL,digits), DoubleToString(TP,digits)); class=class="str">"cmt">//--- 一切准备工作就绪,向服务器发送限价买入挂单 if(!trade.BuyLimit(volume,price,symbol,SL,TP,ORDER_TIME_DAY,expiration,comment)) { class=class="str">"cmt">//--- 错误报告 Print("BuyLimit() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("BuyStop() 方法运行成功. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- class="num">1. 设置止损买入挂单的示例 class="type">class="kw">string symbol="RTS-class="num">6.16"; 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=ask+class="num">100*point; class=class="str">"cmt">// 未规范化的建仓价格 price=NormalizeDouble(price,digits); class=class="str">"cmt">// 规范化建仓价格 class=class="str">"cmt">//--- 一切准备工作就绪,把止损买入挂单发送给服务器 if(!trade.BuyStop(class="num">1,price)) { class=class="str">"cmt">//--- 错误报告 Print("BuyStop() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("BuyStop() 方法运行成功. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); }
挂止损买入前先把价格规整好
在 MT5 里用 BuyStop 挂单,最容易被忽略的一步是价格规范化。EURUSD 这类五位数品种,SYMBOL_POINT 通常是 1e-5,若直接拿 ask+100*point 当挂单价,浮点误差可能让价格多出一位无效小数,经纪商端会直接拒单。 double price=ask+100*point; price=NormalizeDouble(price,digits); 上面两行就是先算未规范化建仓价,再用品种小数位 digits 收口。digits 来自 SymbolInfoInteger(_Symbol,SYMBOL_DIGITS),对多数外汇对是 4 或 5,贵金属 XAUUSD 常见 2。 止损获利同理:SL=price-SL_pips*point 后必须 NormalizeDouble 一次,否则挂单参数里的止损价可能落在非报价精度上。下面这段是完整拼装与发送: datetime expiration=TimeTradeServer()+PeriodSeconds(PERIOD_D1); string comment=StringFormat("止损买入 %s %G 手,价位 %s, 止损=%s 获利=%s", symbol,volume,DoubleToString(price,digits),DoubleToString(SL,digits),DoubleToString(TP,digits)); if(!trade.BuyStop(volume,price,symbol,SL,TP,ORDER_TIME_DAY,expiration,comment)) Print("BuyStop() 方法失败. 返回代码=",trade.ResultRetcode(),". 代码描述: ",trade.ResultRetcodeDescription()); else Print("BuyStop() 方法运行成功。返回代码=",trade.ResultRetcode()," (",trade.ResultRetcodeDescription(),")"); ORDER_TIME_DAY 配合 expiration 设的是挂单当日有效,超时自动撤销。外汇与贵金属杠杆高,挂单未成交前也可能因跳空触发滑点,实际操作前建议在策略测试器用历史数据跑一遍 BuyStop 回放。
class="type">class="kw">double price=ask+class="num">100*point; class=class="str">"cmt">// 未规范化的建仓价格 price=NormalizeDouble(price,digits); class=class="str">"cmt">// 规范化建仓价格 class="type">int SL_pips=class="num">100; class=class="str">"cmt">// 止损点数 class="type">int TP_pips=class="num">100; 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("止损买入 %s %G 手,价位 %s, 止损=%s 获利=%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_DAY,expiration,comment)) { class=class="str">"cmt">//--- 错误报告 Print("BuyStop() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("BuyStop() 方法运行成功。返回代码=",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">100*point,digits); class="type">class="kw">double TP=NormalizeDouble(price+class="num">100*point,digits); class=class="str">"cmt">//--- 加上注释
「用 CTrade 跑通开仓平仓与改仓」
下面这段演示代码把买入开仓、同品种平仓、再改 SL/TP 的完整链路都走了一遍,核心依赖 MT5 自带的 CTrade 封装类。外汇与贵金属杠杆高,任何市价单都可能在滑点后偏离预期价格,实盘前务必在策略测试器里先跑通。 开仓前先取 SYMBOL_DIGITS 和 SYMBOL_POINT,再用 SYMBOL_BID 拿当前买价;示例里 SL 设为 price-100*point、TP 设为 price+100*point,即固定 100 点缓冲。PositionOpen 失败时不要只盯返回值,trade.ResultRetcodeDescription() 会给出中文错误语义,比光看数字好排错。 平仓用 PositionClose(_Symbol) 一把平掉当前标的全部持仓;随后又用 PositionModify 把同一仓的止损拉到 price-100 点、获利拉到 price+100 点位置。注意这段演示里开仓和平仓紧挨着执行,真实波段策略里两者绝不可能同根触发,否则只是自成交。 最后一段走的是挂单查询分支:OrderSelect(ticket) 找不到就直接 return,随后从 OrderGetString(ORDER_SYMBOL) 取品种、用 OrderGetDouble(ORDER_PRICE_OPEN) 取建仓价,并把 SL 重新算成 price-200*point。也就是说,对历史挂单改止损时缓冲翻倍到了 200 点,参数和实时持仓不是一套。
class="type">class="kw">string comment="Buy "+_Symbol+" class="num">1 at "+DoubleToString(price,digits); class=class="str">"cmt">//--- 一切准备工作就绪,尝试建立买入仓位 if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,class="num">1,price,SL,TP,comment)) { class=class="str">"cmt">//--- 错误报告 Print("PositionOpen() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("PositionOpen() 方法运行成功. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- 在当前交易品种下平仓 if(!trade.PositionClose(_Symbol)) { class=class="str">"cmt">//--- 错误报告 Print("PositionClose() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("PositionClose() 方法运行成功. 返回代码=",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_BID); class=class="str">"cmt">//--- 计算和规范化止损和获利水平 class="type">class="kw">double SL=NormalizeDouble(price-class="num">100*point,digits); class="type">class="kw">double TP=NormalizeDouble(price+class="num">100*point,digits); class=class="str">"cmt">//--- 一切准备工作就绪,尝试修改买入仓位 if(!trade.PositionModify(_Symbol,SL,TP)) { class=class="str">"cmt">//--- 错误报告 Print("PositionModify() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("PositionModify() 方法运行成功. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- 检查订单是否存在 if(!OrderSelect(ticket)) { Print("订单 #",ticket," 没有找到。"); class="kw">return; } class=class="str">"cmt">//--- 交易品种 class="type">class="kw">string symbol=OrderGetString(ORDER_SYMBOL); 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=OrderGetDouble(ORDER_PRICE_OPEN); class=class="str">"cmt">//--- 计算并规范化止损和获利水平 class="type">class="kw">double SL=NormalizeDouble(price-class="num">200*point,digits);
◍ 挂单改删的容错写法
在 MT5 里动已存在的挂单,先改后删是常见流程。下面这段把止盈设成入场价上方 200 点,并用 NormalizeDouble 按品种精度截断,避免小数位越界被拒单。 double TP=NormalizeDouble(price+200*point,digits); 这一行先算 TP,point 是该品种一点价值,digits 是报价小数位。200 点只是示例参数,实战里按波段幅度调,外汇和贵金属杠杆高,点值波动可能吞掉预期空间。 OrderModify 失败不要裸奔,必须把 ResultRetcode 和 Description 打出来,否则复盘时你不知道是权限问题还是价格违规。改完单再用 OrderSelect 确认 ticket 还在,找不到就直接 return,别接着删——删一个不存在的 ticket 只会刷错误日志。 OrderDelete 同样走成功/失败双分支打印。整套写法没有隐藏异常,开 MT5 把 ticket 换成你账户里的真实挂单号就能跑通验证。
class="type">class="kw">double TP=NormalizeDouble(price+class="num">200*point,digits); class=class="str">"cmt">//--- 一切准备工作就绪,准备修改订单 if(!trade.OrderModify(ticket,price,SL,TP,ORDER_TIME_DAY,class="num">0)) { class=class="str">"cmt">//--- 错误报告 Print("OrderModify() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("OrderModify() 方法运行成功. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); } class=class="str">"cmt">//--- 检查订单是否存在 if(!OrderSelect(ticket)) { Print("订单 #",ticket," 没有找到。"); class="kw">return; } class=class="str">"cmt">//--- 一切准备就绪,尝试删除订单 if(!trade.OrderDelete(ticket)) { class=class="str">"cmt">//--- 错误报告 Print("OrderDelete() 方法失败. 返回代码=",trade.ResultRetcode(), ". 代码描述: ",trade.ResultRetcodeDescription()); } else { Print("OrderDelete() 方法运行成功. 返回代码=",trade.ResultRetcode(), " (",trade.ResultRetcodeDescription(),")"); }
把脏活交给交易类
前面几节把下单价、挂单的写法拆完了,但 MQL5 交易类里还有一批现成工具,专门替你扛底层杂活。COrderInfo 管当前订单、CHistoryOrderInfo 翻历史订单、CPositionInfo 读持仓、CDealInfo 碰成交记录、CTerminalInfo 取终端状态——这五个类直接调,你就不用自己解析底层结构体。 对贵金属和外汇 EA 来说,行情跳空和拒绝成交是常态,用这些类把订单状态、持仓盈亏拉出来,逻辑层只管策略判断,技术细节交给库。外汇/贵金属杠杆高,任何自动逻辑都要先在策略测试器跑历史,再上模拟盘验证。 CTrade 本身还能当学习样本:挂上调试器单步跟交易请求,看返回码怎么走。跟熟了可以继承 CTrade 写自己的类,把‘发单—回执—异常处理’封进去,以后每个 EA 直接复用。
「记住这一条就够了」
把那几个示例脚本拖进 MT5 的脚本目录就能跑:accountinfo.mq5 仅 4.6 KB,expert_sample.mq5 约 37.09 KB,limit_sample.mq5 12.86 KB。新手直接编译 expert_sample 看日志,比看文档理解更快。 外汇和贵金属杠杆高、滑点跳空频繁,脚本只做验证用,实盘前务必在模拟账户跑满不同行情时段。 写 EA 没有圣杯,本文给的只是入口;真要落地,先从改一个参数开始,而不是照搬。