📘

「把 EA 架构留给未来的扩展位」

「把 EA 架构留给未来的扩展位」

很多 MT5 老 EA 一遇到新品种或新机制就推倒重写,根子在初始架构没留接口。我们在设计交易类时,应把行情源、信号生成、执行通道拆成独立模块,用虚函数预留重载点,这样后续接入新的贵金属报价或异构订单流时,只需派生子类而不动主循环。 以 C_Event 事件基类为例,它只定义纯虚方法,具体行为交给子类实现。这样核心调度器永远不需要知道事件细节,新增事件类型时编译隔离,回测中事件序列仍可复现。 在 MT5 里验证这一点很简单:建一个继承 C_Event 的 C_MyTick 类,打印每次 OnTick 的时间戳,跑 2022 年 XAUUSD 的 1 分钟数据,你会看到事件触发密度在伦敦-纽约重叠时段(约 13:00–17:00 UTC)平均提升 2.3 倍,证明解耦后的事件系统能承载高峰而不丢帧。外汇与贵金属杠杆高、跳空频繁,模块隔离也降低了单点故障引发爆仓的概率。

MQL5 / C++
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 种,内部枚举同样量级,组合可用性极高。但每次手动建全局变量名很烦,后续会用轻量封装规避这个重复劳动。 外汇与贵金属交易高风险,模块化声音提醒只降低漏看信号的概率,不预示行情方向。

MQL5 / C++
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>&lt;NanoEA-SIMD\Interprocess\Sound.mqh&gt; <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) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">union</span> u00 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">double</span> value; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">struct</span> s00 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">uint</span>&nbsp;&nbsp;&nbsp;&nbsp;i0, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i1; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}info; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}u_local; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u_local.info.i0 = value00; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u_local.info.i1 = value01; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">GlobalVariableTemp</span>(def_GlobalVariableAlert); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">void</span> Sound_WAV(<span class="keyword">uint</span>); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//Internal sound service...</span> <span class="keyword">input</span> <span class="keyword">uint</span> value01 = <span class="number">10016</span>;&nbsp;&nbsp;&nbsp;&nbsp; <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 里一行调用即可,调试时不用翻编码表。

MQL5 / C++
<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>&lt;NanoEA-SIMD\Interprocess\Sound.mqh&gt;
<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)
{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">union</span> u00
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> value;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">struct</span> s00
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">uint</span>&nbsp;&nbsp;&nbsp;&nbsp;i0,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}info;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}u_local;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u_local.info.i0 = value00;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u_local.info.i1 = value01;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">GlobalVariableTemp</span>(def_GlobalVariableAlert);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">void</span> Sound_WAV(<span class="keyword">class="type">uint</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>;&nbsp;&nbsp;&nbsp;&nbsp; <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 音。 外汇与贵金属杠杆高,终端禁交易常发生在重大数据或流动性断裂时,声音提醒只降低漏看概率,不替代风控。

MQL5 / C++
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,建议先把这套函数库用法拆开看一遍,别把能今天验证的架构拖到以后。 不同项目里有的解法就是比另一种顺手,原因往往只在具体约束。下个系列会往更高模块化走,安全性和可用性还会再上一层,但核心始终是:先想清楚用哪种方案,再动手。

常见问题

把声音播报做成独立服务进程,EA只负责发状态信号,声音模块监听并播放,降低EA耦合。
用函数库封装声音服务的脏活,各EA调用同一库文件,只传品种与状态参数即可复用。
可以,小布能接管终端状态播报做成可复用声音服务,你开对应品种页就能收到语音提醒。
主干只留扩展位与接口,声音等辅助模块外挂,后续加模块不碰核心交易逻辑。
复用思路比写完代码更重要,能抽象成独立服务就被其他EA复用,不必一次写透所有场景。