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

17 十一月 2020, 09:46
Alexander Fedosov
0
1 020

内容目录

概述

MetaTrader 5 交易终端拥有声音警报的选项。 该系统涵盖 11 个事件,可为它们分配单独的声音警报。 然而,用户需要接收声音通知时,也许会有很多情况,例如交易系统信号的出现,或智能交易系统的动作,包括开仓、平仓或修改持仓。 现如今,语音助手在人类生活中起着举足轻重的作用,因为我们会经常使用导航、语音搜索和翻译。 在 MetaTrader 5 终端中进行交易时可用到此主意。 在本文中,我将尝试为各种交易事件、市场状态、或由交易信号生成的信号开发一个简单,且用户友好的语音通知系统。


开发语音通知系统

在开始创建系统之前,我想添加一个提醒。 为了实现语音通知,我选择了一些事件,它们只是为了系统演示。 如果此设置还不够用,您可自行添加事件和相关的语音警报。 阅读本文之后,即使您不具备丰富的 MQL5 知识,系统的扩展和定制也将非常容易。

该系统在包含文件中,作为 CSoundsLib 类实现。 故此,打开 MQL5/Include 文件夹,并创建一个名为 SoundsLib 的文件夹,在其内您应创建 SoundsLib.mqh 文件。 在创建该类之前,我们引入两个枚举,这些枚举以后会在处理语音警报时用到。 它们中的第一个是 LANGUAGE,它是为了选择警报语言。 该系统将支持两种语言:英语和俄语。

//+------------------------------------------------------------------+
//| Enumeration for switching the notification language              |
//+------------------------------------------------------------------+
enum LANGUAGE
{
   RUSSIAN,       // Russian
   ENGLISH        // English
};

第二个枚举包含我为演示目的选择的一组事件。 在本文的进一步内容中,我将展示如何将它们嵌入各种成品系统之中,包括指标、智能交易系统,和快捷交易工具箱。 该枚举称为 MESSAGE:

//+------------------------------------------------------------------+
//| List of voice alerts                                             |
//+------------------------------------------------------------------+
enum MESSAGE
{
   STATUS_ON,                          // Status of enabled voice alerts
   SIGNAL_BUY,                         // A Buy signal
   SIGNAL_SELL,                        // A Sell signal
   BUY_ORDER_SET,                      // A Buy order has been placed
   SELL_ORDER_SET,                     // A Sell order has been placed
   BUYLIMIT_ORDER_SET,                 // A Limit Buy order has been placed
   BUYSTOP_ORDER_SET,                  // A Stop Buy order has been placed
   SELLLIMIT_ORDER_SET,                // A Limit Sell order has been placed
   SELLSTOP_ORDER_SET,                 // A Stop Sell order has been placed
   BUYLIMIT_ORDER_DELETE,              // A Limit Buy order has been deleted
   BUYSTOP_ORDER_DELETE,               // A Stop Buy order has been deleted
   SELLLIMIT_ORDER_DELETE,             // A Limit Sell order has been deleted
   SELLSTOP_ORDER_DELETE,              // A Stop Sell order has been deleted
   BUY_ORDER_CLOSE_PROFIT,             // A Buy order has closed with a profit
   BUY_ORDER_CLOSE_LOSS,               // A Buy order has closed with a loss
   SELL_ORDER_CLOSE_PROFIT,            // A Sell order has closed with a profit
   SELL_ORDER_CLOSE_LOSS,              // A Sell order has closed with a loss
   BUY_ORDER_CLOSE_TP,                 // A Buy order has been closed by Take Profit
   BUY_ORDER_CLOSE_SL,                 // A Buy order has been closed by Stop Loss
   SELL_ORDER_CLOSE_TP,                // A Sell order has been closed by Take Profit
   SELL_ORDER_CLOSE_SL,                // A Sell order has been closed by Stop Loss
   MARKET_CLOSE,                       // Market is closed
   AUTO_TRADING_ON,                    // Automated trading is allowed
   AUTO_TRADING_OFF,                   // Automated trading is prohibited
};

基本集包含 24 个警报。 其中大多数与持仓和挂单的操作和状态有关。 某些警报用于交易环境通知。 最后三个通知与常见事件有关。 有关启用语音警报系统状态的通知,以及有关出现买卖信号的通知,可便于在手动或半自动智能交易系统、或包括简单和作为交易策略一部分的指标操作。

现在,我们来创建 CSoundsLib 类,并添加操作所需的方法。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSoundsLib
{
private:
   LANGUAGE          m_language;
   bool              m_activity_status;
public:
                     CSoundsLib(void); 
                    ~CSoundsLib(void);
   //--- Set the notification language
   void              Language(LANGUAGE lang);
   //--- Set/get the status of the voice alerts system
   void              IsActive(bool flag);
   bool              IsActive(void);
   //--- Play the specified notification
   bool              Message(MESSAGE msg);
};

