监视多币种的交易信号(第五部分):复合信号

26 八月 2020, 09:12
Alexander Fedosov
0
2 034

内容

概述

在系列文章的前一个部分里,我们致力于创建多币种交易信号监控器,我们引入了使用自定义指标的可能性,并扩展了逻辑规则系统,允许过滤数据。 然而,许多交易者和开发人员采用基于多个指标的交易系统。 每种指标类型,包括趋势、横盘和振荡指标,在某些行情里都有其劣势。

例如,在长时间的横盘走势中,趋势指标通常会产生许多假信号。 因此,它们需要一些其他工具来确认。 这可以通过添加更多非主要指标来完成,这些指标可用作主要指标生成信号的过滤器。 选项则是,交易者可以基于一组示意相同行情状态的指标来创建交易系统。 在这种情况下,可以同时用若干个指标来确认所生成的入场信号。

因此,我们开发指标的下一个逻辑步骤是引入由可用的简单信号组成的复合信号。 故此,我们将用复合信号的概念来操作。 我们将用上一个部分里的项目作为基础。 该项目提供在上一篇文章的结尾


复合信号

首先,我们来定义项目中所用的“复合信号”的概念。 在早前的版本里,我们只采用了简单信号,它们只是接收源数据并应用特定条件而创建的,可以由各种指标(例如 RSI、WPR 和 CCI)来表达。 此外,还实现了运用自定义指标的可能性。

复合信号是由两个或更多个简单信号合成的信号,这些信号通过逻辑 AND(与)/ OR(或)运算符相互连接。

因此,复合信号将包括几个先前创建的简单信号,这些信号将用逻辑运算符进行交互。 还有可能创建一个复杂条件的信号,其中包含给定时间段内同时存在的两个或三个简单信号。 因此,交易系统将拥有一个主要信号和一个过滤器。 逻辑 “OR(或)”运算符可让我们能够一次性在若干个方向上搜索交易信号,从而在分析当前行情状况时涵盖更大的范围。

为了更好的解释,这里有一些更新和增强我们的应用程序的计划。 首先,我们需要更新应用程序界面,并添加新控件。


图例 1. 加入 UI 元素

图例 1 的左侧您已很熟悉了。 有一个添加信号的按钮,后随已添加的简单信号。 现在,需要创建一个添加复合信号的按钮(1),已添加复合信号的列表(2),和第三组开关按钮(3),能为简单信号设置使用标志:

  • S(Simple) — 一个简单信号。 它在监视器中作为独立信号,无法在复合信号中选择。
  • C(Composite) — 一个简单的信号,只能作为复合信号的一部分。 它不能在监视器中作为独立信号
  • B(Both) — 两种应用类型。 这样的信号可在监视器中作为一个独立的信号进行搜索,并且可作为复合信号的一部分。

复合信号创建规程如下图例 2 所示。 名为“Simple 1” 和 “Simple 2” 的元素示意已创建的简单信号列表,这些可用于创建复杂信号。 至于信号用法属性,图例 2 中所示的信号应具有属性 “C” 或 “B”,否则不会将它们显示在列表之中。

图例 2 复合信号创建和编辑窗口的规程

接下来是称为 Rule(规则)的部分,在其中创建复合信号。 可将形成上述列表中的信号加入到三个批次中的任意一个。 利用按钮选择卡槽之间的逻辑运算符。 这为该复合信号设置规则。 例如,如果您设置了 Simple 1 和 Simple 2,则仅当这两个简单信号同时出现时才显示复合信号。


实现功能

应用程序中引入新功能之前,必须确定现有元素已准备好与新功能一起操作。 简单信号列表的 UI 元素需要修改。 图例 3 所示,简单按钮上的简单信号系统名称会被用户指定信号名称的文本标签所替代,以及 “Edit” 按钮和信号使用标志按钮。


图例 3. 更新信号编辑控件

现在,打开从上一篇文章下载的项目,然后继续进行添加。 首先,删除 CreateSignalEditor() 方法,和创建编辑按钮所需的 m_signal_editor[] 数组。 在继续创建新的元素和方法之前,我们引入两个宏替换,它们将确定简单和复合信号的最大允许数量。

#define SIMPLE_SIGNALS 10
#define COMPOSITE_SIGNALS 5

 接下来,转到 CreateStepWindow() 主窗口创建方法的主体末尾,并替换以下代码:

   for(int i=0; i<5; i++)
   {
      if(!CreateSignalEditor(m_signal_editor[i],"Signal_"+string(i),10,40*i+90))
         return(false);
   }

添加新的显示和编辑新信号的实现。

   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(!CreateLabel(m_signal_label[i],10,40*i+95,"Signal_"+string(i)))
         return(false);
      if(!CreateSignalSet(m_signal_ind[i],"Edit",C'255,195,50',150,40*i+90))
         return(false);
      if(!CreateSignalSet(m_signal_type[i],"S",C'75,190,240',150+35,40*i+90))
         return(false);
   }

该实现含有两个新的 CButton 类实例数组 m_signal_ind[] 和 m_signal_type[],以及 CTextLabel 的实例数组 m_signal_label[]。 将它们添加到项目的基类 CProgram 当中。

   CTextLabel        m_signal_label[SIMPLE_SIGNALS];
   CButton           m_signal_ind[SIMPLE_SIGNALS];
   CButton           m_signal_type[SIMPLE_SIGNALS];

另外,声明并实现新的 CreateSignalSet() 方法。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSignalSet(CButton &button,string text,color baseclr,const int x_gap,const int y_gap)
{
//--- Store the window pointer
   button.MainPointer(m_step_window);
//--- Set properties before creation
   button.XSize(30);
   button.YSize(30);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(baseclr);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(baseclr);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

因此,我们用图例 3 中更新的交互界面形成了一个简单信号列表。 添加复合信号列表,仅需简单地在基类和如下所示的 CreateStepWindow() 方法里加入变量 m_c_signal_label[] 和 m_c_signal_ind[]

CTextLabel        m_c_signal_label[COMPOSITE_SIGNALS];
CButton           m_c_signal_ind[COMPOSITE_SIGNALS];
//---
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(!CreateLabel(m_c_signal_label[i],300,40*i+95,"Signal_"+string(i)))
         return(false);
      if(!CreateSignalSet(m_c_signal_ind[i],"Edit",C'255,195,50',150+290,40*i+90))
         return(false);
   }

新创建的显示和编辑简单和复合信号的工具。 现在,在应用程序初始设置的第 3 步中,添加一个创建复合信号的按钮,以及一个文本标题。 这是把 m_add_signal 变量变为一个静态数组 m_add_signal[2] 来完成,该变量是 CButton 类的实例,并在 AddSignal 按钮中所用。 在 CreateStepWindow() 方法主体中替换以下代码

   if(!CreateIconButton(m_add_signal,m_lang[15],10,30))
      return(false);

