为莫斯科交易所开发一个交易机器人从哪里开始呢?·综合运用
🤖

为莫斯科交易所开发一个交易机器人从哪里开始呢?·综合运用

(3/3)· 前两篇铺垫语言与类库,本篇把市价限价、止损挂单与交易类串成可跑的自动化闭环

实战向 第 3/3 篇
不少 MOEX 自动化新手把 STOP 挂单当成交易所原生单,结果触发逻辑和期货通道阈值完全算错。平台里这类单先存本地再转发,和 LIMIT 的交易所托管不是一回事。弄清保存与执行位置,才不会在实盘里吃到意外滑点。

◍ 用 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 能立刻看到日志输出。

MQL5 / C++
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,这一节没给全,下一段接着拆。

MQL5 / C++
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(成功)或具体报错,比看文档直观。

MQL5 / C++
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 核对,贵金属杠杆高、滑点大,挂单成交价可能偏离预期。

MQL5 / C++
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 回放。

MQL5 / C++
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 点,参数和实时持仓不是一套。

MQL5 / C++
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 换成你账户里的真实挂单号就能跑通验证。

MQL5 / C++
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 没有圣杯,本文给的只是入口;真要落地,先从改一个参数开始,而不是照搬。

把回测前的重复劳动交给小布
这些 MOEX 订单映射与类方法调用路径,小布盯盘已整理成速查卡,打开对应品种页即可对照,你只需专注策略信号本身。

常见问题

市价买入用 ORDER_TYPE_BUY,卖出用 ORDER_TYPE_SELL;限价买入用 ORDER_TYPE_BUY_LIMIT,卖出用 ORDER_TYPE_SELL_LIMIT,后两者由交易所托管。
MOEX 的 BUY_STOP / SELL_STOP 先存于 MetaTrader 5 服务端,触发后期货产品会转成限价单以最差通道阈值价格发往交易所,而非直接市价。
可以,小布盯盘的品种页内置了 MOEX 订单类型速查与类方法提示,能减少手动翻文档的重复劳动,但策略逻辑仍需你自己定。
建议先写脚本跑通下单与状态回读,确认市价、限价在仿真环境的保存与执行表现,再叠加上层交易类封装。
不算,它们是在经纪商系统内部处理后再转成市价或限价单发往 MOEX,本质仍是本地计算结果。