私密部分有两个警报, m_language m_activity_status ,它们是以下 Language() IsActive() 方法所需要的。 因此,它们用于设置语音警报的语言,并获取/设置系统活动状态。 此处是上述警报的实现:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSoundsLib::Language(LANGUAGE lang)
{
   m_language=lang;
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSoundsLib::IsActive(void)
{
   return(m_activity_status);
}

另一个方法是 Message()。 它播放从 MESSAGE 枚举中选择的通知。 此方法的实现也很容易理解:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool  CSoundsLib::Message(MESSAGE msg)
{
   if(!m_activity_status)
      return(false);
   string name=(m_language==RUSSIAN ? EnumToString(msg)+"_RU" : EnumToString(msg)+"_EN");
   if(PlaySound("\\Files\\SoundsLib\\"+name+".wav"))
      return(true);
   else
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         Print("Файл не найден");
      else
         Print("File not found");
      return(false);
   }
}

请注意以下要点 — 通过添加自己的语音通知,它们能帮助您正确地进一步扩展系统。 第一点是保存音频文件的正确位置:默认情况下,它们位于 MQL5/Files/SoundsLib 文件夹当中。 您应当创建这个 SoundsLib 文件夹。 其次,确保在创建的文件夹中设置相应的名称和音频文件格式。 请注意以下代码行:在此,附加 _RU 或 _EN 后缀到 MESSAGE 类型枚举中。 这就是为何,文件名相对应,例如,与买入信号警报 SIGNAL_BUY 相关的两个音频文件 SIGNAL_BUY _RU 和 SIGNAL_BUY_EN,分别是俄语和英语语音警报。 另外,不要忘记系统函数 PlaySound() 只能播放 *.WAV 格式的文件,因此, SoundsLib 内带有扩展名的完整文件名看起来像这样

图例 1 音频文件的全名和扩展名。

因此,对于 MESSAGE 枚举中的 24 个事件,我们有 48 个音频文件:每个事件有两个不同语言的文件。 接下来,我将展示自己创建的语音警报方法。 无论如何,您可以使用任何偏爱方法。 在本文中,我用了免费服务将文本转换为语音。

图例 2 将文本转换为读音的服务。

该服务为执行所需任务提供了良好的功能。 它允许选择语言,以及所需格式的类型。 不过,WAV 格式尚不支持英语。 在此,我们可用任何在线转换器,或任何其他软件将 mp3 转换为 wav。 我已经为系统准备了所有必需的文件,并根据 MESSAGE 枚举和语言后缀将它们以正确的格式和名称保存到文件夹 MQL5\ Files\SoundsLib 当中。 此为我的结果列表:

图例 3 语音警报的音频文件的完整列表。

以下是有关如何创建自己的语音通知的分步指南。

步骤 1. 将语音事件添加到系统。

打开文件 SoundsLib.mqh ,并找到 MESSAGE 枚举。 选用有意义的名称添加事件。 命名示例显示在图例 3 和上面的代码中。

步骤 2. 创建语音警报的音频文件。

转到服务(您可以使用任何喜欢的服务),配置所需的参数(如图例 2 所示),并将文件以 WAV 格式保存在 MQL5\Files\SoundsLib 之下,取决于语言,名称为“MESSAGE 枚举中您的事件名称”+_RU(_EN)。 如果所有步骤均正确完成,则新的声音文件将与在步骤 1 中添加的新事件相链接,并可随时使用。


指标的实际应用

现在,来看看不同示例它是如何工作的。 我们基于下表中描述的两个指标信号创建一个综合指标:

参数 说明
用到的指标 ADXCloud
用到的指标 ColorZerolagRVI
时间帧选择 任意
买入条件 ADXCloud 云为绿色,ColorZerolagRVI 云从红色转至绿色区域。
卖出条件 ADXCloud 云为红色,ColorZerolagRVI 云从绿色转至红色区域。

基于指标信号的入场示例如图例 4 所示,它们非常简单。 我们将以此为基础创建一个复合信号指标,该指标在图表上以箭头显示入场点。 

图例 4 按指标信号的入场条件。