新代码包括一个复合信号创建按钮:

   if(!CreateIconButton(m_add_signal[0],m_lang[15],10,30))
      return(false);
   if(!CreateIconButton(m_add_signal[1],m_lang[43],300,30))
      return(false);

为了显示简单和复合信号列表的标题,将 m_signal_header 变量替换为 m_signal_header[2] 静态数组。 之前的代码:

   if(!CreateLabel(m_signal_header,10,30+30+10,m_lang[16]))
      return(false);

应该用两个标题替换:

   if(!CreateLabel(m_signal_header[0],10,30+30+10,m_lang[16]))
      return(false);
   if(!CreateLabel(m_signal_header[1],300,30+30+10,m_lang[44]))
      return(false);

完成上述所有修改后,步骤 3 中更新的界面如下所示:

图例 4 添加并更新创建交易信号的按钮 

现在,我们需要将简单交易信号列表里早前创建的对象与创建和编辑事件相关联。 在此,我添加一个简短通知。 鉴于 UI 元素的数量在增加,导致可能的用户交互数量也在增加,因此 OnEvent() 应答程序主体变得太长,且很难理解某个事件或一组事件属于哪一个元素。 这就是为什么我决定将所有与 UI 的关键交互包装在相应方法里的原因。 还有两个优点: OnEvent() 含有关键事件的列表,因此可以轻松访问每个关键事件的逻辑和代码。

找到负责添加新的简单交易信号的代码片段,加入所需的代码,并将其移至 AddSimpleSignal() 方法当中:

//+------------------------------------------------------------------+
//| Adds a new simple trading signal                                 |
//+------------------------------------------------------------------+
void CProgram::AddSimpleSignal(long lparam)
{
   if(lparam==m_new_signal.Id())
   {
      if(m_number_signal<0)
      {
         if(SaveSignalSet(m_total_signals))
         {
            m_set_window.CloseDialogBox();
            if(m_total_signals<SIMPLE_SIGNALS)
            {
               m_total_signals++;
               m_signal_label[m_total_signals-1].Show();
               m_signal_label[m_total_signals-1].LabelText(m_signal_name.GetValue());
               m_signal_label[m_total_signals-1].Update(true);
               m_signal_type[m_total_signals-1].Show();
               m_signal_ind[m_total_signals-1].Show();
            }
            else
               MessageBox("Maximum number of signals is "+string(SIMPLE_SIGNALS),"Signal Monitor");
            //---
            if(m_total_signals>1)
            {
               m_add_signal[1].IsLocked(false);
               m_add_signal[1].Update(true);
            }
         }
      }
      else
      {
         if(SaveSignalSet(m_number_signal,false))
         {
            m_set_window.CloseDialogBox();
            m_signal_label[m_number_signal].LabelText(m_signal_name.GetValue());
            m_signal_label[m_number_signal].Update(true);
         }
      }
   }
}

