MT5交易事件声音通知实战
🔊

MT5交易事件声音通知实战

用MQL5资源嵌入音效打造EA提醒面板

MQL5 编程 难度 · 进阶 2026-03-24 6 分钟阅读
#MQL5#声音通知#EA开发#资源管理

为什么要把声音文件嵌入EA内部

在MetaTrader 5中开发EA交易时,交易员常常希望在仓位开通、止损命中或出错时听到提示音。传统做法是将wav文件放在终端的Sounds文件夹,并随EA一起发给用户,同时说明存放路径。这种方式在分发编译后的ex5文件时极不方便:用户可能放错目录,导致PlaySound找不到文件而报警告。MQL5提供的#resource编译指令允许把声音文件直接编译进ex5内部,成为EA的二进制资源。这样你只需提供一个ex5文件,声音随程序自带,不会出现路径丢失。此外,资源机制不仅能嵌声音,也可嵌入bmp界面图、文本甚至指标,使EA成为真正独立的应用程序。

✦ 适用场景
当你准备向社区出售或分享EA,又不希望附带一堆外部文件时,使用资源嵌入是最干净的方案。

Alert与PlaySound两种提醒方式对比

MQL5提供至少两种在交易中发声的手段。其一是Alert()函数,它会在弹出提示窗口的同时播放终端统一的警示声。该声音在工具-选项-Ctrl+O-事件选项卡中设置,所有Alert共用一个音效,无法为不同事件定制。其二是PlaySound()函数,它不弹窗,仅播放指定wav,因此可以为开仓、平仓盈利、平仓亏损分别指定独特音效,体验远优于Alert。本文重点使用PlaySound配合资源实现定制音效。

使用#resource指令声明声音资源

在mq5源代码顶部,使用#resource加双引号路径把wav编入程序。路径以反斜杠开头,指向编译环境的Files\Sounds目录。注意文件名大小写在声明与调用时必须完全一致,否则运行报ERR_FILE_NOT_EXIST。下面代码声明了26个测试音效,用于后续声音面板。

#resource "\Files\Sounds\alert.wav"
#resource "\Files\Sounds\AHOOGA.wav"
#resource "\Files\Sounds\APPLAUSE.wav"
#resource "\Files\Sounds\BONK.wav"
#resource "\Files\Sounds\CARBRAKE.wav"
#resource "\Files\Sounds\CASHREG.wav"
#resource "\Files\Sounds\CLAP.wav"
#resource "\Files\Sounds\CORKPOP.wav"
#resource "\Files\Sounds\DOG.wav"
#resource "\Files\Sounds\DRIVEBY.wav"
#resource "\Files\Sounds\DRUMROLL.wav"
#resource "\Files\Sounds\EXPLODE.wav"
#resource "\Files\Sounds\FINALBEL.wav"
#resource "\Files\Sounds\FROG.wav"
#resource "\Files\Sounds\GLASS.wav"
#resource "\Files\Sounds\GUNSHOT.wav"
#resource "\Files\Sounds\LASER.wav"
#resource "\Files\Sounds\LATNWHIS.wav"
#resource "\Files\Sounds\PIG.wav"
#resource "\Files\Sounds\RICOCHET.wav"
#resource "\Files\Sounds\RINGIN.wav"
#resource "\Files\Sounds\SIREN.wav"
#resource "\Files\Sounds\TRAIN.wav"
#resource "\Files\Sounds\UH_OH.wav"
#resource "\Files\Sounds\VERYGOOD.wav"
#resource "\Files\Sounds\WHOOSH.wav"

在代码中调用资源时,路径前加双冒号::表示按资源名引用,而非从磁盘读取。例如"::Files\Sounds\alert.wav"。若写成单冒号或磁盘绝对路径,运行期会去终端目录找文件,失去嵌入意义。

构建图形化声音面板示例

