數據科學在量化金融中的應用:指數預測(上)

語言: CN / TW / HK

自2018年末以來,全球金融基本盤由早期的穩步上升變得起伏不定。由於投資者對市場走向和未來展望的不確定,這樣的市場情緒帶動着大盤和指數起起伏伏。同時中美貿易戰給零售帶來的巨大關税壓力讓市場情緒再度低迷;2020年初,新冠肺炎疫情在全球肆虐蔓延,造成醫療資源緊張,疫情防控措施使商品和人員流動速度減慢,多地商店停業甚至倒閉,大大影響了人們的經濟生活,進一步導致全球各地股票市場出現熔斷甚至跌停的狀況。面對動盪的金融市場,政府和金融監管機構推行了各式各樣的政策以應對疫情造成的影響,穩住經濟與股票基本盤。 

電腦螢幕畫面

中度可信度描述已自動生成 

對於許多金融從業者而言,市場的動盪意味着新的投資機遇,而2008年金融危機的教訓也時刻提醒着人們,機遇背後往往伴隨着巨大的風險。著名的《巴塞爾協定III》就是在應對全球金融危機、強化金融監管的背景下應運而生的,它也是現階段被廣泛運用於各大金融機構的全球金融監管標準之一。如何識別風險、保證資產安全並保持其相應的流動性一直都是風險管理中重要的組成部分。在股票市場的風險管理中,金融從業者需要先查看價格變化是否在預期範圍內,並判斷這樣的變化是由系統風險還是政策變化或重大事件所產生。如非系統風險,金融從業者則需要在對政策或重大事件充分的瞭解後,決定是否需要調整當下的策略,買入或出售相應的資產來保障機構金融層面的安全。判斷系統風險的方法有多種,其中較為流行的方法是通過歷史價格信息來預測未來價格,並比較真實價格與預測結果的差距。在本篇中,我們將使用這樣的方法進行股價預測,並與真實價格進行對比。

解決方案結構 

為了實現對歷史數據進行建模來預測未來價格,本篇採用如下結構的解決方案,同時大部分數據科學的項目也都可以使用類似的結構來完成:

  • 確定預測目標 
  • 數據收集 
  • 探索性數據分析 
  • 數據預處理 
  • 特徵工程 
  • 模型選擇和訓練 
  • 模型評估 
  • 模型預測 

確定預測目標 

在股票市場中,波動較大的股票不佔少數。為了解市場狀況,金融從業人員會傾向於研究分析股票指數而非單個股票的價格。因為指數相對於單個股票來説更穩定,也更利於精準地建模。指數通常由多種股票構成,一般反映市場績效的是廣基指數,如道瓊斯指數、標普500指數、日經指數、恆生指數等。這樣的指數既可以反映出股票基本盤狀況,又可以反映出投資人對經濟現況的敏感度。在本篇中,我們選擇的目標預測指數是標普500指數,是全球最具標誌性的追蹤美國高市值公司的股票指數之一。 

數據收集 

收集金融數據的平台眾多,其中像Bloomberg、Qliq、Quandl 這樣的國外市場平台,抑或是如同花順、萬德這樣的國內平台都受到大眾的喜愛。大部分企業為了保證數據的準確性、安全性和時效性,在生產環境下會使用成熟的收費平台。在本篇中,我們使用的是 Yahoo Finance,一個免費的公開金融數據平台,它不僅包含了大部分公開股票的實時數據,還提供了 Python API,可以通過股票代碼和時間區間直接查詢歷史價格。

import yfinance as yf                                             
df_sp = yf.download('^GSPC',start="2012-11-01",end="2022-11-01") 

Table

Description automatically generated

Pandas數據集“df_sp”包含了從2012年11月1日至2022年10月31日十年以來的標普500指數數據,記錄了每個日期所對應的開市價格、最高價格、最低價格、閉市價格、閉市調整價格和交易量。 

探索性數據分析 

在收集完需要的數據後,首先可以查看數據集中可能存在的空缺記錄和需要調整的字段。我們使用以下函數來了解數據集中包含多少條記錄、是否有空缺值以及每個字段所對應的數據類型。 

df_sp.info() 

Text, table

Description automatically generated