OnEvent() 应答程序里调用它。 由此,我们降低了方法主体的大小,并将构造其实现部分。 图例 3 所示,新的实现能够支持设置自定义信号名称。 这可以通过在简单信号创建和编辑窗口中添加以下字段来完成。 创建一个名为 CreateSignalName() 的新方法,该方法将为信号名称添加一个输入字段。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSignalName(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Store the pointer to the main control
   text_edit.MainPointer(m_set_window);
//--- Properties
   text_edit.XSize(110);
   text_edit.YSize(24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.GetTextBoxPointer().XGap(110);
   text_edit.GetTextBoxPointer().XSize(200);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(m_lang[44]);
//--- Create a control element
   if(!text_edit.CreateTextEdit(m_lang[44],x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(1,text_edit);
   return(true);
}

同样,不要忘记将简单信号名称的输入字段添加到 SIGNAL 结构当中。

uchar             signal_name[50];

方法 SaveSignalSet() LoadSignalSet() 也应进行更新,从而可在现有设置之外,还能记录信号名称。 创建新信号时,应避免出现新信号名称与之前创建的信号之一重名的情况。 应避免的另一种情况是将参数保存到早前创建的信号文件之中。 因此,应为 SaveSignalSet() 方法添加第二个 first_save 参数。 它表示第一次是保存信号呢?还是保存早前创建的信号的已编辑参数。

bool              SaveSignalSet(int index,bool first_save=true);

该方法的完整实现如下所示:

bool CProgram::SaveSignalSet(int index,bool first_save=true)
{
//---
   if(first_save && !CheckSignalNames(m_signal_name.GetValue()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Это имя уже используется","Монитор сигналов");
      else
         MessageBox("This name is already in use","Signal Monitor");
      return(false);
   }
//---
   int h=FileOpen("Signal Monitor\\signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Monitor");
      return(false);
   }
   if(index>SIMPLE_SIGNALS-1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Максимальное число сигналов не должно быть больше "+string(SIMPLE_SIGNALS),"Монитор сигналов");
      else
         MessageBox("Maximum number of signals is "+string(SIMPLE_SIGNALS),"Signal Monitor");
      return(false);
   }
//--- Save the selection
//--- Indicator name
   if(m_signal_name.GetValue()=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Введите Имя Сигнала","Монитор сигналов");
      else
         MessageBox("Enter the Signal Name","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else if(StringLen(m_signal_name.GetValue())<3)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Имя Сигнала должно быть не менее 3 букв","Монитор сигналов");
      else
         MessageBox("Signal Name must be at least 3 letters","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else
      StringToCharArray(m_signal_name.GetValue(),m_signal_set[index].signal_name);
//--- Indicator type
   m_signal_set[index].ind_type=m_indicator_type.GetListViewPointer().SelectedItemIndex();
//--- Indicator period
   if(m_signal_set[index].ind_type!=9)
   {
      m_signal_set[index].ind_period=(int)m_period_edit.GetValue();
      //--- Type of applied price
      m_signal_set[index].app_price=m_applied_price.GetListViewPointer().SelectedItemIndex();
   }
   else
   {
      string path=m_custom_path.GetValue();
      string param=m_custom_param.GetValue();
      if(path=="")
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Введите путь к индикатору","Монитор сигналов");
         else
            MessageBox("Enter the indicator path","Signal Monitor");
         FileClose(h);
         return(false);
      }
      if(param=="")
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Введите параметры индикатора через запятую","Монитор сигналов");
         else
            MessageBox("Enter indicator parameters separated by commas","Signal Monitor");
         FileClose(h);
         return(false);
      }
      StringToCharArray(path,m_signal_set[index].custom_path);
      StringToCharArray(param,m_signal_set[index].custom_val);
      m_signal_set[index].ind_period=(int)m_period_edit.GetValue();
   }
//--- Rule type
   m_signal_set[index].rule_int=m_rule_interval.GetListViewPointer().SelectedItemIndex();
//--- Comparison type
   m_signal_set[index].rule_type=m_rule_type.GetListViewPointer().SelectedItemIndex();
//--- Rule value
   m_signal_set[index].rule_value1=(double)m_rule_value[0].GetValue();
   m_signal_set[index].rule_value2=(double)m_rule_value[1].GetValue();
//--- Text label display type
   m_signal_set[index].label_type=m_label_button[0].IsPressed()?0:1;
//--- Save the value of the text field for the second type
   if(m_label_button[1].IsPressed())
      StringToCharArray(StringSubstr(m_text_box.GetValue(),0,3),m_signal_set[index].label_value);
//--- Color of the text label
   m_signal_set[index].label_color=m_color_button[0].CurrentColor();
//--- Backdrop color
   if(m_set_param[0].IsPressed())
      m_signal_set[index].back_color=m_color_button[1].CurrentColor();
   else
      m_signal_set[index].back_color=clrNONE;
//--- Border color
   if(m_set_param[1].IsPressed())
      m_signal_set[index].border_color=m_color_button[2].CurrentColor();
   else
      m_signal_set[index].border_color=clrNONE;
//--- Hint value
   m_signal_set[index].tooltip=m_set_param[2].IsPressed();
   if(m_signal_set[index].tooltip)
      StringToCharArray(m_tooltip_text.GetValue(),m_signal_set[index].tooltip_text);
//--- Selected image
   m_signal_set[index].image=m_set_param[3].IsPressed();
   if(m_signal_set[index].image)
      m_signal_set[index].img_index=m_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
//--- Selected timegrames
   int tf=0;
   for(int i=0; i<21; i++)
   {
      if(!m_tf_button[i].IsLocked() && m_tf_button[i].IsPressed())
      {
         m_signal_set[index].timeframes[i]=true;
         StringToCharArray(m_tf_button[i].LabelText(),m_signal_set[index].tf_name[i].tf);
         tf++;
      }
      else
         m_signal_set[index].timeframes[i]=false;
   }
//---
   if(tf<1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не выбран ни один таймфрейм","Монитор сигналов");
      else
         MessageBox("No timeframes selected","Signal Monitor");
      FileClose(h);
      return(false);
   }
//---
   FileWriteStruct(h,m_signal_set[index]);
   FileClose(h);
   Print("Configuration "+m_signal_name.GetValue()+" successfully saved");
//---
   return(true);
}

在上面的代码中,我突出显示了检查新信号的符号和 CheckSignalNames() 方法,该方法检查是否存在指定名称的信号。 更进一步代码检查是否存在信号名称,且其长度至少要三个字符。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckSignalNames(string name)
{
//--- Search for set signals
   SIGNAL signal_set[];
   int cnt=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
         cnt++;
   }
   if(cnt<1)
      return(true);
   //---
   ArrayResize(signal_set,cnt);
   ZeroMemory(signal_set);
//---
   for(int i=0; i<cnt; i++)
   {
      int h=FileOpen("Signal Monitor\\signal_"+string(i)+".bin",FILE_READ|FILE_BIN);
      if(h==INVALID_HANDLE)
      {
         MessageBox("Configuration not found","Signal Monitor");
         return(false);
      }
      FileReadStruct(h,signal_set[i]);
      if(CharArrayToString(m_signal_set[i].signal_name)==name)
      {
         FileClose(h);
         return(false);
      }
      FileClose(h);
   }
   return(true);
}

现在,我们可以创建新格式的简单交易信号,如下面的屏幕截屏所示。 我们激活已创建信号编辑按钮,并查看如何给简单信号分配用法符号。

图例 5 创建新格式的简单交易信号 

为了启用简单信号的编辑,创建一个新的 EditSimpleSignal() 方法,并在 OnEvent() 应答程序中对其进行调用。 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::EditSimpleSignal(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(lparam==m_signal_ind[i].Id())
      {
         LoadSignalSet(i);
         m_new_signal.LabelText(m_lang[38]);
         m_new_signal.Update(true);
         m_set_window.OpenWindow();
         m_number_signal=i;
      }
   }
}

与此类似,若要切换用法符号,创建一个新的 SignalSwitch() 方法,并在应答程序中调用它。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SignalSwitch(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      //---
      if(lparam==m_signal_type[i].Id())
      {
         if(m_signal_type[i].LabelText()=="S")
            SetButtonParam(m_signal_type[i],"C",clrMediumOrchid);
         else if(m_signal_type[i].LabelText()=="C")
            SetButtonParam(m_signal_type[i],"B",C'240,120,0');
         else if(m_signal_type[i].LabelText()=="B")
            SetButtonParam(m_signal_type[i],"S",C'75,190,240');
      }
   }
}

切换如图例 6 所示。 只需单击简单按钮即可执行。

图例 6 简单信号用法标志的可视开关

交易信号现已准备,可用作复合交易信号的逻辑元素。

之前我们曾添加了一个按钮,是为了创建新的复合信号 - “Add Composite Signal”。 现在,我们需要实现一个对话框窗口,能够创建、设置和编辑复合信号。 早前,当创建一个简单信号编辑窗口时,我们曾创建了一个包含文件 SetWindow.mqh。 因此,现在我们来创建 CSetWindow.mqh,在其中创建复合信号编辑器的 UI。 打开新创建的文件,并添加 Program.mqh,以便访问 CProgram 基类。

//+------------------------------------------------------------------+
//|                                                   CSetWindow.mqh |
//|                                Copyright 2020, Alexander Fedosov |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#include "Program.mqh"

In the Program.mqh file, connect CSetWindow.mqh to existing controls:

//+------------------------------------------------------------------+
//| Add Controls                                                     |
//+------------------------------------------------------------------+
#include "MainWindow.mqh"
#include "SetWindow.mqh"
#include "StepWindow.mqh"
#include "CSetWindow.mqh"

为了创建对话框窗口,我们 CreateCompositeEdit() 基类中引入一个新方法,并在新创建的包含文件中实现它。

protected:
   //--- Forms
   bool              CreateStepWindow(const string caption_text);
   bool              CreateSetWindow(const string caption_text);
   bool              CreateColorWindow(const string caption_text);
   bool              CreateFastEdit(const string caption_text);
   bool              CreateCompositeEdit(const string caption_text);