为演示资源调用,原文创建了一个由OBJ_BUTTON组成的面板,加载EA时在图表上生成26个按钮,每个按钮随机颜色,点击即播对应声。核心步骤包括:定义数组大小ARRAY_SIZE=26;声明三个字符串数组分别存资源路径、对象名、按钮文本;编写CreateButton函数创建按钮;GetRandomColor返回随机色;SetSoundPanel布局两列按钮;OnInit调用设置,OnDeinit删除;OnChartEvent捕获点击并PlaySound;ChangeColorsOnSoundPanel做视觉反馈。

#define ARRAY_SIZE 26
string sound_paths[ARRAY_SIZE]=
  {
   "::Files\Sounds\alert.wav",
   "::Files\Sounds\AHOOGA.wav",
   "::Files\Sounds\APPLAUSE.wav",
   "::Files\Sounds\BONK.wav",
   "::Files\Sounds\CARBRAKE.wav",
   "::Files\Sounds\CASHREG.wav",
   "::Files\Sounds\CLAP.wav",
   "::Files\Sounds\CORKPOP.wav",
   "::Files\Sounds\DOG.wav",
   "::Files\Sounds\DRIVEBY.wav",
   "::Files\Sounds\DRUMROLL.wav",
   "::Files\Sounds\EXPLODE.wav",
   "::Files\Sounds\FINALBEL.wav",
   "::Files\Sounds\FROG.wav",
   "::Files\Sounds\GLASS.wav",
   "::Files\Sounds\GUNSHOT.wav",
   "::Files\Sounds\LASER.wav",
   "::Files\Sounds\LATNWHIS.wav",
   "::Files\Sounds\PIG.wav",
   "::Files\Sounds\RICOCHET.wav",
   "::Files\Sounds\RINGIN.wav",
   "::Files\Sounds\SIREN.wav",
   "::Files\Sounds\TRAIN.wav",
   "::Files\Sounds\UH_OH.wav",
   "::Files\Sounds\VERYGOOD.wav",
   "::Files\Sounds\WHOOSH.wav"
  };

CreateButton封装了ObjectCreate与各类OBJPROP设置,锚点、角落、字体、颜色均可参配。GetRandomColor用MathRand模26从预设色彩挑一个,让面板多彩。SetSoundPanel用两列循环布局,每两个按钮换一行。以下为布局片段。

void SetSoundPanel()
  {
   int   column_count =0;
   int   x_dist       =10;
   int   y_dist       =15;
   int   x_size       =100;
   int   y_size       =20;
   color button_color =clrNONE;
   for(int i=0; i<ARRAY_SIZE; i++)
     {
      column_count++;
      button_color=GetRandomColor();
      CreateButton(0,0,sound_names[i],sound_texts[i],
                   ANCHOR_LEFT_UPPER,CORNER_LEFT_UPPER,"Arial",8,
                   clrWhite,button_color,button_color,x_size,y_size,x_dist,y_dist,1);
      if(column_count==2)
        {
         x_dist=10; y_dist+=20; column_count=0;
        }
      else x_dist+=x_size;
     }
   ChartRedraw(0);
  }

交互上,OnChartEvent发现CHARTEVENT_OBJECT_CLICK且对象名含sound_button时,用GetSoundPath匹配名取路径并PlaySound,随后ChangeColorsOnSoundPanel重着色。这样用户点按钮就能试听嵌入音效,验证资源可用。

void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(StringFind(sparam,"sound_button",0)>=0)
        {
         if(!PlaySound(GetSoundPath(sparam)))
            Print("Error: ",GetLastError());
         ChangeColorsOnSoundPanel();
        }
     }
  }

在真实EA中绑定交易事件音效

原文将声音机制移植到此前优化结果保存的EA中。先建Resources.mqh集中放#resource与路径变量;外部参数加UseSound开关;枚举ENUM_SOUNDS区分错误、开仓、调单、盈平、亏平;PlaySoundByID根据ID与UseSound、IsRealtime播声。OpenPosition等交易函数成功或失败调用之;OnTrade配合SoundNotification监控历史平仓位,按DEAL_ENTRY_OUT/INOUT与利润播对应声;追踪止损修改末尾也加声。

