目錄
簡介
我們都知道,外匯交易者使用貨幣對(duì)報(bào)價(jià)進(jìn)行交易。所謂貨幣對(duì),就是把一個(gè)國家的貨幣值以另一個(gè)國家的貨幣來表示。很容易就能在多個(gè)貨幣對(duì)中找到相同的貨幣。因?yàn)樨泿诺膬r(jià)值是由它所在國家的經(jīng)濟(jì)狀況決定的,我們可能會(huì)問,當(dāng)國家經(jīng)濟(jì)有變化的時(shí)候,它的貨幣在不同的貨幣對(duì)中的影響是否是一致的,看起來答案肯定是這樣的,但這只是當(dāng)只有一個(gè)國家的經(jīng)濟(jì)狀態(tài)有變時(shí)的完美特例,現(xiàn)實(shí)生活中我們的世界是不斷變化的,一個(gè)國家的經(jīng)濟(jì)變化會(huì)直接或者間接地影響到全球的經(jīng)濟(jì)。
在互連網(wǎng)上,您可以找到很多信息是關(guān)于分析貨幣價(jià)格在不同貨幣對(duì)中的改變以及尋找不同貨幣對(duì)之間的相互關(guān)聯(lián)的。在本站中,您可以找到關(guān)于交易貨幣對(duì)籃子的文章 [1, 2, 3]。盡管如此,分析時(shí)間序列之間相互關(guān)聯(lián)的問題依然存在,在本文中,我提出了開發(fā)一種圖形化分析時(shí)間序列之間相互關(guān)聯(lián)的工具,它可以可視化所分析的貨幣對(duì)報(bào)價(jià)的時(shí)間序列之間的相互關(guān)聯(lián)。
1. 設(shè)定任務(wù)
在我們開始工作之前,讓我們定義我們的目標(biāo)。我們最后想要得到哪種工具?首先,它應(yīng)當(dāng)是一個(gè)圖形面板,包含著傳入的時(shí)間序列之間相互關(guān)聯(lián)的圖形。這個(gè)工具應(yīng)當(dāng)是足夠多功能的,可以用于處理不同數(shù)量的時(shí)間序列。
為了在面板上分析時(shí)間序列,我們將為每個(gè)時(shí)間序列構(gòu)建一個(gè)分布柱形圖,我們還將準(zhǔn)備成對(duì)顯示的散點(diǎn)圖,用來分析時(shí)間序列以尋找相互關(guān)聯(lián)。在散點(diǎn)圖上會(huì)加上趨勢(shì)線,作為可視化的參考。
圖形的布局將是一個(gè)交叉表的形式,這樣可以提高整個(gè)工具的可讀性。這種方法統(tǒng)一了數(shù)據(jù)的表現(xiàn)形式,并且從視覺方面來看更加簡單。所述工具的布局如下所示。