//+------------------------------------------------------------------+
//| Creates a window for creating and editing composite signals      |
//+------------------------------------------------------------------+
bool CProgram::CreateCompositeEdit(const string caption_text)
{
//--- Add a window pointer to the window array
   CWndContainer::AddWindow(m_composite_edit);
//--- Properties
   m_composite_edit.XSize(590);
   m_composite_edit.YSize(590);
//--- Coordinates
   int x=10;
   int y=10;
//---
   m_composite_edit.CaptionHeight(22);
   m_composite_edit.IsMovable(true);
   m_composite_edit.CaptionColor(m_caption);
   m_composite_edit.CaptionColorLocked(m_caption);
   m_composite_edit.CaptionColorHover(m_caption);
   m_composite_edit.BackColor(m_background);

   m_composite_edit.FontSize(m_base_font_size);
   m_composite_edit.Font(m_base_font);
   m_composite_edit.WindowType(W_DIALOG);
//--- Create the form
   if(!m_composite_edit.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
   return(true);
}

现在,我们在主界面创建方法中添加方法调用。

bool CProgram::CreateGUI(void)
{
//---
   ChangeLanguage();
//--- Step 1-3. Symbol selection window.
   if(!CreateStepWindow(m_lang[0]))
      return(false);
//---
   if(!CreateSetWindow(m_lang[17]))
      return(false);
//--- Creating form 2 for the color picker
   if(!CreateColorWindow(m_lang[39]))
      return(false);
//--- Creating a quick edit form
   if(!CreateFastEdit(m_lang[40]))
      return(false);
//---
   if(!CreateCompositeEdit(m_lang[46]))
      return(false);
//--- Complete GUI creation
   CWndEvents::CompletedGUI();
   return(true);
}

创建的窗口是一个对话框,且应在发生特定事件时打开,即单击 “Add Composite Signal” 按钮。 我们创建 OpenCompositeSignalEditor() 方法,并出于演示目的而在其主体中添加一个命令。 该方法必须在事件应答程序中调用。

void CProgram::OpenCompositeSignalEditor(long lparam)
{
   if(lparam==m_add_signal[1].Id())
   {
      m_composite_edit.OpenWindow();
   }
}

图例 7 显示添加的对话框,该对话框通过单击按钮打开。

图例 7 添加创建复合信号的对话框窗口

现在,我们需要用图例 2 所示的 UI 元素填充对话框窗口:

  • 输入复合信号名称的字段。
  • 创建复合信号的可用简单信号列表。
  • 创建复合信号逻辑规则的界面。

所有新方法都将添加到 CProgram 基类当中,并将在 CSetWindow.mqh 文件中实现。 该方法与对话框创建方法主体在同一文件中应用。 首先,我们要创建 CreateCSignalName() 输入字段方法,实现它,并添加到对话框窗口之中。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCSignalName(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Store the pointer to the main control
   text_edit.MainPointer(m_composite_edit);
//--- Properties
   text_edit.XSize(110);
   text_edit.YSize(24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.GetTextBoxPointer().XGap(110);
   text_edit.GetTextBoxPointer().XSize(200);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText(m_lang[45]);
//--- Create a control element
   if(!text_edit.CreateTextEdit(m_lang[45],x_gap,y_gap))
      return(false);
//--- Add the object to the common array of object groups
   CWndContainer::AddToElementsArray(4,text_edit);
   return(true);
}
利用新的 CreateSimpleSignal() 方法来创建一组两个状态的按钮。 它们将用于选择要添加到复合信号卡槽(图例 2)里的简单信号。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSimpleSignal(CButton &button,const int x_gap,const int y_gap)
{
//---
   color baseclr=clrDodgerBlue;
   color pressed=clrCrimson;
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(110);
   button.YSize(40);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BackColorLocked(clrWhiteSmoke);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.BorderColorLocked(clrWhiteSmoke);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.LabelColorLocked(clrWhiteSmoke);
   button.IsCenterText(true);
   button.TwoState(true);
//--- Create a control element
   if(!button.CreateButton(" — ",x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

CreateCompositeEdit() 方法如下:

....
//--- Signal name
   if(!CreateCSignalName(m_c_signal_name,10,40))
      return(false);
//--- Create a simple signal selection list
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(i<5)
      {
         if(!CreateSimpleSignal(m_simple_signal[i],10+115*i,90))
            return(false);
      }
      else
      {
         if(!CreateSimpleSignal(m_simple_signal[i],10+115*(i-5),90+45))
            return(false);
      }
   }
...

按钮有两种状态:已用的简单信号(红色),和未用的信号(蓝色)。 但这仅是模板。

图例 8 选择简单交易信号的模板

下一步是在模板里应用以下逻辑:

  • 当 'Add Composite Signal' 按下,必须检查至少有两个已创建信号可用。
  • 甚或,已创建简单信号列表必须至少含有两个信号,属性即可是 C(复合),亦或是 B(两者)。
  • 如果满足上述条件,请显示可用的简单信号,这些信号可用于形成复合信号。

为了满足所有这些条件,需创建特殊的验证方法 LoadForCompositeSignal() ,该方法仅显示正确选择的信号。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadForCompositeSignal(void)
{
   int cnt=0;
//---
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      m_simple_signal[i].IsLocked(true);
      m_simple_signal[i].Update(true);
   }
//--- Check if there are at least two available signals with the required attributes
   for(int i=0; i<SIMPLE_SIGNALS; i++)
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
         cnt++;
//---
   if(cnt<2)
      return(false);
   else
      cnt=0;
//---
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
      {
         m_simple_signal[cnt].IsLocked(false);
         cnt++;
      }
   }
//---
   cnt=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(m_signal_label[i].IsVisible() && (m_signal_type[i].LabelText()=="C" || m_signal_type[i].LabelText()=="B"))
      {
         m_simple_signal[cnt].LabelText(m_signal_label[i].LabelText());
         m_simple_signal[cnt].Update(true);
         cnt++;
      }
   }
   return(true);
}

应该将它应用于早前创建的 OpenCompositeSignalEditor() 方法:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::OpenCompositeSignalEditor(long lparam)
{
   if(lparam==m_add_signal[1].Id())
   {
      if(!LoadForCompositeSignal())
      {
         if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
            MessageBox("Необходимо минимум два простых сигнала для создания составного!","Внимание");
         else
            MessageBox("You need at least two simple signals to create a composite!","Warning");
      }
      else
      {
         m_composite_edit.X(m_add_signal[1].X());
         m_composite_edit.Y(m_add_signal[1].Y()+40);
         m_composite_edit.OpenWindow();
         //---
         m_c_signal_name.SetValue("");
         m_c_signal_name.Update(true);
         //--- Clear signal selection
         for(int i=0; i<SIMPLE_SIGNALS; i++)
         {
            m_simple_signal[i].IsPressed(false);
            m_simple_signal[i].Update(true);
         }
      }
   }
}

结果则是(图例 9),我们可以正确选择满足我们条件的信号。 正如所见,没有选择名为Test 2 的简单信号来创建复合信号,因为它的属性为 S(简单)。

图例 9 选择用于复合信号的简单信号

现在,我们需要添加系统,用选定的简单信号来形成复合信号。 我们从 UI 元素开始。 有两种类型的元素: 

  • 卡槽。 三个放置选定简单信号的卡槽。
  • 逻辑操作符。 两个切换按钮具有三个状态 AND、OR 和(OR)。

两个相似的运算符 OR 和(OR)实际上是不同的。 当它们连接三个表达式时,OR 逻辑操作符和 AND 逻辑操作符可用不同的方式进行解释。 图例 10 展示了一个示例,其中逻辑表达式的结果会根据括号而有所不同。


图例 10 OR 和(OR)之间的区别

我们在合成信号创建和编辑窗口里添加卡槽和逻辑运算符。 实现 CreateRuleSlot() 方法来创建卡槽:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleSlot(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   //color baseclr=C'70,180,70';
   color baseclr=clrLightSteelBlue;
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(110);
   button.YSize(40);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(baseclr);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(baseclr);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

对于逻辑运算符,创建 CreateRuleSelector() 方法:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateRuleSelector(CButton &button,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'75,190,240';
//--- Store the window pointer
   button.MainPointer(m_composite_edit);
//--- Set properties before creation
   button.XSize(40);
   button.YSize(40);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(baseclr);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(baseclr);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Create a control element
   if(!button.CreateButton("AND",x_gap,y_gap))
      return(false);
//--- Add a pointer to the element to the database
   CWndContainer::AddToElementsArray(4,button);
   return(true);
}

在实现对话框窗口的 CreateCompositeEdit() 方法里加入这两个方法。

...
//--- Header Rule
   if(!CreateSetLabel1(m_c_set_header[0],10,90+45*2,"1."+m_lang[24]))
      return(false);
//--- Create slots for creating a composite signal
   for(int i=0; i<3; i++)
      if(!CreateRuleSlot(m_rule_element[i],"Slot "+string(i+1),57+10+173*i,int(90+45*2.5)))
         return(false);
//--- Create logical operators
   for(int i=0; i<2; i++)
      if(!CreateRuleSelector(m_rule_selector[i],189+173*i,int(90+45*2.5)))
         return(false);
...

在此步骤中,创建复合信号逻辑的模块可视部分如下所示:

图例 11 创建复合信号逻辑的可视界面部分

若要启用逻辑开关,键入 LogicSwitch() 方法,将其实现并添加到事件应答程序里。 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::LogicSwitch(long lparam)
{
   for(int i=0; i<2; i++)
   {
      if(lparam==m_rule_selector[i].Id())
      {
         if(m_rule_selector[i].LabelText()=="OR")
            SetButtonParam(m_rule_selector[i],"(OR)",clrTomato);
         else if(m_rule_selector[i].LabelText()=="(OR)")
            SetButtonParam(m_rule_selector[i],"AND",C'75,190,240');
         else if(m_rule_selector[i].LabelText()=="AND")
            SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed);
      }
   }
}

现在我们移步至与填充上述卡槽相关的另一个重点。 如何按要求的顺序填充它们? 从可用信号列表中选择一个简单信号,然后单击它。 第一个卡槽的颜色会变为蓝色,并显示所添加信号的名称。 单击第二个信号将填充第二个卡槽。 如果卡槽已填满,且您单击已加入列表中的信号,则该卡槽会将占位的信号释放。 为了实现此机制,创建 SignalSelection() 方法,并将其添加到事件应答程序里:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SignalSelection(long lparam)
{
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(lparam==m_simple_signal[i].Id())
      {
         //--- If the signal is selected
         if(m_simple_signal[i].IsPressed())
         {
            //--Find the first free slot
            for(int j=0; j<3; j++)
            {
               if(m_rule_element[j].BackColor()==clrLightSteelBlue)
               {
                  SetButtonParam(m_rule_element[j],m_simple_signal[i].LabelText(),clrDodgerBlue);
                  break;
               }
            }
         }
         //--- If no signal is selected
         else
         {
            for(int j=0; j<3; j++)
            {
               if(m_rule_element[j].LabelText()==m_simple_signal[i].LabelText())
               {
                  SetButtonParam(m_rule_element[j],"Slot "+string(j+1),clrLightSteelBlue);
                  break;
               }
            }
         }
      }
   }
}