//+------------------------------------------------------------------+
//|                                                      Example.mq5 |
//|                                                         Alex2356 |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Alex2356"
#property link      "https://www.mql5.com/en/users/alex2356"
#property version   "1.00"
#property indicator_chart_window
//--- two buffers are used for calculating and drawing the indicator
#property indicator_buffers 2
//--- used graphic constructions
#property indicator_plots   2
#property indicator_label1  "Buy Signal"
#property indicator_type1   DRAW_ARROW
//---
#property indicator_label2  "Sell Signal"
#property indicator_type2   DRAW_ARROW
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
input group "ADX Cloud Parameters"
input int                  ADXPeriod         =  8;
input double               Alpha1            =  0.25;
input double               Alpha2            =  0.25;
input group "RVI Color Parameters"
input uint                 Smoothing         =  15;
//----
input double               Weight1           =  0.05;
input int                  RVI_period1       =  8;
//----
input double               Weight2           = 0.10;
input int                  RVI_period2       =   21;
//----
input double               Weight3           = 0.16;
input int                  RVI_period3       =   34;
//----
input double               Weight4           = 0.26;
input int                  RVI_period4       =   55;
//----
input double               Weight5           = 0.43;
input int                  RVI_period5       =   89;
//---
double BuySignal[],SellSignal[],ADXCloud[],FastRVI[],SlowRVI[];
int ADX_Handle,RVI_Hadnle,min_rates_total;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   SetIndexBuffer(0,BuySignal,INDICATOR_DATA);
   SetIndexBuffer(1,SellSignal,INDICATOR_DATA);
//---
   PlotIndexSetInteger(0,PLOT_ARROW,233);
   PlotIndexSetInteger(1,PLOT_ARROW,234);
//---
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,clrDodgerBlue);
   PlotIndexSetInteger(1,PLOT_LINE_COLOR,clrCrimson);
//---
   ArraySetAsSeries(SellSignal,true);
   ArraySetAsSeries(BuySignal,true);
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,20);
   PlotIndexSetInteger(1,PLOT_ARROW_SHIFT,-20);
//---
   ADX_Handle=iCustom(Symbol(),PERIOD_CURRENT,"adxcloud",ADXPeriod,Alpha1,Alpha2);
   if(ADX_Handle==INVALID_HANDLE)
   {
      Print(" Failed to create indicator handle");
      return(INIT_FAILED);
   }
//---
   RVI_Hadnle=iCustom(Symbol(),PERIOD_CURRENT,"colorzerolagrvi",
                      Smoothing,
                      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");
      return(INIT_FAILED);
   }
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
//--- c
   if(BarsCalculated(ADX_Handle)<rates_total || BarsCalculated(RVI_Hadnle)<rates_total || rates_total<min_rates_total)
      return(0);
//--- 
   int limit,to_copy,i;
//--- 
   ArraySetAsSeries(ADXCloud,true);
   ArraySetAsSeries(FastRVI,true);
   ArraySetAsSeries(SlowRVI,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
//--- 
   if(prev_calculated>rates_total || prev_calculated<=0) 
      limit=rates_total-2;
   else
      limit=rates_total-prev_calculated; 
   to_copy=limit+2;
//---
   if(CopyBuffer(ADX_Handle,0,0,to_copy,ADXCloud)<=0)
      return(0);
//---
   if(CopyBuffer(RVI_Hadnle,0,0,to_copy,FastRVI)<=0)
      return(0);
   if(CopyBuffer(RVI_Hadnle,1,0,to_copy,SlowRVI)<=0)
      return(0);
//--- 
   for(i=limit-1; i>=0 && !IsStopped(); i--)
   {
      if(ADXCloud[i+1]>0 && FastRVI[i+1]>SlowRVI[i+1] && FastRVI[i+2]<SlowRVI[i+2])
      {
         BuySignal[i]=low[i];
         SellSignal[i]=EMPTY_VALUE;
      }
      else if(ADXCloud[i+1]<0 && FastRVI[i+1]<SlowRVI[i+1] && FastRVI[i+2]>SlowRVI[i+2])
      {
         SellSignal[i]=high[i];
         BuySignal[i]=EMPTY_VALUE;
      }
      else
      {
         BuySignal[i]=EMPTY_VALUE;
         SellSignal[i]=EMPTY_VALUE;
      }
   }
//--- return value of prev_calculated for the next call
   return(rates_total);
}
//+------------------------------------------------------------------+

最终实现的效果如图例 5 所示。 现在我们需要实现语音通知系统。

图例 5 基于两个指标的箭头指标。

首先,把文件 SoundsLib.mqh 与指标相连。

#include <SoundsLib/SoundsLib.mqh>

创建语音通知类的实例:

CSoundsLib Notify;

OnInit() 初始化函数里,设置通知语言。 在此,我将其设为英语。 实际上,英语作为默认情况设置,因此无需额外设置。 此处这样做,我们只是出于演示目的。

Notify.Language(ENGLISH);

由于箭头指标仅显示入场点,或买/卖信号,因此我们将用到 MESSAGE 枚举中的两个语音通知: 

   SIGNAL_BUY,                         // A Buy signal
   SIGNAL_SELL,                        // A Sell signal

当我们将通知系统嵌入指标当中,不应在整个历史记录中生成警报,而应在当前柱线上生成警报。 因此,如下修改信号搜索循环:

