量化交易軟件:直方圖形式的統(tǒng)計分布, 無需指標緩沖區(qū)和數組

概論
直方圖允許研究人員根據其浸透到某個 (預判) 間隔的頻率來直觀評估統(tǒng)計數據組的分布。
直方圖及其在統(tǒng)計數據分析中的使用是一個研究充分的主題, 已發(fā)表了多篇文章 [1, 2, 3, 4, 5, 6, 7], 且在代碼庫中有大量例程 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]。不過, 采用的算法均基于使用指標緩沖區(qū)或數組。在本文中, 赫茲量化考慮無需復雜的計算、排序、抽樣等即能建立不同市場特征統(tǒng)計分布的可能性。為了實現這一點, 我們將使用 "圖形" 內存 — 圖形對象屬性的部分存儲。由于在繪制自定義直方圖時我們需要 圖形對象, 我們可以使用它們的 "隱藏" 能力和豐富的函數來滿足需求。
文章的目標是為標準統(tǒng)計問題提供簡單的解決方案。本文主要關注統(tǒng)計分布的可視化及其基本特征。赫茲量化不打算討論直方圖的釋義及其實際優(yōu)勢。

編輯切換為居中
直方圖繪制基礎
直方圖是頻率的柱線圖。其中一根數軸代表變量值, 另一根數軸代表這些值出現的頻率。每根柱線的高度代表那些間隔等于列寬的數值的頻率 (數量)。這些示意圖通常水平顯示, 即, 變量值位于水平軸上, 而頻率 — 坐落于垂直軸。使用直方圖來表示研究的數據, 使得統(tǒng)計數據更直觀, 并且更容易理解和分析。
在本文中, 赫茲量化將關注變化序列的垂直直方圖: 分析參數的價格值將按照升序位于垂直軸上, 而頻率位于水平軸上 (圖例. 1)。傳入的價格數據在當前柱線上分布并分組, 并且可以從左側、右側或兩側同時相對于其數軸顯示。

