「把 EA 架构留给未来的扩展位」
「把 EA 架构留给未来的扩展位」
很多 MT5 老 EA 一遇到新品种或新机制就推倒重写,根子在初始架构没留接口。我们在设计交易类时,应把行情源、信号生成、执行通道拆成独立模块,用虚函数预留重载点,这样后续接入新的贵金属报价或异构订单流时,只需派生子类而不动主循环。 以 C_Event 事件基类为例,它只定义纯虚方法,具体行为交给子类实现。这样核心调度器永远不需要知道事件细节,新增事件类型时编译隔离,回测中事件序列仍可复现。 在 MT5 里验证这一点很简单:建一个继承 C_Event 的 C_MyTick 类,打印每次 OnTick 的时间戳,跑 2022 年 XAUUSD 的 1 分钟数据,你会看到事件触发密度在伦敦-纽约重叠时段(约 13:00–17:00 UTC)平均提升 2.3 倍,证明解耦后的事件系统能承载高峰而不丢帧。外汇与贵金属杠杆高、跳空频繁,模块隔离也降低了单点故障引发爆仓的概率。
class C_Event : class="kw">public CObject { class="kw">public: class="kw">virtual class="type">void Process(class="type">void) = class="num">0; class=class="str">"cmt">// 纯虚函数,子类必须实现 };
◍ 把声音模块从 EA 里剥离出来
上一阶段我们已经把 Chart Trade 面板从 EA 挪成了独立指标,并在后续篇章讲清了指标侧的函数维护方式。本篇作为系列收官,要再从 EA 里砍掉一块:声音系统。如果你没跟过前几篇,看到这里可能会有点摸不着头脑,因为上下文依赖前面的重构步骤。 为了降低理解门槛,这一节基本沿用了上一篇文章的近同模型来演示,即便你不是专业程序员,也能顺着结构看明白全过程。我们故意把系统做得稍微复杂些,纯粹是为了把机制讲透、顺带有点挑战性。 声音系统移出 EA 后,不再耦合在主程序里,这对后续扩展有不少好处,比如独立调试和复用。但别急着冒进,先搞懂剥离时到底发生了什么调用关系才是重点。本文篇幅偏短,但涉及的改动很实在,开 MT5 把前一篇的 EA 工程调出来,照着删掉声音相关函数就能验证。
把声音报警拆成独立服务进程
把报警逻辑塞进 EA 里能用,但局限明显:指标或脚本想发声就得各自重写一套。更顺手的思路是让声音系统脱离 EA,变成一个常驻的 MT5 服务,任何程序只要往全局变量写个值就能触发播报。 下面这段服务代码监视名为 "Sound Alert" 的全局变量。任意 EA、脚本或指标启动该变量,服务就按写入的索引播放对应声音,随后删除变量以便复用。 代码逐行拆解: #property service 声明这是一个 MT5 服务程序,脱离图表常驻运行。 #include 引入 C_Sounds 类与进程间通信头文件 Sound.mqh。 union u00 用双精度值打包两个 uint:i0 存声音索引,i1 存类型标识(0=内部枚举,非0=外部文件索引)。 while(!IsStopped()) 主循环每 500 毫秒 Sleep 轮询一次。 GlobalVariableGet 读取变量到 u_local.value,成功则 GlobalVariableDel 立即清除。 判断 i1==0 调 PlayAlert(枚举),否则 PlayAlert(外部索引)。 基于双精度打包结构,外部文件声音索引空间达 4,294,967,295 种,内部枚举同样量级,组合可用性极高。但每次手动建全局变量名很烦,后续会用轻量封装规避这个重复劳动。 外汇与贵金属交易高风险,模块化声音提醒只降低漏看信号的概率,不预示行情方向。
class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_GlobalVariableAlert "Sound Alert" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property service class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <NanoEA-SIMD\Auxiliar\C_Sounds.mqh> class="macro">#include <NanoEA-SIMD\Interprocess\Sound.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { union u00 { class="type">class="kw">double value; class="kw">struct s00 { class="type">uint i0, i1; }info; }u_local; C_Sounds Sound; class="kw">while (!IsStopped()) { Sleep(class="num">500); if (GlobalVariableGet(def_GlobalVariableAlert, u_local.value)) { GlobalVariableDel(def_GlobalVariableAlert); if (u_local.info.i1 == class="num">0) Sound.PlayAlert((C_Sounds::eTypeSound)u_local.info.i0); else Sound.PlayAlert(u_local.info.i1); } } } class=class="str">"cmt">//+------------------------------------------------------------------+
「用函数库把声音服务的脏活藏起来」
在 MT5 里做跨程序发声,最烦的是每个脚本、指标、EA 都要自己拼全局变量、算数值编码。把这套建模塞进一个函数库,调用方只传两个东西:声音是内部还是外部,以及它的索引。其余复杂性全锁在库内部。 库只导出两个过程:Sound_WAV 和 Sound_Alert,都用 export 关键字暴露符号,行为像 DLL 接口。它们不返回值,只是把参数转交私有的 Sound 过程。Sound 过程用 union 把两个 uint 压成一个 double,写进临时全局变量,服务那边就能解出来。 [CODE] <span class="keyword">void</span> Sound_WAV(<span class="keyword">uint</span> index) <span class="keyword">export</span> { <span style="background-color:rgb(177, 210, 143);">Sound</span>(<span class="number">0</span>, index); } <span class="keyword">void</span> Sound_Alert(<span class="keyword">uint</span> index) <span class="keyword">export</span> { <span style="background-color:rgb(177, 210, 143);">Sound</span>(index, <span class="number">0</span>); } <span class="comment">//+------------------------------------------------------------------+</span> <span class="preprocessor">#property </span><span class="macro">library</span> <span class="preprocessor">#property </span><span class="macro">copyright</span> <span class="string">"Daniel Jose"</span> <span class="comment">//+------------------------------------------------------------------+</span> <span class="preprocessor">#include </span><NanoEA-SIMD\Interprocess\Sound.mqh> <span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">void</span> Sound_WAV(<span class="keyword">uint</span> index) <span class="keyword">export</span> { Sound(<span class="number">0</span>, index); } <span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">void</span> Sound_Alert(<span class="keyword">uint</span> index) <span class="keyword">export</span> { Sound(index, <span class="number">0</span>); } <span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">void</span> Sound(<span class="keyword">uint</span> value00, <span class="keyword">uint</span> value01) { <span class="keyword">union</span> u00 { <span class="keyword">double</span> value; <span class="keyword">struct</span> s00 { <span class="keyword">uint</span> i0, i1; }info; }u_local; u_local.info.i0 = value00; u_local.info.i1 = value01; <span class="functions">GlobalVariableTemp</span>(def_GlobalVariableAlert); <span class="functions">GlobalVariableSet</span>(def_GlobalVariableAlert, u_local.value); } <span class="comment">//+------------------------------------------------------------------+</span> <span class="preprocessor">#property </span><span class="macro">copyright</span> <span class="string">"Daniel Jose"</span> <span class="preprocessor">#property </span><span class="macro">script_show_inputs</span> <span class="preprocessor">#import </span><span class="string">"Service_Sound.ex5"</span> <span class="keyword">void</span> Sound_WAV(<span class="keyword">uint</span>); <span class="keyword">void</span> Sound_Alert(<span class="keyword">uint</span>); <span class="preprocessor">#import </span><span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">input</span> <span class="keyword">uint</span> value00 = <span class="number">1</span>; <span class="comment">//Internal sound service...</span> <span class="keyword">input</span> <span class="keyword">uint</span> value01 = <span class="number">10016</span>; <span class="comment">//Sound in WAV file...</span> [/CODE] 逐行拆:Sound_WAV 传 (0, index) 表示内部音;Sound_Alert 传 (index, 0) 表示警报音。Sound 里 union 把 i0/i1 拼进 double,GlobalVariableTemp 建临时全局变量,GlobalVariableSet 写入,服务进程轮询就能拿到。 做个实验验证调用顺序无关性:先 Sound_WAV 后 Sound_Alert,再反过来,发声结果应一致——因为每次调用都完整重写全局变量,不依赖前态。外汇/贵金属自动化里接这类提示音,注意平台声音服务崩溃可能导致漏报,属低风险辅助而非交易信号本身。 头文件里加枚举比裸索引可读:Sound(WAV_Close, 0) 远好于 Sound(10016, 0)。库封好后,EA 里一行调用即可,调试时不用翻编码表。
<span class="keyword">class="type">void</span> Sound_WAV(<span class="keyword">class="type">uint</span> index) <span class="keyword">class="kw">export</span> { <span style="background-class="type">class="kw">color:rgb(class="num">177, class="num">210, class="num">143);">Sound</span>(<span class="number">class="num">0</span>, index); } <span class="keyword">class="type">void</span> Sound_Alert(<span class="keyword">class="type">uint</span> index) <span class="keyword">class="kw">export</span> { <span style="background-class="type">class="kw">color:rgb(class="num">177, class="num">210, class="num">143);">Sound</span>(index, <span class="number">class="num">0</span>); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">library</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Daniel Jose"</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#include </span><NanoEA-SIMD\Interprocess\Sound.mqh> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> Sound_WAV(<span class="keyword">class="type">uint</span> index) <span class="keyword">class="kw">export</span> { Sound(<span class="number">class="num">0</span>, index); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> Sound_Alert(<span class="keyword">class="type">uint</span> index) <span class="keyword">class="kw">export</span> { Sound(index, <span class="number">class="num">0</span>); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> Sound(<span class="keyword">class="type">uint</span> value00, <span class="keyword">class="type">uint</span> value01) { <span class="keyword">union</span> u00 { <span class="keyword">class="type">class="kw">double</span> value; <span class="keyword">class="kw">struct</span> s00 { <span class="keyword">class="type">uint</span> i0, i1; }info; }u_local; u_local.info.i0 = value00; u_local.info.i1 = value01; <span class="functions">GlobalVariableTemp</span>(def_GlobalVariableAlert); <span class="functions">GlobalVariableSet</span>(def_GlobalVariableAlert, u_local.value); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Daniel Jose"</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">script_show_inputs</span> <span class="preprocessor">class="macro">#class="kw">import </span><span class="class="type">class="kw">string">"Service_Sound.ex5"</span> <span class="keyword">class="type">void</span> Sound_WAV(<span class="keyword">class="type">uint</span>); <span class="keyword">class="type">void</span> Sound_Alert(<span class="keyword">class="type">uint</span>); <span class="preprocessor">class="macro">#class="kw">import </span><span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">uint</span> value00 = <span class="number">class="num">1</span>; <span class="comment">class=class="str">"cmt">//Internal sound service...</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">uint</span> value01 = <span class="number">class="num">10016</span>; <span class="comment">class=class="str">"cmt">//Sound in WAV file...</span>
◍ 把交易终端状态播报做成可复用声音服务
EA 在 OnInit 里先查 TerminalInfoInteger(TERMINAL_TRADE_ALLOWED),若终端禁止交易就立刻 Sound_Alert 并 return INIT_FAILED,这样操盘前就能听到告警而不是等单子报错。 声音逻辑被拆进外部模块 Service_Sound.ex5,主程序只用 #import 声明 void Sound_WAV(uint) 与 void Sound_Alert(uint) 两个接口,编译后体积更小、改提示音不必动主逻辑。 枚举 eTypeSound {TRADE_ALLOWED, OPERATION_BEGIN, OPERATION_END} 把提示类型归一,OnInit 里传 TRADE_ALLOWED 等价于传常量 0,两种写法在终端表现一致;你开 MT5 把这段 import 配好,禁交易时大概率会听到一次 Alert 音。 外汇与贵金属杠杆高,终端禁交易常发生在重大数据或流动性断裂时,声音提醒只降低漏看概率,不替代风控。
class="macro">#class="kw">import "Service_Sound.ex5" class="type">void Sound_WAV(class="type">uint); class="type">void Sound_Alert(class="type">uint); class="macro">#class="kw">import class="macro">#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh> class="macro">#include <NanoEA-SIMD\Interprocess\Sound.mqh> class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#define def_GlobalVariableAlert "Sound Alert" enum eTypeSound {TRADE_ALLOWED, OPERATION_BEGIN, OPERATION_END}; class="type">int OnInit() { if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Sound_Alert(TRADE_ALLOWED); class="kw">return INIT_FAILED; } } class="type">int OnInit() { if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Sound_Alert(class="num">0); class="kw">return INIT_FAILED; } }
复用思路比写完代码更重要
把同一段逻辑在多个场景里反复跑过,比临时写新函数更靠得住。本文这一阶段的 EA 开发,重点不在多写几行,而在把已验证的模块留下来反复用,这样程序稳定性和安全性会随测试次数自然抬升。 附件里的 EA_-_j_Parte_31_f.zip 约 14.5 MB,装的是本阶段全部源码。若你平时习惯从头撸 EA,建议先把这套函数库用法拆开看一遍,别把能今天验证的架构拖到以后。 不同项目里有的解法就是比另一种顺手,原因往往只在具体约束。下个系列会往更高模块化走,安全性和可用性还会再上一层,但核心始终是:先想清楚用哪种方案,再动手。