2. 創(chuàng)建基類
2.1. '基礎(chǔ)'
當(dāng)開發(fā)這樣的工具時(shí),我們應(yīng)當(dāng)記住,用戶可能會(huì)操作不同數(shù)量的交易資產(chǎn)。我相信,最佳的可視化方案是基于區(qū)塊的表示方法,其中使用標(biāo)準(zhǔn)的圖形來作為構(gòu)建一個(gè)統(tǒng)一相互關(guān)聯(lián)表格的“磚石”。
我們將會(huì)從準(zhǔn)備圖表構(gòu)建基礎(chǔ)開始,來開發(fā)我們的工具。MetaTrader 5 基礎(chǔ)發(fā)布部分含有 CGraphic 類,可以用于構(gòu)建科學(xué)圖表,這篇文章 [4] 提供了這個(gè)類的詳細(xì)描述,我們將會(huì)以它為基礎(chǔ)來構(gòu)建我們的圖形圖表。讓我們創(chuàng)建 CPlotBase 基類,并且使它繼承于 CGraphic 標(biāo)準(zhǔn)類,在這個(gè)類中,我們將創(chuàng)建用于生成圖形畫布的方法。將會(huì)有兩個(gè)這樣的方法: 第一個(gè)用于使用給定的邊寬來畫方形區(qū)域,而第二個(gè)用于使用給定的坐標(biāo)來畫長方形區(qū)域。我們還將加上用于在圖形的側(cè)邊顯示文字的方法 (它們將會(huì)幫助我們顯示資產(chǎn)的名稱)。另外,讓我們加上用于在時(shí)間序列圖上改變顯示顏色的方法。
我們應(yīng)該記住,我們所使用的 CGraphic 基類不是派生于 CObject 類的,并且沒有包含在圖形中重新分配、隱藏和顯示對(duì)象的方法,類似的方法在圖形面板上有廣泛使用,所以,我們需要在創(chuàng)建的類中加上這些方法,這樣我們的工具就和構(gòu)建圖形面板的標(biāo)準(zhǔn)類兼容了。
class CPlotBase : public CGraphic
{
protected:
long m_chart_id;
int m_subwin;
public:
CPlotBase();
~CPlotBase();
virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int size);
virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
virtual bool SetTimeseriesColor(uint clr, uint timeserie=0);
virtual void TextUp(string text, uint clr);
virtual void TextDown(string text, uint clr);
virtual void TextLeft(string text, uint clr);
virtual void TextRight(string text, uint clr);
virtual bool Shift(const int dx,const int dy);
virtual bool Show(void);
virtual bool Hide(void);
};
在類的構(gòu)造函數(shù)中,刪除圖形的縱向顯示并且設(shè)置在軸上的最少標(biāo)簽數(shù)量。
CPlotBase::CPlotBase()
{
HistoryNameWidth(0);
HistorySymbolSize(0);
m_x.MaxLabels(3);
m_y.MaxLabels(3);
}
在附件中可以找到所有類方法的完整代碼。
2.2. 散點(diǎn)圖
下一步,開發(fā)用于顯示散點(diǎn)圖的 CScatter 類。這個(gè)類將只包含兩個(gè)方法,用于創(chuàng)建和更新時(shí)間序列的數(shù)據(jù)。
class CScatter : public CPlotBase
{
public:
CScatter();
~CScatter();
int AddTimeseries(const double ×eries_1[],const double ×eries_2[]);
bool UpdateTimeseries(const double ×eries_1[],const double ×eries_2[],uint timeserie=0);
};
向 AddTimeseries 曲線創(chuàng)建方法傳入的是散點(diǎn)圖所依賴的兩個(gè)分析資產(chǎn)的時(shí)間序列,CGraphic 標(biāo)準(zhǔn)類可以根據(jù)兩個(gè)數(shù)據(jù)數(shù)組顯示一個(gè)點(diǎn)形圖。我們將會(huì)使用這個(gè)功能。在方法的開始,根據(jù)兩個(gè)數(shù)據(jù)數(shù)組創(chuàng)建一個(gè)點(diǎn)曲線,如果曲線創(chuàng)建失敗,就返回 '-1' 的結(jié)果退出函數(shù)。如果成功創(chuàng)建了曲線,設(shè)置曲線點(diǎn)的大小并設(shè)置趨勢(shì)線顯示的標(biāo)志。在進(jìn)行所有操作之后,方法會(huì)返回所創(chuàng)建曲線的索引。
int CScatter::AddTimeseries(const double ×eries_1[],const double ×eries_2[])
{
CCurve *curve=CGraphic::CurveAdd(timeseries_1,timeseries_2,CURVE_POINTS);
if(curve==NULL)
return -1;
curve.PointsSize(2);
curve.TrendLineVisible(true);
return (m_arr_curves.Total()-1);
}
為了更新曲線數(shù)據(jù),我們創(chuàng)建了 UpdateTimeseries 方法,傳給它的參數(shù)是兩個(gè)用于創(chuàng)建曲線的數(shù)據(jù)數(shù)組和需要更新數(shù)據(jù)曲線的索引。在函數(shù)的開始,檢查指定的曲線編號(hào)是否有效,如果編號(hào)是錯(cuò)誤指定的,就退出函數(shù),返回 'false'。
然后,比較所得到時(shí)間序列的維度,如果數(shù)組的大小不同或者數(shù)組是空的,退出函數(shù)并返回 'false'。
下一步是指定根據(jù)索引的曲線對(duì)象的指針,如果指針不正確,就退出函數(shù)返回 'false'。
在全部檢查之后,把時(shí)間序列傳給曲線并退出函數(shù)返回 'true'。
bool CScatter::UpdateTimeseries(const double ×eries_1[],const double ×eries_2[], uint timeserie=0)
{
if((int)timeserie>=m_arr_curves.Total())
return false;
if(ArraySize(timeseries_1)!=ArraySize(timeseries_2) || ArraySize(timeseries_1)==0)
return false;
CCurve *curve=m_arr_curves.At(timeserie);
if(CheckPointer(curve)==POINTER_INVALID)
return false;
curve.Update(timeseries_1,timeseries_2);
return true;
}
2.3. 柱形圖
柱形圖是另一個(gè)用于構(gòu)建我們工具的“磚塊”,我們需要 CHistogram 類來創(chuàng)建它。與 CScatter 類似, 這個(gè)類也要有它自己的曲線數(shù)據(jù)生成和更新方法,但是,和前者不同,這個(gè)類使用一個(gè)時(shí)間序列來構(gòu)建曲線。構(gòu)造這些方法的原則和前一個(gè)類的方法類似,
請(qǐng)記住,CGraphic 基類只能以它的標(biāo)準(zhǔn)形式構(gòu)建柱形圖。為了增加構(gòu)建與市場(chǎng)概況類似的垂直柱形圖的功能,我們將必須重寫 HistogramPlot 方法。另外,我們應(yīng)當(dāng)加上 e_orientation 變量,用于保存柱形圖構(gòu)建的類型,并且重寫圖形畫布創(chuàng)建方法,加上可以指定柱形圖類型的功能。
我們的類和 CGpraphic 基類的另一個(gè)區(qū)別是我們?nèi)〉玫某跏紨?shù)據(jù)的類型,在基類中,會(huì)把取得的數(shù)值數(shù)組直接輸出到圖形中。我們的類得到的是時(shí)間序列,在處理柱形圖之間,會(huì)需要處理獲得的數(shù)據(jù),準(zhǔn)備用于構(gòu)建柱形圖的數(shù)據(jù)是在 CalculateHistogramArray 方法中進(jìn)行的, 柱形圖列的數(shù)量是由 SetCells 方法設(shè)置的,并且會(huì)被保存在 i_cells 變量中。
class CHistogram : public CPlotBase
{
private:
ENUM_HISTOGRAM_ORIENTATION e_orientation;
uint i_cells;
public:
CHistogram();
~CHistogram();
bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int size, ENUM_HISTOGRAM_ORIENTATION orientation=HISTOGRAM_HORIZONTAL);
bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, ENUM_HISTOGRAM_ORIENTATION orientation=HISTOGRAM_HORIZONTAL);
int AddTimeserie(const double ×erie[]);
bool UpdateTimeserie(const double ×erie[],uint timeserie=0);
bool SetCells(uint value) { i_cells=value; }
protected:
virtual void HistogramPlot(CCurve *curve);
bool CalculateHistogramArray(const double &data[],double &intervals[],double &frequency[],
double &maxv,double &minv);
};
CalculateHistogramArray 方法是基于 MQL5 參考中的算法,又增加了一些小的功能。在方法的開始,檢查初始數(shù)據(jù)是否足夠來繪制柱形圖,確定最小值和最大值,計(jì)算每個(gè)間隙的范圍寬度,并準(zhǔn)備數(shù)組用于保存間隔和頻率。
隨后,會(huì)在循環(huán)中設(shè)置間隔并把頻率數(shù)組清空為零。
在下一個(gè)循環(huán)中,迭代時(shí)間序列并對(duì)符合對(duì)應(yīng)間隔的數(shù)值進(jìn)行計(jì)數(shù)。
最后,規(guī)范化頻率,把符合條件計(jì)數(shù)轉(zhuǎn)換為在時(shí)間序列中元素總數(shù)的百分比。
bool CHistogram::CalculateHistogramArray(const double &data[],double &intervals[],double &frequency[],
double &maxv,double &minv)
{
int size=ArraySize(data);
if(size<(int)i_cells*10) return (false);
minv=data[ArrayMinimum(data)];
maxv=data[ArrayMaximum(data)];
double range=maxv-minv;
double width=range/i_cells;
if(width==0) return false;
ArrayResize(intervals,i_cells);
ArrayResize(frequency,i_cells);
for(uint i=0; i<i_cells; i )
{
intervals[i]=minv (i 0.5)*width;
frequency[i]=0;
}
for(int i=0; i<size; i )
{
uint ind=int((data[i]-minv)/width);
if(ind>=i_cells) ind=i_cells-1;
frequency[ind] ;
}
for(uint i=0; i<i_cells; i )
frequency[i]*=(100.0/(double)size);
return (true);
}
柱形圖是使用 HistogramPlot 方法畫在圖形中的,這個(gè)函數(shù)是根據(jù) CGraphic 基類的算法構(gòu)建的,為了使用時(shí)間序列以及柱形圖的生成方向進(jìn)行了修改。
在方法的開始,準(zhǔn)備用于繪制柱形圖的數(shù)據(jù),為此,我們從曲線數(shù)據(jù)中取得時(shí)間序列并調(diào)用 CalculateHistogramArray 方法。在這個(gè)函數(shù)成功執(zhí)行之后,我們就得到了柱形圖的寬度,并可以檢查構(gòu)成數(shù)據(jù)的數(shù)組大小。
下一步,根據(jù)柱形圖的顯示類型格式化軸上的數(shù)值。
最后,在圖形區(qū)域中循環(huán)顯示圖表列。
CHistogram::HistogramPlot(CCurve *curve)
{
double data[],intervals[],frequency[];
double max_value, min_value;
curve.GetY(data);
if(!CalculateHistogramArray(data,intervals,frequency,max_value,min_value))
return;
int histogram_width=fmax(curve.HistogramWidth(),2);
if(ArraySize(frequency)==0 || ArraySize(intervals)==0)
return;
switch(e_orientation)
{
case HISTOGRAM_HORIZONTAL:
m_y.AutoScale(false);
m_x.Min(intervals[ArrayMinimum(intervals)]);
m_x.Max(intervals[ArrayMaximum(intervals)]);
m_x.MaxLabels(3);
m_x.ValuesFormat('%.0f');
m_y.Min(0);
m_y.Max(frequency[ArrayMaximum(frequency)]);
m_y.ValuesFormat('%.2f');
break;
case HISTOGRAM_VERTICAL:
m_x.AutoScale(false);
m_y.Min(intervals[ArrayMinimum(intervals)]);
m_y.Max(intervals[ArrayMaximum(intervals)]);
m_y.MaxLabels(3);
m_y.ValuesFormat('%.0f');
m_x.Min(0);
m_x.Max(frequency[ArrayMaximum(frequency)]);
m_x.ValuesFormat('%.2f');
break;
}
CalculateXAxis();
CalculateYAxis();
int originalY=m_height-m_down;
int originalX=m_width-m_right;
int yc0=ScaleY(0.0);
int xc0=ScaleX(0.0);
uint clr=curve.Color();
for(uint i=0; i<i_cells; i )
{
if(!MathIsValidNumber(frequency[i]) || !MathIsValidNumber(intervals[i]))
continue;
if(e_orientation==HISTOGRAM_HORIZONTAL)
{
int xc=ScaleX(intervals[i]);
int yc=ScaleY(frequency[i]);
int xc1 = xc - histogram_width/2;
int xc2 = xc histogram_width/2;
int yc1 = yc;
int yc2 = (originalY>yc0 && yc0>0) ? yc0 : originalY;
if(yc1>yc2)
yc2 ;
else
yc2--;
m_canvas.FillRectangle(xc1,yc1,xc2,yc2,clr);
}
else
{
int yc=ScaleY(intervals[i]);
int xc=ScaleX(frequency[i]);
int yc1 = yc - histogram_width/2;
int yc2 = yc histogram_width/2;
int xc1 = xc;
int xc2 = (originalX>xc0 && xc0>0) ? xc0 : originalX;
if(xc1>xc2)
xc2 ;
else
xc2--;
m_canvas.FillRectangle(xc1,yc1,xc2,yc2,clr);
}
}
}
所有類和方法的完整代碼在附件中。
2.4. 用于操作時(shí)間序列的類
為了構(gòu)建工具,我們還需要另一個(gè)“磚塊”來下載所需的歷史數(shù)據(jù)以及準(zhǔn)備時(shí)間序列用于畫圖,這項(xiàng)工作將是在 CTimeserie 類中進(jìn)行的。在初始化類的時(shí)候,會(huì)傳給它資產(chǎn)名稱、時(shí)段和使用的價(jià)格類型。另外,還需要?jiǎng)?chuàng)建改變資產(chǎn)名稱、時(shí)段、使用的價(jià)格類型以及歷史深度的方法。
class CTimeserie : public CObject
{
protected:
string s_symbol;
ENUM_TIMEFRAMES e_timeframe;
ENUM_APPLIED_PRICE e_price;
double d_timeserie[];
int i_bars;
datetime dt_last_load;
public:
CTimeserie(void);
~CTimeserie(void);
bool Create(const string symbol=NULL, const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
void SetBars(const int value) { i_bars=value; }
void Symbol(string value) { s_symbol=value; dt_last_load=0; }
void Timeframe(ENUM_TIMEFRAMES value) { e_timeframe=value; dt_last_load=0; }
void Price(ENUM_APPLIED_PRICE value) { e_price=value; dt_last_load=0; }
string Symbol(void) { return s_symbol; }
ENUM_TIMEFRAMES Timeframe(void) { return e_timeframe; }
ENUM_APPLIED_PRICE Price(void) { return e_price; }
virtual bool UpdateTimeserie(void);
bool GetTimeserie(double ×erie[]) { return ArrayCopy(timeserie,d_timeserie)>0; }
};
主要的數(shù)據(jù)準(zhǔn)備工作是在 UpdateTimeserie 方法中完成的。在這個(gè)方法的開始,檢查當(dāng)前的柱上是否已經(jīng)載入了所需的信息,如果信息已經(jīng)載入完畢,就退出函數(shù)返回 'true'。如果需要準(zhǔn)備數(shù)據(jù),根據(jù)指定的價(jià)格上傳所需的歷史數(shù)據(jù),如果無法上傳信息,就退出函數(shù)返回 'false'。請(qǐng)注意,我們對(duì)價(jià)格本身并沒有興趣,關(guān)注的是它的變化。所以上傳的歷史數(shù)據(jù)要比指定的數(shù)量多一個(gè)柱。在下一步,我們?cè)谘h(huán)中在每個(gè)柱上重新計(jì)算價(jià)格的變化并把它保存到數(shù)組中。以后,用戶就可以通過使用 GetTimeserie 方法來取得這些信息了。
bool CTimeserie::UpdateTimeserie(void)
{
datetime cur_date=(datetime)SeriesInfoInteger(s_symbol,e_timeframe,SERIES_LASTBAR_DATE);
if(dt_last_load>=cur_date && ArraySize(d_timeserie)>=i_bars)
return true;
MqlRates rates[];
int bars=0,i;
double data[];
switch(e_price)
{
case PRICE_CLOSE:
bars=CopyClose(s_symbol,e_timeframe,1,i_bars 1,data);
break;
case PRICE_OPEN:
bars=CopyOpen(s_symbol,e_timeframe,1,i_bars 1,data);
case PRICE_HIGH:
bars=CopyHigh(s_symbol,e_timeframe,1,i_bars 1,data);
case PRICE_LOW:
bars=CopyLow(s_symbol,e_timeframe,1,i_bars 1,data);
case PRICE_MEDIAN:
bars=CopyRates(s_symbol,e_timeframe,1,i_bars 1,rates);
bars=ArrayResize(data,bars);
for(i=0;i<bars;i )
data[i]=(rates[i].high rates[i].low)/2;
break;
case PRICE_TYPICAL:
bars=CopyRates(s_symbol,e_timeframe,1,i_bars 1,rates);
bars=ArrayResize(data,bars);
for(i=0;i<bars;i )
data[i]=(rates[i].high rates[i].low rates[i].close)/3;
break;
case PRICE_WEIGHTED:
bars=CopyRates(s_symbol,e_timeframe,1,i_bars 1,rates);
bars=ArrayResize(data,bars);
for(i=0;i<bars;i )
data[i]=(rates[i].high rates[i].low 2*rates[i].close)/4;
break;
}
if(bars<=0)
return false;
dt_last_load=cur_date;
if(ArraySize(d_timeserie)!=(bars-1) && ArrayResize(d_timeserie,bars-1)<=0)
return false;
double point=SymbolInfoDouble(s_symbol,SYMBOL_POINT);
for(i=0;i<bars-1;i )
d_timeserie[i]=(data[i 1]-data[i])/point;
return true;
}
所有類和它們方法的完整代碼都在附件中。
3. 組裝 PairPlot
在創(chuàng)建了 '磚塊' 之后, 我們就可以開始開發(fā)我們的工具了。讓我們創(chuàng)建 CPairPlot 類,它派生于 CWndClient 類。這種方法將可以使在使用標(biāo)準(zhǔn)的 CAppDialog 類所創(chuàng)建的圖形面板中使用我們的工具更加容易 (這種應(yīng)用程序的詳細(xì)情況可以在下列文章中找到 [5,6]).
在我們類的 'private' 部分,聲明 CPlotBase 類對(duì)象的指針數(shù)組用于保存圖形的指針,CArrayObj 類對(duì)象用于保存時(shí)間序列對(duì)象的指針,以及用于保存所使用的時(shí)段、價(jià)格、柱形圖方向、歷史深度和在圖形中顯示資產(chǎn)名稱顏色的變量。
class CPairPlot : public CWndClient
{
private:
CPlotBase *m_arr_graphics[];
CArrayObj m_arr_symbols;
ENUM_TIMEFRAMES e_timeframe;
ENUM_APPLIED_PRICE e_price;
int i_total_symbols;
uint i_bars;
ENUM_HISTOGRAM_ORIENTATION e_orientation;
uint i_text_color;
public:
CPairPlot();
~CPairPlot();
bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
bool Refresh(void);
bool HistogramOrientation(ENUM_HISTOGRAM_ORIENTATION value);
ENUM_HISTOGRAM_ORIENTATION HistogramOrientation(void) { return e_orientation; }
bool SetTextColor(color value);
virtual bool Shift(const int dx,const int dy);
virtual bool Show(void);
virtual bool Hide(void);
};
類方法聲明在 'public' 部分,類的初始化是在 Create 方法中進(jìn)行的,需要傳入的參數(shù)有圖形 ID, 對(duì)象名稱, 應(yīng)用的子窗口索引, 創(chuàng)建的坐標(biāo), 使用的交易品種數(shù)組, 使用的時(shí)段, 價(jià)格, 歷史深度和柱形圖的列數(shù)。
在方法的開始,檢查傳入的資產(chǎn)名稱數(shù)組和指定的歷史深度,如果它們不能達(dá)到我們的最低要求,就退出函數(shù)返回 'false'。然后保存輸入?yún)?shù)的值用于畫圖。
bool CPairPlot::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2, const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10, const ENUM_APPLIED_PRICE price=PRICE_CLOSE)
{
i_total_symbols=0;
int total=ArraySize(symbols);
if(total<=1 || bars<100)
return false;
e_timeframe=timeframe;
i_bars=bars;
e_price=price;
下一步,在循環(huán)中為每個(gè)資產(chǎn)創(chuàng)建 CTimeserie 類的實(shí)例。如果不能為每個(gè)指定的資產(chǎn)創(chuàng)建時(shí)間序列,就退出函數(shù)并返回 'false'。
for(int i=0;i<total;i )
{
CTimeserie *temp=new CTimeserie;
if(temp==NULL)
return false;
temp.SetBars(i_bars);
if(!temp.Create(symbols[i],e_timeframe,e_price))
return false;
if(!m_arr_symbols.Add(temp))
return false;
}
i_total_symbols=m_arr_symbols.Total();
if(i_total_symbols<=1)
return false;
在成功完成了準(zhǔn)備工作之后,就繼續(xù)直接創(chuàng)建圖形對(duì)象。首先,調(diào)用父類的 Create 方法,然后,使 m_arr_graphics 數(shù)組(用于保存圖形指針)的大小設(shè)為與分析的資產(chǎn)數(shù)量相等。根據(jù)全部資產(chǎn)和所分析資產(chǎn)的數(shù)量大小來計(jì)算每個(gè)圖的寬度和高度,
隨后,使用兩個(gè)嵌套的循環(huán)來在所有要分析的資產(chǎn)上迭代,使用圖形對(duì)象創(chuàng)建表格。在有相同名稱的資產(chǎn)交叉時(shí)創(chuàng)建柱形圖,在其它情況下創(chuàng)建散點(diǎn)圖。如果所有的對(duì)象都成功創(chuàng)建,就退出函數(shù)返回 'true'。
if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y2))
return false;
if(ArraySize(m_arr_graphics)!=(i_total_symbols*i_total_symbols))
if(ArrayResize(m_arr_graphics,i_total_symbols*i_total_symbols)<=0)
return false;
int width=Width()/i_total_symbols;
int height=Height()/i_total_symbols;
for(int i=0;i<i_total_symbols;i )
{
CTimeserie *timeserie1=m_arr_symbols.At(i);
if(timeserie1==NULL)
continue;
for(int j=0;j<i_total_symbols;j )
{
string obj_name=m_name '_' (string)i '_' (string)j;
int obj_x1=m_rect.left j*width;
int obj_x2=obj_x1 width;
int obj_y1=m_rect.top i*height;
int obj_y2=obj_y1 height;
if(i==j)
{
CHistogram *temp=new CHistogram();
if(CheckPointer(temp)==POINTER_INVALID)
return false;
if(!temp.Create(m_chart_id,obj_name,m_subwin,obj_x1,obj_y1,obj_x2,obj_y2,e_orientation))
return false;
m_arr_graphics[i*i_total_symbols j]=temp;
temp.SetCells(cells);
}
else
{
CScatter *temp=new CScatter();
if(CheckPointer(temp)==POINTER_INVALID)
return false;
if(!temp.Create(m_chart_id,obj_name,m_subwin,obj_x1,obj_y1,obj_x2,obj_y2))
return false;
CTimeserie *timeserie2=m_arr_symbols.At(j);
if(timeserie2==NULL)
continue;
m_arr_graphics[i*i_total_symbols j]=temp;
}
}
}
return true;
}
Refresh 方法是用于更新時(shí)間序列數(shù)據(jù)和在圖形中顯示數(shù)據(jù)的。在方法的開始,會(huì)在循環(huán)中更新所有時(shí)間序列的數(shù)據(jù)。請(qǐng)注意,當(dāng)您構(gòu)建一個(gè)分布圖時(shí),時(shí)間序列是成對(duì)使用的,所以,數(shù)據(jù)應(yīng)當(dāng)可以比較。為了確保數(shù)據(jù)的兼容性,圖形對(duì)象的數(shù)據(jù)不會(huì)更新,而只要在更新至少一個(gè)時(shí)間序列出現(xiàn)錯(cuò)誤的時(shí)候,方法就會(huì)返回 'false'。
在更新了時(shí)間序列數(shù)據(jù)之后,會(huì)再進(jìn)行循環(huán)把更新過的時(shí)間序列傳給圖形對(duì)象。注意,要使用 'false' 參數(shù)來調(diào)用圖形對(duì)象的 Update 方法。這樣的調(diào)用可以確保圖形對(duì)象更新的時(shí)候不必更新應(yīng)用程序正運(yùn)行在上方的圖形,這種方法會(huì)在更新每個(gè)圖形對(duì)象時(shí)排除掉之前更新過的,這樣可以減少終端的負(fù)載并減少函數(shù)的執(zhí)行時(shí)間。圖形在更新了所有的圖形元素后、在退出函數(shù)之前再更新一次。
bool CPairPlot::Refresh(void)
{
bool updated=true;
for(int i=0;i<i_total_symbols;i )
{
CTimeserie *timeserie=m_arr_symbols.At(i);
if(timeserie==NULL)
continue;
updated=(updated && timeserie.UpdateTimeserie());
}
if(!updated)
return false;
for(int i=0;i<i_total_symbols;i )
{
CTimeserie *timeserie1=m_arr_symbols.At(i);
if(CheckPointer(timeserie1)==POINTER_INVALID)
continue;
double ts1[];
if(!timeserie1.GetTimeserie(ts1))
continue;
for(int j=0;j<i_total_symbols;j )
{
if(i==j)
{
CHistogram *temp=m_arr_graphics[i*i_total_symbols j];
if(CheckPointer(temp)==POINTER_INVALID)
return false;
if(temp.CurvesTotal()==0)
{
if(temp.AddTimeserie(ts1)<0)
continue;
}
else
{
if(!temp.UpdateTimeserie(ts1))
continue;
}
if(!temp.CurvePlotAll())
continue;
if(i==0)
temp.TextUp(timeserie1.Symbol(),i_text_color);
if(i==(i_total_symbols-1))
temp.TextDown(timeserie1.Symbol(),i_text_color);
if(j==0)
temp.TextLeft(timeserie1.Symbol(),i_text_color);
if(j==(i_total_symbols-1))
temp.TextRight(timeserie1.Symbol(),i_text_color);
temp.Update(false);
}
else
{
CScatter *temp=m_arr_graphics[i*i_total_symbols j];
if(CheckPointer(temp)==POINTER_INVALID)
return false;
CTimeserie *timeserie2=m_arr_symbols.At(j);
if(CheckPointer(timeserie2)==POINTER_INVALID)
continue;
double ts2[];
if(!timeserie2.GetTimeserie(ts2))
continue;
if(temp.CurvesTotal()==0)
{
if(temp.AddTimeseries(ts1,ts2)<0)
continue;
}
else
if(!temp.UpdateTimeseries(ts1,ts2))
continue;
if(!temp.CurvePlotAll())
continue;
if(i==0)
temp.TextUp(timeserie2.Symbol(),i_text_color);
if(i==(i_total_symbols-1))
temp.TextDown(timeserie2.Symbol(),i_text_color);
if(j==0)
temp.TextLeft(timeserie1.Symbol(),i_text_color);
if(j==(i_total_symbols-1))
temp.TextRight(timeserie1.Symbol(),i_text_color);
temp.Update(false);
}
}
}
ChartRedraw(m_chart_id);
return true;
}
之前,我已經(jīng)提到過,圖形元素是基于 CGraphic 類而不是派生于 CObject 類,因?yàn)檫@個(gè)原因,我們?cè)?CPlotBase 基類中加入了 Shift, Hide 和 Show 方法,因?yàn)橥瑯拥脑?,我們也必須?CPairPlot 類中重寫對(duì)應(yīng)的方法。所有類和它們方法的完整代碼都在附件中。
4. 使用 CPairPlot 類的實(shí)例
現(xiàn)在,在做了這么多工作之后,是時(shí)候看看結(jié)果了。為了運(yùn)行演示工具,讓我們制作一個(gè)指標(biāo)來顯示相互關(guān)聯(lián)圖形,例如對(duì)于最新的1000個(gè)燭形,在每個(gè)新柱時(shí)顯示。
上面已經(jīng)提到,這個(gè)工具是為了圖形面板創(chuàng)建的,所以,讓我們首先創(chuàng)建派生于 CAppDialog 類的 CPairPlotDemo 類。操作 CAppDialog 類的詳細(xì)情況可以在文章 [5, 6] 中找到。在此,我將只會(huì)指出使用這個(gè)工具所特有的地方。
在 'private' 部分聲明 CPairPlot 類的實(shí)例,在 'public' 部分,聲明 Create 方法,其中含有所有工具初始化和運(yùn)行時(shí)所需的輸入?yún)?shù)。在此,我們還要聲明 Refresh 和 HistogramOrientation 方法,它們會(huì)調(diào)用我們工具中的對(duì)應(yīng)方法。
class CPairPlotDemo : public CAppDialog
{
private:
CPairPlot m_PairPlot;
public:
CPairPlotDemo();
~CPairPlotDemo();
bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2,const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10);
bool Refresh(void);
bool HistogramOrientation(ENUM_HISTOGRAM_ORIENTATION value) { return m_PairPlot.HistogramOrientation(value); }
ENUM_HISTOGRAM_ORIENTATION HistogramOrientation(void) { return m_PairPlot.HistogramOrientation(); }
};
在 Create 方法中,首先調(diào)用父類中的相應(yīng)方法,然后調(diào)用元件實(shí)例的相同方法并把指針加到 CPairPlot 的類實(shí)例中的控件元件集合。
bool CPairPlotDemo::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2,const string &symbols[],const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const int bars=1000, const uint cells=10)
{
if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
return false;
if(!m_PairPlot.Create(m_chart_id,m_name 'PairPlot',m_subwin,0,0,ClientAreaWidth(),ClientAreaHeight(),symbols,timeframe,bars,cells))
return false;
if(!Add(m_PairPlot))
return false;
return true;
}
現(xiàn)在,讓我們創(chuàng)建指標(biāo)。我們指標(biāo)中使用的參數(shù)有,使用逗號(hào)分隔的資產(chǎn)名稱字符串, 分析歷史深度的柱數(shù),柱形圖的列數(shù)和指向。
input string i_Symbols = 'EURUSD, GBPUSD, EURGBP';
input uint i_Bars = 1000;
input uint i_Cells = 50;
input ENUM_HISTOGRAM_ORIENTATION i_HistogramOrientation = HISTOGRAM_HORIZONTAL;
在全局變量中聲明 CPairPlotDemo 類的實(shí)例。
CPairPlotDemo *PairPlot;
在 OnInit 函數(shù)中,根據(jù)指標(biāo)外部參數(shù)中的字符串創(chuàng)建使用資產(chǎn)的數(shù)組,然后創(chuàng)建一個(gè) CPairPlotDemo 類的實(shí)例,傳給它指定的柱形圖指向然后再調(diào)用它的 Create 方法。在成功初始化之后,使用 Run 方法載入類并運(yùn)行,并使用 Refresh 方法更新數(shù)據(jù)。
int OnInit()
{
string symbols[];
int total=StringSplit(i_Symbols,',',symbols);
if(total<=0)
return INIT_FAILED;
for(int i=0;i<total;i )
{
StringTrimLeft(symbols[i]);
StringTrimRight(symbols[i]);
}
PairPlot=new CPairPlotDemo;
if(CheckPointer(PairPlot)==POINTER_INVALID)
return INIT_FAILED;
if(!PairPlot.HistogramOrientation(i_HistogramOrientation))
return INIT_FAILED;
if(!PairPlot.Create(0,'Pair Plot',0,20,20,620,520,symbols,PERIOD_CURRENT,i_Bars,i_Cells))
return INIT_FAILED;
if(!PairPlot.Run())
return INIT_FAILED;
PairPlot.Refresh();
return INIT_SUCCEEDED;
}
在 OnCalculate 函數(shù)中,在每個(gè)新柱處調(diào)用 Refresh 方法。在 OnChartEvent 和 OnDenint 函數(shù)中將會(huì)調(diào)用相應(yīng)的類方法。
在附件中有所有函數(shù)和類的完整代碼。
下面您將可以看到指標(biāo)是如何工作的。

