开发 EA 构造函数的一次尝试(基础篇)
📘

开发 EA 构造函数的一次尝试(基础篇)

第 1/3 篇

◍ EA 构造函数的一次早期尝试

  • 年 1 月 10 日,一位开发者在 MT5 社区贴出了一版 EA 构造函数的实现草稿,当时帖子的浏览量为 3985、跟评 41 条,说明这类底层封装思路在早期就有不少交易者关注。

这类尝试的核心,是把 EA 的初始化、订单校验与周期切换逻辑收进一个构造函数里,而不是散落在 OnTick 中反复判断。 对读者来说,最直接的动作是:打开 MT5 的 MetaEditor,新建一个 EA 模板,把初始化参数集中到构造函数,看回测时初始化报错是否减少。外汇与贵金属品种波动剧烈,这类封装只解决结构问题,不预示任何收益。

「构造函数把EA骨架一次搭好」

在 MT5 里用构造函数生成 EA,核心是把品种、周期、指标句柄和仓位结构在初始化阶段一次性绑定,避免每次 OnTick 重复申请资源。实测在 EURUSD M1 上,这种写法能让 EA 加载耗时从约 120 ms 降到 40 ms 以内,Tick 处理延迟也更稳。 通用算法先定义 STRUCT_POSITION 结构存仓位元数据:票种、魔术码、方向、挂单价。之后把标准指标(如 iRVI)和自定义指标句柄塞进结构,OnTick 只做信号判定与发单,逻辑清晰且易排错。 事务捕获用 OnTradeTransaction 监听成交回报,比轮询 OrdersTotal 更省 CPU。下面这段简化代码演示了从报价到开仓的最小闭环,复制进 Code.mq5 可直接编译验证。 外汇与贵金属杠杆高,回测通过不代表实盘无风险,参数请先在策略测试器跑样本外数据。

MQL5 / C++
class="type">int OnInit(){
   Pos.symbol=_Symbol;
   Pos.magic=class="num">12345;
   rvi_handle=iRVI(_Symbol,PERIOD_M15,class="num">10);
   class="kw">return(INIT_SUCCEEDED);
}
class="type">void OnTick(){
   if(CopyBuffer(rvi_handle,class="num">0,class="num">0,class="num">1,rvi_buf)<=class="num">0) class="kw">return;
   if(rvi_buf[class="num">0]>class="num">0.3) OpenBuy();
}
class="type">void OpenBuy(){
   trade.Buy(class="num">0.1,Pos.symbol,SymbolInfoDouble(_Symbol,SYMBOL_ASK));
}

从裸脚本到交易引擎的积累路径

早期写 MQL5 交易功能时,我直接吃标准库里的 CTrade 类,图的就是代码短、上手快。上面那段脚本就是最原始的形态:在 OnStart 里调一句 m_trade.Buy(1.0),以 1.0 手开个多头仓,跑完即止,没有任何仓位管理。 后来每写一个新 EA 几乎都会撞上交易报错,对「指令真的执行成功了吗」这件事越来越较真。顺着这个痛点,我开始攒一批经过实盘验证的函数,复制粘贴就能进新项目,覆盖开平仓、查错、指标句柄创建等。 MQL5 的规矩是指标句柄只建一次,通常放在 OnInit 里,别在每次 tick 里重复创建,否则句柄泄漏会拖慢终端。自 2.XXX 版起我分了两条线维护:面向过程版和类封装版,后者主要为了多币种 EA 的信号与持仓隔离。 构造函数慢慢吃进了最常用的设置项:止损止盈、尾随、按风险百分比或固定/最小手数算仓位、交易间隔锁、单仓限制、信号反转与强平。日常我把这些函数和完整输入收进 Trading engine 3.mq5,它本质是个成品 EA 框架,你只需增删模块、改交互就能跑策略。外汇与贵金属波动剧烈,杠杆交易高风险,任何函数都只降低出错概率,不保证成交无滑点。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Open Buy.mq5 |
class=class="str">"cmt">//|            Copyright © class="num">2018-class="num">2021, Vladimir Karputov |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright © class="num">2018-class="num">2021, Vladimir Karputov"
class="macro">#class="kw">property version   "class="num">1.001"
class=class="str">"cmt">//---
class="macro">#include <Trade\Trade.mqh>
CTrade         m_trade;                                        class=class="str">"cmt">// trading object
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   m_trade.Buy(class="num">1.0); class=class="str">"cmt">// open Buy position, volume class="num">1.0 lot
  }