以上动作如图例 12 所示。 

图例 12 由简单信号创建复合交易信号的示例

现在,我们需要添加用于在监视器中设置复合信号显示的选项。 这些选项会被添加到复合信号创建窗口。 我们将要创建一个与简单信号窗口类似的元素部分。 设置创建原则曾在第二篇文章里研究过。 图例 13 下面展示复合信号编辑窗口的最终外观。

图例 13 复合信号创建窗口的最终版本

我们已准备好可视部分。 现在我们需要集成计算组件,为此我们要利用复合信号保存和编辑机制。 一旦设置好所需的参数之后(如图例 13 所示),单击 “Add(添加)” 按钮将指定参数保存到文件当中。 上述功能的函数实现如下添加。 它从 SaveCompositeSignalSet() 里调用。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::SaveCompositeSignalSet(int index,bool first_save=true)
{
//---
   if(first_save && !CheckCSignalNames(m_c_signal_name.GetValue()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Это имя уже используется","Монитор сигналов");
      else
         MessageBox("This name is already in use","Signal Monitor");
      return(false);
   }
//---
   int h=FileOpen("Signal Monitor\\c_signal_"+string(index)+".bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Не удалось создать файл конфигурации","Монитор сигналов");
      else
         MessageBox("Failed to create configuration file","Signal Monitor");
      return(false);
   }
   if(index>COMPOSITE_SIGNALS-1)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Максимальное число сигналов не должно быть больше "+string(COMPOSITE_SIGNALS),"Монитор сигналов");
      else
         MessageBox("Maximum number of signals is "+string(COMPOSITE_SIGNALS),"Signal Monitor");
      return(false);
   }