//--- 
   for(i=limit-1; i>=0 && !IsStopped(); i--)
   {
      if(ADXCloud[i+1]>0 && FastRVI[i+1]>SlowRVI[i+1] && FastRVI[i+2]<SlowRVI[i+2])
      {
         BuySignal[i]=low[i];
         SellSignal[i]=EMPTY_VALUE;
         if(i==0)
            Notify.Message(SIGNAL_BUY);
      }
      else if(ADXCloud[i+1]<0 && FastRVI[i+1]<SlowRVI[i+1] && FastRVI[i+2]>SlowRVI[i+2])
      {
         SellSignal[i]=high[i];
         BuySignal[i]=EMPTY_VALUE;
         if(i==0)
            Notify.Message(SIGNAL_SELL);
      }
      else
      {
         BuySignal[i]=EMPTY_VALUE;
         SellSignal[i]=EMPTY_VALUE;
      }
   }

此处,我们检查零号柱线上是否有信号,若有,则通知终端用户。 


在智能交易系统中的实际应用

通常,对于指标来说,两种语音警报就足够了。 此外,您可以实现警报来通知进入振荡器的超买区域、布林带通道突破,等等。 智能交易系统中可以使用更多警报。 因此,我们创建一个测试交易机器人,该机器人不仅会通知入场信号,还会评论其他行动,例如,要开何种类型的仓位。 首先,我们另外定义专家顾问的市场进入策略。 

参数 说明
用到的指标 ColorStDev
用到的指标 三重 Tirone 等级
时间帧选择 任意
买入条件 ColorStdDev 直方图为红色(趋势强烈),而当前价格应高于上层 Tirone 级别。
卖出条件 ColorStdDev 直方图为红色(趋势强烈),而当前价格应低于底层 Tirone 级别。
离场条件   止盈/止损

入场点可直观显示,如图例 6 所示。

图例 6 此策略中的入场示例。

现在,我们来为 MetaTrader 5 实现该策略。 语音警报将用于某些事件。 