編輯切換為居中
圖例. 1. 買賣價格分布的垂直直方圖
讓赫茲量化來研究一個具體的任務:
繪制買賣價格分布直方圖;
將采購價數據定位在當前柱線的右側, 而供給價 — 位于左側;
當新的即時報價抵達, 計算每筆入價值的頻率, 即, 直方圖間隔等于當前品種的最小點數大小。
現在, 我們令條件更復雜: 無指標緩沖區(qū), 數組或結構。
如何解決這個問題?
首先, 我們要確定在哪里保存每個直方圖列的累積頻率。即便在圖例. 1 上, 赫茲量化可以看到, 可能存在不確定數量的直方圖條。首先進入腦海的是使用動態(tài)數組, 因為在價格圖表的選定時間間隔上, 可能的價格范圍 (柱線數量) 不可預知。但數組不允許這種問題條件。
其次, 赫茲量化應該解決搜索和排序任務: 何處以及如何搜索重新計算和重繪直方圖的數據。
結果是 MQL5 語言開發(fā)人員已經創(chuàng)建了必要的 (和相當強大的) 功能。它基于使用圖形對象功能組的 "隱藏" (非顯性) 特征。每個對象都有自己的 屬性 — 此變量與對象一起創(chuàng)建, 并用于存儲各類多個參數。一些屬性在使用時可以完全不同于它們的設計用途, 同時保持功能完整。赫茲量化將這些屬性稱之為 "圖形" 內存。換言之, 如果您需要保存一個變量并接收其數值, 創(chuàng)建一個圖形對象并將變量值賦給一個特定的屬性。
所以, 圖形對象屬性 實際上可作為更有效的終端全局變量。全局變量自其最后訪問以來可在客戶終端中存在四個星期, 且隨后被自動刪除。圖形內存在圖形對象被移除之前會一直存在, 從而為我們提供了大量的機會。
赫茲量化可以在圖形內存中使用哪些圖形對象屬性
當創(chuàng)建圖形對象時, 我們應為它分配一個獨有的名稱。該名稱是可以由子串組成的文本字符串,而子字符串可以包含格式化的基本數據類型:整數,布爾值,浮點數,顏色,日期和時間。因此, OBJPROP_NAME 可保存主要用來讀取數據的變量。
另外其它可用的屬性是 OBJPROP_TEXT 對象描述。這是一個文本字符串, 與前一個屬性相比具有更大可能性的。在屬性字段中可以讀取和寫入變量。
任何圖形對象均有其自己的坐標: 價格 OBJPROP_PRICE 和時間 OBJPROP_TIME。它們也可以用在圖形內存中。
讓我們回到我們的目標。頻率將存儲在 OBJPROP_TEXT 屬性, 而供給價和采購價 — 在 OBJPROP_NAME 中。創(chuàng)建對象和收集頻率的函數代碼如下:
void DrawHistogram(bool draw, ? ? // 向左側或右側繪制直方圖 ? ? ? ? ? ? ? ? ? string h_name, // 對象名稱的獨有前綴 ? ? ? ? ? ? ? ? ? double price, ?// 價格 (分析參數) ? ? ? ? ? ? ? ? ? datetime time, // 將直方圖綁定到當前柱線 ? ? ? ? ? ? ? ? ? int span, ? ? ?// 已分析參數的數位容量 ? ? ? ? ? ? ? ? ? int swin=0) ? ?// 直方圖窗口 ?{ ? double y=NormalizeDouble(price,span); ? string pfx=DoubleToString(y,span); // 如果 draw=true, 向右側繪制直方圖 ? if(draw) ? ? { ? ? ?string name="+ "+h_name+pfx; ? ? ? ? ? ? ? ? ? // 對象名稱: 前綴+價格 ? ? ?ObjectCreate(0,name,OBJ_TREND,swin,time,y); ? ?// 創(chuàng)建對象 ? ? ?ObjectSetInteger(0,name,OBJPROP_COLOR,color_R_active); // 設置對象顏色 ? ? ?ObjSet; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 宏代碼縮寫 ? ? ?if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0) ? ? ? ?{// 如果結果價格首次進入樣本 ? ? ? ? ObjectSetString(0,name,OBJPROP_TEXT,"*1"); ? ? ? ? ?// 價格頻率為 1 ? ? ? ? ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize); // 定義時間坐標 ? ? ? ?} ? ? ?else ? ? ? ?{// 如果結果價格并非首次進入樣本 ? ? ? ? string str=ObjectGetString(0,name,OBJPROP_TEXT); ? ?// 獲取屬性值 ? ? ? ? string strint=StringSubstr(str,1); ? ? ? ? ? ? ? ? ?// 獲得子字符串 ? ? ? ? long n=StringToInteger(strint); ? ? ? ? ? ? ? ? ? ? // 獲取頻率用于進一步計算 ? ? ? ? n++; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 數值遞增 1 ? ? ? ? ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); // 新數值寫入到屬性 ? ? ? ? ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize*n);//定義時間坐標 ? ? ? ?} ? ? } // 如果 draw=false, 向左側寫入直方圖 ? if(!draw) ? ? { ? ? ?string name="- "+h_name+pfx; ? ? ?ObjectCreate(0,name,OBJ_TREND,swin,time,y); ? ? ?ObjectSetInteger(0,name,OBJPROP_COLOR,color_L_active); ? ? ?ObjSet; ? ? ?if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0) ? ? ? ?{ ? ? ? ? ObjectSetString(0,name,OBJPROP_TEXT,"*1"); ? ? ? ? ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize); ? ? ? ?} ? ? ?else ? ? ? ?{ ? ? ? ? string str=ObjectGetString(0,name,OBJPROP_TEXT); ? ? ? ? string strint=StringSubstr(str,1); ? ? ? ? long n=StringToInteger(strint); ? ? ? ? n++; ? ? ? ? ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); ? ? ? ? ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize*n); ? ? ? ?} ? ? } ? ChartRedraw(); ?}
函數由兩個類似的部分組成, 分別作用于供給價和采購價。因此, 注釋僅存在于第一個模塊中。
您也許會問, 如果在 OBJPROP_PRICE 屬性可用, 則對象名中的價格翻倍說明什么?
赫茲量化來多說一點。當新價格抵達時, 我們應為其定義適當的列, 并相應地增加頻率值。 如果我們使用來自其本地屬性的價格坐標, 我們務必通過所有圖形對象傳遞, 請求屬性值并將其與接收的價格進行比較。只有在這之后, 我們才能將新值寫入適當的列。然而,比較實際的 double-類型數字是相當一個扯后腿的任務, 大大降低了算法的效率。。更優(yōu)雅的解決方案是在新的價格值到達后, 將新頻率直接寫入必要的目的地。我們如何實現呢?當我們創(chuàng)建一個名稱類似于已存在對象的新圖形對象時, 新對象不會被創(chuàng)建, 且屬性字段不會設置為零。換言之, 我們創(chuàng)建對象時忽略了它們將被加倍的事實。不需要額外的檢查, 因為不需要創(chuàng)建副本和克隆。終端和圖形對象功能將負責于此。赫茲量化只需要定義價格是否是首次進入樣本。為此,請在 DrawHistogram() 函數的 OBJPROP_TEXT 對象屬性中使用星號 (*) 前綴。沒有星號表示首次進入樣本的價格。實際上, 當我們創(chuàng)建一個新對象時, 該字段為空。具有指定前綴的頻率值將在后續(xù)調用期間存儲在那里。
接下來, 讓我們向右側平移直方圖。當出現新的柱線時, 圖表向左移動, 但是赫茲量化需要在當前柱線上顯示直方圖。換言之, 它應該在相反的方向上移動。以下是我們如何實現:
//--- 將直方圖平移到新的柱線 ? if(time[0]>prevTimeBar) // 定義新柱線抵達 ? ? { ? ? ?prevTimeBar=time[0]; ? ? ?// 通過所有圖形對象傳遞 ? ? ?for(int obj=ObjectsTotal(0,-1,-1)-1;obj>=0;obj--) ? ? ? ?{ ? ? ? ? string obj_name=ObjectName(0,obj,-1,-1); ? ? ? ? ? ? ? // 獲取已發(fā)現對象的名稱 ? ? ? ? if(obj_name[0]==R) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 搜索直方圖元素前綴 ? ? ? ? ? { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 如果已發(fā)現直方圖元素 ? ? ? ? ? ?ObjectSetInteger(0,obj_name,OBJPROP_TIME, ? ? ? ? ? // 設置新坐標值 ? ? ? ? ? ? ? ? ? ? ? ? ? ? 0,time[0]); ? ? ? ? ? ? ? ? ? ? ? ?// 對于錨點 "0" ? ? ? ? ? ?string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);// 從對象屬性中讀取變量 ? ? ? ? ? ?string strint=StringSubstr(str,1); ? ? ? ? ? ? ? ? ?// 從接收的變量中分離出一個子字符串 ? ? ? ? ? ?long n=StringToInteger(strint); ? ? ? ? ? ? ? ? ? ? // 將字符串轉換為長整型變量 ? ? ? ? ? ?ObjectSetInteger(0,obj_name,OBJPROP_TIME, ? ? ? ? ? // 計算新坐標值 ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1,time[0]+hsize*n); ? ? ? ? ? ? ? ?// 對于錨點 "1" ? ? ? ? ? ?ObjectSetInteger(0,obj_name,OBJPROP_COLOR, ? ? ? ? ? ? ? ? ? ? ? ? ? ? color_R_passive); ? ? ? ? ? ? ? ? ?// 改變已移動直方圖元素的顏色 ? ? ? ? ? } ? ? ? ? if(obj_name[0]==L) ? ? ? ? ? { ? ? ? ? ? ?ObjectSetInteger(0,obj_name,OBJPROP_TIME,0,time[0]); ? ? ? ? ? ?string str=ObjectGetString(0,obj_name,OBJPROP_TEXT); ? ? ? ? ? ?string strint=StringSubstr(str,1); ? ? ? ? ? ?long n=StringToInteger(strint); ? ? ? ? ? ?ObjectSetInteger(0,obj_name,OBJPROP_TIME,1,time[0]-hsize*n); ? ? ? ? ? ?ObjectSetInteger(0,obj_name,OBJPROP_COLOR,color_L_passive); ? ? ? ? ? } ? ? ? ?} ? ? ?ChartRedraw(); ? ? }