//--- Save the selection
//--- Indicator name
   if(m_c_signal_name.GetValue()=="")
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Введите Имя Сигнала","Монитор сигналов");
      else
         MessageBox("Enter the Signal Name","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else if(StringLen(m_c_signal_name.GetValue())<3)
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Имя Сигнала должно быть не менее 3 букв","Монитор сигналов");
      else
         MessageBox("Signal Name must be at least 3 letters","Signal Monitor");
      FileClose(h);
      return(false);
   }
   else
      StringToCharArray(m_c_signal_name.GetValue(),m_c_signal_set[index].signal_name);
//--- Slot values
   if(!CheckCorrectSlots(m_rule_element[0].LabelText(),m_rule_element[1].LabelText(),m_rule_element[2].LabelText()))
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         MessageBox("Неверно установлено правило","Монитор сигналов");
      else
         MessageBox("Invalid rule","Signal Monitor");
      FileClose(h);
      return(false);
   }
   StringToCharArray(m_rule_element[0].LabelText(),m_c_signal_set[index].slot_1);
   StringToCharArray(m_rule_element[1].LabelText(),m_c_signal_set[index].slot_2);
   StringToCharArray(m_rule_element[2].LabelText(),m_c_signal_set[index].slot_3);
//--- Values of logical operators
   for(int i=0; i<2; i++)
   {
      if(m_rule_selector[i].LabelText()=="AND")
         m_c_signal_set[index].logics[i]=1;
      else if(m_rule_selector[i].LabelText()=="OR")
         m_c_signal_set[index].logics[i]=2;
      else if(m_rule_selector[i].LabelText()=="(OR)")
         m_c_signal_set[index].logics[i]=3;
   }
//--- Text label value
   StringToCharArray(StringSubstr(m_c_text_box.GetValue(),0,3),m_c_signal_set[index].label_value);
//--- Color of the text label
   m_c_signal_set[index].label_color=m_c_color_button[0].CurrentColor();
//--- Backdrop color
   if(m_c_set_param[0].IsPressed())
      m_c_signal_set[index].back_color=m_c_color_button[1].CurrentColor();
   else
      m_c_signal_set[index].back_color=clrNONE;
//--- Border color
   if(m_c_set_param[1].IsPressed())
      m_c_signal_set[index].border_color=m_c_color_button[2].CurrentColor();
   else
      m_c_signal_set[index].border_color=clrNONE;
//--- Hint value
   m_c_signal_set[index].tooltip=m_c_set_param[2].IsPressed();
   if(m_c_signal_set[index].tooltip)
      StringToCharArray(m_c_tooltip_text.GetValue(),m_c_signal_set[index].tooltip_text);
//--- Selected image
   m_c_signal_set[index].image=m_c_set_param[3].IsPressed();
   if(m_c_signal_set[index].image)
      m_c_signal_set[index].img_index=m_c_pictures_slider.GetRadioButtonsPointer().SelectedButtonIndex();
//---
   FileWriteStruct(h,m_c_signal_set[index]);
   FileClose(h);
   Print("Конфигурация "+m_c_signal_name.GetValue()+" успешно сохранена");
//---
   return(true);
}

若现在尝试编译项目,会产生三个主要错误:缺少 m_c_signal_set 变量和两个检查方法 — CheckCSignalNames() CheckCorrectSlots() 。 变量类型则是存储一组复合信号参数而创建的新 C_SIGNAL 结构:

struct C_SIGNAL
{
   uchar             slot_1[50];
   uchar             slot_2[50];
   uchar             slot_3[50];
   int               logics[2];
   uchar             signal_name[50];
   uchar             label_value[10];
   color             label_color;
   color             back_color;
   color             border_color;
   bool              tooltip;
   uchar             tooltip_text[100];
   bool              image;
   int               img_index;
};

CheckCSignalNames() 方法与 CheckSignalNames() 非常相似:它检查指定的复合信号名称是否唯一。 新的 CheckCorrectSlots() 方法验证已创建复合信号逻辑结构的完整性和正确性:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CheckCorrectSlots(string name1,string name2,string name3)
{
   bool slot1=(name1=="Slot 1")?true:false;
   bool slot2=(name2=="Slot 2")?true:false;
   bool slot3=(name3=="Slot 3")?true:false;
   int cnt=0;
   //---
   if(slot1)
      return(false);
   if(slot2 && !slot3)
      return(false);
   //---
   if(!slot1)
      cnt++;
   if(!slot2)
      cnt++;
   if(!slot3)
      cnt++;
   if(cnt<2)
      return(false);
   return(true);
}

创建新的复合信号所需的功能已准备就绪,如此,我们现在来开发 AddCompositeSignal() 方法:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AddCompositeSignal(long lparam)
{
   if(lparam==m_c_new_signal.Id())
   {
      if(m_c_number_signal<0)
      {
         if(SaveCompositeSignalSet(m_c_total_signals))
         {
            m_composite_edit.CloseDialogBox();
            if(m_c_total_signals<COMPOSITE_SIGNALS)
            {
               m_c_total_signals++;
               m_c_signal_label[m_c_total_signals-1].Show();
               m_c_signal_label[m_c_total_signals-1].LabelText(m_c_signal_name.GetValue());
               m_c_signal_label[m_c_total_signals-1].Update(true);
               m_c_signal_ind[m_c_total_signals-1].Show();
            }
            else
               MessageBox("Maximum number of composite signals is "+string(COMPOSITE_SIGNALS),"Signal Monitor");
         }
      }
      else
      {
         if(SaveCompositeSignalSet(m_c_number_signal))
         {
            m_composite_edit.CloseDialogBox();
            m_c_signal_label[m_c_number_signal].LabelText(m_c_signal_name.GetValue());
            m_c_signal_label[m_c_number_signal].Update(true);
         }
      }
   }
}

为编辑已创建交易信号而创建功能之前,我们增加一种机制,将设置从文件里加载到复合信号编辑窗口。 这是由 LoadCompositeSignalSet() 方法来完成的:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::LoadCompositeSignalSet(int index)
{
   int h=FileOpen("Signal Monitor\\c_signal_"+string(index)+".bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE)
   {
      MessageBox("Configuration not found","Signal Monitor");
      return(false);
   }
   ZeroMemory(m_c_signal_set[index]);
   FileReadStruct(h,m_c_signal_set[index]);
//--- Loading the indicator name
   m_c_signal_name.SetValue(CharArrayToString(m_c_signal_set[index].signal_name));
   m_c_signal_name.GetTextBoxPointer().Update(true);
//--- Slot values
   string slot_1=CharArrayToString(m_c_signal_set[index].slot_1);
   string slot_2=CharArrayToString(m_c_signal_set[index].slot_2);
   string slot_3=CharArrayToString(m_c_signal_set[index].slot_3);
   color back=clrDodgerBlue;
   if(slot_1=="Slot 1")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[0],slot_1,back);
   if(slot_2=="Slot 2")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[1],slot_2,back);
   if(slot_3=="Slot 3")
      back=clrLightSteelBlue;
   else
      back=clrDodgerBlue;
   SetButtonParam(m_rule_element[2],slot_3,back);
