[Quant 1.3] 粗略的用DNN做一個標(biāo)普500交易策略



嘗試搭建一個簡單的深度神經(jīng)網(wǎng)絡(luò),以S&P500做標(biāo)的模擬一個基礎(chǔ)的交易策略。我模仿了原視頻up的思路,但是沒有照抄代碼,基本都是我看了視頻之后自己寫下來的。這個up大神的視頻給了我很多啟發(fā)。
策略的基本思路還是找technical indicators作為神經(jīng)網(wǎng)絡(luò)的輸入,來預(yù)測S&P500在下一個交易日是漲還是跌。technical indicators的選取是基于up給的論文https://sci-hub.se/10.1016/j.eswa.2016.01.018。我在里面選了一些,具體如下

一共25個指標(biāo)(實(shí)際上是21個,我把weekday轉(zhuǎn)成了one-hot編碼)。OHLC和Volume就是正常的價量數(shù)據(jù),帶lag的就是歷史數(shù)據(jù),例如lag1_open就是前一天的開盤價。RSI指數(shù)在論文中有特定的計算公式,我選擇了7,15,30,60,90天的RSI指數(shù)。weekday指得是當(dāng)天是星期幾。對于每一個交易日,weekday_0到weekday_4這5個指標(biāo)中只有一個是1,而其余4個是0。labels就是如果和昨天相比漲了就是1,沒漲就是0。
1. 庫Packages
前面都是常見的包,我就不贅述了。yfinance是雅虎金融的數(shù)據(jù)接口,我用這個來調(diào)取S&P500的價量數(shù)據(jù)。價量數(shù)據(jù)取的是從一年前到今天的數(shù)據(jù),數(shù)據(jù)頻率是一天。
2. 因子計算
這是一堆冗雜的數(shù)據(jù)處理,最后計算出來的factors都拼到raw_data的數(shù)據(jù)框里面。

3. 將數(shù)據(jù)框轉(zhuǎn)化為可用作DNN輸入的類型
對上一步計算得到的數(shù)據(jù)框,首先我們需要進(jìn)行一些標(biāo)準(zhǔn)化操作。因?yàn)镾&P500的價格在4000左右,而william R的數(shù)值基本在-1到1之間。數(shù)據(jù)數(shù)量級的巨大差別會造成擬合參數(shù)的數(shù)量級的巨大差別,因此在浮點(diǎn)數(shù)運(yùn)算時會造成比較大的誤差。所以我們需要進(jìn)行變換將數(shù)據(jù)轉(zhuǎn)換成相近的數(shù)量級。
我跟up一樣對價格和成交量取了log。然后對RSI指數(shù)除以100,這一步體現(xiàn)在計算RSI函數(shù)的定義里面。weekday的處理方式就是變成one-hot編碼。最后比較每一天和之后一天的收盤價判斷后一天是漲還是跌,跌是0,漲是1。最終直接對features和labels對應(yīng)的columns取np.array(),我們就獲得了ndarray,離合適的DNN的輸入又近了一步。
4. 定義DNN
我在這個DNN中定義了4個全連接層。然后在前向傳播函數(shù)里面給前3層使用了sigmoid激活函數(shù),但是輸出層也就是第4層沒有用激活函數(shù)。這是因?yàn)閜ytorch內(nèi)置的交叉熵?fù)p失函數(shù)會先對輸出進(jìn)行一次softmax函數(shù)操作,這一點(diǎn)up在視頻里面也強(qiáng)調(diào)過了。
5. 將ndarray轉(zhuǎn)化為張量并將輸入數(shù)據(jù)分批
這里我定義了一個generator,他的輸入是我們在第3步獲得的ndarray還有我們想一批輸入幾個樣本進(jìn)到DNN里面。每一次調(diào)用這個generator,它會生成一批次的features和labels張量。
在初始化這個generator之后,有兩種使用方法。一是使用next函數(shù),會自動輸出下一批的張量,知道遍歷訓(xùn)練集;二是使用for循環(huán),將generator轉(zhuǎn)化為iterator,每一次迭代一批訓(xùn)練機(jī)樣本。后面我們會用第二種方式。
6. 訓(xùn)練模型
我把前2/3的數(shù)據(jù)作為訓(xùn)練集,后1/3部分的數(shù)據(jù)用作測試集基礎(chǔ)學(xué)習(xí)率是0.1,然后隨著學(xué)習(xí)次數(shù)增加,學(xué)習(xí)率會逐漸降低,這里改變學(xué)習(xí)率的函數(shù)參照了up的代碼。在開始迭代之前,一定不要忘記三個要素,模型,損失函數(shù)和優(yōu)化器。
在每一個epoch下面,我們建立generator來把訓(xùn)練集合分批次。然后就是一套范式了,訓(xùn)練集前向傳播-計算損失-梯度清零-計算梯度-優(yōu)化器更新-調(diào)整學(xué)習(xí)率-條件性輸出。
7. 回測
為了得到預(yù)測結(jié)果,我把測試集的features做了前向傳播之后取softmax,這是因?yàn)槲覀兌x的DNN的最后一層是沒有激活函數(shù)的全連接層,所以想要獲得漲跌概率的話,需要對前向傳播的輸出套上softmax函數(shù)。如果預(yù)測結(jié)果是漲的概率或者跌的概率大于90%,那么我們就在當(dāng)天做交易。即如果預(yù)測出來明天漲的概率超過了0.9,我們就在明天開盤買入;如果預(yù)測出來明天跌的概率超過了0.9,我們就在明天開盤做空。為了計算方便,我們假設(shè)每日等額做多或者做空,并當(dāng)日清算,這就形成了一個策略的基本邏輯。這個策略的benchmark是從測試集日期的第一天一開始做多并持有至最后一天。然后我們做一下可視化對比一下我們的策略和benchmark收益率的對比。

在層層理想化的假設(shè)(交易不受限,等額交易,沒有交易成本,不考慮slippage和market effect),策略是能打敗S&P500的。但是up提到的問題很明顯,每次擬合出來的模型帶來的收益率都不一樣,這是因?yàn)槊看文P统跏蓟臅r候會隨機(jī)生成一組參數(shù),并以此參數(shù)為起點(diǎn)梯度下降,結(jié)果很可能會下降到局部最小值,所以最終的參數(shù)會有一些差異。