enum ENUM_SOUNDS
  {
   SOUND_ERROR             =0,
   SOUND_OPEN_POSITION     = 1,
   SOUND_ADJUST_ORDER      = 2,
   SOUND_CLOSE_WITH_PROFIT = 3,
   SOUND_CLOSE_WITH_LOSS   = 4
  };
void PlaySoundByID(ENUM_SOUNDS id)
  {
   if(IsRealtime() && UseSound)
     {
      switch(id)
        {
         case SOUND_ERROR              : PlaySound(SoundError); break;
         case SOUND_OPEN_POSITION      : PlaySound(SoundOpenPosition); break;
         case SOUND_ADJUST_ORDER       : PlaySound(SoundAdjustOrder); break;
         case SOUND_CLOSE_WITH_PROFIT  : PlaySound(SoundCloseWithProfit); break;
         case SOUND_CLOSE_WITH_LOSS    : PlaySound(SoundCloseWithLoss); break;
        }
     }
  }

SoundNotification通过HistorySelect抓全量历史,倒序找当前品种平仓单,用静态last_ticket防重复播。若profit>=0播盈利声,否则亏损声。该函数同时放于OnInit与OnTrade,保证初始化与实时成交均覆盖。

void SoundNotification()
  {
   if(IsRealtime() && UseSound)
     {
      ulong ticket=0; int total=0; static ulong last_ticket=0;
      if(!HistorySelect(0,TimeCurrent()+1000)) return;
      total=HistoryDealsTotal();
      for(int i=total-1; i>=0; i--)
        {
         if((ticket=HistoryDealGetTicket(i))>0)
           {
            GetHistoryDealProperties(ticket,D_SYMBOL);
            if(deal.symbol==_Symbol)
              {
               GetHistoryDealProperties(ticket,D_ENTRY);
               if(deal.entry==DEAL_ENTRY_OUT || deal.entry==DEAL_ENTRY_INOUT)
                 {
                  if(ticket==last_ticket || last_ticket==0)
                    { last_ticket=ticket; return; }
                  GetHistoryDealProperties(ticket,D_PROFIT);
                  if(deal.profit>=0) { PlaySoundByID(SOUND_CLOSE_WITH_PROFIT); last_ticket=ticket; return; }
                 }
              }
           }
        }
     }
  }

常见错误与排查要点

⚠ 编译期位置
#resource引用的\Files\Sounds是编译机上的路径,不是用户终端路径;编译后已入ex5,用户无需该文件。

与其他提醒方案的对比及扩展

除内嵌音效,MT5还可用CMIDI类播MIDI曲,或借助手机推送、邮件通知。相较之下,PlaySound资源方案零依赖、低延迟、适合桌面盯盘;推送类适合离屏。若需语音播报盈亏数值,可结合TTS外部程序,但原生MQL5不直接支持。对于分发到MQL5市场的产品,单ex5内嵌资源能通过审核且用户体验最佳。综上,掌握#resource与PlaySound是进阶EA开发的一项实用素养。

常见问题

在mq5顶部用#resource "\Files\Sounds\xxx.wav"声明,调用时写"::Files\Sounds\xxx.wav"传给PlaySound,编译后声音进ex5,分发只需一个文件。
检查#resource路径文件名与PlaySound的::路径是否完全一致(含大小写);确认前缀是双冒号::而非单斜杠;重新编译EA使资源写入ex5。
Alert弹窗且用终端统一警示声,不能定制;PlaySound无弹窗可指定任意wav,适合为开仓、盈利平仓等分配不同音效。
在OnTrade里用HistorySelect抓历史成交,按DEAL_ENTRY_OUT/INOUT和profit正负判断,调用PlaySound播放对应资源音,并用静态ticket去重。
可以。用#resource嵌入的ex5是单文件,不依赖外部目录,便于用户安装,也符合市场分发规范。