//--- Values of logical operators
   for(int i=0; i<2; i++)
   {
      switch(m_c_signal_set[index].logics[i])
      {
      case  1:
         SetButtonParam(m_rule_selector[i],"AND",C'75,190,240');
         break;
      case  2:
         SetButtonParam(m_rule_selector[i],"(OR)",clrTomato);
         break;
      case  3:
         SetButtonParam(m_rule_selector[i],"OR",clrOrangeRed);
         break;
      default:
         break;
      }
   }
//--- Loading a text label
   m_c_text_box.ClearTextBox();
   m_c_text_box.AddText(0,CharArrayToString(m_c_signal_set[index].label_value));
   m_c_text_box.Update(true);
//--- Loading the color of the text label
   m_c_color_button[0].CurrentColor(m_c_signal_set[index].label_color);
   m_c_color_button[0].Update(true);
//--- Loading the background color
   if(m_c_signal_set[index].back_color==clrNONE)
   {
      m_c_set_param[0].IsPressed(false);
      m_c_set_param[0].Update(true);
      m_c_color_button[1].IsLocked(true);
      m_c_color_button[1].GetButtonPointer().Update(true);
   }
   else
   {
      m_c_set_param[0].IsPressed(true);
      m_c_set_param[0].Update(true);
      m_c_color_button[1].IsLocked(false);
      m_c_color_button[1].CurrentColor(m_c_signal_set[index].back_color);
      m_c_color_button[1].GetButtonPointer().Update(true);
   }
//--- Loading the border color
   if(m_c_signal_set[index].border_color==clrNONE)
   {
      m_c_set_param[1].IsPressed(false);
      m_c_set_param[1].Update(true);
      m_c_color_button[2].IsLocked(true);
      m_c_color_button[2].GetButtonPointer().Update(true);
   }
   else
   {
      m_c_set_param[1].IsPressed(true);
      m_c_set_param[1].Update(true);
      m_c_color_button[2].IsLocked(false);
      m_c_color_button[2].CurrentColor(m_c_signal_set[index].border_color);
      m_c_color_button[2].GetButtonPointer().Update(true);
   }
//--- Loading the tooltip value
   if(!m_c_signal_set[index].tooltip)
   {
      m_c_set_param[2].IsPressed(false);
      m_c_set_param[2].Update(true);
      m_c_tooltip_text.IsLocked(true);
      m_c_tooltip_text.Update(true);
   }
   else
   {
      m_set_param[2].IsPressed(true);
      m_set_param[2].Update(true);
      m_c_tooltip_text.IsLocked(false);
      m_c_tooltip_text.ClearTextBox();
      m_c_tooltip_text.AddText(0,CharArrayToString(m_c_signal_set[index].tooltip_text));
      m_c_tooltip_text.Update(true);
   }
//--- Loading the image
   if(!m_c_signal_set[index].image)
   {
      m_c_set_param[3].IsPressed(false);
      m_c_set_param[3].Update(true);
      m_c_pictures_slider.IsLocked(true);
      m_c_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
   else
   {
      m_c_set_param[3].IsPressed(true);
      m_c_set_param[3].Update(true);
      m_c_pictures_slider.IsLocked(false);
      m_c_pictures_slider.GetRadioButtonsPointer().SelectButton(m_c_signal_set[index].img_index);
      m_c_pictures_slider.GetRadioButtonsPointer().Update(true);
   }
//---
   FileClose(h);
   return(true);
}

现在,我们可以利用新的 EditCompositeSignal() 方法来编辑已创建信号:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::EditCompositeSignal(long lparam)
{
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(lparam==m_c_signal_ind[i].Id())
      {
         LoadCompositeSignalSet(i);
         m_c_new_signal.LabelText(m_lang[38]);
         m_c_new_signal.Update(true);
         m_composite_edit.OpenWindow();
         m_c_number_signal=i;
         for(int j=0; j<SIMPLE_SIGNALS; j++)
            m_simple_signal[j].IsLocked(true);
      }
   }
}

创建、保存、编辑和加载复合信号功能的软件实现已准备就绪。 剩下的最后一个应用程序开发步骤则是:将创建的复合信号添加到监视器。 在早前版本之中,这是由 SearchSignals() 方法执行的,因此需对其进行修改。 方法代码旨在确保最大程度的理解和清晰 — 这是由引入其他辅助方法来实现的。 尽管如此,它会被划分为两个逻辑模块:搜索简单信号,和搜索复合信号。 我们来研究致力于简单信号搜索的第一部分。 注意代码中的变化。

//+------------------------------------------------------------------+
//| Signal search                                                    |
//+------------------------------------------------------------------+
bool CProgram::SearchSignals(void)
{
//--- Search for the number of created simple signals
   int cnt1=0;
   for(int i=0; i<SIMPLE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\signal_"+string(i)+".bin"))
         cnt1++;
   }
//--- Search for simple signals
   SIGNAL signal_set;
   ZeroMemory(signal_set);
   for(int i=0; i<cnt1; i++)
   {
      //--- Skip a signal if it is only set for use in composite signals
      if(m_signal_type[i].LabelText()=="C")
         continue;
      //---
      if(GetSimpleSignal(signal_set,i))
         return(false);
      //---
      for(int j=0; j<ArraySize(m_signal_button); j++)
      {
         //---
         string sy=GetSymbol(j);
         ENUM_TIMEFRAMES tf=GetTimeframe(j);
         //---
         if(!CheckTimeframe(tf,signal_set))
            continue;
         //---
         if(GetSignal(sy,tf,signal_set))
            SetVisualSignal(signal_set,j);
         else
            SetDefaultVisual(j);
      }
   }
.....

我们早前介绍了简单信号用法属性,因此我们需要添加简单信号过滤,从而可跳过那些仅能用作复合信号一部分的信号添加了 GetSimpleSignal() 信号,可获取指定文件中每个简单信号的设置,并将其写入结构中。。 另外两个新方法 SetVisualSignal() SetDefaultVisual() 根据当前找到的信号按指定视觉设置实现显示。 如果没有信号,则显示默认信号模块。 如果我们比较新、旧简单信号搜索实现,那么新版本更易于理解,且缩短了将近三倍。

现在,我们移至信号搜索方法的第二部分,该方法搜索复合交易信号。

.....
//--- Search for composite signals
   C_SIGNAL c_signal_set;
   ZeroMemory(c_signal_set);
   int cnt2=0;
   int signal_number[3];
   ArrayInitialize(signal_number,-1);
//--- Search for the number of created composite signals
   for(int i=0; i<COMPOSITE_SIGNALS; i++)
   {
      if(FileIsExist("Signal Monitor\\c_signal_"+string(i)+".bin"))
         cnt2++;
   }
//-- Exit if there are no signals
   if(cnt2<1)
      return(true);
//--- Search for configurations with composite signals
   for(int i=0; i<cnt2; i++)
   {
      //---
      int h=FileOpen("Signal Monitor\\c_signal_"+string(i)+".bin",FILE_READ|FILE_BIN);
      if(h==INVALID_HANDLE)
      {
         MessageBox("Configuration not found","Signal Monitor");
         return(false);
      }
      ZeroMemory(c_signal_set);
      FileReadStruct(h,c_signal_set);
      FileClose(h);
      //--- Search for simple signals used in the composite one (C or B)
      for(int m=0; m<cnt1; m++)
      {
         if(m_signal_type[m].LabelText()!="S")
            GetSimpleSignal(signal_set,m);
         //---
         if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_1))
            signal_number[0]=m;
         else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_2))
            signal_number[1]=m;
         else if(CharArrayToString(signal_set.signal_name)==CharArrayToString(c_signal_set.slot_3))
            signal_number[2]=m;
      }
      ArrayPrint(signal_number);
   }
