高頻數據處理技巧:如何將高頻信號轉化成離散的買賣信號

高頻交易中,我們通常首先基於tick級的報價信息和交易信息來生成信號量,然後將這些信號量轉化成離散的買賣信號,譬如說 1 (買入), 0 (不變), -1(賣出),接着根據資金和已有頭寸以及其他優化規則來生成訂單發送到交易系統。本文要討論第二個步驟,即如何將信號量轉化成離散的買賣信號,也就是把一個浮點數類型的數組signal轉化成一個取值爲1,0或-1的整型數組direction。

如果轉化規則簡單,譬如超過某一個閾值t1爲+1 (買入信號),低於某一個閾值t2爲-1(賣出信號),其他情況爲0,那麼實現起來也很簡單。譬如在DolphinDB中用下面這個表達式就可以實現。

iif(signal > t1, 1, iif(signal <t2, -1, 0))

實踐中,爲了讓系統更加的健壯,不要頻繁的切換買賣方向,通常不會這麼處理。一個常用的做法是這樣:當信號量超過某一個閾值t1時,開始轉化爲買入信號,後續的信號量在衰減到低於t10之前,一直保持買入信號(+1);同理當信號量低於某一個閾值t2時,開始轉化爲賣出信號,後續的信號量在增強到大於t20之前,一直保持賣出信號(-1);其他情況爲0。這兒t1, t10, t2, t20滿足下面的規則:

t1 > t10 > t20 > t2

當系統按照上面的規則運行時,決定買賣方向的除了當前的信號量值,還有前一個買賣信號的狀態,這是典型的路徑依賴問題。通常我們認爲路徑依賴問題不適合向量化的方法來處理,或者說需要非常高的技巧。而我們用來回測高頻數據的語言通常都是腳本語言(譬如DolphinDB和kdb+),腳本語言在處理量化問題時效率很高,但是如果需要逐行處理路徑依賴問題,解析成本會很高,效率低下。今天我們會介紹一些技巧,如何化解這個矛盾?

我們先找出買入信號。在一個向量中找到大於t1的點很容易(買入信號的臨界點),找到不可能是買入信號的點也很簡單(小於t10)。這樣我們把一個向量上的點分成了三種狀態,買入信號臨界點(+1),不可能是買入信號的點(0),其他狀態未知的點(NULL)。根據前面的規則,如果狀態未知的點,前面出現了買入臨界點,那麼該點也應該置爲買入信號點;如果前面出現了非買入信號點(0),那麼該點也應該置爲非買入信號點。因此我們可以使用front fill來實現。我們用同樣的方法可以找出賣出信號(賣出信號爲+1,其他信號爲0)。兩者相減可以得到最終的信號。可能還存在一些爲null的信號,把這部分信號替換爲0。DolphinDB的全部代碼如下:

buy = iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)).ffill()
sell = iif(signal <t2, 1h, iif(signal > t20, 0h, 00h)).ffill()
direction = (buy - sell).nullFill(0h)

上面的代碼可以合併成單個表達式:

direction = (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))).ffill().nullFill(0h)

一個簡單的測試如下:

t1= 60t10 = 50t20 = 30t2 = 20signal =10 20 70 59 42 49 19 25 26  35direction = (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))).ffill().nullFill(0h)[-1,-1,1,1,0,0,-1,-1,-1,0]

如果改用kdb+腳本來實現,則表達式如下:

direction: 0h^fills(-).(0N 1h)[(signal>t1;signal<t2)]^'(0N 0h)[(signal<t10;signal>t20)]

如果使用pandas實現,代碼如下:

t1 = 60
t10 = 50
t20 = 30
t2 = 20
signal = pd.Series([10,20,70,59,42,49,19,25,26,35])
direction = (signal.apply(lambdas: 1 if s > t1 else (0 if s < t10 else np.nan)) -
             signal.apply(lambdas: 1 if s < t2 else (0 if s > t20 else np.nan))).ffill().fillna(0)

下面我們生成一個長度爲1000萬的在0~100之間的隨機信號數組,測試DolphinDB、kdb+和pandas的性能。測試使用的機器配置如下:

CPU:Intel® Core™ i7-7700 CPU @3.60GHz 3.60 GHz

內存:16GB

OS:Windows 10

DolphinDB耗時330ms, kdb+耗時800ms,pandas耗時6.8s左右。DolphinDB的測試腳本如下:

t1= 60
t10 = 50
t20 = 30
t2 = 20
signal = rand(100.0, 10000000)
timer direction = (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))).ffill().nullFill(0h)

kdb+的測試腳本如下:

t1:60t10:50t20:30t2:20signal: 10000000 ? 100.0\t  0h^fills(-).(0N 1h)[(signal>t1;signal<t2)]^'(0N 0h)[(signal<t10;signal>t20)]

pandas的測試腳本如下:

import time
t1= 60
t10= 50
t20= 30
t2= 20
signal= pd.Series(np.random.random(10000000) * 100)
start= time.time()
direction= (signal.apply(lambdas:1 if s > t1 else (0 if s < t10 else np.nan)) -
            signal.apply(lambdas:1 if s < t2 else (0 if s > t20 else np.nan))).ffill().fillna(0)
end= time.time()
print(end- start)

通過上面這個例子,也不難發現,DolphinDB和kdb+的腳本在本質上有很多共性的東西。kdb+的腳本基本上能逐句逐詞的翻譯成DolphinDB腳本。區別在於kdb+是從左到右解析腳本的,而DolphinDB跟常規的編程語言一樣,是從右到左;kdb+喜歡用符號來代表某一個功能,而DolphinDB更喜歡用函數來表達某一個功能,可讀性會比較好但也會冗長一點。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章