开发 EA 构造函数的一次尝试(基础篇)
◍ 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 可直接编译验证。 外汇与贵金属杠杆高,回测通过不代表实盘无风险,参数请先在策略测试器跑样本外数据。
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 框架,你只需增删模块、改交互就能跑策略。外汇与贵金属波动剧烈,杠杆交易高风险,任何函数都只降低出错概率,不保证成交无滑点。
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; // 未确认 } };
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 策略测试器跑一遍初始化。 下面这段是从示例里抠出来的关键代码,黄色高亮即你要搬的部分:
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">//---