MQL5 向导:如何创建未平仓位的追踪模块·进阶篇
⚙️

MQL5 向导:如何创建未平仓位的追踪模块·进阶篇

(2/3)· 想用向导生成能改保护单的 EA,却卡在 CExpertTrailing 不会扩写?本文拆开追踪模块骨架

偏理论进阶 第 2/3 篇
不少人以为向导生成的 EA 只能套用标准追踪,遇到要按波动调止损就束手无策。直接改 CExpert 主类会让向导失效,未平仓位追踪得走独立子类挂载。理清基类接口前别急着写代码,否则编译过了也改不动单。

「跟踪止损类的骨架怎么搭」

在 MT5 里写自定义跟踪止损逻辑,第一步往往是继承标准库里的 CExpertTrailing。下面这段头文件骨架只做了一件事:声明 CSampleTrailing 类,并让它公开继承自 CExpertTrailing,此时类体为空,纯粹是占位结构。 实际开发中你会往这个类里重写 TrailingStop() 之类的方法,但原骨架把版权和链接属性写在了重复两次的头部注释块里:#property copyright "Copyright 2010, MetaQuotes Software Corp." 与 #property link 指向的域名,只是元信息,不影响编译行为。 #include <Expert\ExpertTrailing.mqh> 这一行才是关键,它把官方追踪止损基类拉进工程;没有它,继承语句会直接报找不到符号。开 MT5 新建一个 .mqh,把下面代码贴进去能立刻通过编译,验证继承链是否通。

MQL5 / C++
class=class="str">"cmt">// class="macro">#class="kw">import "stdlib.ex5"
class=class="str">"cmt">//   class="type">class="kw">string ErrorDescription(class="type">int error_code);
class=class="str">"cmt">// class="macro">#class="kw">import
class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#include <Expert\ExpertTrailing.mqh>
class CSampleTrailing : class="kw">public CExpertTrailing
  {
  };

◍ 无风险平仓线的类骨架

MT5 标准库里做移动止损,通常会从 CExpertTrailing 派生一个子类,把“把止损推到不亏钱的位置”这件事封装成独立逻辑。下面这段就是最薄的一层壳:只声明了两个可调参数,还没写追踪的具体算法。 m_profit 是触发动作的盈利阈值(单位依品种点值而定,常见设 10~50 点),m_stop_level 是推到成本线之上多少点算“无风险”。两个 setter 直接赋值成员变量,方便在 EA 初始化时从外部传参。 外汇与贵金属杠杆高、滑点突发行情多,这类保本 trailing 只能降低回撤概率,无法消除穿损风险,参数需按品种波动率手调。

MQL5 / C++
class CSampleTrailing : class="kw">public CExpertTrailing
  {
class="kw">protected:
   class="type">int               m_profit;        class=class="str">"cmt">//threshold level of profit
   class="type">int               m_stop_level;    class=class="str">"cmt">// lossless level
class="kw">public:
   class=class="str">"cmt">//--- methods of setting adjustable parameters
   class="type">void              Profit(class="type">int value)      { m_profit=value;     }
   class="type">void              StopLevel(class="type">int value)   { m_stop_level=value; }
  };

无风险平仓阈值的类封装

把移动止损逻辑做成独立类,最实用的一点是把「盈利多少点后才护本」和「护本止损摆几点」拆成两个可调整型参数。下面这段头文件定义里,m_profit 就是触发阈值,m_stop_level 是无损平移的止损距离,二者都在 protected 区,外部只能通过公共方法改写。 CSampleTrailing 继承自 CExpertTrailing,等于在官方跟踪止损基类上挂了一层「先赚后移、移到成本位」的规则。Profit() 和 StopLevel() 是两个单行 setter,直接赋值成员变量,没有边界检查——这意味着你在 EA 里调参时若填了负数或极端值,不会在构造阶段报错,只能靠 ValidationSettings() 去拦。 验证方法很直接:在 MT5 的 MQL5 编辑器里新建一个继承该类的 EA,把 m_profit 设成 30、m_stop_level 设成 15,跑历史回测看持仓是否在浮盈 30 点后把止损推到开仓价上方 15 点处。外汇与贵金属杠杆高,这类护本逻辑可能降低回撤但也可能被扫损后踏空,属概率性保护而非确定性收益。

