MQL5 向导: 根据计算价位下单, 止损和止盈。标准库扩展(基础篇)
📘

MQL5 向导: 根据计算价位下单, 止损和止盈。标准库扩展(基础篇)

第 1/3 篇

用标准库扩展让向导按计算价位挂单

MT5 的 MQL5 向导原生只支持简单市价单与固定止损止盈,想在生成 EA 时按指标计算出的动态价位去下单、设止损止盈,需要对标准库做扩展。这类扩展最早在 2015-01-14 由社区成员 Andrey Shpilev 发布示例,至今该帖仍有 6413 次查看、3 条讨论,说明手动改标准库的需求一直存在。 扩展思路是继承标准库的 CTrade 或相关信号/交易类,把 OnTick 或向导生成逻辑里的固定价格参数替换为通过 iCustom 或内置指标句柄算出的 double 价位。这样向导产出的代码无需从头写,就能按通道边界、均线交叉价等条件去挂单。 高风险提示:外汇与贵金属杠杆品种中,动态价位单若遇滑点可能偏离计算值,实盘前务必在 MT5 策略测试器用历史数据回验扩展后的 EA。

「向导生成的 EA 为何能力受限」

MQL5 向导用对话模式在几分钟内把现成模块拼成 EA,并按句柄自动声明参数,模块一多能省掉大量重复劳动。但直接在标准类上生成的系统有个硬伤:只能从当前价按距离挂止损止盈,没法在指定价位直接下单。 本文的思路是用面向对象里的继承和多态,写新类替掉向导生成的标准类代码,这样向导和标准库的便利全保留,扩展能力却打开了。实测改动极小,核心代码只多了四个字符串。 之前有文章用“强制改信号模块参数”的办法让 EA 在任意价位挂单,缺点是破坏了过滤器与信号模块的协作,向导优化也就失去意义。本系列后续会在继承类里实现任意价位下单、止损、止盈,且模块间不会冲突。外汇与贵金属杠杆交易高风险,扩展框架前请先在 MT5 策略测试器验证逻辑。

◍ 向导 EA 的开仓判定链

MQL5 向导产出的 EA 以 CExpert 类为核心,内部持有 CExpertSignal 指针,本文称之为主信号。主信号之下挂若干从属过滤器,均为 CExpertSignal 的派生类,负责各自维度的预测。 无持仓且无挂单时,每来一个即时报价,EA 会先问主信号能否开仓。主信号依次轮询从属滤波器,把各自预测做加权平均,得到方向值。该值绝对值若越过 m_threshold_open 阈值,才把订单参数与 bool 条件往上层传。 传上来的条件若成立,EA 要么市价开仓,要么按设定距离挂单。止损只能放在固定距离,市价开仓位、止损距、止盈距分别锁在 m_price_level、m_stop_level、m_take_level。也就是说,开仓要同时满足:品种无持仓,且加权平均预测绝对值超阈值——意味着趋势极强。 这种结构在图例里看得很清楚:固定止损加阈值触发,把向导 EA 框在了特定场景。长线做外汇或贵金属时,波动性漂移大,写死止损距离往往失效;用挂单也常需按波动率动态算价位,而非写死。这类品种杠杆高、回撤快,直接搬向导默认值风险偏高,建议开 MT5 把 m_stop_level 调成 ATR 派生值再测。

把趋势方向和入场价位拆开算

原信号模块只能吐出预测值,本身不碰价位,导致下单位置和信号死锁。解法是在主信号之外加一类价格模块,专门产出订单参数,主信号只负责挑方向并把这些参数往 EA 传。 修改后算法(图例2)不和挂单打交道,核心是把加权平均预测定义的方向,与价格模块给的入场价位拆成两件事。方向先由筛选后的最优交易方向定下来,再从同方向的价格模块里取一组订单参数;若有多组可用,选 m_weight 最大的那组。方向定了但暂时没价位,EA 就空转不激活。 新订单要能放下,得同时满足三个硬条件:当前品种无持仓也无其他订单;加权平均预测绝对值过阈值;至少算出一个开盘价。这套逻辑能并行处理多个候选入场点,按方向过滤后留最好的进 EA。

「两套进阶类如何重写模块间数据流」

