用于交易事件和信号的语音通知系统·进阶篇
📘

用于交易事件和信号的语音通知系统·进阶篇

第 2/3 篇

◍ 多周期 RVI 权重与指标句柄初始化

这套指标把 5 组 RVI 周期做了非对称加权:周期 21 权重 0.10、34 权重 0.16、55 权重 0.26、89 权重 0.43,长周期占比明显更高,倾向让慢线主导信号过滤。 OnInit 里先给 BuySignal、SellSignal 两个缓冲绑定 INDICTOR_DATA,箭头分别设为 233 / 234,颜色用 DodgerBlue 和 Crimson,上下偏移 ±20 点避免压住 K 线。 ADX 云和零延迟 RVI 都用 iCustom 挂自定义指标;若 ADX_Handle 返回 INVALID_HANDLE 直接 INIT_FAILED 退出。开 MT5 后把 adxcloud 与 colorzerolagrvi 两个文件放进 Indicators 目录,否则编译能过但运行时拿不到句柄。外汇与贵金属波动剧烈,自定义指标失效可能导致信号真空,属高风险场景。

MQL5 / C++
input class="type">class="kw">double         Weight2         = class="num">0.10;
input class="type">int             RVI_period2     =   class="num">21;
class=class="str">"cmt">//----
input class="type">class="kw">double         Weight3         = class="num">0.16;
input class="type">int             RVI_period3     =   class="num">34;
class=class="str">"cmt">//----
input class="type">class="kw">double         Weight4         = class="num">0.26;
input class="type">int             RVI_period4     =   class="num">55;
class=class="str">"cmt">//----
input class="type">class="kw">double         Weight5         = class="num">0.43;
input class="type">int             RVI_period5     =   class="num">89;
class=class="str">"cmt">//---
class="type">class="kw">double BuySignal[],SellSignal[],ADXCloud[],FastRVI[],SlowRVI[];
class="type">int ADX_Handle,RVI_Hadnle,min_rates_total;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
class=class="str">"cmt">//---
   SetIndexBuffer(class="num">0,BuySignal,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,SellSignal,INDICATOR_DATA);
class=class="str">"cmt">//---
   PlotIndexSetInteger(class="num">0,PLOT_ARROW,class="num">233);
   PlotIndexSetInteger(class="num">1,PLOT_ARROW,class="num">234);
class=class="str">"cmt">//---
   PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,clrDodgerBlue);
   PlotIndexSetInteger(class="num">1,PLOT_LINE_COLOR,clrCrimson);
class=class="str">"cmt">//---
   ArraySetAsSeries(SellSignal,true);
   ArraySetAsSeries(BuySignal,true);
   PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetDouble(class="num">1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetInteger(class="num">0,PLOT_ARROW_SHIFT,class="num">20);
   PlotIndexSetInteger(class="num">1,PLOT_ARROW_SHIFT,-class="num">20);
class=class="str">"cmt">//---
   ADX_Handle=iCustom(Symbol(),PERIOD_CURRENT,"adxcloud",ADXPeriod,Alpha1,Alpha2);
   if(ADX_Handle==INVALID_HANDLE)
   {
      Print(" Failed to create indicator handle");
      class="kw">return(INIT_FAILED);
   }
class=class="str">"cmt">//---
   RVI_Hadnle=iCustom(Symbol(),PERIOD_CURRENT,"colorzerolagrvi",
                     Smoothing,

「多周期RVI权重与句柄校验的落地写法」

在自定义指标初始化阶段,用五组 Weight 与 RVI_period 成对传参,是给相对活力指数做多周期加权的基础。上面这段片段里,五个周期分别由 Weight1~Weight5 和 RVI_period1~RVI_period5 控制,句柄创建失败就直接 Print 报错并返回 INIT_FAILED,避免后续 OnCalculate 空跑。 OnCalculate 里先卡 BarsCalculated(ADX_Handle) 和 BarsCalculated(RVI_Hadnle) 是否都小于 rates_total,同时要求 rates_total 不小于 min_rates_total,任一不满足就 return(0) 不画。这个前置判断能挡掉历史数据没加载完时的乱信号。 limit 的计算分两种:首跑或重载时取 rates_total-2,增量更新时取 rates_total-prev_calculated,to_copy 再补 2 根确保 CopyBuffer 不越界。ADXCloud、FastRVI、SlowRVI 以及 high/low 全部 ArraySetAsSeries(true) 按时间倒序排,CopyBuffer 从 0 偏移拉 to_copy 长度,RVI 快慢线分别取 0 和 1 号缓冲区。 开 MT5 把这段嵌进你的指标骨架,重点调 RVI_period1~5 和对应 Weight,看不同周期权重组合下快慢 RVI 交叉频次的变化,外汇和贵金属波动大,参数乱配可能放大假信号,属于高风险验证。

MQL5 / C++
                                                                               Weight1,RVI_period1,
                                                                               Weight2,RVI_period2,
                                                                               Weight3,RVI_period3,
                                                                               Weight4,RVI_period4,
                                                                               Weight5,RVI_period5
                                                                       );
   if(RVI_Hadnle==INVALID_HANDLE)
   {
      Print(" Failed to create indicator handle");
      class="kw">return(INIT_FAILED);
   }
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">class="kw">datetime &time[],
                const class="type">class="kw">double &open[],
                const class="type">class="kw">double &high[],
                const class="type">class="kw">double &low[],
                const class="type">class="kw">double &close[],
                const class="type">long &tick_volume[],
                const class="type">long &volume[],
                const class="type">int &spread[])
{
class=class="str">"cmt">//--- c
   if(BarsCalculated(ADX_Handle)<rates_total || BarsCalculated(RVI_Hadnle)<rates_total || rates_total<min_rates_total)
      class="kw">return(class="num">0);
class=class="str">"cmt">//--- 
   class="type">int limit,to_copy,i;
class=class="str">"cmt">//--- 
   ArraySetAsSeries(ADXCloud,true);
   ArraySetAsSeries(FastRVI,true);
   ArraySetAsSeries(SlowRVI,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
class=class="str">"cmt">//--- 
   if(prev_calculated>rates_total || prev_calculated<=class="num">0)
      limit=rates_total-class="num">2;
   else
      limit=rates_total-prev_calculated;
   to_copy=limit+class="num">2;
class=class="str">"cmt">//---
   if(CopyBuffer(ADX_Handle,class="num">0,class="num">0,to_copy,ADXCloud)<=class="num">0)
      class="kw">return(class="num">0);
class=class="str">"cmt">//---
   if(CopyBuffer(RVI_Hadnle,class="num">0,class="num">0,to_copy,FastRVI)<=class="num">0)
      class="kw">return(class="num">0);
   if(CopyBuffer(RVI_Hadnle,class="num">1,class="num">0,to_copy,SlowRVI)<=class="num">0)

给信号加实时弹窗而非只画箭头

上面的循环段把 ADXCloud 与 Fast/Slow RVI 的穿越条件跑完,只是在数组里写 BuySignal/SellSignal,图表上能看到箭头,但人不在屏幕前就漏了。真正能落地的是在 i==0 这一根把信号推出来。 原逻辑用 ADXCloud[i+1]>0 且 FastRVI 刚上穿 SlowRVI(i+1 在上方、i+2 在下方)判多;反之为空。加 Notify.Message(SIGNAL_BUY/SELL) 后,仅在最新柱 i==0 触发,避免历史重绘时狂响。 include 的 SoundsLib 里 CSoundsLib 对象要先 Notify.Language(ENGLISH) 设语言,否则英文模板消息可能乱码。外汇与贵金属波动大,这类提示只作辅助,信号失效概率不低,真要跟单请先在 MT5 策略测试器跑一轮。

MQL5 / C++
class="kw">return(class="num">0);
class=class="str">"cmt">//--- 
  for(i=limit-class="num">1; i>=class="num">0 && !IsStopped(); i--)
  {
    if(ADXCloud[i+class="num">1]>class="num">0 && FastRVI[i+class="num">1]>SlowRVI[i+class="num">1] && FastRVI[i+class="num">2]<SlowRVI[i+class="num">2])
    {
      BuySignal[i]=low[i];
      SellSignal[i]=EMPTY_VALUE;
    }
    else if(ADXCloud[i+class="num">1]<class="num">0 && FastRVI[i+class="num">1]<SlowRVI[i+class="num">1] && FastRVI[i+class="num">2]>SlowRVI[i+class="num">2])
    {
      SellSignal[i]=high[i];
      BuySignal[i]=EMPTY_VALUE;
    }
    else
    {
      BuySignal[i]=EMPTY_VALUE;
      SellSignal[i]=EMPTY_VALUE;
    }
  }
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call
  class="kw">return(rates_total);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <SoundsLib/SoundsLib.mqh>
CSoundsLib Notify;
Notify.Language(ENGLISH);
  SIGNAL_BUY,                     class=class="str">"cmt">// A Buy signal
  SIGNAL_SELL,                    class=class="str">"cmt">// A Sell signal
class=class="str">"cmt">//--- 
  for(i=limit-class="num">1; i>=class="num">0 && !IsStopped(); i--)
  {
    if(ADXCloud[i+class="num">1]>class="num">0 && FastRVI[i+class="num">1]>SlowRVI[i+class="num">1] && FastRVI[i+class="num">2]<SlowRVI[i+class="num">2])
    {
      BuySignal[i]=low[i];
      SellSignal[i]=EMPTY_VALUE;
      if(i==class="num">0)
        Notify.Message(SIGNAL_BUY);
    }
    else if(ADXCloud[i+class="num">1]<class="num">0 && FastRVI[i+class="num">1]<SlowRVI[i+class="num">1] && FastRVI[i+class="num">2]>SlowRVI[i+class="num">2])
    {
      SellSignal[i]=high[i];
      BuySignal[i]=EMPTY_VALUE;
      if(i==class="num">0)
        Notify.Message(SIGNAL_SELL);
    }
    else
    {
      BuySignal[i]=EMPTY_VALUE;
      SellSignal[i]=EMPTY_VALUE;
    }
  }

◍ 用语音警报把信号说出口的 EA 落地法

把指标信号接进智能交易系统,光靠终端“智能系统”选项卡里的文本提示很容易漏看。实测中,交易者在盯多周期图表时漏掉文本警报的概率明显偏高,而语音播报能把“发现买信号”“已开仓”这类动作直接念出来,比单纯蜂鸣声信息密度更高。 这里用 ColorStDev 与三重 Tirone 等级搭一套可语音播报的测试 EA:时间帧任意,买条件是 ColorStdDev 直方图为红色(趋势强烈)且现价高于上层 Tirone 级别;卖条件是直方图为红色且现价低于底层 Tirone 级别;离场用止盈止损。初始化时要先查终端是否允许自动交易,禁用的话立刻播报提醒,避免 EA 静默罢工。 OnTick() 里一旦识别到期望信号,先语音告知“发现所需买/卖信号”,再尝试下单;开仓成功再播一条“已开仓”。这样用户不需要一直盯着日志,也能知道 EA 当前在干什么。外汇与贵金属杠杆高,语音通知只是降低漏单概率,不表示信号必然盈利。 下面这段是 MT5 里的参数与头文件引入部分,逐行看: #include <SoundsLib/SoundsLib.mqh> 引入语音库,负责播放语音警报 #include <DoEasy25/Engine.mqh> 引入 DoEasy 引擎,简化指标与交易对象处理 input uint InpStopLoss=150; 止损点数设为 150 点 input uint InpTakeProfit=250; 止盈点数设为 250 点 input double InpLot=0.1; 每单手数 0.1 input ulong InpDeviation=10; 成交最大允许滑点偏差 10 点 input int InpMagic=2356; 魔幻号 2356,用于区分本 EA 订单 input LANGUAGE NotifyLanguage=ENGLISH; 语音播报语言选英文 input int StDevPeriod=12; ColorStDev 的平滑周期取 12

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 VoiceNotify.mq5 |
class=class="str">"cmt">//|                                                                 Alex2356 |
class=class="str">"cmt">//|                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Alex2356"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <SoundsLib/SoundsLib.mqh>
class="macro">#include <DoEasy25/Engine.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
input class="type">uint                InpStopLoss         =  class="num">150;                    class=class="str">"cmt">// Stop Loss, in pips
input class="type">uint                InpTakeProfit       =  class="num">250;                    class=class="str">"cmt">// Take Profit, in pips
input class="type">class="kw">double              InpLot              =  class="num">0.1;                    class=class="str">"cmt">// Take Profit, in pips
input class="type">ulong               InpDeviation        =  class="num">10;                     class=class="str">"cmt">// Deviation
input class="type">int                 InpMagic            =  class="num">2356;                   class=class="str">"cmt">// Magic number
input LANGUAGE            NotifyLanguage      =  ENGLISH;                class=class="str">"cmt">// Notification Language
class=class="str">"cmt">//--- ColorStDev indicator parameters
input class="type">int                 StDevPeriod         =  class="num">12;                     class=class="str">"cmt">// Smoothing period StDev

「EA初始化里的指标句柄与交易开关」

这套语音提醒 EA 在 OnInit 里先检查终端是否允许自动交易,若被关闭就调用 notify.Message(AUTO_TRADING_OFF) 弹提示。外汇和贵金属波动剧烈、杠杆风险高,自动交易前务必确认券商端已放行。 随后分别用 iCustom 抓取两个自定义指标句柄:colorstddev 传入 StDevPeriod、MA_Method(默认 MODE_EMA)、applied_price(默认 PRICE_CLOSE)、MaxTrendLevel=90、MiddLeTrendLevel=50、FlatLevel=20;tirone_levels_x3 只传 TironePeriod=13 和偏移 0。任一句柄返回 INVALID_HANDLE 就打印错误并 INIT_FAILED,EA 直接起不来。 在 MT5 里新建 EA 时,把这两个 iCustom 路径写成 'ArticleVoiceNotify\\colorstddev' 和 'ArticleVoiceNotify\\tirone_levels_x3',改 TironePeriod 从 13 调到 21,可能让 Tierone 通道对日线噪声的过滤更钝,但信号延迟也会变大,建议先开策略测试器跑一周验证。

MQL5 / C++
input ENUM_MA_METHOD      MA_Method            = MODE_EMA;      class=class="str">"cmt">// Histogram smoothing method
input ENUM_APPLIED_PRICE  applied_price        = PRICE_CLOSE;    class=class="str">"cmt">// Applied price
input class="type">int                 MaxTrendLevel       = class="num">90;             class=class="str">"cmt">// Maximum trend level
input class="type">int                 MiddLeTrendLevel    = class="num">50;             class=class="str">"cmt">// Middle trend level
input class="type">int                 FlatLevel           = class="num">20;             class=class="str">"cmt">// Flat level
class=class="str">"cmt">//--- Tirone Levels indicator parameters
input class="type">int                 TironePeriod        = class="num">13;             class=class="str">"cmt">// Tirone Period
class=class="str">"cmt">//---
CEngine trade;
CSoundsLib notify;
class="type">int Handle1,Handle2;
class="type">class="kw">double stdev[],tirone_b[],tirone_s[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
class=class="str">"cmt">//---
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      notify.Message(AUTO_TRADING_OFF);
class=class="str">"cmt">//---
   OnInitTrading();
class=class="str">"cmt">//--- Get the handle of the ColorStDev indicator
   Handle1=iCustom(Symbol(),PERIOD_CURRENT,"ArticleVoiceNotify\\colorstddev",
                     StDevPeriod,
                     MA_Method,
                     applied_price,
                     MaxTrendLevel,
                     MiddLeTrendLevel,
                     FlatLevel
                     );
   if(Handle1==INVALID_HANDLE)
   {
      Print("Failed to get colorstddev handle");
      Print("Handle = ",Handle1,"  error = ",GetLastError());
      class="kw">return(INIT_FAILED);
   }
class=class="str">"cmt">//--- Getting the handle of the Tirone Levels indicator
   Handle2=iCustom(Symbol(),PERIOD_CURRENT,"ArticleVoiceNotify\\tirone_levels_x3",TironePeriod,class="num">0);
   if(Handle2==INVALID_HANDLE)
   {
      Print("Failed to get Tirone Levels handle");

信号触发与下单的执行链路

EA 在 OnTick 里先判断当前品种是否已有持仓:ExistPositions(Symbol(),-1,InpMagic)<1 才允许新开仓,避免同魔法码重复进场。 无持仓时调用 GetIndValue() 刷新指标缓冲区,失败直接 return。随后并行检查 BuySignal() 与 SellSignal(),任一为真就先推 notify.Message 信号提示,再交由 trade.OpenBuy / OpenSell 按 InpLot、InpStopLoss、InpTakeProfit 发单。 开仓成功后 Sleep(1400) 毫秒再发 BUY_ORDER_SET / SELL_ORDER_SET 回执——这个 1.4 秒停顿是为让券商确认成交、防止同 tick 内重复操作。 买条件看前一根 Tirone 中线是否高于前收:tirone_b[1]>iClose(Symbol(),PERIOD_CURRENT,1) 且实时标准差 stdev[0]>FlatLevel 才返回 true,意味着价格跌破通道中轨且波动脱离平坦区,倾向走出空头反转概率更高。外汇与贵金属杠杆高,信号失效时回撤可能快速扩大,须以实盘点差验证。

MQL5 / C++
class="type">void OnTick()
{
class=class="str">"cmt">//--- If there are no market positions
   if(ExistPositions(Symbol(),-class="num">1,InpMagic)<class="num">1)
   {
      class=class="str">"cmt">//--- Getting data for calculations
      if(!GetIndValue())
         class="kw">return;
      class=class="str">"cmt">//--- Open an order if there is a buy signal
      if(BuySignal())
      {
         notify.Message(SIGNAL_BUY);
         if(trade.OpenBuy(InpLot,Symbol(),InpMagic,InpStopLoss,InpTakeProfit))
         {
            Sleep(class="num">1400);
            notify.Message(BUY_ORDER_SET);
         }
      }
      class=class="str">"cmt">//--- Opening an order if there is a sell signal
      if(SellSignal())
      {
         notify.Message(SIGNAL_SELL);
         if(trade.OpenSell(InpLot,Symbol(),InpMagic,InpStopLoss,InpTakeProfit))
         {
            Sleep(class="num">1400);
            notify.Message(SELL_ORDER_SET);
         }
      }
   }
}

class="type">bool BuySignal()
{
   class="kw">return(tirone_b[class="num">1]>iClose(Symbol(),PERIOD_CURRENT,class="num">1) && stdev[class="num">0]>FlatLevel)?true:class="kw">false;
}

class="type">bool SellSignal()
{

常见问题

在 OnInit 里用 iRVI 按各周期分别取句柄并存入数组,随后用句柄!=INVALID_HANDLE 做校验,任一时钟失败就返回 INIT_FAILED,避免后续误算。
把原来调用箭头绘制函数的地方换成 MessageBox 或 Comment 弹窗逻辑,并在信号判断分支里只触发一次弹窗标志,防止每 tick 重复弹。
可以。小布盯盘已内置多周期 RVI 诊断与 AIGC 语音播报,打开对应品种页即可听到信号语音,不必自己写 EA。
默认建议关,用输入参数 InputTradeEnabled 交由用户手动开启,避免加载即自动下单带来外汇贵金属高风险。
用静态变量或全局标志记录上一次已播报的 bar 时间或信号状态,仅在新 bar 且状态翻转时才调用 Speech 函数播报一次。