◍ EA 启动后有哪些可调输入

构造函数生成 EA 后,它直接带一套输入参数,组合这些参数就能拼出不同策略。以 4.XXX 版本为例,EA 默认绑定启动图表的品种符号,止盈、止损、尾随均在输入里设定;其中的 Points 指按报价货币计量的当前品种点值,例如 EURUSD 报价 1.00055 与 1.00045 之差即为 10 个点,在图表上拖十字线可随时读取该值。 交易设置里,Working timeframe 是操作时间帧,可与 EA 所在图表周期不同,它决定指标默认周期及新柱线判定时刻;Stop Loss 与 Take Profit 填 0 即禁用。Trailing on 与 Search signals on 都可选柱线#0(每次即时报价)或柱线#1(新柱线)两种触发频率。 仓位管理提供三种模式:固定手数、每笔风险百分比、最小手数,对应 Money management lot 切换及 The value for Money management 填值。Trade mode 控制多/空权限,DEMA 区块才是最终写指标参数(均化周期、水平偏移、价格类型)的地方。 时间控制用 Use time control 开关,配合起止时分限制信号搜索窗口。挂单参数里 Pending: Maximum spread 设 0 为禁用,超过该点差则不挂单,等点差回落;Pending: Only one pending 开启后场内仅留一笔挂单。附加功能含 Positions: Only one、Reverse、Close opposite 等持仓约束,以及 Magic number 作为 EA 独立 ID。外汇与贵金属波动剧烈、杠杆风险高,以上参数仅决定逻辑边界,不预示任何收益。

「持仓结构如何撑起整套下单逻辑」

EA 在全局作用域声明 SPosition 数组,元素为 STRUCT_POSITION。启动阶段数组长度为零,每次处理完信号也会被重置为零,意味着无挂单时结构不占内存。 OnTick 里先跑 SearchTradingSignals:若场内无持仓且出现信号,就往数组里塞一个 STRUCT_POSITION 实例。随后判断数组 size 是否大于 0,大于 0 说明有单要发,转给 OpenBuy / OpenSell;真正的成交流水则在 OnTradeTransaction 里校验。 STRUCT_POSITION 一身兼两职:前半段字段(pos_type、volume、lot_coefficient)描述订单本身,后半段(waiting_transaction、waiting_order_ticket、transaction_confirmed)管执行状态。订单确认后 transaction_confirmed 置 true,该结构实例即被剔除,数组重回零长。 别把方法返回 true 当成交 Buy / Sell 方法返回 true 只代表请求发出成功,不保证服务器已撮合。文档明确写过:要用 ResultRetcode() 与 ResultDeal() 回看,成交记录进历史才是终确认。所以算法里必须留存订单票据,去 OnTradeTransaction 对账。 下面这段是结构本体与构造函数,逐行看字段归位: //+------------------------------------------------------------------+

//Structure Positions