库的扩展只落在一个点上:用 CExpertSignalAdvanced 与 CExpertAdvanced 两个类,分别继承 CExpertSignal 和 CExpert,把 EA 各模块交换数据的接口换掉。原来的信号、价格、订单模块是松耦合的,现在要让他们直接对话。 以图例.2 的形态算法为例,它必须让价格模块(作为类后代)和主要信号类 CExpertSignalAdvanced 实时交互;一旦报价或形态参数变动,CExpertAdvanced 就要跟主要信号握手,把已挂订单刷新一遍。 这一阶段能落地的动作很具体:在 MT5 里把参数动态变更接入开仓逻辑,并在更优入场点出现时重算已放置订单。下面直接看 CExpertSignalAdvanced 的骨架,开 MT5 新建 EA 时照这个类改继承关系即可验证。

◍ 进阶信号类如何接管价格模块

CExpertSignalAdvanced 接替了旧版信号类在主干信号里的位置,成为价格模块的运行底座,就像旧类曾是过滤模块的地基。它在内部声明了八个订单参数缓冲变量,开多/空的开仓价、止损、止盈、过期时间各占一对,数值统一在 Prices() 里刷新,相当于给 EA 备好一套可读写的订单草稿。 m_price_module 这个整型成员记录了价格模块在 m_filters 数组里的起点下标。数组头部放标准过滤器指针,从 m_price_module 开始才轮到价格模块;一个数组最多挂 64 个模块,实盘里通常绰绰有余,也免得重写指标和时间序列的初始化逻辑。 CheckOpenLong 被 CExpert 调用时先扫一遍有没有挂价格模块,没有就直接丢给父类方法。有则取 Direction() 的加权方向预测,比对 m_threshold_open 阈值,过了线就跑 Prices() 填参数,再交 OpenLongParams() 回传 EA,返回保存结果。CheckOpenShort 同构,只是方向相反。 Direction() 只循环 m_filters 里下标 0 到 m_price_module 之前的过滤器做加权平均,价格模块不参与方向判断。Prices() 反过来只遍历 m_price_module 到末尾,用 OpenLongParams / OpenShortParams 覆写类变量;循环前先清空,过程中若当前模块权重 m_weight 大于已查模块,才允许改写,最终留下权重最高的那组参数或空值。 外汇与贵金属杠杆高,这类模块拆分仅降低逻辑耦合,不预示任何胜率。挂单参数若算错,OpenLongParams 会因开仓价等于 EMPTY_VALUE 直接返 false,EA 侧应拦住这笔单子。

MQL5 / C++
class CExpertSignalAdvanced : class="kw">public CExpertSignal
  {
class="kw">protected:
   class=class="str">"cmt">//---data members for storing parameters of the orders being placed
   class="type">class="kw">double            m_order_open_long;       class=class="str">"cmt">//opening price of the order to buy
   class="type">class="kw">double            m_order_stop_long;       class=class="str">"cmt">//Stop Loss of the order to buy
   class="type">class="kw">double            m_order_take_long;       class=class="str">"cmt">//Take Profit of the order to buy
   class="type">class="kw">datetime          m_order_expiration_long; class=class="str">"cmt">//expiry time of the order to buy
   class="type">class="kw">double            m_order_open_short;      class=class="str">"cmt">//opening price of the order to sell
   class="type">class="kw">double            m_order_stop_short;      class=class="str">"cmt">//Stop Loss of the order to sell
   class="type">class="kw">double            m_order_take_short;      class=class="str">"cmt">//Take Profit of the order to sell
   class="type">class="kw">datetime          m_order_expiration_short; class=class="str">"cmt">//expiry time of the order to sell
   class=class="str">"cmt">//---               
   class="type">int               m_price_module;          class=class="str">"cmt">//index of the first price module in the m_filters array
class="kw">public:
                     CExpertSignalAdvanced();
                    ~CExpertSignalAdvanced();
   class="kw">virtual class="type">void      CalcPriceModuleIndex() {m_price_module=m_filters.Total();}
   class="kw">virtual class="type">bool      CheckOpenLong(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration);
   class="kw">virtual class="type">bool      CheckOpenShort(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration);

常见问题

向导默认用标准参数写死止损止盈距离,开仓逻辑没接价格计算模块,需用标准库扩展重写数据流才能按计算价位挂单。
把信号类只输出趋势方向,另写价格类算入场位,再在进阶类里把两路数据拼回开仓判定链即可。
可以,把EA逻辑描述或代码片段给小布,它能指出判定链断点并给出模块拆分建议,省去手动排查。
在品种页用历史行情回放,看计算价位是否随信号类输出实时变化,错位即说明数据流接反了。
不会自动失效,但需在重写时显式调用原止损接口,否则扩展类会跳过默认止损只按新价位开仓。