//---
   int used_slots=GetUsedSlots(CharArrayToString(c_signal_set.slot_1),CharArrayToString(c_signal_set.slot_2),CharArrayToString(c_signal_set.slot_3));
//---
   for(int j=0; j<ArraySize(m_signal_button); j++)
   {
      //---
      string sy=GetSymbol(j);
      ENUM_TIMEFRAMES tf=GetTimeframe(j);
      //---
      GetSimpleSignal(signal_set,signal_number[0]);
      bool sig1=GetSignal(sy,tf,signal_set);
      GetSimpleSignal(signal_set,signal_number[1]);
      bool sig2=GetSignal(sy,tf,signal_set);
      if(used_slots==2)
      {
         //--- AND
         if(c_signal_set.logics[0]==1)
            if(sig1 && sig2)
               SetVisualCompositeSignal(c_signal_set,j);
         //--- OR
         if(c_signal_set.logics[0]>1)
            if(sig1 || sig2)
               SetVisualCompositeSignal(c_signal_set,j);
      }
      else if(used_slots==3)
      {
         GetSimpleSignal(signal_set,signal_number[2]);
         bool sig3=GetSignal(sy,tf,signal_set);
         //--- AND OR
         if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==2)
         {
            if((sig1 && sig2) || sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- AND (OR)
         else if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==3)
         {
            if(sig1 && (sig2 || sig3))
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- OR AND
         else if(c_signal_set.logics[0]==2 && c_signal_set.logics[1]==1)
         {
            if(sig1 || (sig2 && sig3))
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- (OR) AND
         else if(c_signal_set.logics[0]==3 && c_signal_set.logics[1]==1)
         {
            if((sig1 || sig2) && sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- AND AND
         else if(c_signal_set.logics[0]==1 && c_signal_set.logics[1]==1)
         {
            if(sig1 && sig2 && sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
         //--- OR OR
         else if(c_signal_set.logics[0]>1 && c_signal_set.logics[1]>1)
         {
            if(sig1 || sig2 || sig3)
               SetVisualCompositeSignal(c_signal_set,j);
         }
      }
   }
   return(true);
}

搜索复合信号还需要用到其他方法进行检查,并在监视器里可视化。 检查方法是 GetUsedSlots()。 它判断搜索系统中所用复合信号的类型:是否它是由两个、亦或三个简单信号组成。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CProgram::GetUsedSlots(string name1,string name2,string name3)
{
   int cnt=0;
   if(name1!="Slot 1")
      cnt++;
   if(name2!="Slot 2")
      cnt++;
   if(name3!="Slot 3")
      cnt++;
   return(cnt);
}

第二个方法 SetVisualCompositeSignal() 采用当前复合信号的一组可视参数来显示信号模块中找到的复合信号。

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::SetVisualCompositeSignal(C_SIGNAL &signal_set,int block)
{
   //---
   SetLabel(block,CharArrayToString(signal_set.label_value),signal_set.label_color);
   //---
   if(signal_set.back_color!=clrNONE)
      SetBackground(block,signal_set.back_color);
   //---
   if(signal_set.border_color!=clrNONE)
      SetBorderColor(block,signal_set.border_color);
   else
      SetBorderColor(block,signal_set.back_color);
   //---
   if(signal_set.tooltip)
      SetTooltip(block,CharArrayToString(signal_set.tooltip_text));
   //---
   if(signal_set.image)
      SetIcon(block,signal_set.img_index);
   else
      SetIcon(block,-1);
}

信号监视器现已彻底准备就绪。 未来可对其进一步修改或完善,从而实现用户的建议,或添加其他功能。

本系列以前的文章:

结束语

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


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


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

附加的文件 |
MQL5.zip (2009.3 KB)
DoEasy 函数库中的时间序列(第四十二部分):抽象指标缓冲区对象类 DoEasy 函数库中的时间序列(第四十二部分):抽象指标缓冲区对象类

在本文中,我们开始为 DoEasy 库开发指标缓冲区类。 我们将创建抽象缓冲区的基类,该基类将作为开发不同类型指标缓冲区的基础。

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

在本文中,我们将研究一个运用 DoEasy 库时间序列类的多品种多周期指标样品,该类在子窗口中以蜡烛的形式显示选定时间帧内选定货币对的图表。 我稍微修改了库类,并创建了一个单独的文件来存储程序输入的枚举,并选择一种编译语言。

DoEasy 函数库中的时间序列(第四十三部分):指标缓冲区对象类 DoEasy 函数库中的时间序列(第四十三部分):指标缓冲区对象类

本文研究开发指标缓冲区对象类,其为抽象缓冲区对象的衍生类,从而可简化声明,并可操控指标缓冲区,同时创建基于 DoEasy 库的自定义指标程序。

MQL 作为 MQL 程序图形界面的标记工具(第三部)。 窗体设计师 MQL 作为 MQL 程序图形界面的标记工具(第三部)。 窗体设计师

在篇论文当中,我们将用 MQL 的结构完成构建 MQL 程序窗口界面的概念讲述。 专业的图形编辑器能够交互式地设置由 GUI 元素的基本类组成的布局,然后将其以 MQL 描述导出,从而可在您的 MQL 项目中使用。 此片论文介绍了编辑器的内部设计和用户指南。 附带源代码。