赫兹股票量化交易软件:监视多币种的交易信号5--复合信号
复合信号
首先,我们来定义项目中所用的“复合信号”的概念。 在早前的版本里,我们只采用了简单信号,它们只是接收源数据并应用特定条件而创建的,可以由各种指标(例如 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); }