數據科學在量化金融中的應用:指數預測(下)
回顧《數據科學在量化金融中的應用:指數預測(上)》,我們對股票指數數據進行了收集、探索性分析和預處理。接下來,本篇會重點介紹特徵工程、模型選擇和訓練、模型評估和模型預測的詳細過程,並對預測結果進行分析總結。
特徵工程
在正式建模之前,我們需要對數據再進行一些高級處理 — 特徵工程,從而保證每個變量在模型訓練中的公平性。根據現有數據的特點,我們執行的特徵工程流程大致有以下三個步驟:
- 處理缺失值並提取所需變量
- 數據標準化
- 處理分類變量
1. 處理缺失值並提取所需變量
首先,我們需要剔除包含缺失值的行,並只保留需要的變量 x_input,為下一步特徵工程做準備。
x_input = (df_model.dropna()[['Year','Month','Day','Weekday','seasonality','sign_t_1','t_1_PricePctDelta','t_2_PricePctDelta','t_1_VolumeDelta']].reset_index(drop=True))
x_input.head(10)
然後,再將目標預測列 y 從數據中提取出來。
y = df_model.dropna().reset_index(drop=True)['AdjPricePctDelta']
2. 數據標準化
由於價格百分比差與交易量差在數值上有很大差距,如果不標準化數據,可能導致模型對某一個變量有傾向性。為了平衡各個變量對於模型的影響,我們需要調整除分類變量以外的數據,使它們的數值大小相對近似。Python 提供了多種數據標準化的工具,其中 sklearn 的 StandardScaler 模塊比較常用。數據標準化的方法有多種,我們選擇的是基於均值和標準差的標準化算法。這裏,大家可以根據對數據特性的理解和模型類型的不同來決定使用哪種算法。比如對於樹形模型來説,標準化不是必要步驟。
scaler = StandardScaler()
x = x_input.copy()
x[['t_1_PricePctDelta','t_2_PricePctDelta','t_1_VolumeDelta']]=scaler.fit_transform(x[['t_1_PricePctDelta','t_2_PricePctDelta','t_1_VolumeDelta']])
3. 處理分類變量
最常見的分類變量處理方法之一是 one-hot encoding。對於高基數的分類變量,經過編碼處理後,變量數量增加,大家可以考慮通過降維或更高階的算法來降低計算壓力。
x_mod = pd.get_dummies(data=x, columns=['Year','Month','Day','Weekday','seasonality'])
x_mod.columns
x_mod.shape
至此,我們完成了特徵工程的全部步驟,處理後的數據就可以進入模型訓練環節了。
模型選擇和訓練
首先,我們需要拆分訓練集和測試集。對於不需要考慮記錄順序的數據,可以隨機選取一部分數據作為訓練集,剩下的部分作為測試集。而對於時間序列數據來説,記錄之間的順序是需要考慮的,比如我們想要預測2月份的價格變動,那麼模型就不能接觸2月份以後的價格,以免數據泄露。由於股票指數數據為時間序列,我們將時間序列前75%的數據設為訓練數據,後25%的數據設為測試數據。
· 模型選擇
在模型選擇階段,我們會根據數據的特點,初步確定模型方向,並選擇合適的模型評估指標。
因為變量中包含歷史價格和交易量,且這些變量的相關性過高(high correlation),以線性模型為基礎的各類迴歸模型並不適合目標數據。因此,我們模型嘗試的重心將放在集成方法(ensemble method),以這類模型為主。
在訓練過程中,我們需要酌情考慮,選擇合適的指標來評估模型表現。對於迴歸預測模型而言,比較流行的選擇是 MSE(Mean Squared Error)。而對於股票指數數據來説,由於其時間序列的特性,我們在 RMSE 的基礎上又選擇了 MAPE(Mean Absolute Percentage Error),一種相對度量,以百分比為單位。比起傳統的 MSE,它不受數據大小的影響,數值保持在0-100之間。因此,我們將 MAPE 作為主要的模型評估指標。
· 模型訓練
在模型訓練階段,所有的候選模型將以默認參數進行訓練,我們根據 MAPE 的值來判斷最適合進一步細節訓練的模型類型。我們嘗試了包括線性迴歸、隨機森林等多種模型算法,並將經過訓練集訓練的各模型在測試集中的模型表現以字典的形式打印返回。
模型評估
通過運行以下方程,我們可以根據預測差值(MAPE)的大小對各模型的表現進行排列。大家也可以探索更多種不同的模型,根據評估指標的高低擇優選取模型做後續微調。
trail_result = ensemble_method_reg_trails(x_train, y_train, x_test, y_test)
pd.DataFrame(trail_result).sort_values('model_test_mape', ascending=True)
由此可以看出,在眾多模型類型中,Ada Boost 在訓練和測試集上的效果最好,MAPE 值最小,所以我們選擇 Ada Boost 進行下一步的細節調優。與此同時,我們發現 random forest 和 gradient boosting 也有不錯的預測表現。注意,Ada Boost 雖然在訓練集上準確度高,但是模型的表現不是很穩定。
接下來的模型微調分為兩個步驟:
- 使用 RandomizedSearchCV 尋找最佳參數的大致範圍
- 使用 GridSearchCV 尋找更精確的參數
影響 Ada Boost 性能的參數大致如下:
- n_estimators
- base_estimator
- learning_rate
注意,RandomizedSearchCV 和 GridSearchCV 都會使用交叉驗證來評估各個模型的表現。在前文中我們提到,時間序列是需要考慮順序的。對於已經經過轉換來適應機器學習模型的時間序列,每條記錄都有其相對應的時間信息,訓練集中也沒有測試集的信息。訓練集中記錄的順序可以按照特定的交叉驗證順序排列(較為複雜),也可以被打亂。這裏,我們認為訓練集數據被打亂不影響模型訓練。
base_estimator 是 ada boost 提升算法的基礎,我們需要提前建立一個 base_estimator 的列表。
l_base_estimator = []
for i in range(1,16):
base = DecisionTreeRegressor(max_depth=i, random_state=42)
l_base_estimator.append(base)
l_base_estimator += [LinearSVR(random_state=42,epsilon=0.01,C=100)]
1. 使用 RandomizedSearchCV 尋找最佳參數的大致範圍
使用 RandomSearchCV,隨機嘗試參數。這裏,我們嘗試了500種不同的參數組合。
randomized_search_grid = {'n_estimators':[10, 50, 100, 500, 1000, 5000],
'base_estimator':l_base_estimator,
'learning_rate':np.linspace(0.01,1)}
search = RandomizedSearchCV(AdaBoostRegressor(random_state=42),
randomized_search_grid,
n_iter=500,
scoring='neg_mean_absolute_error',
n_jobs=-1,
cv=5,
random_state=42)
result = search.fit(x_train, y_train)
可以看到,500種參數組合中表現最佳的是:
result.best_params_
result.best_score_
2. 使用 GridSearchCV 尋找更精確的參數
根據 Randomized Search 的結果,我們再使用 GridSearchCV 進行更深一步的微調:
- n_estimators: 1-50
- base_estimator: Decision Tree with max depth 9
- learning_rate: 0.7左右
search_grid = {'n_estimators':range(1,51),
'learning_rate':np.linspace(0.6,0.8,num=20)}
GridSearchCV 的結果如下:
根據 GridSearchCV 的結果,我們保留最佳模型,讓其在整個訓練集上訓練,並在測試集上進行預測,對結果進行評估。
可以看到,結合訓練集的交叉驗證結果,最佳模型在測試集中的表現與模型選擇和訓練階段的結果相比,準確度略有提升。最佳模型平衡了訓練集和測試集表現,可以更有效地防止過擬合的情況出現。
在確定模型以後,因為之前的模型都只接觸過訓練集,為了預測未來的數據,我們需要將模型在所有數據上重新訓練一遍,並以 pickle 文件的格式保存這個最佳模型。
best_reg.fit(x_mod, y)
該模型在全量數據的預測結果中MAPE值為:
m_forecast = best_reg.predict(x_mod)
mean_absolute_percentage_error(y, m_forecast)
模型預測
與傳統的 ARIMA 模型不同,現有模型的每次預測都需要將預測信息重新整合,輸入進模型後才能得到新的預測結果。輸入數據的重新整合可以用以下方程進行開發,方便適應各種應用場景的需求。
def forecast_one_period(price_info_adj_data, ml_model, data_processor):
# Source data: Data acquired straight from source
last_record = price_info_adj_data.reset_index().iloc[-1,:]
next_day = last_record['Date'] + relativedelta(days=1)
next_day_t_1_PricePctDelta = last_record['AdjPricePctDelta']
next_day_t_2_PricePctDelta = last_record['t_1_PricePctDelta']
next_day_t_1_VolumeDelta = last_record['Volume_in_M'] - last_record['t_1_VolumeDelta']
if next_day_t_1_PricePctDelta > 0:
next_day_sign_t_1 = 1
else:
next_day_sign_t_1 = 0
# Value -99999 is a placeholder which won't be used in the following modeling process
next_day_input = (pd.DataFrame({'Date':[next_day],
'Volume_in_M':[-99999],
'AdjPricePctDelta':[-99999],
't_1_PricePctDelta':[next_day_t_1_PricePctDelta],
't_2_PricePctDelta':[next_day_t_2_PricePctDelta],
't-1volume': last_record['Volume_in_M'],
't-2volume': last_record['t-1volume'],
't_1_VolumeDelta':[next_day_t_1_VolumeDelta],
'sign_t_1':next_day_sign_t_1}).set_index('Date'))
# If forecast period is post Feb 15, 2020, input data starts from 2020-02-16,
# as our model is dedicated for market under Covid Impact.
# Another model could be used for pre-Covid market forecast.
if next_day > datetime.datetime(2020, 2, 15):
price_info_adj_data = price_info_adj_data[price_info_adj_data.index > datetime.datetime(2020, 2, 15)]
price_info_adj_data_next_day = pd.concat([price_info_adj_data, next_day_input])
# Add new record to original data for modeling preparation
input_modified = processor.data_modification(price_info_adj_data_next_day)
# Prep for modeling
x,y = data_processor.data_modeling_prep(input_modified)
next_day_x = x.iloc[-1:]
forecast_price_delta = ml_model.predict(next_day_x)
# Consolidate prediction results
forecast_df = {'Date':[next_day], 'price_pct_delta':[forecast_price_delta[0]], 'actual_pct_delta':[np.nan]}
return pd.DataFrame(forecast_df)
我們讀取之前保存的模型,對未來一個工作日的價格變動進行預測。輸出的結果中,actual_pct_delta 是為未來價格發佈後保存真實結果所預留的結構。
根據預測結果,我們認為2022年11月1日這天標普指數會有輕微的上升。
分析預測結果
根據近兩年的數據走向,我們有了這樣的預測結果:標普指數會有輕微的上升。但當我們查看2022年11月1日發佈的實際數據時發現,指數在當天是下降的。這意味着外界的某種信息,可能是經濟指標抑或是政策風向的改變,導致市場情緒有所變化。搜索相關新聞後,我們發現了以下信息:
“Stocks finished lower as data showing a solid US labor market bolstered speculation that Federal Reserve policy could remain aggressively tight even with the threat of a recession.”
在經濟面臨多重考驗的同時,招聘市場職位數量上升的信息釋出,導致投資者認為招聘市場表現穩健,美聯儲不會考慮放寬當下的經濟政策;這種負面的展望在股票市場上得到了呈現,導致當日指數收盤價下降。
模型在實際應用中不僅僅充當着預測的工作,在本文的案例中,指數價格變動的預測更類似於一種 “標線”。通過模型學習歷史數據,模型的結果代表着如果按照歷史記錄的信息,沒有外部重大幹擾的情況下,我們所期待的變動大致是怎樣的,即當日實際發生的變動是“系統”層面的變動,還是需要深度挖掘的非“系統”因素所造成的變動。在模型的基礎上,我們可以將這些結果舉一反三,開發出各式各樣的功能,讓數據儘可能地發揮其價值。
總結
回顧上下兩篇文章的全部內容,標普500股票指數的價格預測思路總結如下:
- 確定預測目標:反映北美股票市場的指數 — 標普500 ;
- 數據收集:從公共金融網站下載歷史價格數據;
- 探索性數據分析:初步瞭解數據的特性,數據可視化,將時間序列信息以圖像的形式呈現;
- 數據預處理:將時間轉換為變量,更改價格數據,尋找週期和季節性,根據週期調整交易量數據;
- 數據工程:處理缺失值並提取所需變量,數據標準化,處理分類變量;
- 模型選擇和訓練:拆分訓練集和測試集,確定模型方向和評估指標,嘗試訓練各種模型;
- 模型評估:根據指標選定最優模型,使用 RandomizedSearchCV 尋找最佳參數的大致範圍,再使用 GridSearchCV 尋找更精確的參數;
- 模型預測:整合輸入數據,預測未來一個工作日的價格變動;
- 分析預測結果:結合當日的實際情況,理解市場變動,發揮模型價值。
參考資料:
- Time series into supervised learning problem
- Tuning Ada Boost
- S&P 500 historical data
- Bloomberg News
- 阿布(2021)。《量化交易之路 用Python做股票量化分析》。機械工業出版社。
- 數據科學在文本分析中的應用 :中英文 NLP(上)
- 『堅如磐石的 PieCloudDB』:透明加密模塊的設計與實現
- 後疫情時代,數據科學賦能旅遊行業服務質量提升
- OpenPie 和 ChatGPT 聊聊雲上數據計算的那些事兒
- 正式上市丨拓數派發布eMPP存算分離軟硬件一體機
- 『Postgres.Live 技術沙龍回顧』揭祕 PieCloudDB Database eMPP 架構設計
- PieCloudDB Database 雲上商業智能的最佳實踐
- 數據科學在量化金融中的應用:指數預測(下)
- 數據科學在量化金融中的應用:指數預測(上)
- 【DTCC 2022】雲原生數據庫PieCloudDB全新eMPP架構是如何煉成的
- 數據科學,為企業創造更大的數據價值
- 擁抱開放|OpenPie引領PostgreSQL中國代碼貢獻力