赫茲股票量化交易軟件:監(jiān)視多幣種的交易信號5--復(fù)合信號
復(fù)合信號
首先,我們來定義項(xiàng)目中所用的“復(fù)合信號”的概念。 在早前的版本里,我們只采用了簡單信號,它們只是接收源數(shù)據(jù)并應(yīng)用特定條件而創(chuàng)建的,可以由各種指標(biāo)(例如 RSI、WPR 和 CCI)來表達(dá)。 此外,還實(shí)現(xiàn)了運(yùn)用自定義指標(biāo)的可能性。
復(fù)合信號是由兩個(gè)或更多個(gè)簡單信號合成的信號,這些信號通過邏輯 AND(與)/ OR(或)運(yùn)算符相互連接。
因此,復(fù)合信號將包括幾個(gè)先前創(chuàng)建的簡單信號,這些信號將用邏輯運(yùn)算符進(jìn)行交互。 還有可能創(chuàng)建一個(gè)復(fù)雜條件的信號,其中包含給定時(shí)間段內(nèi)同時(shí)存在的兩個(gè)或三個(gè)簡單信號。 因此,交易系統(tǒng)將擁有一個(gè)主要信號和一個(gè)過濾器。 邏輯 “OR(或)”運(yùn)算符可讓我們能夠一次性在若干個(gè)方向上搜索交易信號,從而在分析當(dāng)前行情狀況時(shí)涵蓋更大的范圍。赫茲股票量化交易軟件
為了更好的解釋,這里有一些更新和增強(qiáng)我們的應(yīng)用程序的計(jì)劃。 首先,我們需要更新應(yīng)用程序界面,并添加新控件。赫茲股票量化交易軟件

編輯
圖例 1. 加入 UI 元素
圖例 1 的左側(cè)您已很熟悉了。 有一個(gè)添加信號的按鈕,后隨已添加的簡單信號。 現(xiàn)在,需要?jiǎng)?chuàng)建一個(gè)添加復(fù)合信號的按鈕(1),已添加復(fù)合信號的列表(2),和第三組開關(guān)按鈕(3),能為簡單信號設(shè)置使用標(biāo)志:
S(Simple) — 一個(gè)簡單信號。 它在監(jiān)視器中作為獨(dú)立信號,無法在復(fù)合信號中選擇。
C(Composite) — 一個(gè)簡單的信號,只能作為復(fù)合信號的一部分。 它不能在監(jiān)視器中作為獨(dú)立信號
B(Both) — 兩種應(yīng)用類型。 這樣的信號可在監(jiān)視器中作為一個(gè)獨(dú)立的信號進(jìn)行搜索,并且可作為復(fù)合信號的一部分。
赫茲股票量化交易軟件
復(fù)合信號創(chuàng)建規(guī)程如下圖例 2 所示。 名為“Simple 1” 和 “Simple 2” 的元素示意已創(chuàng)建的簡單信號列表,這些可用于創(chuàng)建復(fù)雜信號。 至于信號用法屬性,圖例 2 中所示的信號應(yīng)具有屬性 “C” 或 “B”,否則不會(huì)將它們顯示在列表之中。

編輯
圖例 2 復(fù)合信號創(chuàng)建和編輯窗口的規(guī)程
接下來是稱為 Rule(規(guī)則)的部分,在其中創(chuàng)建復(fù)合信號。 可將形成上述列表中的信號加入到三個(gè)批次中的任意一個(gè)。 利用按鈕選擇卡槽之間的邏輯運(yùn)算符。 這為該復(fù)合信號設(shè)置規(guī)則。 例如,如果您設(shè)置了 Simple 1 和 Simple 2,則僅當(dāng)這兩個(gè)簡單信號同時(shí)出現(xiàn)時(shí)才顯示復(fù)合信號。
赫茲股票量化交易軟件
實(shí)現(xiàn)功能
應(yīng)用程序中引入新功能之前,必須確定現(xiàn)有元素已準(zhǔn)備好與新功能一起操作。 簡單信號列表的 UI 元素需要修改。 圖例 3 所示,簡單按鈕上的簡單信號系統(tǒng)名稱會(huì)被用戶指定信號名稱的文本標(biāo)簽所替代,以及 “Edit” 按鈕和信號使用標(biāo)志按鈕。

編輯
圖例 3. 更新信號編輯控件
現(xiàn)在,打開從上一篇文章下載的項(xiàng)目,然后繼續(xù)進(jìn)行添加。 首先,刪除 CreateSignalEditor() 方法,和創(chuàng)建編輯按鈕所需的 m_signal_editor[] 數(shù)組。 在繼續(xù)創(chuàng)建新的元素和方法之前,我們引入兩個(gè)宏替換,它們將確定簡單和復(fù)合信號的最大允許數(shù)量。赫茲股票量化交易軟件
#define SIMPLE_SIGNALS 10 #define COMPOSITE_SIGNALS 5
接下來,轉(zhuǎn)到 CreateStepWindow() 主窗口創(chuàng)建方法的主體末尾,并替換以下代碼:
? for(int i=0; i<5; i++) ? { ? ? ?if(!CreateSignalEditor(m_signal_editor[i],"Signal_"+string(i),10,40*i+90)) ? ? ? ? return(false); ? }
添加新的顯示和編輯新信號的實(shí)現(xiàn)。
? 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); ? }
該實(shí)現(xiàn)含有兩個(gè)新的 CButton 類實(shí)例數(shù)組 m_signal_ind[] 和 m_signal_type[],以及 CTextLabel 的實(shí)例數(shù)組 m_signal_label[]。 將它們添加到項(xiàng)目的基類 CProgram 當(dāng)中。
? CTextLabel ? ? ? ?m_signal_label[SIMPLE_SIGNALS]; ? CButton ? ? ? ? ? m_signal_ind[SIMPLE_SIGNALS]; ? CButton ? ? ? ? ? m_signal_type[SIMPLE_SIGNALS];
另外,聲明并實(shí)現(xiàn)新的 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 中更新的交互界面形成了一個(gè)簡單信號列表。 添加復(fù)合信號列表,僅需簡單地在基類和如下所示的 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); ? }
新創(chuàng)建的顯示和編輯簡單和復(fù)合信號的工具。 現(xiàn)在,在應(yīng)用程序初始設(shè)置的第 3 步中,添加一個(gè)創(chuàng)建復(fù)合信號的按鈕,以及一個(gè)文本標(biāo)題。 這是把 m_add_signal 變量變?yōu)橐粋€(gè)靜態(tài)數(shù)組 m_add_signal[2] 來完成,該變量是 CButton 類的實(shí)例,并在 AddSignal 按鈕中所用。 在 CreateStepWindow() 方法主體中替換以下代碼
? if(!CreateIconButton(m_add_signal,m_lang[15],10,30)) ? ? ?return(false);
新代碼包括一個(gè)復(fù)合信號創(chuàng)建按鈕:
? 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);
為了顯示簡單和復(fù)合信號列表的標(biāo)題,將 m_signal_header 變量替換為 m_signal_header[2] 靜態(tài)數(shù)組。 之前的代碼:
? if(!CreateLabel(m_signal_header,10,30+30+10,m_lang[16])) ? ? ?return(false);
應(yīng)該用兩個(gè)標(biāo)題替換:
? 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 添加并更新創(chuàng)建交易信號的按鈕
現(xiàn)在,我們需要將簡單交易信號列表里早前創(chuàng)建的對象與創(chuàng)建和編輯事件相關(guān)聯(lián)。 在此,我添加一個(gè)簡短通知。 鑒于 UI 元素的數(shù)量在增加,導(dǎo)致可能的用戶交互數(shù)量也在增加,因此 OnEvent() 應(yīng)答程序主體變得太長,且很難理解某個(gè)事件或一組事件屬于哪一個(gè)元素。 這就是為什么我決定將所有與 UI 的關(guān)鍵交互包裝在相應(yīng)方法里的原因。 還有兩個(gè)優(yōu)點(diǎn): OnEvent() 含有關(guān)鍵事件的列表,因此可以輕松訪問每個(gè)關(guān)鍵事件的邏輯和代碼。赫茲股票量化交易軟件
找到負(fù)責(zé)添加新的簡單交易信號的代碼片段,加入所需的代碼,并將其移至 AddSimpleSignal() 方法當(dāng)中:
//+------------------------------------------------------------------+ //| 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() 應(yīng)答程序里調(diào)用它。 由此,我們降低了方法主體的大小,并將構(gòu)造其實(shí)現(xiàn)部分。 圖例 3 所示,新的實(shí)現(xiàn)能夠支持設(shè)置自定義信號名稱。 這可以通過在簡單信號創(chuàng)建和編輯窗口中添加以下字段來完成。 創(chuàng)建一個(gè)名為 CreateSignalName() 的新方法,該方法將為信號名稱添加一個(gè)輸入字段。
//+------------------------------------------------------------------+ //| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| //+------------------------------------------------------------------+ 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 結(jié)構(gòu)當(dāng)中。
uchar ? ? ? ? ? ? signal_name[50];
方法 SaveSignalSet() 和 LoadSignalSet() 也應(yīng)進(jìn)行更新,從而可在現(xiàn)有設(shè)置之外,還能記錄信號名稱。 創(chuàng)建新信號時(shí),應(yīng)避免出現(xiàn)新信號名稱與之前創(chuàng)建的信號之一重名的情況。 應(yīng)避免的另一種情況是將參數(shù)保存到早前創(chuàng)建的信號文件之中。 因此,應(yīng)為 SaveSignalSet() 方法添加第二個(gè) first_save 參數(shù)。 它表示第一次是保存信號呢?還是保存早前創(chuàng)建的信號的已編輯參數(shù)。赫茲股票量化交易軟件
bool ? ? ? ? ? ? ?SaveSignalSet(int index,bool first_save=true);
該方法的完整實(shí)現(xiàn)如下所示:
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); }