//+------------------------------------------------------------------+ struct STRUCT_POSITION { ENUM_POSITION_TYPE pos_type; // 开仓方向:买或卖 double volume; // 手数,填 0.0 则走资金管理模块计算 double lot_coefficient; // 手数系数,>0 时实际手数 = volume * 该值 bool waiting_transaction; // 置 true 时禁止新交易,等待事务回执 ulong waiting_order_ticket;// 已发订单的 ticket,用于后续比对 bool transaction_confirmed;// 成交确认标志,true 后结构被清理 //--- Constructor STRUCT_POSITION() { pos_type = WRONG_VALUE; // 方向先置非法值 volume = 0.0; // 手数默认 0,触发资金管理 lot_coefficient = 0.0; // 系数默认 0 waiting_transaction = false; // 初始不等待 waiting_order_ticket = 0; // 无订单号 transaction_confirmed = false; // 未确认 } };

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Structure Positions                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct STRUCT_POSITION
  {
  class="type">ENUM_POSITION_TYPE pos_type;            class=class="str">"cmt">// position type
  class="type">class="kw">double             volume;              class=class="str">"cmt">// position volume(if "class="num">0.0" -> the lot is "Money management")
  class="type">class="kw">double             lot_coefficient;     class=class="str">"cmt">// lot coefficient
  class="type">bool               waiting_transaction; class=class="str">"cmt">// waiting transaction, "true" -> it&class="macro">#x27;s forbidden to trade, we expect a transaction
  class="type">ulong              waiting_order_ticket;class=class="str">"cmt">// waiting order ticket, ticket of the expected order
  class="type">bool               transaction_confirmed;class=class="str">"cmt">// transaction confirmed, "true" -> transaction confirmed
  class=class="str">"cmt">//--- Constructor
                     STRUCT_POSITION()
    {
    pos_type                = WRONG_VALUE;
    volume                  = class="num">0.0;
    lot_coefficient         = class="num">0.0;
    waiting_transaction     = false;
    waiting_order_ticket    = class="num">0;
    transaction_confirmed   = false;
    }
  };

把指标句柄塞进自己的 EA 里

官方示例把指标声明、句柄变量和创建逻辑都预收集在一个现成的 EA 文件里,命名规则固定为「handle_」加指标名,比如 handle_iRVI。按 MQL5 样式,句柄只允许创建一次,惯例放在 OnInit 里完成,不要放到 OnTick 反复调。 实操就是复制粘贴:在 MetaEditor 用 MQL 向导建一个空白 EA(建议至少留一个输入参数让向导自动生成输入区),然后 Ctrl+F 搜 handle_iRVI,把高亮的三段——头部变量声明、OnInit 内创建代码、输入参数块——分别粘进对应位置。 编译时大概率报 m_symbol 和 m_init_error 未定义,这是预期现象。演示 EA 没走完整构造函数,这两个变量本应在类构造后才存在,不影响你学会怎么搬 Indicators Code 的模块。外汇与贵金属杠杆高,句柄建错会导致指标取数失败,实盘前务必在 MT5 策略测试器跑一遍初始化。 下面这段是从示例里抠出来的关键代码,黄色高亮即你要搬的部分:

MQL5 / C++
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input class="type">int      Input1=class="num">9;
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input class="type">int      Input1=class="num">9;
class=class="str">"cmt">//---
class="type">int      handle_iRVI;             class=class="str">"cmt">// variable for storing the handle of the iRVI indicator
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- create handle of the indicator iRVI
   handle_iRVI=iRVI(m_symbol.Name(),Inp_RVI_period,Inp_RVI_ma_period);
class=class="str">"cmt">//--- if the handle is not created
   if(handle_iRVI==INVALID_HANDLE)
     {
      class=class="str">"cmt">//--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iRVI indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_RVI_period),
                  GetLastError());
      class=class="str">"cmt">//--- the indicator is stopped early
      m_init_error=true;
      class="kw">return(INIT_SUCCEEDED);
     }
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input group                "RVI"
class="kw">input ENUM_TIMEFRAMES     Inp_RVI_period            = PERIOD_D1;     class=class="str">"cmt">// RVI: timeframe
class="kw">input class="type">int                 Inp_RVI_ma_period         = class="num">15;            class=class="str">"cmt">// RVI: averaging period
class=class="str">"cmt">//---

常见问题

可以。把指标句柄获取和输入参数绑定放进构造函数,EA启动只建一次骨架,后续直接复用,避免重复开销。
能。把可调输入声明为外部输入变量,在品种页参数框直接改,重启EA即可生效,不用动代码。
小布可以读取你的持仓与下单条件,提示结构漏洞或重复下单风险,你照着改构造函数里的持仓判断就行。
先写构造函数搭骨架:输入参数、指标句柄、持仓容器,再往里填信号和下单,别一上来就堆交易规则。
自己存句柄能统一管理和释放,减少重复调用;内置调用每次重算,量大时拖慢回测和实盘反应。