//+------------------------------------------------------------------+
//|                                                  VoiceNotify.mq5 |
//|                                                         Alex2356 |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Alex2356"
#property link      "https://www.mql5.com/en/users/alex2356"
#property version   "1.00"
#include <SoundsLib/SoundsLib.mqh>
#include <DoEasy25/Engine.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
input uint                 InpStopLoss          =  150;              // Stop Loss, in pips
input uint                 InpTakeProfit        =  250;              // Take Profit, in pips
input double               InpLot               =  0.1;              // Take Profit, in pips
input ulong                InpDeviation         =  10;               // Deviation
input int                  InpMagic             =  2356;             // Magic number
input LANGUAGE             NotifyLanguage       =  ENGLISH;          // Notification Language
//--- ColorStDev indicator parameters
input int                  StDevPeriod          =  12;               // Smoothing period StDev
input ENUM_MA_METHOD       MA_Method            =  MODE_EMA;         // Histogram smoothing method
input ENUM_APPLIED_PRICE   applied_price        =  PRICE_CLOSE;      // Applied price
input int                  MaxTrendLevel        =  90;               // Maximum trend level
input int                  MiddLeTrendLevel     =  50;               // Middle trend level
input int                  FlatLevel            =  20;               // Flat level
//--- Tirone Levels indicator parameters
input int                  TironePeriod         =  13;               // Tirone Period
//---
CEngine trade;
CSoundsLib notify;
int Handle1,Handle2;
double stdev[],tirone_b[],tirone_s[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()


{
//---
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      notify.Message(AUTO_TRADING_OFF);
//---
   OnInitTrading();
//--- 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());
      return(INIT_FAILED);
   }
//--- Getting the handle of the Tirone Levels indicator
   Handle2=iCustom(Symbol(),PERIOD_CURRENT,"ArticleVoiceNotify\\tirone_levels_x3",TironePeriod,0);
   if(Handle2==INVALID_HANDLE)
   {
      Print("Failed to get Tirone Levels handle");
      Print("Handle = ",Handle2,"  error = ",GetLastError());
      return(INIT_FAILED);
   }
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//--- If there are no market positions
   if(ExistPositions(Symbol(),-1,InpMagic)<1)
   {
      //--- Getting data for calculations
      if(!GetIndValue())
         return;
      //--- Open an order if there is a buy signal
      if(BuySignal())
      {
         notify.Message(SIGNAL_BUY);
         if(trade.OpenBuy(InpLot,Symbol(),InpMagic,InpStopLoss,InpTakeProfit))
         {
            Sleep(1400);
            notify.Message(BUY_ORDER_SET);
         }
      }
      //--- Opening an order if there is a sell signal
      if(SellSignal())
      {
         notify.Message(SIGNAL_SELL);
         if(trade.OpenSell(InpLot,Symbol(),InpMagic,InpStopLoss,InpTakeProfit))
         {
            Sleep(1400);
            notify.Message(SELL_ORDER_SET);
         }
      }
   }
}
//+------------------------------------------------------------------+
//| Buy conditions                                                   |
//+------------------------------------------------------------------+
bool BuySignal()
{
   return(tirone_b[1]>iClose(Symbol(),PERIOD_CURRENT,1) && stdev[0]>FlatLevel)?true:false;
}
//+------------------------------------------------------------------+
//| Sell conditions                                                  |
//+------------------------------------------------------------------+
bool SellSignal()
{
   return(tirone_b[1]<iClose(Symbol(),PERIOD_CURRENT,1) && stdev[0]>FlatLevel)?true:false;
}
//+------------------------------------------------------------------+
//| Getting the current values of indicators                         |
//+------------------------------------------------------------------+
bool GetIndValue()
{
   return(CopyBuffer(Handle1,0,0,2,stdev)<=0    ||
          CopyBuffer(Handle2,0,0,2,tirone_b)<=0 ||
          CopyBuffer(Handle2,2,0,2,tirone_s)<=0
         )?false:true;
}
//+----------------------------------------------------------------------------+
//|  Returns the number of open orders                                         |
//+----------------------------------------------------------------------------+
//|  Parameters:                                                               |
//|    op - operation                  (-1   - any position)                   |
//|    mn - MagicNumber                (-1   - any magic number)               |
//+----------------------------------------------------------------------------+
int ExistPositions(string sy,int op=-1,int mn=-1)
{
   int pos=0;
   uint total=PositionsTotal();
//---
   for(uint i=0; i<total; i++)
   {
      if(SelectByIndex(i))
         if(PositionGetString(POSITION_SYMBOL)==sy)
            if(op<0 || PositionGetInteger(POSITION_TYPE)==op)
               if(mn<0 || PositionGetInteger(POSITION_MAGIC)==mn)
                  pos++;
   }
   return(pos);
}
//+------------------------------------------------------------------+
//| Select a position on the index                                   |
//+------------------------------------------------------------------+
bool SelectByIndex(const int index)
{
   ENUM_ACCOUNT_MARGIN_MODE margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
//---
   if(margin_mode==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      ulong ticket=PositionGetTicket(index);
      if(ticket==0)
         return(false);
   }
   else
   {
      string name=PositionGetSymbol(index);
      if(name=="")
         return(false);
   }
//---
   return(true);
}
//+------------------------------------------------------------------+
//| Trading Environment Initialization                               |
//+------------------------------------------------------------------+
void OnInitTrading()
{
   string array_used_symbols[];
//--- Fill in the array of used symbols
   CreateUsedSymbolsArray(SYMBOLS_MODE_CURRENT,"",array_used_symbols);
//--- Set the type of the used symbol list in the symbol collection and fill in the list of symbol timeseries
   trade.SetUsedSymbols(array_used_symbols);
//--- Pass all existing collections to the trading class
   trade.TradingOnInit();
   trade.TradingSetMagic(InpMagic);
   trade.TradingSetLogLevel(LOG_LEVEL_ERROR_MSG);
//--- Set synchronous passing of orders for all used symbols
   trade.TradingSetAsyncMode(false);
//--- Set correct order expiration and filling types to all trading objects
   trade.TradingSetCorrectTypeExpiration();
   trade.TradingSetCorrectTypeFilling();
}
//+------------------------------------------------------------------+

我们根据语音警报的使用情况,更详近地研究此代码。 交易机器人的初始化函数包括检查是否允许交易系统在终端中进行交易。 如果禁用此选项,将播放相应的语音警报,从而通知用户。 然后,在 OnTick() 函数里,如果发现期望的交易信号,则 EA 会通知已发现所需的买/卖信号。 根据信号尝试开仓。 如果成功,则将播放另一个语音警报,从而通知用户已开仓。

这些通知效率更高,因为用户可能会错过终端中“智能系统”选项卡中的文本警报。 对于标准声音警报,声音的含义并不总是很清晰,它们在指标和智能交易系统中可能有所不同。 语音通知提供了准确的信息,因此更加方便。


快捷交易工具中的实际应用

在我之前发的文章中,我为手动交易者开发了一个工具箱,可独立搜索入场时机,手动下订单,管理持仓,和平仓。 出于演示目的,我想往此工具箱里添加语音通知系统。 这也表明可以将语音警报功能轻松添加到任何工具当中。 作为基础,我将借用来自此文章中的附件。 首先,我们定义添加语音警报的动作和事件的列表。

  • 成功开立买卖仓位。
  • 成功下挂单,并删除。
  • 检查是否可以使用智能交易系统下单。