結(jié)論
在本文中,我們提供了一個(gè)很有趣的工具,它允許交易者快速而簡單地看到幾乎任意數(shù)量的交易資產(chǎn)之間的相互關(guān)聯(lián)。這個(gè)工具的主要目的是分析交易資產(chǎn)而開發(fā)各種套利策略。當(dāng)然,這個(gè)工具本身還不足以開發(fā)一個(gè)完整功能的交易系統(tǒng),但是它可以在開發(fā)交易系統(tǒng)的第一階段大量使用 - 尋找相關(guān)的資產(chǎn)和它們的依賴關(guān)系。
參考
- 在外匯交易市場(chǎng)上操作貨幣籃子
- 測(cè)試當(dāng)交易貨幣對(duì)籃子時(shí)出現(xiàn)的形態(tài)Part I
- 測(cè)試當(dāng)交易貨幣對(duì)籃子時(shí)出現(xiàn)的形態(tài)第二部分
- 顯示這個(gè)!MQL5 圖形庫與 R 語言的 'plot' 類似
- 如何創(chuàng)建任意復(fù)雜度的圖形面板
- 增強(qiáng)面板:加上了透明度,修改背景色以及繼承于CAppDialog/CWndClient
本文中使用的程序
#
|
名稱
|
類型
|
描述 |
1 |
PlotBase.mqh |
類庫 |
Base class for building graphs |
2 |
Scatter.mqh |
類庫 |
用于創(chuàng)建散點(diǎn)圖的類 |
3 |
Histogram.mqh |
類庫 |
用于創(chuàng)建柱形圖的類
|
4 |
PairPlot.mqh |
類庫 |
PairPlot 工具類 |
5 |
PairPlotDemo.mqh |
類庫 |
用于演示工具連接的類 |
6 |
PairPlot.mq5 |
指標(biāo) |
用于演示工具工作的指標(biāo) |
|