在 EA 交易代码中实现指标的计算·综合运用
◍ 把指标类塞进 EA 的骨架怎么搭
想对比 EA 内部算数据和直接读指标谁更快,没必要一上来就写完整交易逻辑。先弄一个空白 EA,把指标参数暴露成输入项,你自己往后填信号处理就行。 全局区声明一个指标类指针(如 CMA *MA)和接收缓冲的 double 数组 c_data[],类实例和数组都放全局,方便 OnTick 反复用。OnInit 里 new 出类、用 Create 绑定当前品种与周期,SetBufferSize(3) 预留 3 根 K 线的缓冲;指针无效或创建失败直接返回 INIT_FAILED。 OnDeinit 做收尾:CheckPointer 确认非空就 delete 掉类实例,避免内存泄漏。真正取数在 OnTick——用 static datetime 记录上一根 Bar 时间,和 SERIES_LASTBAR_DATE 比对,相同就 return,只在新柱触发。 新柱成立后调 MA.CopyBuffer(MAIN_LINE,0,3,c_data) 把最近 3 根柱的主线值拉进数组,后面注释位置就是你写信号判断和下单的地方。下面这段代码在 MT5 里建个 Test_Class.mq5 就能编译跑,先验证缓冲能正常刷新再接逻辑。
class="kw">input class="type">int MA_Period = class="num">25; class="kw">input class="type">int MA_Shift = class="num">0; class="kw">input ENUM_MA_METHOD MA_Method = MODE_SMA; class="kw">input ENUM_APPLIED_PRICE MA_Price = PRICE_CLOSE; CMA *MA; class="type">class="kw">double c_data[]; class="type">int OnInit() { class=class="str">"cmt">//--- MA=new CMA; if(CheckPointer(MA)==POINTER_INVALID) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- if(!MA.Create(_Symbol,PERIOD_CURRENT,MA_Period,MA_Shift,MA_Method,MA_Price)) class="kw">return INIT_FAILED; MA.SetBufferSize(class="num">3); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- if(CheckPointer(MA)!=POINTER_INVALID) class="kw">delete MA; } class="type">void OnTick() { class=class="str">"cmt">//--- class="kw">static class="type">class="kw">datetime last_bar=class="num">0; class="type">class="kw">datetime cur_date=(class="type">class="kw">datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE); if(last_bar==cur_date) class="kw">return; last_bar=cur_date; class=class="str">"cmt">//--- if(!MA.CopyBuffer(MAIN_LINE,class="num">0,class="num">3,c_data)) class="kw">return; class=class="str">"cmt">//--- class=class="str">"cmt">// 在这里加上您用于处理信号和交易操作的代码 class=class="str">"cmt">//--- class="kw">return; }
三种取数方式的耗时实测
把同一个 MA 数据用三种通道喂给 EA,能直接看出迁移代码到底亏不亏性能。三种来源分别是终端内建 iMA、自定义指标 iCustom、以及 EA 内直接算,且都只在新烛形开启时取数。 我在策略测试器用 M15 跑了 15 个月回测。OnTick 占了 99.14% 的总时间,但细分下来:iCustom 取句柄花了 235676 毫秒、占 OnInit 的 0.84%,比 iMA 取句柄慢了 10 倍以上;可一旦句柄就绪,iCustom 取数只要 0.748 毫秒,比 iMA 的 2.231 毫秒快约 3 倍,比 EA 内计算的 21.524 毫秒快近 30 倍。 MT5 的指标值是异步计算的——句柄挂上图表后,指标运算在 EA 流程之外跑,EA 只管在传数据时交互。所以类里用 CopyClose 取历史的 1.729 毫秒和取指标值的耗时接近,并不意味着指标本身零开销。用 MetaEditor 剖析器去估指标计算时间,本身是错的。 换个干净做法:放四个 EA,一个空白、一个类内算、一个接 iMA、一个接自定义指标,各跑 11 次优化取单趟均值。结论很直白,EA 内算最省时,自定义指标取数最拖。 把 MA 换成按加权收盘价做线性加权平均,计算负担加重后再跑一遍,所有取数方式单趟时间都按比例变长,前面结论没有被推翻。外汇与贵金属测试涉及杠杆品种,回测耗时差异会随品种波动放大,实盘前请在 MT5 自带测试器自测确认。
「迁移到 EA 后的性能边界」
把指标计算搬进 EA 并用 OOP 封装后,取数接口和直接读指标缓冲区几乎一致,改 EA 源码时改动面最小。这点对老策略重构很实用:你不用重写整条指标链,只要换掉数据来源层。 回测侧的收益是实的。实验里同样的逻辑跑测试和优化,整体耗时比外挂指标更低,因为少了跨指标句柄的同步开销。 但别把这当成实时也必快。MT5 是多线程架构,实时 tick 进来时线程调度会摊薄掉这部分优势,实盘里可能感觉不到明显提速。外汇与贵金属杠杆高、滑点跳空频繁,任何性能优化都不构成盈利保证,上线前务必用真实点差环境自测。
◍ 附件里那几个文件怎么用
这套迁移方案附带了 6 个工程文件,直接在 MT5 的 MQL5 目录下解压 MQL5.zip(164.68 KB)就能看到:Indicarot.mqh 是指标迁移的基类,MA.mqh 负责在 EA 内算均线,Test.mq5 跑实验 1,Test_Class / Test_iMA / Test_iCustom 三个 EA 分别验证类内计算、iMA 调用和 iCustom 调用三种取数路径。 想复现文中结论,最省事的是把 Test_iMA.mq5 和 Test_Class.mq5 同时挂到同一品种,对比两者在每根新 K 线时的 CPU 占用——类内计算只在 m_last_calculate 之后补算增量,iMA 每次都走终端历史接口。外汇和贵金属杠杆高,回测差异不等于实盘表现,参数未经验证前勿直接上资金。 下方 CMA::Calculate 的片段展示了增量重算的判定逻辑:先抓当前末根时间,若与上次计算一致且缓冲区长度够就跳过,否则重载历史再算偏移量。 有读者在原讨论里问过 CIndicator 只给了一个 m_source_data 数组、不好做多价型指标,作者回帖建议把 m_source_data 改成 MqlRates 数组,比用多个 CArrayBuffer 更易同步。你要写基于高低差的价格行为指标,照这个改基类即可。
class="type">bool CMA::Calculate(class="type">void) { class="type">class="kw">datetime cur_date=(class="type">class="kw">datetime)SeriesInfoInteger(m_Symbol,m_Timeframe,SERIES_LASTBAR_DATE); if(m_last_calculate==cur_date && ArraySize(m_source_data)==m_history_len) class="kw">return true; class=class="str">"cmt">//--- if(!LoadHistory()) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="type">int shift=Bars(m_Symbol,m_Timeframe,m_last_calculate,cur_date)-class="num">1;