预测模型搭建:线性模型

在这一章节,我们尝试使用一些线性模型,对我们之前获取的股票价格进行预测。具体来说我们要处理的是一个基于时间序列的预测问题,给定一个时间序列的历史值:,我们来预测明天对应的下一个数值,例如根据今天为止的收盘价信息,预测明天的收盘价。我们也可以利用其他信息,例如开盘价、最高价、最低价以及我们之前提取的一些特征等,这些信息也可以帮助我们预测明天的情况。

这一章节的重点是线性模型,我们会利用最近的已知值、过去一段时间的平均值和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>

全部评论

相关推荐

04-18 15:58
已编辑
门头沟学院 设计
kaoyu:这一看就不是计算机的,怎么还有个排斥洗碗?
点赞 评论 收藏
分享
Yki_:以下条件优先录用: 喜欢去缅北当猪仔的
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务