MQL5 / C++
class=class="str">"cmt">//|                                                                 SampleTrailing.mqh |
class=class="str">"cmt">//|                     Copyright class="num">2010, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                       [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| include files                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Expert\ExpertTrailing.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class CSampleTrailing.                                           |
class=class="str">"cmt">//| Purpose: Class for trailing of open positions                    |
class=class="str">"cmt">//|          by moving Stop order to a lossless level.               |
class=class="str">"cmt">//|          Is derived from the CExpertTrailing class.              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSampleTrailing : class="kw">public CExpertTrailing
  {
class="kw">protected:
   class="type">int               m_profit;         class=class="str">"cmt">// threshold level of profit
   class="type">int               m_stop_level;     class=class="str">"cmt">// lossless level
class="kw">public:
                     CSampleTrailing();
   class=class="str">"cmt">//--- methods of setting adjustable parameters
   class="type">void              Profit(class="type">int value)      { m_profit=value;      }
   class="type">void              StopLevel(class="type">int value)   { m_stop_level=value; }
   class=class="str">"cmt">//--- method of validating the adjustable parameters
   class="kw">virtual class="type">bool      ValidationSettings();
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Validation of adjustable parameters.                             |
class=class="str">"cmt">//| INPUT:  no.                                                     |
class=class="str">"cmt">//| OUTPUT: true if parameter are correct, class="kw">false - if not.          |
class=class="str">"cmt">//| REMARK: no.                                                     |

「盈利阈值与止损位的参数校验逻辑」

在自定义移动止损类 CSampleTrailing 里,ValidationSettings() 先调用基类校验,再补两道自己的关: symbol 指针为空直接否,盈利阈值和止损位的关系不满足也否。 核心约束写在 (m_profit-m_stop_level)*m_adjusted_point <= m_symbol.StopsLevel()*m_symbol.Point() && m_profit!=0.0 这一行——意思是盈利触发阈值减去保本位后的实际点数,必须严格大于经纪商规定的最小止损距离,否则打印报错并返回 false。 类里 m_profit 是触发移动止损的盈利点数门槛,m_stop_level 是推到开仓价的保本位;两者都靠 Profit() 和 StopLevel() 两个 setter 注入。若你在 MT5 里改了这两个参数却没法初始化,八成是阈值减保本位后挤进了 StopsLevel() 的限制区。 多头检查函数 CheckTrailingStopLong 开头就拦三种情况:position 指针空、m_profit 为 0、或者当前止损已经大于等于开仓价(说明早进保盈区了),直接 return false 不再计算。

MQL5 / C++
class="type">bool CSampleTrailing::ValidationSettings()
  {
   if(!CExpertTrailing::ValidationSettings())
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- check wheter the Init method is called
   if(m_symbol==NULL) class="kw">return(class="kw">false);
class=class="str">"cmt">//--- check parameters
   if((m_profit-m_stop_level)*m_adjusted_point<=m_symbol.StopsLevel()*m_symbol.Point() && m_profit!=class="num">0.0)
     {
      printf(__FUNCTION__+": threshold level of profit must be greater than the level of  setting of orders");
      class="kw">return(class="kw">false);
     }
class=class="str">"cmt">//--- ok
   class="kw">return(true);
  }
class CSampleTrailing : class="kw">public CExpertTrailing
  {
class="kw">protected:
   class="type">int               m_profit;            class=class="str">"cmt">// threshold level of profit
   class="type">int               m_stop_level;        class=class="str">"cmt">// lossless level
class="kw">public:
                     CSampleTrailing();
   class=class="str">"cmt">//--- methods of setting adjustable parameters
   class="type">void              Profit(class="type">int value)      { m_profit=value;     }
   class="type">void              StopLevel(class="type">int value)   { m_stop_level=value; }
   class=class="str">"cmt">//--- method of validation of adjustable parameters
   class="kw">virtual class="type">bool      ValidationSettings();
   class=class="str">"cmt">//--- methods of generation of position modification signals
   class="kw">virtual class="type">bool      CheckTrailingStopLong(CPositionInfo* position,class="type">class="kw">double& sl,class="type">class="kw">double& tp);
   class="kw">virtual class="type">bool      CheckTrailingStopShort(CPositionInfo* position,class="type">class="kw">double& sl,class="type">class="kw">double& tp);
   };
class="type">bool CSampleTrailing::CheckTrailingStopLong(CPositionInfo* position,class="type">class="kw">double& sl,class="type">class="kw">double& tp)
  {
class=class="str">"cmt">//--- check of pointer
   if(position==NULL) class="kw">return(class="kw">false);
class=class="str">"cmt">//--- check of parameter
   if(m_profit==class="num">0.0)  class="kw">return(class="kw">false);
class=class="str">"cmt">//--- already in a lossless zone?
   class="type">class="kw">double open=position.PriceOpen();
   if(position.StopLoss()>=open) class="kw">return(class="kw">false);

◍ 空单保本移动的触发与止损重算

多头那段讲完,空单的保本逻辑是镜像的。函数 CheckTrailingStopShort 先判指针和 m_profit 是否为 0,任一为空直接返回 false,不浪费计算。 已处于无损区(即原止损价高于开仓价)时函数也直接退出,说明只在浮亏或刚平价阶段才考虑把止损拉到成本附近,避免重复修改订单。 核心判断是 open - Ask() > m_profit * m_adjusted_point:空单盈利空间超过设定点数(默认 Profit 参数 20 点)后,把 sl 重算为 open - m_stop_level * m_adjusted_point,并经 NormalizePrice 对齐报价精度。 m_stop_level 默认 0,意味着盈利达标后止损直接钉在开仓价;若设为 5,则留 5 点缓冲防毛刺。外汇与贵金属杠杆高,保本触发后仍需防滑点扫损,建议用策略测试器跑 EURUSD 15 分钟验证触发频率。

MQL5 / C++
class="type">bool CSampleTrailing::CheckTrailingStopShort(CPositionInfo* position,class="type">class="kw">double& sl,class="type">class="kw">double& tp)
  {
class=class="str">"cmt">//--- check of pointer
   if(position==NULL) class="kw">return(class="kw">false);
class=class="str">"cmt">//--- check of parameter
   if(m_profit==class="num">0.0)  class="kw">return(class="kw">false);
class=class="str">"cmt">//--- already in a lossless zone?
   class="type">class="kw">double open=position.PriceOpen();
   if(position.StopLoss()<=open) class="kw">return(class="kw">false);
class=class="str">"cmt">//--- check of profit
   sl=EMPTY_VALUE;
   tp=EMPTY_VALUE;
   if(open-m_symbol.Ask()>m_profit*m_adjusted_point)
      sl=m_symbol.NormalizePrice(open-m_stop_level*m_adjusted_point);
class=class="str">"cmt">//---
   class="kw">return(sl!=EMPTY_VALUE);
  }
把追踪逻辑交给小布盯盘回测
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到不同波动下的保护单触发分布,你专注决策而非反复手算阈值。

常见问题

多发生在指标句柄或交易品种信息未就绪时返回 false,导致后续 Check 调用直接跳过,需在子类 Init 里显式校验依赖。
向导靠标准类工厂反射加载,未注册子类不会进入生成器下拉,EA 源码里也就不会实例化你的追踪逻辑。
可以,把 EA 的追踪参数贴进小布对应品种页的 AIGC 诊断区,它会按近期贵金属与外汇波动给出阈值倾向,但外汇贵金属属高风险,结果仅作概率参考。
该方法只通知 CExpert 有必要修改,最终由 EA 决策执行;若仓位方向或冻结状态不符,主类会放弃发单。
能,通过 CPositionInfo 成员获取,但成交量计算仍归 CExpertMoney 管,两者职责不交叉。