可以看到,數據集中沒有需要處理的空缺值,並且每個字段的類型都是統一的。除日期以外(Python Datetime類型格式),其他字段都是數字類型。價格相關的都為浮點數,交易量相關的為整數。接下來,我們通過以下函數查看數據的統計類信息。 

df_sp.describe() 

Table

Description automatically generated

可以看到,指數數據都是正數,大部分以千為單位。唯一在單位上與其他列有所區別的是交易量數據。交易量的絕對數值與價格相比相差巨大,為了更有可比性,我們需要調整交易量的絕對數值大小。在本步驟中,我們選擇先將交易量除以1000000,使其變成以百萬為單位,那麼在數值上,交易量數值大小縮小至千,與價格相似。 

Table

Description automatically generated 

初步分析數據後,我們可以將價格信息的時間序列可視化,更加直觀地瞭解價格趨勢。 

Background pattern

Description automatically generated

通過圖像,我們發現開市、閉市、最高、最低和閉市調整的價格在趨勢上非常相似;交易量和價格之間也有一定的聯繫。我們需要對交易量和價格做進一步的分析來明確兩者之間的關係。這裏,我們使用相關性矩陣(correlation matrix)來研究變量之間的關係。 

Calendar

Description automatically generated with medium confidence

由此可以看出,交易量與價格之間沒有非常高的相關性,但這並不能證明交易量不應該在價格預測中被考慮。從另一個角度來説,各價格變量之間的相關性非常高,我們只需挑選其中一個(閉市調整價格)進行預測即可。在下一步中,我們只保留需要用到的信息即可。 

df = df_sp[['Adj Close','Volume_in_M']] 

另一個值得注意的特徵是,相比於2020年之前總體上升的趨勢,指數價格在2020至2021年間呈下降趨勢。我們需要放大這段時間的數據圖像來了解具體情況。 

A picture containing whiteboard

Description automatically generated 

2020年3月,由於疫情的蔓延,全球多個國家都開始實行封控政策。從結果可以看出市場對封控政策的反響巨大,但與此同時多地政府也採取了各式各樣的經濟政策來穩定市場,人們的生產生活在疫情後開啟了新的模式。在這裏,我們假設疫情的一系列政策會在短時間內持續下去。根據公共信息的評估,大家可以酌情調整模型和假設。在本項目中,我們的目標是使用自2020年2月15日起的歷史數據,預測未來1個工作日的調整後閉市指數價格。 

至此,我們完成了從原數據集中提取後續建模所需數據的全部步驟。為了發揮提取數據的最大價值,我們需要對現有的兩個變量進行一些處理,從而儘可能完整地展現數據的特性。特別是對於時間序列數據而言,季節性和週期性都是非常重要的信息,而這些信息是可以從時間序列本身提取的。必要的數據處理雖然會增加計算量,但可以換來更精準更完善的模型結果。 

數據預處理 

由於指數數據是時間序列,相比於大多數迴歸預測,我們不僅要考慮模型需要什麼樣的變量,也需要考慮時間順序。在本篇中,迴歸預測的數學公式如下,對於需要預測的未來時間為t+1: 

 

即運用n條歷史記錄來預測未來的價格。 

為了提取有效信息輔助預測,我們將數據預處理大致分為四步: 

  • 將時間轉換為變量 
  • 更改價格數據 
  • 尋找週期和季節性 
  • 根據週期調整交易量數據 

1. 將時間轉換為變量

首先,我們需要將時間信息從索引中提取出來。 

df_mod_forecast = df_mod.reset_index() 
df_mod_forecast.info() 

Table

Description automatically generated

從日期信息中,提取年、月、日和工作日信息,經過處理後的數據如下: 

df_mod_forecast.head() 

Table

Description automatically generated

2. 更改價格數據 

本篇的預測目標是未來價格,對於指數而言,金融從業者相對看重的是價格變動而非是其數值的大小。我們可以選擇預測價格,也可以選擇預測價格變動。我們將根據數據本身的特性來決定二者當中誰更適合建模。 

首先,我們可以將每日和前一個工作日的價格差百分比計算出來。 

df_mod_forecast['AdjPricePctDelta'] = df_mod_forecast['Adj Close'].pct_change() 