在我们开始集成语音通知之前,我们将相关函数库连接到该项目。 打开 Program.mqh,并在开头加入以下内容。

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                                                         Alex2356 |
//|                    https://www.mql5.com/en/users/alex2356/       |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndEvents.mqh>
#include <DoEasy25\Engine.mqh>
#include "Defines.mqh"
#include <SoundsLib/SoundsLib.mqh>

转到 CFastTrading 类的私密部分,并创建一个 CSoundsLib 类的实例变量。

   //---
   CSoundsLib        m_notify;

另外,在工具箱中设置两个新参数,这将允许启用/禁用通知,并选择语言。 打开 SimpleTrading.mq5,并在 EA 的“输入参数”部分中添加新参数:

//+------------------------------------------------------------------+
//| Expert Advisor input parameters                                  |
//+------------------------------------------------------------------+
input int                  Inp_BaseFont      =  10;                  // Base FontSize
input color                Caption           =  C'0,130,225';        // Caption Color
input color                Background        =  clrWhite;            // Back color
input LANG                 Language          =  ENGLISH;             // Interface language
input ulong                MagicNumber       =  1111;                // Magic Number
//---
input bool                 UseVoiceNotify    =  true;                // Use Voice Notify
input LANGUAGE             NotifyLanguage    =  ENGLISH;             // Notification Language

为了将它们传递给 CSoundsLib 类的实例 m_notify ,请在 CFastTrading 基类的公开部分中创建两个方法,并实现它们:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CFastTrading::SetNotifyLanguage(LANGUAGE lang)
{
   m_notify.Language(lang);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CFastTrading::UseVoiceNotify(bool state)
{
   m_notify.IsActive(state);
}
//+------------------------------------------------------------------+

现在,在 SimpleTrading.mq5 中的 OnInit() 函数中实现它们,并将输入参数传递给新创建的方法。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   tick_counter=GetTickCount();
//--- Initialize class variables
   program.FontName("Trebuchet MS");
   program.FontSize(Inp_BaseFont);
   program.BackgroundColor(Background);
   program.CaptionColor(Caption);
   program.SetLanguage(Language);
   program.SetMagicNumber(MagicNumber);
   program.UseVoiceNotify(UseVoiceNotify);
   program.SetNotifyLanguage(NotifyLanguage);
//--- Set up the trading panel
   if(!program.CreateGUI())
   {
      Print(__FUNCTION__," > Failed to create graphical interface!");
      return(INIT_FAILED);
   }
   program.OnInitEvent();
//---
   return(INIT_SUCCEEDED);
}

因此,我们已经设置了语音通知系统的主要输入参数。 现在,找到设置入场买卖开仓的方法。 这些是 SetBuyOrder() SetSellOrder() 方法,位于 CFastTrading 基类之中。 打开下订单方法的主体,找到要检查开仓是否成功的部分。 在此添加相应的语音警报  BUY_ORDER_SET:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFastTrading::SetBuyOrder(int id,long lparam)
{
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buy_execute.Id()) ||
         (id==CHARTEVENT_KEYDOWN && lparam==KEY_B))
   {
      //---
      double lot;
      if(m_switch_button[0].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[0].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[0].GetValue()));
      if(m_switch_button[1].IsPressed() && m_switch_button[2].IsPressed())
      {
         double tp=double(m_tp_edit[0].GetValue());
         double sl=double(m_sl_edit[0].GetValue());
         if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(BUY_ORDER_SET);
            return(true);
         }
      }
      else if(!m_switch_button[1].IsPressed() && !m_switch_button[2].IsPressed())
      {
         int tp=int(m_tp_edit[0].GetValue());
         int sl=int(m_sl_edit[0].GetValue());
         if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(BUY_ORDER_SET);
            return(true);
         }
      }
      else if(m_switch_button[1].IsPressed() && !m_switch_button[2].IsPressed())
      {
         double tp=double(m_tp_edit[0].GetValue());
         int sl=int(m_sl_edit[0].GetValue());
         if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(BUY_ORDER_SET);
            return(true);
         }
      }
      else if(!m_switch_button[1].IsPressed() && m_switch_button[2].IsPressed())
      {
         int tp=int(m_tp_edit[0].GetValue());
         double sl=double(m_sl_edit[0].GetValue());
         if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(BUY_ORDER_SET);
            return(true);
         }
      }
   }
   return(false);
}

针对开立空头仓位的方法进行同样的修改。 所用的语音警报是 SELL_ORDER_SET:

