预测模型搭建:线性模型
在这一章节,我们尝试使用一些线性模型,对我们之前获取的股票价格进行预测。具体来说我们要处理的是一个基于时间序列的预测问题,给定一个时间序列的历史值:,我们来预测明天对应的下一个数值
,例如根据今天为止的收盘价信息,预测明天的收盘价。我们也可以利用其他信息,例如开盘价、最高价、最低价以及我们之前提取的一些特征等,这些信息也可以帮助我们预测明天的情况。
这一章节的重点是线性模型,我们会利用最近的已知值、过去一段时间的平均值和ARIMA模型,预测未来一天的收盘价和对数收益率。
1. 准备数据
在这一部分,我们还是使用之前下载的平安银行的股票价格数据。当我们想要设计方法来预测时间序列时,其实都是在已知的历史数据上进行实验,这样为了客观地评价不同方法的预测表现,我们会把一部分数据隐藏起来,作为最终评估不同方法的测试集,而已知的部分可以作为训练集和验证集。与其他数据集打散顺序不一样,时间序列数据集的划分一般是按照时间顺序,因为我们会倾向于利用历史上的数据,来预测未来的情况。训练集一般用来训练模型内部的参数,例如神经网络中的权重;而验证集用来挑选模型外部设置的超参数,例如我们的神经网络中有多少节点,这个是我们通过验证集上的表现挑选的,而不是模型学出来的。测试集则是用特定的评价指标,在我们考察的方法中挑出最好的方法。如下图所示:
我们载入数据,并把数据分成训练、验证和测试集:
import pandas as pd import numpy as np import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'svg' import warnings warnings.filterwarnings('ignore') # 忽略一些warnings def split_data(): df = pd.read_csv('1.csv', dtype={'trade_date': str}) df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d') df = df.set_index('trade_date', drop=False) # 指明drop=False保留trade_dt这一列 df = df.sort_index() # 按照时间顺序排序 # 计算对数收益率(log return) df['close1'] = df['close'].shift(-1) # 1天之后的收盘价 df['log_ret'] = np.log(df['close1'] / df['close']) # 1天之后的对数收益率 df = df.dropna() train = df['2010-01-01':'2017-12-31'] # 我们使用2010-2017年的数据作为训练集 cv = df['2018-01-01': '2018-12-31'] # 我们使用2018年的数据作为验证集 train_cv = df['2010-01-01':'2018-12-31'] # 训练集和验证集 test = df['2019-01-01': '2019-12-31'] # 我们使用2019年的数据作为测试集 return df, train, cv, train_cv, test df, train, cv, train_cv, test = split_data() print('Train dataset shape: %s' % str(train.shape)) print('Validation dataset shape: %s' % str(cv.shape)) print('Test dataset shape: %s' % str(test.shape))
输出结果对应训练集、验证集和测试集的大小:
Train dataset shape: (1874, 13) Validation dataset shape: (243, 13) Test dataset shape: (243, 13)
我们可以画出这三段时间的收盘价:
col_to_plot = 'close' # 也可以改成画其他列 ax = train.plot(x='trade_date', y=col_to_plot, style='b-', grid=True) ax = cv.plot(x='trade_date', y=col_to_plot, style='y-', grid=True, ax=ax) ax = test.plot(x='trade_date', y=col_to_plot, style='g-', grid=True, ax=ax) ax.legend(['train', 'validation', 'test']) ax.set_xlabel("date") ax.set_ylabel(col_to_plot);
结果如下图所示:
我们用均方根误差(Root Mean Squared Error, RMSE)作为我们主要的评价标准:
均方根误差越小,说明预测越准确。其他指标还有均方误差,平均绝对误差,R平方等,大家也可以上网找到这些指标的具体定义。
定义RMSE的代码如下:
def get_rmse(y_true, y_pred): ''' Compute root mean squared error (RMSE) ''' # 把输入的变量转成numpy的array格式 y_true, y_pred = np.asarray(y_true), np.asarray(y_pred) return np.sqrt(((y_true - y_pred) ** 2).mean())
2. 预测收盘价
我们首先来看对于明天的收盘价的预测问题。我们先定义一个滑动预测的通用函数,根据一个大小为N的时间窗口内的数据预测后一天的收盘价。
def get_preds_rolling(df, target_col, N, pred_func, offset): # 我们在一个DataFrame df上应用rolling函数,例如train_cv # target_col是我们要预测的column,例如这里是close # N是时间窗的大小,也就是说我们会使用t-1, t-2, t-N天的数据 # pred_func是我们真正使用的预测函数,基于滑动窗内的数据预测下一个数据点,之后会给出不同的定义 # offset是返回结果时,我们提取出真正要预测的部分,例如输入train_cv时,我们要提取cv的部分,即offset=train.shape[0] # 直接调用rolling得到的结果是t, t-1, t-(N-1)的结果 pred_list = df[target_col].rolling(window = N).apply(pred_func) # 因此我们将预测结果整体往后位移一位,最前面补上一个np.nan pred_list = np.concatenate((np.array([np.nan]), np.array(pred_list[:-1]))) # 我们返回的结果是对应offset之后到末尾的部分 return pred_list[offset:]
然后我们设计并传入不同的pred_func。
2.1 直接用今天的收盘价作为预测值
在这种情况下,我们只需要定义时间窗大小为N=1,pred_func返回原值就可以。为了让大家更好地了解get_preds_rolling的工作原理,我们一行一行运行:
N = 1 target_col = 'close' # 定义我们的预测函数 def pred_func(x): return x pred_list = train_cv[target_col].rolling(window = N).apply(pred_func) # 打印部分结果 print(pred_list[:5]) print(train_cv[['close']].head(5))
输出为:
trade_date 2010-01-04 23.71 2010-01-05 23.30 2010-01-06 22.90 2010-01-07 22.65 2010-01-08 22.60 Name: close, dtype: float64 close trade_date 2010-01-04 23.71 2010-01-05 23.30 2010-01-06 22.90 2010-01-07 22.65 2010-01-08 22.60
到这里pred_list中还是显示的当天的收盘价,然而我们需要用的是前一天的收盘价作为我们的预测值,因此需要将预测结果整体往后位移一位,最前面补上一个np.nan:
pred_list = np.concatenate((np.array([np.nan]), np.array(pred_list[:-1]))) print(pred_list[:5]) print(len(pred_list)) print(train_cv.shape)
输出为:
[ nan 23.71 23.3 22.9 22.65] 2117 (2117, 13)
此时我们也检验了pred_list和训练-验证集的总长度是一致的。
我们需要获取对应验证集,即cv部分的预测值,因此下标是从offset=train.shape[0]开始:
offset = train.shape[0] y_pred = pred_list[offset:] print(len(y_pred)) print(cv.shape)
输出为:
243 (243, 13)
同样检查了时间长度一致。
我们现在要评价测试集上的表现,因此提取cv中的close一列作为y_true:
y_true = cv['close'].values # 评估RMSE print(get_rmse(y_true, y_pred))
输出为:
0.23726320228913259
完整的版本如下:
target_col = 'close' N = 1 # 定义我们的预测函数 def pred_func(x): return x offset = train.shape[0] # 应用在train_cv上进行滑动预测 y_pred = get_preds_rolling(train_cv, target_col, N, pred_func, offset) y_true = cv[target_col].values # 评估RMSE print(get_rmse(y_true, y_pred))
为了跟其他方法进行比较,我们需要在测试集上进行评估:
target_col = 'close' N = 1 # 定义我们的预测函数 def pred_func(x): return x offset = train_cv.shape[0] # 这次应用在整个df上进行滑动预测 y_pred = get_preds_rolling(df, target_col, N, pred_func, offset) y_true = test[target_col].values # 评估RMSE oneday_rmse = get_rmse(y_true, y_pred) print('Results of one-day prediction') print('RMSE: %f' % oneday_rmse)
输出为:
Results of one-day prediction RMSE: 0.272834
2.2 使用前N天的收盘价的平均值作为预测值
在前一部分的基础上,我们再进一步,看通过使用前N天的收盘价的平均值作为预测值,能否带来预测效果的提升,这里我们通过验证集上的误差变化来选择比较好的N值。
我们需要修改pred_func:
def pred_func(x): # 返回一个时间窗口内收盘价的平均值 return np.mean(x)
接下来我们进行参数搜索,看看N从2到10的范围内,验证集上的误差如何变化:
cv_rmse_list = [] target_col = 'close' def pred_func(x): # 返回一个时间窗口内收盘价的平均值 return np.mean(x) offset = train.shape[0] for N in np.arange(2, 11): y_pred = get_preds_rolling(train_cv, target_col, N, pred_func, offset) y_true = cv[target_col].values # 评估
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
<p> 在这个专刊中,我们会学习Python在金融中的应用: ·掌握Python进行数据分析和处理的方法; ·了解Python在量化投资中的应用; ·转行到金融数据分析和量化交易领域的基础等内容。 这个专刊适合: ·想要学习Python编程的在校学生; ·想要转行到金融领域的在职人士; ·想要利用业余时间进行量化投资的在职人士等。 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p>