3. 尋找週期和季節性 — 解構時間序列與自相關分析 

指數價格數據是以日期為單位的,且只有在工作日才有數據。我們在嘗試週期時可以有多種選擇,例如以周、月或者季度為單位。在本篇中,時間序列分解效果最好的是月份。從2020年2月到2022年11月大約有20個月的時間。合適的週期會幫助我們更好的尋找數據中的季節性。 

注意,在之前的時間序列圖像中我們注意到,全球疫情蔓延之後,股票市場表現出高於平常的波動。我們需要使用 multiplicative 模型來解決這個問題。我們首先嚐試分解價格的時間序列。 

A picture containing text, window, screenshot

Description automatically generated   

通過以上圖像可以看出,每100天大約有5個週期,每個週期大約有20天。除此之外,價格數據殘差(residual)部分表現並不佳,徘徊在1左右。在這個基礎上,我們再進行自迴歸分析,價格的ACF和PACF圖像如下: 

A picture containing chart

Description automatically generatedA picture containing chart

Description automatically generated 

ACF和PACF顯示近30天的歷史價格都對當下的價格有影響,而太多的信息會影響模型的效率,甚至帶來過擬合的風險。我們再來看看每日價格變動的解構結果。注意,因為價格差有負值,multiplicative 模型並不適用,我們需要使用 additive 模型來進行時間序列分解。 

Background pattern

Description automatically generated

價格百分比差也表現出類似的季節性。殘差比之前表現略有進步,徘徊在0左右,但是可以看出2月份左右的數據波動性遠高於平常。價格變動的ACF和PACF如下: 

Chart

Description automatically generatedChart

Description automatically generated 

價格差的ACF和PACF中,我們可以看到近兩天的歷史價格變動對當天價格的影響比較大。在本項目中,前兩天的歷史信息將納入模型中考慮。根據本節的發現,我們可以在原先的基礎上進一步處理數據,加入季節信息。 

我們先計算出大致需要多少個週期,以0-20的正整數為一週期填充數列,就可以得到代表週期性的變量。 

n_seasonal_cycles = math.ceil(df_mod_forecast.shape[0]/20) 
df_mod_forecast['seasonality'] = (list(range(0,20))*n_seasonal_cycles[:df_mod_forecast.shape[0]] 

4. 根據週期調整交易量數據 

時間序列較為傳統的模型為 ARIMA 模型,這類模型只需要一條時間序列,根據數據週期性等特徵調試參數。時間序列也可以使用機器學習進行建模,這樣的模型可以輸入更多的變量,但需要對數據進行調整。和 ARIMA 類型的模型不同,我們需要使用 t-1 的價格波動和t-2的價格波動作為變量的一部分,輸入模型進行訓練。

在這裏我們需要運用Pandas的shift方程,將價格變動數據分別下移一個和兩個時間間隔。Shift方程需要索引作為時間的依據,因此我們需要將日期設為索引。根據日期,將價格變動數據下移。 

df_model['t_1_PricePctDelta'] = df_model['AdjPricePctDelta'].shift(periods=1) 
df_model['t_2_PricePctDelta'] = df_model['AdjPricePctDelta'].shift(periods=2) 

以此類推,我們將交易量數據分別下移一個和兩個時間間隔,兩者求差即可得到每個交易日昨天(t-1)與前天(t-2)之間的交易量差。 

df_model['t-1volume'] = df_model['Volume_in_M'].shift(periods=1) 
df_model['t-2volume'] = df_model['Volume_in_M'].shift(periods=2) 
df_model['t_1_VolumeDelta'] = df_model['t-1volume'] - df_model['t-2volume'] 

通過交易日前一天的價格變動,我們可以將其轉換為漲跌符號來作為模型輸入的一部分。 

df_model['sign_t_1'] = np.where(df_model['t_1_PricePctDelta']>0,1,0*df_model['t_1_PricePctDelta']) 

至此,我們完成了標普500指數數據從下載到轉換的全過程。

在下篇中我們將着手完成模型訓練和預測步驟,根據模型來預測未來一個工作日的價格變化,並將預測結果與真實情況進行比對。 

 

 


參考資料: