內容概述在文章的第一部分 當中,我曾描述過一個修訂的 ZigZag 指標和一個用于接收該類型指標數據的類。 在此,我將展示如何基于這些工具開發(fā)指標,并編寫一款根據 ZigZag 指標形成的信號進行交易的 EA 來測試。 作為補充,本文將介紹一套開發(fā)圖形用戶界面的新版 EasyAndFast 函數庫。 文章的主要話題:
定義價格行為的指標我們來研究定義價格行為的三個指標。
這些指標中每一個的代碼結構與文章第一部分中描述的 ZigZag 指標相同。 所以,我們只關注接收數據和填充指標緩沖區(qū)的主函數(FillIndicatorBuffers)。 FrequencyChangeZZ 指標對于 FrequencyChangeZZ 指標,主要函數的代碼與下面的清單相同。 要將柱線索引和時間數組傳遞給函數。 接下來,從當前柱線時間復制必要數量的 ZigZag 指標和時間數組元素(源數據)。 如果收到源數據,則請求最終數據。 之后,它仍然只是調用一個方法來返回線段集合當中的柱線數量。 結果將保存到指標緩沖區(qū)的當前元素。 // ------------------------------------------------------------------
//| 填充指標緩沖區(qū) |
// ------------------------------------------------------------------
void FillIndicatorBuffers(const int i,const datetime &time[])
{
int copy_total=1000;
for(int t=0; t<10; t )
{
if(::CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
::CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
::CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
{
//--- 獲取 ZZ 數據
zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
//--- 將線段集合中的柱線數量保存到指標緩沖區(qū)
segments_bars_total_buffer[i]=zz.SegmentsTotalBars();
break;
}
}
}
在指標外部參數中,我們將指定以下內容:
本文中的所有指標都具有相同的參數。 圖例 1. 指標外部參數 FrequencyChangeZZ 指標在子窗口中顯示圖表,如下所示。 ZigZag 指標會被加載到主圖表上以便更直觀。 指標清晰顯示價格在選擇方向時會放緩。 圖例 2. FrequencyChangeZZ 指標 SumSegmentsZZ 指標在 SumSegmentsZZ 指標中,獲取數據的主要函數如下所示。 全部都與前一個示例中的相同。 唯一的區(qū)別是這里分別填充了三個指標緩沖區(qū),分別用于向上和向下線段。 多出的一個緩沖區(qū) 用來依據當前值計算這些參數的平均值。 // ------------------------------------------------------------------ //| 填充指標緩沖區(qū) | // ------------------------------------------------------------------ void FillIndicatorBuffers(const int i,const datetime &time[]) { int copy_total=1000; for(int t=0; t<10; t ) { if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total && CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total && CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total) { //--- 獲取 ZZ 數據 zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp); //--- 獲取線段數據 segments_up_total_buffer[i] =zz.SumSegmentsUp(); segments_dw_total_buffer[i] =zz.SumSegmentsDown(); segments_average_buffer[i] =(segments_up_total_buffer[i] segments_dw_total_buffer[i])/2; break; } } } 在圖表上加載 SumSegmentsZZ 之后,您將看到如下截圖中的結果。 在此我們可以看到,在藍線超過紅線之后,向上線段的總和大于向下線段的總和。 如果紅線超過藍線,情況就會逆轉。 只有在策略測試器當中的實驗可以告訴我們,這是否能作為未來價格方向的可靠信息來源。 乍一看,單向線段總和超過反向線段總和的時間越長,逆轉概率越高。 圖例 3. SumSegmentsZZ 指標 PercentageSegmentsZZ 指標現在,我們看一下 PercentageSegmentsZZ 指標。 與前一種情況一樣,應在指標的主要函數中填充三個指標緩沖區(qū):用于指向(1)向上(2)向下的線段百分比各占一個緩沖區(qū),以及一個緩沖區(qū)(3) 對于這些數值之間的差值。 // ------------------------------------------------------------------
//| 填充指標緩沖區(qū) |
// ------------------------------------------------------------------
void FillIndicatorBuffers(const int i,const datetime &time[])
{
int copy_total=1000;
for(int t=0; t<10; t )
{
if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
{
//--- 獲取 ZZ 數據
zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
//--- 獲取線段上的數據
double sum_up =zz.SumSegmentsUp();
double sum_dw =zz.SumSegmentsDown();
double sum =sum_up sum_dw;
//--- 百分比和差值
if(sum>0)
{
segments_up_total_buffer[i] =zz.PercentSumSegmentsUp();
segments_dw_total_buffer[i] =zz.PercentSumSegmentsDown();
segments_difference_buffer[i] =fabs(segments_up_total_buffer[i]-segments_dw_total_buffer[i]);
break;
}
}
}
}
結果示意如下。 我們嘗試解釋一下。 當多向線段之間的百分比差值小于某個閾值時,可視為橫盤。 在這種情況下,我們還應該記住,比率應該經?;Q,因為價格能夠在一個方向上移動很長時間,而差值低于優(yōu)化器選擇的級別。 在這些情況下,我們應該考慮按照一定順序的形態(tài)格式來應用模型。 圖例 4. PercentageSegmentsZZ 指標 MultiPercentageSegmentsZZ 指標在前一篇文章中,我們已經演示了 EA 同時分析來自較高和較低時間幀的 ZigZag 指標數據。 因此,可以更詳細地從較高時間幀內的線段分析價格行為。 換句話說,我們定義了較高時間幀內的線段如何在較低時間幀上形成。 我們看看這組參數將如何以一個單獨指標的形式,基于價格歷史顯示這些數值。 就像前一篇文章的 EA 一樣,我們將收到四個反向線段百分比之間的差值:一個值用于較高的時間幀,三個值用于較低時間幀。 這些數值依據較高時間幀上的最后三個 ZigZag 指標線段計算。 指標緩沖區(qū)的顏色與前一部分的 EA 相同。 之后,我們將開發(fā)一款 EA 來測試指標,這樣我們就可以更容易地理解圖表上觀察到的時間段與數據如何對應。 //--- 緩沖區(qū)數量 #property indicator_buffers 4 #property indicator_plots 4 //--- 緩沖區(qū)顏色 #property indicator_color1 clrSilver #property indicator_color2 clrRed #property indicator_color3 clrLimeGreen #property indicator_color4 clrMediumPurple 聲明 CZigZagModule 類的四個實例: #include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_higher_tf;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;
我們在外部參數里增添設定 更高指標時間幀 的能力: input int NumberOfBars =0; // 計算 ZZ 所需的柱線數量 input int MinImpulseSize =0; // 線段的最小點數 input int CopyExtremum =5; // 復制極值 input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // 較高時間幀 填充指標緩沖區(qū)的主要函數實現如下。 首先,從外部參數中指定的較高時間幀獲取源數據。 然后獲取最終數據并保存參數值。 接著,我們始終從較高的時間幀獲取三個指標線段的數據。 之后,填充所有指標緩沖區(qū)。 我必須開發(fā)兩個獨立的代碼塊,以便可以基于歷史以及在實時/測試器的最后一根柱線上正確計算指標。 // ------------------------------------------------------------------
//| 填充指標緩沖區(qū) |
// ------------------------------------------------------------------
void FillIndicatorBuffers(const int i,const int total,const datetime &time[])
{
int index=total-i-1;
int copy_total=1000;
int h_buff=2,l_buff=3;
datetime start_time_in =NULL;
datetime stop_time_in =NULL;
//--- 從更高的時間幀獲取源數據
datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total);
CopyBuffer(zz_handle_htf,2,time[i],stop_time,h_zz_buffer_temp);
CopyBuffer(zz_handle_htf,3,time[i],stop_time,l_zz_buffer_temp);
CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp);
//--- 從更高的時間幀獲取最終數據
zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
double htf_value=zz_higher_tf.PercentSumSegmentsDifference();
//--- 第一根線段數據
zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,0,start_time_in,stop_time_in);
zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- 第二根線段數據
zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,1,start_time_in,stop_time_in);
zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- 第三根線段數據
zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,2,start_time_in,stop_time_in);
zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- 基于最后一根柱線
if(i<total-1)
{
buffer_zz_higher_tf[i] =htf_value;
buffer_segment_0[i] =zz_current0.PercentSumSegmentsDifference();
buffer_segment_1[i] =zz_current1.PercentSumSegmentsDifference();
buffer_segment_2[i] =zz_current2.PercentSumSegmentsDifference();
}
//--- 基于歷史
else
{
//--- 如果更高時間幀有新的柱線
if(new_bar_time!=t_zz_buffer_temp[0])
{
new_bar_time=t_zz_buffer_temp[0];
//---
if(i>2)
{
int f=1,s=2;
buffer_zz_higher_tf[i-f] =buffer_zz_higher_tf[i-s];
buffer_segment_0[i-f] =buffer_segment_0[i-s];
buffer_segment_1[i-f] =buffer_segment_1[i-s];
buffer_segment_2[i-f] =buffer_segment_2[i-s];
}
}
else
{
buffer_zz_higher_tf[i] =htf_value;
buffer_segment_0[i] =zz_current0.PercentSumSegmentsDifference();
buffer_segment_1[i] =zz_current1.PercentSumSegmentsDifference();
buffer_segment_2[i] =zz_current2.PercentSumSegmentsDifference();
}
}
}
我們從前一篇文章中復制 EA,并添加幾行代碼來測試 MultiPercentageSegmentsZZ 指標。 添加外部參數 以便設定更高時間幀。 為了在測試器的可視化模式下進行 EA 測試期間顯示指標,獲取其句柄就足夠了。 //--- 外部參數 input uint CopyExtremum =3; // 復制極值 input int MinImpulseSize =0; // 最小值 impulse size input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // 較高時間幀 ... // ------------------------------------------------------------------ //| 智能系統(tǒng)初始化函數 | // ------------------------------------------------------------------ int OnInit(void) { ... //--- ZZ 指標的路徑 string zz_path1='Custom\\ZigZag\\ExactZZ_Plus.ex5'; string zz_path2='Custom\\ZigZag\\MultiPercentageSegmentsZZ.ex5'; //--- 獲取指標句柄 zz_handle_current =::iCustom(_Symbol,_Period,zz_path1,0,MinImpulseSize,false,false); zz_handle_higher_tf =::iCustom(_Symbol,HigherTimeframe,zz_path1,0,MinImpulseSize,false,false); zz_handle =::iCustom(_Symbol,_Period,zz_path2,0,MinImpulseSize,CopyExtremum,HigherTimeframe); ... return(INIT_SUCCEEDED); } 這是它在測試器中的樣子: 圖例 5. MultiPercentageSegmentsZZ 指標 上述所有指標可以在不同的時間幀內以各種組合使用。 現在,我們利用所描述的工具收集一些品種集合的統(tǒng)計數據,以便了解哪些品種更適合在價格通道內進行交易。 用于收集和顯示統(tǒng)計數據的 EA作為補充,本文介紹一套用于開發(fā)圖形用戶界面的新版本 EasyAndFast 函數庫。 這里我們只列出函數庫的新功能:
圖例 6. 將元素合并為組
可以在 代碼庫 中下載新版函數庫。 接下來,我們利用新版 EasyAndFast 函數庫創(chuàng)建一個測試 EA,來收集一些統(tǒng)計信息。 我們將從開發(fā)應用程序的圖形用戶界面(GUI)開始,然后過渡到收集和顯示統(tǒng)計信息的方法。 我們先定義我們需要的 GUI 控件:
如前所提,CWndCreate 類應作為基類包含在自定義類中,以便更快、更便捷地開發(fā) GUI。 完整連接如下所示: CWndContainer -> CWndEvents -> CWndCreate -> CProgram。 CWndCreate 類的存在允許在單行中創(chuàng)建 GUI 元素,而無需在自定義類中創(chuàng)建單獨的方法。 該類包含幾乎所有庫元素的不同模板。 如有必要,您可以自行添加新模板。 若要創(chuàng)建 GUI,請聲明上面列表中包含的元素,如下列代碼所示。 當前版本的 CWndCreate 類沒有快速創(chuàng)建表格的模板,因此我們自行開發(fā)此方法。 // ------------------------------------------------------------------
//| Program.mqh |
//| 版權所有 2018, MetaQuotes 軟件公司 |
//| http://www. |
// ------------------------------------------------------------------
#include <EasyAndFastGUI\WndCreate.mqh>
// ------------------------------------------------------------------
//| 用于開發(fā)應用程序的類 |
// ------------------------------------------------------------------
class CProgram : public CWndCreate
{
private:
//--- 窗體
CWindow m_window;
//--- 狀態(tài)欄
CStatusBar m_status_bar;
//--- 下拉式日歷
CDropCalendar m_from_date;
CDropCalendar m_to_date;
//--- 按鈕
CButton m_request;
//--- 輸入字段
CTextEdit m_filter;
CTextEdit m_level;
//--- 組合框
CComboBox m_data_type;
//--- 表格
CTable m_table;
//--- 進度欄
CProgressBar m_progress_bar;
//---
public:
//--- 創(chuàng)建 GUI
bool CreateGUI(void);
//---
private:
//--- 表格
bool CreateTable(const int x_gap,const int y_gap);
};
若要創(chuàng)建包含此種內容的圖形界面,只需指定屬性的數值,并將其作為參數來調用 CWndCreate 類的必要方法,如下列代碼所示。 若要定義與方法參數相關的屬性,在其中設置文本光標并單擊 Ctrl Shift Space: 圖例 7. 查看方法參數 如果需要設置其他屬性,可采用示例 “貨幣過濾器輸入字段” 中所示的相同方式。 此處示意的是在創(chuàng)建元素后 默認啟用復選框。 // ------------------------------------------------------------------ //| 創(chuàng)建 GUI | // ------------------------------------------------------------------ bool CProgram::CreateGUI(void) { //--- 創(chuàng)建控制窗體 if(!CWndCreate::CreateWindow(m_window,'ZZ Market Scanner',1,1,640,480,true,true,true,true)) return(false); //--- 狀態(tài)欄 string text_items[1]; text_items[0]='For Help, press F1'; int width_items[]={0}; if(!CWndCreate::CreateStatusBar(m_status_bar,m_window,1,23,22,text_items,width_items)) return(false); //--- 貨幣過濾器輸入字段 if(!CWndCreate::CreateTextEdit(m_filter,'Symbols filter:',m_window,0,true,7,25,627,535,'USD','Example: EURUSD,GBP,NOK')) return(false); else m_filter.IsPressed(true); //--- 下拉日歷 if(!CWndCreate::CreateDropCalendar(m_from_date,'From:',m_window,0,7,50,130,D'2018.01.01')) return(false); if(!CWndCreate::CreateDropCalendar(m_to_date,'To:',m_window,0,150,50,117,::TimeCurrent())) return(false); //--- 用于指定級別的輸入字段 if(!CWndCreate::CreateTextEdit(m_level,'Level:',m_window,0,false,280,50,85,50,100,0,1,0,30)) return(false); //--- 按鈕 if(!CWndCreate::CreateButton(m_request,'Request',m_window,0,375,50,70)) return(false); //--- 表格 if(!CreateTable(2,75)) return(false); //--- 進度欄 if(!CWndCreate::CreateProgressBar(m_progress_bar,'Processing:',m_status_bar,0,2,3)) return(false); //--- 結束 GUI 開發(fā) CWndEvents::CompletedGUI(); return(true); } 若是表格的情況下,創(chuàng)建一個自定義方法,因為它是一個復雜元素,在創(chuàng)建元素之前應指定大量屬性。 它擁有四列。 第一列將顯示貨幣對。 其余的將顯示三個時間幀的統(tǒng)計數據: M5, H1 和 H8。 // ------------------------------------------------------------------
//| 創(chuàng)建表格 |
// ------------------------------------------------------------------
bool CProgram::CreateTable(const int x_gap,const int y_gap)
{
#define COLUMNS1_TOTAL 4
#define ROWS1_TOTAL 1
//--- 保存指向主元素的指針
m_table.MainPointer(m_window);
//--- 列寬數組
int width[COLUMNS1_TOTAL];
::ArrayInitialize(width,50);
width[0]=80;
//--- 沿 X 軸的列中文本偏移的數組
int text_x_offset[COLUMNS1_TOTAL];
::ArrayInitialize(text_x_offset,7);
//--- 列中的文本對齊數組
ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
::ArrayInitialize(align,ALIGN_CENTER);
align[0]=ALIGN_LEFT;
//--- 屬性
m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
m_table.TextAlign(align);
m_table.ColumnsWidth(width);
m_table.TextXOffset(text_x_offset);
m_table.ShowHeaders(true);
m_table.IsSortMode(true);
m_table.IsZebraFormatRows(clrWhiteSmoke);
m_table.AutoXResizeMode(true);
m_table.AutoYResizeMode(true);
m_table.AutoXResizeRightOffset(2);
m_table.AutoYResizeBottomOffset(24);
//--- 創(chuàng)建控件
if(!m_table.CreateTable(x_gap,y_gap))
return(false);
//--- 標題
string headers[]={'Symbols','M5','H1','H8'};
for(uint i=0; i<m_table.ColumnsTotal(); i )
m_table.SetHeaderText(i,headers[i]);
//--- 將對象添加到公共對象組數組
CWndContainer::AddToElementsArray(0,m_table);
return(true);
}
現在我們考察獲取數據的方法。 首先,我們需要獲得我們要使用的品種。 在此 EA 版本中,我們將接收來自外匯品種的數據。 同時,我們將排除禁止交易的品種。 在此我們還需要 CheckFilterText() 輔助方法按照過濾器來檢查品種。 在輸入字段中,用戶可以輸入理應存在的品種名稱,并用逗號分隔文本值。 如果禁用字段復選框或未輸入文本,則不執(zhí)行檢查。 如果驗證通過,則應能發(fā)現匹配項,輸入的文本被切分成子串,并按照必要的字符串執(zhí)行搜索。 class CProgram : public CWndCreate { private: //--- 按照過濾器檢查品種 bool CheckFilterText(const string symbol_name); }; // ------------------------------------------------------------------ //| 按照過濾器檢查品種 | // ------------------------------------------------------------------ bool CProgram::CheckFilterText(const string symbol_name) { bool check=false; //--- 如果啟用了品種名稱過濾器 if(!m_filter.IsPressed()) return(true); //--- 如果輸入了文本 string text=m_filter.GetValue(); if(text=='') return(true); //--- 切分為子串 string elements[]; ushort sep=::StringGetCharacter(',',0); ::StringSplit(text,sep,elements); //--- 匹配檢查 int elements_total=::ArraySize(elements); for(int e=0; e<elements_total; e ) { //--- 刪除邊外空白 ::StringTrimLeft(elements[e]); ::StringTrimRight(elements[e]); //--- 如果檢測到匹配 if(::StringFind(symbol_name,elements[e])>-1) { check=true; break; } } //--- 結果 return(check); } 在 CProgram::GetSymbols() 方法里,循環(huán)中傳遞服務器上存在的所有品種,將符合指定條件的品種收集到數組中。 在常規(guī)循環(huán)中,所有品種都將從 “市場觀察” 窗口中刪除。 之后,只將數組中包含的數據添加到窗口。 class CProgram : public CWndCreate
{
private:
//--- 品種數組
string m_symbols[];
//---
private:
//--- 獲取品種
void GetSymbols(void);
};
// ------------------------------------------------------------------
//| 獲取品種 |
// ------------------------------------------------------------------
void CProgram::GetSymbols(void)
{
//--- 進度
m_progress_bar.LabelText('Get symbols...');
m_progress_bar.Update(0,1);
//--- 清楚品種數組
::ArrayFree(m_symbols);
//--- 收集外匯品種的數組
int symbols_total=::SymbolsTotal(false);
for(int i=0; i<symbols_total; i )
{
//--- 獲取品種名稱
string symbol_name=::SymbolName(i,false);
//--- 在市場觀察窗口中隱藏它
::SymbolSelect(symbol_name,false);
//--- 如果這不是外匯代碼,則轉到下一個
if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
continue;
//--- 如果禁止交易,則轉到下一個
if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED)
continue;
//--- 按照過濾器檢查品種
if(!CheckFilterText(symbol_name))
continue;
//--- 將品種保存到數組
int array_size=::ArraySize(m_symbols);
::ArrayResize(m_symbols,array_size 1,1000);
m_symbols[array_size]=symbol_name;
}
//--- 如果數組為空,則將當前品種設置為默認值
int array_size=::ArraySize(m_symbols);
if(array_size<1)
{
::ArrayResize(m_symbols,array_size 1);
m_symbols[array_size]=_Symbol;
}
//--- 在市場觀察窗口中顯示
int selected_symbols_total=::ArraySize(m_symbols);
for(int i=0; i<selected_symbols_total; i )
::SymbolSelect(m_symbols[i],true);
}
若要獲取有關所收集品種的數據,我們應首先獲取綁定它們的指標句柄。 每次我們獲取指標句柄,我們需要等到計算結束,然后再復制數據以便進一步分析。 接收所有數據完畢后,進行必要的計算。 CProgram::GetSymbolsData() 方法即用于此。 它接受兩個參數:品種和時間幀。 收到指標句柄后,在指定時間范圍內找出有多少根柱線存在。 可以利用應用程序的 GUI 控件指定日期范圍。 接下來,我們嘗試獲取已計算的指標數據量。 收到句柄后,指標計算可能無法立即完成。 所以,如果 BarsCalculated() 函數返回 -1,我們要再次嘗試獲取有效值,直到 等于或超過指定時間范圍內的總柱線數量。 在計算指標數據后,我們可以嘗試將它們放入數組中。 也許在數量 大于或等于柱線總數 之前,需要若干次嘗試。 如果指標已成功復制到數組,則剩下的僅僅是進行必要的計算。 在這種情況下,我們計算 數據總量與數量的百分比,其中指標值高于指定級別。 也可以在應用程序的 GUI 中指定此級別。 在方法結束時,移除指標句柄來釋放其計算部分。 針對所選的品種列表和若干時間幀,CProgram::GetSymbolsData() 方法會被多次調用。 每次計算它們時只應執(zhí)行一次,結果值顯示在 GUI 表格中,然后句柄不再需要,可以刪除。 class CProgram : public CWndCreate { private: //--- 獲取品種數據 double GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period); }; // ------------------------------------------------------------------ //| 獲取品種數據 | // ------------------------------------------------------------------ double CProgram::GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period) { double result =0.0; int buffer_index =2; //--- 獲取指標句柄 string path ='::Indicators\\Custom\\ZigZag\\PercentageSegmentsZZ.ex5'; int handle =::iCustom(symbol,period,path,0,0,5); if(handle!=INVALID_HANDLE) { //--- 復制指定范圍內的數據 double data[]; datetime start_time =m_from_date.SelectedDate(); datetime end_time =m_to_date.SelectedDate(); //--- 指定范圍內的柱線數量 int bars_total=::Bars(symbol,period,start_time,end_time); //--- 指定范圍內的柱線數量 int bars_calculated=::BarsCalculated(handle); if(bars_calculated<bars_total) { while(true) { ::Sleep(100); bars_calculated=::BarsCalculated(handle); if(bars_calculated>=bars_total) break; } } //--- 獲取數據 int copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data); if(copied<1) { while(true) { ::Sleep(100); copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data); if(copied>=bars_total) break; } } //--- 如果沒有收到數據則退出 int total=::ArraySize(data); if(total<1) return(result); //--- 計算重復次數 int counter=0; for(int k=0; k<total; k ) { if(data[k]>(double)m_level.GetValue()) counter ; } //--- 百分比 result=((double)counter/(double)total)*100; } //--- 釋放指標 ::IndicatorRelease(handle); //--- 返回數值 return(result); } 每次形成新的品種列表時,都需要重建該表。 為此,只需刪除表格的所有行,并添加必要的數額即可。 class CProgram : public CWndCreate
{
private:
//--- 重新構建表格
void RebuildingTables(void);
};
// ------------------------------------------------------------------
//| 重新構建表格 |
// ------------------------------------------------------------------
void CProgram::RebuildingTables(void)
{
//--- 刪除所有行
m_table.DeleteAllRows();
//--- 添加數據
int symbols_total=::ArraySize(m_symbols);
for(int i=1; i<symbols_total; i )
m_table.AddRow(i);
}
CProgram::SetData() 方法用來將數據填充至表格列內。 它需要傳遞兩個參數(列索引和時間幀)。 在此,我們遍歷指定列的單元格,在循環(huán)中用計算出的數值填充它們。 進度欄 顯示品種和時間幀,剛剛收到的數據,以便用戶了解當前正在做什么。 class CProgram : public CWndCreate { private: //--- 在指定的列設置數值 void SetData(const int column_index,const ENUM_TIMEFRAMES period); //--- 時間幀轉化為字符串 string GetPeriodName(const ENUM_TIMEFRAMES period); }; // ------------------------------------------------------------------ //| 在指定的列設置數值 | // ------------------------------------------------------------------ void CProgram::SetData(const int column_index,const ENUM_TIMEFRAMES period) { for(uint r=0; r<(uint)m_table.RowsTotal(); r ) { double value=GetSymbolsData(m_symbols[r],period); m_table.SetValue(column_index,r,string(value),2,true); m_table.Update(); //--- 進度 m_progress_bar.LabelText('Data preparation [' m_symbols[r] ',' GetPeriodName(period) ']...'); m_progress_bar.Update(r,m_table.RowsTotal()); } } // ------------------------------------------------------------------ //| 返回周期字符串對應的值 | // ------------------------------------------------------------------ string CProgram::GetPeriodName(const ENUM_TIMEFRAMES period) { return(::StringSubstr(::EnumToString(period),7)); } 用數據填充表格的主要方法是 CProgram::SetDataToTable()。 該表格首先在這里重建。 接著,我們需要在其中設置標題和數據類型(TYPE_DOUBLE)。 在第一列內設置所收集的品種。 重新繪制表格以便立即查看變化。 現在我們可以開始接收所有指定品種和時間幀的指標數據。 為此,只需調用 CProgram::SetData() 方法,將列索引和時間幀作為參數傳遞給它。 class CProgram : public CWndCreate
{
private:
//--- 在表格中填充數據
void SetDataToTable(void);
};
// ------------------------------------------------------------------
//| 在表格中填充數據 |
// ------------------------------------------------------------------
void CProgram::SetDataToTable(void)
{
//--- 進度
m_progress_bar.LabelText('Data preparation...');
m_progress_bar.Update(0,1);
//--- 重新構建表格
RebuildingTable();
//--- 標題
string headers[]={'Symbols','M5','H1','H8'};
for(uint i=0; i<m_table.ColumnsTotal(); i )
m_table.SetHeaderText(i,headers[i]);
for(uint i=1; i<m_table.ColumnsTotal(); i )
m_table.DataType(i,TYPE_DOUBLE);
//--- 在第一列里設置數值
for(uint r=0; r<(uint)m_table.RowsTotal(); r )
m_table.SetValue(0,r,m_symbols[r],0,true);
//--- 顯示表格
m_table.Update(true);
//--- 用數據填充剩余的列
SetData(1,PERIOD_M5);
SetData(2,PERIOD_H1);
SetData(3,PERIOD_H8);
}
在使用 CProgram::GetData() 方法接收新數據之前,我們應借助 CProgram::StartProgress() 方法令進度條可見。 接收到新數據后,隱藏進度條并從按下的按鈕中移除焦點。 為此,調用 CProgram::EndProgress() 方法。 class CProgram : public CWndCreate { private: //--- 獲取數據 void GetData(void); //--- 進度(1)開始,(2)結束 void StartProgress(void); void EndProgress(void); }; // ------------------------------------------------------------------ //| 獲取數據 | // ------------------------------------------------------------------ void CProgram::GetData(void) { //--- 進度開始 StartProgress(); //--- 獲取品種列表 GetSymbols(); //--- 在表格中填充數據 SetDataToTable(); //--- 進度結束 EndProgress(); } // ------------------------------------------------------------------ //| 進度開始 | // ------------------------------------------------------------------ void CProgram::StartProgress(void) { m_progress_bar.LabelText('Please wait...'); m_progress_bar.Update(0,1); m_progress_bar.Show(); m_chart.Redraw(); } // ------------------------------------------------------------------ //| 進度結束 | // ------------------------------------------------------------------ void CProgram::EndProgress(void) { //--- 隱藏進度欄 m_progress_bar.Hide(); //--- 更新按鈕 m_request.MouseFocus(false); m_request.Update(true); m_chart.Redraw(); } 當用戶單擊 請求 時,將生成 ON_CLICK_BUTTON 自定義事件,并且我們可以按元素 ID 定義按下的按鈕。 如果是 請求 按鈕,啟動數據獲取進程。 在表格創(chuàng)建方法中,我們包含了通過單擊標題對表格進行排序的能力。 ON_SORT_DATA 自定義事件會在每次執(zhí)行此操作時生成。 收到事件后,應更新表格以便顯示變化。 // ------------------------------------------------------------------
//| 事件處理器 |
// ------------------------------------------------------------------
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- 按下按鈕事件
if(id==CHARTEVENT_CUSTOM ON_CLICK_BUTTON)
{
if(lparam==m_request.Id())
{
//--- 獲取數據
GetData();
return;
}
//---
return;
}
//--- 表格排序事件
if(id==CHARTEVENT_CUSTOM ON_SORT_DATA)
{
if(lparam==m_table.Id())
{
m_table.Update(true);
return;
}
//---
return;
}
}
現在,我們看看結果。 如果我們編譯程序并將其加載到圖表上,結果會如下屏幕截圖所示。 以下參數按默認情況設置:
圖例 8. MQL 應用程序的 GUI 按下 請求 啟動數據采集: 圖例 9. 接收數據 收到所有數據后,您可以對它們進行排序: 圖例 10. 表格數據排序 您可以修改并使用此應用程序來解決您的一些任務。 該表格可以按照任意其他參數來填充。 下面我將提供另一個示例,演示如何進一步提高表格數據的直觀性。 正如我在本節(jié)開頭已經提到的,最新版本的 EasyAndFast 函數庫具有設置表格單元格背景顏色的功能。 這可令您按照其它各種表格編輯器中所見到的方式來格式化表格。 下面的屏幕截圖顯示了 Excel 電子表格中的格式數據。 即使在排序數組時,每個單元格的自身背景顏色也會保持相同數值。 圖例 11. Excel 中的色標 這種格式化令快速執(zhí)行直觀數據分析成為可能。 我們對上面研究的 MQL 應用程序略微進行一些修改和補充。 若要為每個表格單元格設置獨有顏色,需禁用斑馬紋格式。 注釋掉這段代碼。 // m_table.IsZebraFormatRows(clrWhiteSmoke);
現在,我們?yōu)楸砀竦母袷交瘎?chuàng)建 CProgram::SetColorsToTable() 方法。 CColors 類用于處理顏色。 它已經存在于創(chuàng)建 GUI 的函數庫當中,因此不需要在項目中包含該文件。 聲明兩個工作數組:(1)用于獲得漸變顏色的數組,和(2)要形成漸變顏色的數組。 我們將創(chuàng)建 三色漸變。 數值越低,顏色越紅(clrTomato)。 數值越高,它變得越藍(clrCornflowerBlue)。 我們添加白色來分隔這兩個顏色區(qū)域。 定義數值范圍的大小,從最小值到最大值。 這將是漸變數組的大小。 CColors::Gradient() 方法用于設置數組大小并填充。 表格單元格的顏色在最終循環(huán)中設置。 為了不超出數組范圍,索引的計算為單元格值減去范圍最小值。 在方法結束時,表格將被更新,以便顯示已施加的變化。 class CProgram : public CWndCreate
{
private:
//--- 用單元格的背景顏色填充表格
void SetColorsToTable(void);
};
// ------------------------------------------------------------------
//| 格式化表格 |
// ------------------------------------------------------------------
void CProgram::SetColorsToTable(void)
{
//--- 用于處理顏色
CColors clr;
//--- 用于接收漸變的數組
color out_colors[];
//--- 三色漸變
color colors[3]={clrTomato,clrWhite,clrCornflowerBlue};
//--- 查找最低和最高表格數值
double max =0;
double min =100;
for(uint c=1; c<(uint)m_table.ColumnsTotal(); c )
{
for(uint r=0; r<(uint)m_table.RowsTotal(); r )
{
max =::fmax(max,(double)m_table.GetValue(c,r));
min =::fmin(min,(double)m_table.GetValue(c,r));
}
}
//--- 調整到最接近的較低整數
max =::floor(max);
min =::floor(min);
//--- 獲得范圍
int range =int(max-min) 1;
//--- 獲得顏色漸變數組
clr.Gradient(colors,out_colors,range);
//--- 設置單元格的背景顏色
for(uint c=1; c<(uint)m_table.ColumnsTotal(); c )
{
for(uint r=0; r<(uint)m_table.RowsTotal(); r )
{
int index=(int)m_table.GetValue(c,r)-(int)min;
m_table.BackColor(c,r,out_colors[index],true);
}
}
//--- 更新表格
m_table.Update();
}
您可以在下面看到 GUI 中的模樣。 在這種情況下,結果表明,數值越小,所考察區(qū)域的趨勢數量越少。 為了盡可能多的利用數據獲取信息,明智的做法是盡可能放寬日期設置。 圖例 12. 可視化表格數據的色標 日期范圍越寬,可利用的數據越多,因此生成數據和計算參數所需的時間就越多。 如果沒有足夠的數據,則嘗試從服務器下載它們。 按大小計算線段數量現在,我們開發(fā)一個程序來計算線段數量。 復制上一節(jié)中的 EA 并對其進行必要的修改和添加。 這里會有兩張表格。 第一張僅用到一列,它是所分析的品種列表。 第二張用到兩個數據列:(1)增加點數的范圍,和(2)第一列中指定范圍內的線段數量。 下面您可以看到在應用程序加載到圖表后 GUI 的外觀。 圖例 13. 按大小計算線段數量的程序 請求 按鈕遵照指定的過濾器請求品種列表。 單擊 計算 時,將收集指定時間范圍內的數據并將其分發(fā)到第二張表格中。 基本上,所有方法都與之前的 EA 保持一致,所以我們只考慮與第二張表格相關的事情。 首先,我們需要接收指標數據。 這是在 CProgram::GetIndicatorData() 方法中完成的。 最初,我們連接到 ZigZag 指標,然后 在指定的時間范圍內獲取其數據。 品種、時間幀和獲取的指標線段數量顯示在狀態(tài)欄中。 class CProgram : public CWndCreate { private: //--- 獲取指標數據 void GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period); }; // ------------------------------------------------------------------ //| 獲取指標數據 | // ------------------------------------------------------------------ void CProgram::GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period) { //--- 獲取指標句柄 string path ='::Indicators\\Custom\\ZigZag\\ExactZZ_Plus.ex5'; int handle =::iCustom(symbol,period,path,0,0); if(handle!=INVALID_HANDLE) { //--- 復制指定范圍內的數據 datetime start_time =m_from_date.SelectedDate(); datetime end_time =m_to_date.SelectedDate(); m_zz.GetZigZagData(handle,2,3,symbol,period,start_time,end_time); //--- 在狀態(tài)欄中顯示數據 string text='[' symbol ',' (string)GetPeriodName(period) '] - Segments total: ' (string)m_zz.SegmentsTotal(); m_status_bar.SetValue(0,text); m_status_bar.GetItemPointer(0).Update(true); } //--- 釋放指標 ::IndicatorRelease(handle); } 按照指定步長計算第一列的價格范圍。 CProgram::GetLevels() 方法即用于此。 為了定義范圍數量,我們應首先取得所采集數據集合中的最大線段大小。 接下來,在循環(huán)中按指定步長填充數組,直至達到最大值。 class CProgram : public CWndCreate
{
private:
//--- 數組范圍
int m_levels_array[];
//---
private:
//--- 獲取級別
void GetLevels(void);
};
// ------------------------------------------------------------------
//| 獲取級別 |
// ------------------------------------------------------------------
void CProgram::GetLevels(void)
{
//--- 釋放數組
::ArrayFree(m_levels_array);
//--- 獲取最大線段大小
int max_value=int(m_zz.LargestSegment()/m_symbol.Point());
//--- 用級別填充數組
int counter_levels=0;
while(true)
{
int size=::ArraySize(m_levels_array);
::ArrayResize(m_levels_array,size 1);
m_levels_array[size]=counter_levels;
//---
if(counter_levels>max_value)
break;
//---
counter_levels =(int)m_step.GetValue();
}
}
CProgram::SetDataToTable2() 方法會用數據填充第二張表格。 在最開始處,將檢查品種在第一張表格得列表中是否高亮顯示。 如果不是,則程序退出,并發(fā)送消息到智能系統(tǒng)日志。 如果出于第一張表格中高亮顯示行,則定義品種并獲取其數據。 此后,調用上述方法接收指標數據并計算級別。 我們接收的指標數據與啟動 EA 的時間幀相同。 當我們知道級別的數量時,我們可以構建一個所需大小的表格并用那些數值填充它。 首先,使用范圍數值填充第一列。 之后,填充第二列。 順序遍歷所有范圍,增加適合此范圍的線段的單元格中的計數器。 class CProgram : public CWndCreate { private: //--- 用數據填寫第二張表格 void SetDataToTable2(void); }; // ------------------------------------------------------------------ //| 用數據填寫第二張表格 | // ------------------------------------------------------------------ void CProgram::SetDataToTable2(void) { //--- 如果不是高亮顯示得行,則退出 if(m_table1.SelectedItem()==WRONG_VALUE) { ::Print(__FUNCTION__,' > Select a symbol in the table on the left!'); return; } //--- 進度開始 StartProgress(); //--- 隱藏表格 m_table2.Hide(); //--- 從第一張表格中獲取品種 string symbol=m_table1.GetValue(0,m_table1.SelectedItem()); m_symbol.Name(symbol); //--- 獲取指標數據 GetIndicatorData(symbol,_Period); //--- 獲取級別 GetLevels(); //--- 重新構建表格 RebuildingTable2(); //--- 在第一列中設置范圍 for(uint r=0; r<(uint)m_table2.RowsTotal(); r ) m_table2.SetValue(0,r,(string)m_levels_array[r],0); //--- 獲取第二列的值 int items_total=::ArraySize(m_levels_array); int segments_total=m_zz.SegmentsTotal(); for(int i=0; i<items_total-1; i ) { //--- 進度 m_progress_bar.LabelText('Get data [' (string)m_levels_array[i] ']...'); m_progress_bar.Update(i,m_table2.RowsTotal()); //--- for(int s=0; s<segments_total; s ) { int size=int(m_zz.SegmentSize(s)/m_symbol.Point()); if(size>m_levels_array[i] && size<m_levels_array[i 1]) { int value=(int)m_table2.GetValue(1,i) 1; m_table2.SetValue(1,i,(string)value,0); } } } //--- 顯示表格 m_table2.Update(true); //--- 結束進度 EndProgress(); } 舉例,我們在 M5 圖表上接收自 2010 年開始至今的 EURUSD 線段。 設置范圍,步長為 100,此值針對五位小數報價系統(tǒng)。 結果顯示在下面的屏幕截圖中。 線段的總數是 302145。 正如我們所看到的,最大線段數量在 0 到 100 范圍內。 進而,線段的數量從一個量級降低到另一個量級。 在指定的時間段內,最大線段大小達到 2400,此值針對五位小數報價系統(tǒng)。 圖例 14. 按大小計算線段數量的結果 按持續(xù)時間計算線段數量了解線段的持續(xù)時間如何形成分組也很好。 若要查找任何形態(tài),我們需要獲得所分析數據的所有統(tǒng)計數據。 我們開發(fā)另一個 EA 版本。 只需復制上一節(jié)中的程序,然后將另一個表格添加到 GUI。 該表格擁有兩列:(1)柱線數量,和(2)該柱線數量內的線段數量。 下面您可以看到在應用程序加載到圖表后 GUI 的外觀。 圖例 15. 按持續(xù)時間計算線段數量的程序 在所有表格中接收數據的動作序列如下:
下列提供了用于接收數據和填充第三張表格的 CProgram::SetDataToTable3() 方法的代碼。 此處高亮顯示的行作為接收范圍,其中線段的數量將按其持續(xù)時間計算。 表格中的行數由獲得的數據集合中的最長(以柱線為單位)線段定義。 當填充表格的第二列時,遍歷所有行并計算相應 所選范圍和線段數量 的線段。 class CProgram : public CWndCreate
{
private:
//--- 用數據填充表格三
void SetDataToTable3(void);
};
// ------------------------------------------------------------------
//| 用數據填充表格三 |
// ------------------------------------------------------------------
void CProgram::SetDataToTable3(void)
{
//--- 如果不是高亮顯示得行,則退出
if(m_table2.SelectedItem()==WRONG_VALUE)
{
::Print(__FUNCTION__,' > Select a range in the table on the left!');
return;
}
//--- 進度開始
StartProgress();
//--- 隱藏表格
m_table3.Hide();
//--- 獲取高亮顯示的行
int selected_row_index=m_table2.SelectedItem();
//--- 范圍
int selected_range=(int)m_table2.GetValue(0,selected_row_index);
//--- 重新構建表格
RebuildingTable3();
//--- 在第一列中設置數值
for(uint r=0; r<(uint)m_table3.RowsTotal(); r )
m_table3.SetValue(0,r,(string)(r 1),0);
//--- 獲取第二列的值
int segments_total=m_zz.SegmentsTotal();
for(uint r=0; r<(uint)m_table3.RowsTotal(); r )
{
//--- 進度
m_progress_bar.LabelText('Get data [' (string)r ']...');
m_progress_bar.Update(r,m_table3.RowsTotal());
//---
for(int s=0; s<segments_total; s )
{
int size =int(m_zz.SegmentSize(s)/m_symbol.Point());
int bars =m_zz.SegmentBars(s);
//---
if(size>selected_range &&
size<selected_range (int)m_step.GetValue() &&
bars==r 1)
{
int value=(int)m_table3.GetValue(1,r) 1;
m_table3.SetValue(1,r,(string)value,0);
}
}
}
//--- 顯示表格
m_table3.Update(true);
//--- 進度結束
EndProgress();
}
高亮顯示表格和列表行時,將生成 ON_CLICK_LIST_ITEM 自定義事件。 在這種情況下,我們使用 第二張表格的 ID 跟蹤事件觸發(fā)。 // ------------------------------------------------------------------ //| 事件處理器 | // ------------------------------------------------------------------ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- 單擊行的事件 if(id==CHARTEVENT_CUSTOM ON_CLICK_LIST_ITEM) { //--- 單擊表格行 if(lparam==m_table2.Id()) { //--- 獲取第三張表格的數據 SetDataToTable3(); return; } //--- return; } ... } 當第一張表格中接收新的品種列表,或計算新高亮顯示的品種數據時,應從表格中清除以前計算的無關數據,以便避免混淆當前顯示的數據。 // ------------------------------------------------------------------
//| 事件處理器 |
// ------------------------------------------------------------------
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- 單擊按鈕的事件
if(id==CHARTEVENT_CUSTOM ON_CLICK_BUTTON)
{
//--- 點擊請求按鈕
if(lparam==m_request.Id())
{
//--- 獲取第一張表格的數據
SetDataToTable1();
//--- 從表格中刪除不相關的數據
m_table2.DeleteAllRows(true);
m_table3.DeleteAllRows(true);
return;
}
//--- 單擊計算按鈕
if(lparam==m_calculate.Id())
{
//--- 獲取第二張表格的數據
SetDataToTable2();
//--- 從表格中刪除不相關的數據
m_table3.DeleteAllRows(true);
}
//---
return;
}
...
}
在圖表上啟動 EA 后,我們獲得如下所示的結果。 在這種情況下,我們形成了以 USD 為參照的貨幣對列表。 之后收到了從 2018 年開始的 GBPUSD 數據,并且按 100 的步長形成了范圍列表(第二張表格),并逐一計算了它們的線段數量。 例如,在第二張表格中高亮顯示范圍 200 的行和 1922(從 200 到 300)的線段數量。 第三張表格顯示第二張表格中高亮顯示的范圍內所有線段的持續(xù)時間。 例如,我們可以看到,在此期間,在指定范圍內只有持續(xù)時間為 10 根柱線的 11 個 線段出現在 GBPUSD 上。 圖例 16. 按持續(xù)時間計算線段數的結果 處理圖形界面的一些細節(jié)作為補充,我愿展示 MQL 程序中 GUI 如何正確處理圖表品種和時間幀的變化事件。 由于 GUI 可能包含多個不同的控件,因此加載和初始化整個集合可能需要一些時間。 有時,可以節(jié)省此時間,這與變更圖表品種和時間幀完全相同。 在此,不需要一遍又一遍地持續(xù)刪除和創(chuàng)建 GUI。 這可以通過以下方式實現: 創(chuàng)建一個字段來保存程序主類中逆始化的最終原因: class CProgram : public CWndCreate { private: //--- 逆初始化的最終原因 int m_last_deinit_reason; }; // ------------------------------------------------------------------ //| 構造函數 | // ------------------------------------------------------------------ CProgram::CProgram(void) : m_last_deinit_reason(WRONG_VALUE) { } 在逆初始化期間,除了原因為 REASON_CHARTCHANGE 的 GUI 之外,其它所有情況下都會刪除 GUI。 // ------------------------------------------------------------------
//| 逆初始化 |
// ------------------------------------------------------------------
void CProgram::OnDeinitEvent(const int reason)
{
//--- 記住最后的逆初始化原因
m_last_deinit_reason=reason;
//--- 如果原因與變更品種和周期無關,則刪除 GUI
if(reason!=REASON_CHARTCHANGE)
{
CWndEvents::Destroy();
}
}
由于 GUI 是初始化過程中調用 CProgram::CreateGUI() 方法創(chuàng)建的,那么現在檢查逆初始化的最后原因就足以了。 如果原因是品種或時間正變更,則無需重新創(chuàng)建 GUI。 代之,只需退出方法,并通知 一切正常。 // ------------------------------------------------------------------ //| 創(chuàng)建 GUI | // ------------------------------------------------------------------ bool CProgram::CreateGUI(void) { //--- 如果圖表或時間幀已變更,則退出 if(m_last_deinit_reason==REASON_CHARTCHANGE) return(true); ... return(true); } 結束語ZigZag 不適合生成交易信號的觀念在交易論壇上廣泛傳播。 這有很大的誤解。 事實上,沒有其他指標能提供如此多的信息來判斷價格行為的性質。 現在,您可以使用工具輕松獲取所有必需的 ZigZag 指標數據,以便進行更詳盡的分析。 在下一部分中,我將展示如何利用文章中開發(fā)的這些工具來獲得其他數據。
|
|