bool CFastTrading::SetSellOrder(int id,long lparam)
{
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_sell_execute.Id()) ||
         (id==CHARTEVENT_KEYDOWN && lparam==KEY_S))
   {
      //---
      double lot;
      if(m_switch_button[3].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[1].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[1].GetValue()));
      //---
      if(m_switch_button[4].IsPressed() && m_switch_button[5].IsPressed())
      {
         double tp=double(m_tp_edit[1].GetValue());
         double sl=double(m_sl_edit[1].GetValue());
         if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(SELL_ORDER_SET);
            return(true);
         }
      }
      else if(!m_switch_button[4].IsPressed() && !m_switch_button[5].IsPressed())
      {
         int tp=int(m_tp_edit[1].GetValue());
         int sl=int(m_sl_edit[1].GetValue());
         if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(SELL_ORDER_SET);
            return(true);
         }
      }
      else if(!m_switch_button[4].IsPressed() && m_switch_button[5].IsPressed())
      {
         int tp=int(m_tp_edit[1].GetValue());
         double sl=double(m_sl_edit[1].GetValue());
         if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(SELL_ORDER_SET);
            return(true);
         }
      }
      else if(m_switch_button[4].IsPressed() && !m_switch_button[5].IsPressed())
      {
         double tp=double(m_tp_edit[1].GetValue());
         int sl=int(m_sl_edit[1].GetValue());
         if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(SELL_ORDER_SET);
            return(true);
         }
      }
   }
   return(false);
}

现在,我们转至处理挂单。 该工具箱支持四种类型,每种都有单独的方法:

   bool              SetBuyStopOrder(int id,long lparam);
   bool              SetSellStopOrder(int id,long lparam);
   bool              SetBuyLimitOrder(int id,long lparam);
   bool              SetSellLimitOrder(int id,long lparam);

应该为每个语音通知设置单独的语音通知。 这是一个 BuyStop 订单示例,其他订单也以类似的方式设置。 从下面的代码可以看出,BUYSTOP_ORDER_SET 用的是通知。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFastTrading::SetBuyStopOrder(int id,long lparam)
{
   if(!m_orders_windows[1].IsVisible())
      return(false);
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buystop_execute.Id()) ||
         (id==CHARTEVENT_KEYDOWN && lparam==KEY_1))
   {
      //---
      double lot;
      if(m_p_switch_button[0].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[2].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[2].GetValue()));
      //---
      double pr=double(m_pr_edit[0].GetValue());
      //---
      if(m_p_switch_button[1].IsPressed() && m_p_switch_button[2].IsPressed())
      {
         double tp=double(m_tp_edit[2].GetValue());
         double sl=double(m_sl_edit[2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            return(true);
         }
      }
      else if(!m_p_switch_button[1].IsPressed() && !m_p_switch_button[2].IsPressed())
      {
         int tp=int(m_tp_edit[2].GetValue());
         int sl=int(m_sl_edit[2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            return(true);
         }
      }
      else if(m_p_switch_button[1].IsPressed() && !m_p_switch_button[2].IsPressed())
      {
         double tp=double(m_tp_edit[2].GetValue());
         int sl=int(m_sl_edit[2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            return(true);
         }
      }
      else if(!m_p_switch_button[1].IsPressed() && m_p_switch_button[2].IsPressed())
      {
         int tp=int(m_tp_edit[2].GetValue());
         double sl=double(m_sl_edit[2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            return(true);
         }
      }
   }
   return(false);
}

现在,当已下挂单的通知准备就绪时,我们需要添加通知来删除早前下的挂单。 RemoveOrder() 方法判定表中的哪笔挂单被选择。 所选订单随后可以修改或删除。 在此,通过单击删除按钮删除订单。 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFastTrading::RemoveOrder(long lparam)
{
//--- Check the element ID
   if(lparam==m_small_button[3].Id())
   {
      //--- Get index and symbol
      if(m_table_orders.SelectedItem()==WRONG_VALUE)
         return(false);
      int row=m_table_orders.SelectedItem();
      ulong ticket=(ulong)m_table_orders.GetValue(0,row);
      //---
      if(OrderSelect(ticket))
      {
         string position_symbol=OrderGetString(ORDER_SYMBOL);                          // symbol
         ulong  magic=OrderGetInteger(ORDER_MAGIC);                                    // order MagicNumber
         ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);            // order type
         if(type==ORDER_TYPE_BUY_STOP)
            m_notify.Message(BUYSTOP_ORDER_DELETE);
         else if(type==ORDER_TYPE_SELL_STOP)
            m_notify.Message(SELLSTOP_ORDER_DELETE);
         else if(type==ORDER_TYPE_BUY_LIMIT)
            m_notify.Message(BUYLIMIT_ORDER_DELETE);
         else if(type==ORDER_TYPE_SELL_LIMIT)
            m_notify.Message(SELLLIMIT_ORDER_DELETE);
         //--- declare the request and the result
         MqlTradeRequest request;
         MqlTradeResult  result;
         //--- zeroing the request and result values
         ZeroMemory(request);
         ZeroMemory(result);
         //--- set the operation parameters
         request.action=TRADE_ACTION_REMOVE;             // trading operation type
         request.order = ticket;                         // order ticket
         //--- sending a request
         bool res=true;
         for(int j=0; j<5; j++)
         {
            res=OrderSend(request,result);
            if(res && result.retcode==TRADE_RETCODE_DONE)
               return(true);
            else
               PrintFormat("OrderSend error %d",GetLastError());  // if unable to send the request, output the error code
         }
      }
   }
//---
   return(false);
}

我们更详细地研究方法主体的修改。 一旦确定所选订单的票据后,我们接收所要删除挂单请求的数据,填写 MqlTradeRequest 结构,并调用 OrderSend() 方法。 根据 type 变量判断表中所选挂单的类型。 根据变量值,在 Message() 方法中设置相应的语音通知。

要实现的最后一项任务是添加语音通知,如果在 MetaTrader 5 终端中禁用了自动交易,则提醒。 该工具箱实际上是一个智能交易系统:尽管用户是手动下单,但终端和经纪商会将其识别为自动交易。 为了添加自动交易检查,请转到基类,找到 OnEvent() 应答程序 -> ON_END_CREATE_GUI 部分,然后添加检查与相应的语音通知

// --- GUI creation completion
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      //---
      SetButtonParam(m_switch_button[0],LOT);
      SetButtonParam(m_switch_button[1],POINTS);
      SetButtonParam(m_switch_button[2],POINTS);
      SetButtonParam(m_switch_button[3],LOT);
      SetButtonParam(m_switch_button[4],POINTS);
      SetButtonParam(m_switch_button[5],POINTS);
      //---
      SetButtonParam(m_p_switch_button[0],LOT);
      SetButtonParam(m_p_switch_button[1],POINTS);
      SetButtonParam(m_p_switch_button[2],POINTS);
      SetButtonParam(m_p_switch_button[3],LOT);
      SetButtonParam(m_p_switch_button[4],POINTS);
      SetButtonParam(m_p_switch_button[5],POINTS);
      SetButtonParam(m_p_switch_button[6],LOT);
      SetButtonParam(m_p_switch_button[7],POINTS);
      SetButtonParam(m_p_switch_button[8],POINTS);
      SetButtonParam(m_p_switch_button[9],LOT);
      SetButtonParam(m_p_switch_button[10],POINTS);
      SetButtonParam(m_p_switch_button[11],POINTS);
      //---
      if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
         m_notify.Message(AUTO_TRADING_OFF);
   }

