共计 6854 个字符,预计需要花费 18 分钟才能阅读完成。
回顾 上篇,咱们对股票指数数据进行了收集、探索性剖析和预处理。接下来,本篇会重点介绍特色工程、模型抉择和训练、模型评估和模型预测的具体过程,并对预测后果进行剖析总结。
特色工程
在正式建模之前,咱们须要对数据再进行一些高级解决 — 特色工程,从而保障每个变量在模型训练中的公平性。依据现有数据的特点,咱们执行的特色工程流程大抵有以下三个步骤:
- 解决缺失值并提取所需变量
- 数据标准化
- 解决分类变量
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 做股票量化剖析》。机械工业出版社。