以下视频展示了语音通知在快捷交易工具箱中的工作方式,其中警报用于市价开仓和挂单。


结束语

附件存档包含所有列出的文件,这些文件应保存至相应的文件夹当中。 为了令其正常运行,您仅需要将 MQL5 文件夹保存到终端文件夹之中。 若要打开 MQL5 文件夹所在的终端根目录,请在 MetaTrader 5 终端中按 Ctrl+Shift+D 组合键,或使用关联菜单,如下图例 7 所示。


图例 7. 在 MetaTrader 5 终端根目录中打开 MQL5 文件夹


本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/8111

附加的文件 |
MQL5.zip (8868.77 KB)
概率论与数理统计示例(第一部分):基础与初级理论 概率论与数理统计示例(第一部分):基础与初级理论

交易总是需要在面对不确定性时做出决定。 这意味着在做出这些决策时,其结局并不十分明朗。 如此看出建立数学模型的理论方法的重要性,它能够令我们以有意义的方式描述这种情况。

DoEasy 函数库中的时间序列(第四十六部分):多周期、多品种指标缓冲区 DoEasy 函数库中的时间序列(第四十六部分):多周期、多品种指标缓冲区

在本文中,我将继续改进指标缓冲区对象类,从而可在多品种模式下操作。 这为自定义程序中创建多品种、多周期指标提供了途径。 我会在计算缓冲区对象里添加缺失的功能,从而令我们可创建多品种、多周期的标准指标。

DoEasy 函数库中的时间序列(第四十七部分):多周期、多品种标准指标 DoEasy 函数库中的时间序列(第四十七部分):多周期、多品种标准指标

在本文中,我着手开发操控标准指标的方法,最终能够基于函数库类创建多品种、多周期的标准指标。 此外,我将在时间序列类中添加“跳过柱线”事件,并将函数库的预备函数移至 CEngine 类,从而消减主程序代码中的过多负载。

检测超买/超卖区域的方法。 第一部分 检测超买/超卖区域的方法。 第一部分

超买/超卖区域是某种市场状态的特征,可由证券价格的疲软变化来区分。 这种负面变化会明显发生在任何尺度趋势发展的最后阶段。 由于交易中的利润价值直接取决于尽可能覆盖更广趋势幅度的能力,因此在任何证券交易中,探测此类区域的准确性是一项关键任务。