Python 金融交易实用指南(三)(1)https://developer.aliyun.com/article/1523770
图 6.10 – 比较原始价格和 ARIMA(36, 1, 2)模型预测价格的绘图
让我们使用这个拟合的模型来预测未来日期的值。首先,我们使用datetools.dates_from_range(...)方法和pandas.DataFrame.append(...)方法构建一个包含另外 4 年的日期索引且没有数据(将使用NaN值填充)的extended_dataset DataFrame,如下所示:
extended_dataset = pd.DataFrame(index=sm.tsa.datetools.dates_from_range('2020m1', length=48)) extended_dataset = dataset.append(extended_dataset) extended_dataset Price PredPrice 2000-01-31 95.317833 0.000000 2000-02-29 100.268895 95.317901 ... ... ... 2023-11-30 NaN NaN 2023-12-31 NaN NaN 288 rows × 2 columns
接着,我们可以再次调用ARIMAResults.predict(...)方法,为整个时间序列生成预测价格,从而对我们添加的新日期进行预测,如下所示:
extended_dataset['PredPrice'] = \ res_ar.predict(extended_dataset.index[0], extended_dataset.index[-1]) extended_dataset Price PredPrice 2000-01-31 95.317833 0.000000 2000-02-29 100.268895 95.317901 ... ... ... 2023-11-30 NaN 215.441777 2023-12-31 NaN 220.337355 288 rows × 2 columns
以下代码块绘制了extended_dataset DataFrame 中的最后 100 个观测值:
extended_dataset['Price'].iloc[-100:].plot(figsize=(12, 6), color='darkgray', linestyle='-', lw=4, legend='Price') extended_dataset['PredPrice'].iloc[-100:].plot(figsize=(12, 6), color='black', linestyle='-.', legend='PredPrice')
这样就得到了一个包含预测的PredPrice值的绘图,如下面的截图所示:
图 6.11 – ARIMA 模型预测的历史和预测价格
在前面截图中显示的图中,预测价格明显遵循过去价格的趋势。
使用 pmdarima 的 SARIMAX 时间序列模型
SARIMA是 ARIMA 模型的扩展,用于具有季节性成分的单变量时间序列。
SARIMAX,是模型的名称,同时支持外生变量。
这些是三个 ARIMA 参数:
p= 趋势自回归阶数d= 趋势差分阶数q= 趋势移动平均阶数
除了前面的参数之外,SARIMA 还引入了另外四个参数,如下所示:
P= 季节性自回归阶数D= 季节性差分阶数。Q= 季节性 MA 阶数。m= 单个季节周期的长度,以时间步数表示。
手动查找这些参数可能会耗费时间,使用自动 ARIMA 模型可能更有优势。
在 Python 中,auto-ARIMA 建模由 pmdarima 库提供。其文档可在 alkaline-ml.com/pmdarima/index.html 上找到。
安装很简单,如下所示:
pip install pmdarima
自动 ARIMA 模型试图通过进行各种统计测试来自动发现 SARIMAX 参数,如下所示:
图 6.12 – 各种统计检验的表格。
一旦找到最佳的 d 值,auto-ARIMA 模型将在由 start_p、max_p、start_q 和 max_q 定义的范围内搜索最适合的模型。如果启用了 seasonal 参数,则一旦确定最佳的 D 值,我们就会使用类似的程序来找到 P 和 Q。
最佳模型通过最小化信息准则的值确定(阿卡奇信息准则 (AIC), 校正 AIC, 贝叶斯信息准则 (BIC), Hannan-Quinn 信息准则 (HQC), 或 袋外 (OOB)—用于验证评分—分别)。
如果未找到合适的模型,auto-ARIMA 将返回 ValueError 输出。
让我们使用前面的数据集进行自动 ARIMA。时间序列具有明显的季节性分量,周期为 12。
请注意下面的代码块中,我们为预测值生成了 95%的置信区间,这对于交易规则非常有用,例如,如果价格高于上限置信区间值,则卖出:
import pmdarima as pm model = pm.auto_arima(dataset['Price'], seasonal=True, stepwise=True, m=12) print(model.summary()) extended_dataset = \ pd.DataFrame( index=sm.tsa.datetools.dates_from_range('2020m1', length=48)) extended_dataset['PredPrice'], conf_int = \ model.predict(48, return_conf_int=True, alpha=0.05) plt.plot(dataset['Price'], c='blue') plt.plot(extended_dataset['PredPrice'], c='green') plt.show() print(extended_dataset) print(conf_int)
输出如下所示:
图 6.13 – SARIMAX 结果来自自动 ARIMA 的统计数据。
图中显示如下的截图:
图 6.14 – 自动 ARIMA 模型预测的历史和预测价格预测。
输出还包括预测价格,如下所示:
PredPrice 2020-01-31 194.939195 ... ... 2023-12-31 222.660698 [48 rows x 1 columns]
另外,输出提供了每个预测价格的置信区间,如下所示:
[[192.39868933 197.4797007 ] [196.80033117 202.32443987] [201.6275806 207.60042584] ... [212.45091331 225.44676173] [216.11548707 229.20590827]]
现在我们将看到使用 Facebook 的 Prophet 库进行时间序列预测。
使用 Facebook 的 Prophet 库进行时间序列预测。
Facebook Prophet 是一个用于预测单变量时间序列的 Python 库,对季节性和节假日效应提供了强大的支持。它特别适用于具有频繁变化趋势的时间序列,并且足够强大以处理异常值。
更具体地说,Prophet 模型是一个具有以下属性的加法回归模型:
- 分段线性或逻辑增长趋势。
- 年度季节性分量采用傅里叶级数模拟。
- 用虚拟变量建模的每周季节性分量。
- 用户提供的节假日列表
Prophet 的安装更加复杂,因为它需要编译器。 安装它的最简单方法是使用 Anaconda,如下所示:
conda install -c conda-forge fbprophet
附带的 Git 存储库包含了带有 Prophet 的 conda 环境设置。
Prophet 库要求输入的 DataFrame 包含两列—ds 代表日期,y 代表值。
让我们将 Prophet 模型拟合到以前的数据集中。请注意在以下代码片段中,我们明确告诉 Prophet 我们希望获得每月的预测值 (freq='M'):
from fbprophet import Prophet prophet_dataset = \ dataset.rename(columns={'Price' : 'y'}).rename_axis('ds')\ .drop('PredPrice', 1).reset_index() print(prophet_dataset) model = Prophet() model.fit(prophet_dataset) df_forecast = model.make_future_dataframe(periods=48, freq='M') df_forecast = model.predict(df_forecast) print(df_forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()) model.plot(df_forecast, xlabel='Date', ylabel='Value') model.plot_components(df_forecast)
预测值与 SARIMAX 模型非常相似,可以在此处看到:
图 6.15 – Prophet 库的输出包括预测值,以及模型组件的值
预测值存储在 yhat 列中,其中包含了 yhat_lower 和 yhat_upper 置信区间。
Prophet 确实生成了 Prophet 组件的图表,这对于理解模型的预测能力非常有用。 一个趋势组件图表可以在这里看到:
图 6.16 – Prophet 模型的趋势组件图表
以下截图显示了年度季节性的输出:
图 6.17 – Prophet 模型的年度季节性组件图表
这是预测图表的输出:
图 6.18 – Prophet 模型的预测图表及置信区间
每个时间序列模型都略有不同,并且最适合不同类别的时间序列。 但总的来说,Prophet 模型非常稳健,并且在大多数情况下最容易使用。
scikit-learn 回归和分类简介
scikit-learn 是一个基于numpy和scipy库构建的 Python 监督 和 无监督 机器学习库。
让我们演示如何使用 scikit-learn 中的 RidgeCV 回归和分类来预测价格变化。
生成数据集
让我们从生成以下示例所需的数据集开始—一个包含了 20 年每日数据的 Pandas DataFrame,其中包含了BookPressure、TradePressure、RelativeValue和Microstructure字段来表示一些基于该数据集构建的合成交易信号(也被称为PriceChange字段代表我们试图预测的价格每日变化(也被称为PriceChange字段一个线性函数,并带有一些随机权重和一些随机噪声。Price字段代表使用pandas.Series.cumsum(...)方法生成的工具的实际价格。 以下代码段中可以看到代码:
import numpy as np import pandas as pd df = pd.DataFrame(index=pd.date_range('2000', '2020')) df['BookPressure'] = np.random.randn(len(df)) * 2 df['TradePressure'] = np.random.randn(len(df)) * 100 df['RelativeValue'] = np.random.randn(len(df)) * 50 df['Microstructure'] = np.random.randn(len(df)) * 10 true_coefficients = np.random.randint(low=-100, high=101, size=4) / 10 df['PriceChange'] = ((df['BookPressure'] * true_coefficients[0]) + (df['TradePressure'] * true_coefficients[1]) + (df['RelativeValue'] * true_coefficients[2]) + (df['Microstructure'] * true_coefficients[3]) + (np.random.randn(len(df)) * 200)) df['Price'] = df['PriceChange'].cumsum(0) + 100000
让我们快速检查分配给我们四个特征的真实权重,如下所示:
true_coefficients array([10\. , 6.2, -0.9, 5\. ])
让我们还检查包含所有数据的 DataFrame,如下所示:
Df BookPressure TradePressure RelativeValue Microstructure PriceChange Price 2000-01-01 4.545869 -2.335894 5.953205 -15.025576 -263.749500 99736.250500 2000-01-02 -0.302344 -186.764283 9.150213 13.795346 -758.298833 98977.951667 ... ... ... ... ... ... ... 2019-12-31 -1.890265 -113.704752 60.258456 12.229772 -295.295108 182827.332185 2020-01-01 1.657811 -77.354049 -39.090108 -3.294086 -204.576735 182622.755450 7306 rows × 6 columns
让我们视觉检查Price字段,如下所示:
df['Price'].plot(figsize=(12, 6), color='black', legend='Price')
图中显示了 20 年来以下逼真的价格演变:
图 6.19 – 合成数据集的价格图
让我们显示除Price列之外的所有列的散点矩阵,如下所示:
pd.plotting.scatter_matrix(df.drop('Price', axis=1), color='black', alpha=0.2, grid=True, diagonal='kde', figsize=(10, 10))
输出如下所示:
图 6.20 – 合成数据集的散点矩阵
散点矩阵显示PriceChange与TradePressure之间存在强关系。
在数据集上运行 RidgeCV 回归
让我们使用 scikit-learn 回归方法将线性回归模型拟合到我们的数据集。我们将使用四个特征尝试拟合和预测PriceChange字段。
首先,我们将特征和目标收集到一个 DataFrame 和一个 Series 中,如下所示:
features = df[['BookPressure', 'TradePressure', 'RelativeValue', 'Microstructure']] target = df['PriceChange']
我们将使用sklearn.linear_model.RidgeCV,一个带有 L2 正则化的线性回归模型(使用 L2 范数惩罚因子以避免过拟合),该模型使用交叉验证学习最佳系数。我们将使用sklearn.linear_model.RidgeCV.fit(...)方法使用特征拟合目标值。代码如下所示:
from sklearn.linear_model import RidgeCV ridge = RidgeCV() ridge.fit(features, target)
结果是一个RidgeCV对象,如下所示:
RidgeCV(alphas=array([ 0.1, 1\. , 10\. ]), cv=None, fit_intercept=True, gcv_mode=None, normalize=False, scoring=None, store_cv_values=False)
我们可以使用RidgeCV.coef_属性访问Ridge模型学到的权重/系数,并将其与实际系数进行比较,如下所示:
true_coefficients, ridge.coef_
模型学到的系数似乎非常接近真实权重,每个系数都有一些误差,如下所示:
(array([10\. , 6.2, -0.9, 5\. ]), array([11.21856334, 6.20641632, -0.93444009, 4.94581522]))
RidgeCV.score(...)方法返回 R2 分数,表示拟合模型的准确性,如下所示:
ridge.score(features, target)
这返回以下 R2 分数,最大值为 1,因此该模型相当适合数据:
0.9076861352499385
RidgeCV.predict(...)方法输出预测的价格变化值,我们将其与pandas.Series.cumsum(...)方法相结合,生成预测的价格系列,然后将其保存在PredPrice字段中,如下所示:
df['PredPrice'] = \ ridge.predict(features).cumsum(0) + 100000; df
这将在我们的 DataFrame 中添加一个新列,如下所示:
... Price PredPrice 2000-01-01 ... 99736.250500 99961.011495 2000-01-02 ... 98977.951667 98862.549185 ... ... ... ... 2019-12-31 ... 182827.332185 183059.625653 2020-01-01 ... 182622.755450 182622.755450 7306 rows × 7 columns
在以下代码块中,将真实的Price字段与预测的PredPrice字段一起绘制:
df['Price'].plot(figsize=(12, 6), color='gray', linestyle='--', legend='Price') df['PredPrice'].plot(figsize=(12, 6), color='black', linestyle='-.', legend='PredPrice')
生成的图表,如下截图所示,显示PredPrice大部分时间都跟踪Price,但在某些时间段会出现预测误差:
图 6.21 – 原始价格与 Ridge 回归模型预测价格的比较图
我们可以缩小到 2010 年第一季度,检查预测误差,如下所示:
df['Price'].loc['2010-01-01':'2010-03-31']\ .plot(figsize=(12, 6), color='darkgray', linestyle='-', legend='Price') df['PredPrice'].loc['2010-01-01':'2010-03-31']\ .plot(figsize=(12, 6), color='black', linestyle='-.', legend='PredPrice')
这产生了下面的图表,显示了那段时间内 Price 和 PredPrice 之间的差异:
图 6.22 – 比较 2010 年第一季度岭回归模型的原始价格和预测价格的图表
我们可以计算预测误差并使用密度图绘制它们,如下代码片段所示:
df['Errors'] = df['Price'] - df['PredPrice'] df['Errors'].plot(figsize=(12, 6), kind='kde', color='black', legend='Errors')
这生成了下面截图中显示的图表,展示了错误的分布:
图 6.23 – 显示岭回归模型预测误差分布的图表
前面截图显示的错误图表表明错误没有明显的偏差。
在数据集上运行分类方法
让我们演示 scikit-learn 的分类方法。
首先,我们需要为分类模型创建离散分类目标标签以进行预测。我们分别给这些条件分配 -2、-1、0、1 和 2 数值标签,并将离散目标标签保存在 target_discrete pandas.Series 对象中,如下所示:
target_discrete = pd.cut(target, bins=5, labels = \ [-2, -1, 0, 1, 2]).astype(int); target_discrete
结果显示如下:
2000-01-01 0 2000-01-02 -1 ... 2019-12-28 -1 2019-12-29 0 2019-12-30 0 2019-12-31 0 2020-01-01 0 Freq: D, Name: PriceChange, Length: 7306, dtype: int64
我们可以使用以下代码可视化五个标签的分布:
target_discrete.plot(figsize=(12, 6), kind='hist', color='black')
结果是一个频率图,如下截图所示,显示了五个标签的出现频率:
图 6.24 – 我们的离散目标-价格变化标签值 [-2, -1, 0, 1, 2] 的频率分布
对于分类,我们使用 sklearn.ensemble.RandomForestClassifier 提供的决策树分类器集合。随机森林是一种使用装袋集成方法的分类器,并通过对从原始数据集中进行带替换的随机抽样生成的数据集训练每棵树来构建决策树森林。使用 max_depth=5 参数,我们限制了每棵树的高度以减少过拟合,然后调用 RandomForestClassifier.fit(...) 方法来拟合模型,如下所示:
from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier(max_depth=5) rf.fit(features, target_discrete)
这构建了以下 RandomForestClassifier 拟合模型:
RandomForestClassifier( bootstrap=True, ccp_alpha=0.0, class_weight=None, criterion='gini', max_depth=5, max_features='auto', max_leaf_nodes=None, max_samples=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=None, oob_score=False, random_state=None, verbose=0, warm_start=False)
RandomForestClassifier.score(...) 方法返回预测与True标签的平均准确度,如下所示:
rf.score(features, target_discrete)
正如我们在这里看到的,准确度分数为 83.5%,非常好:
0.835340815767862
我们向 DataFrame 添加 DiscretePriceChange 和 PredDiscretePriceChange 字段,以保存使用 RandomForestClassifier.predict(...) 方法的真实标签和预测标签,如下所示:
df['DiscretePriceChange'] = target_discrete df['PredDiscretePriceChange'] = rf.predict(features) df
结果如下 DataFrame,带有两个额外的字段:
... DiscretePriceChange PredDiscretePriceChange 2000-01-01 ... 0 0 2000-01-02 ... -1 -1 ... ... ... ... 2019-12-31 ... 0 -1 2020-01-01 ... 0 -1 7306 rows × 10 columns
在下面的代码块中,我们绘制了 2010 年第一季度的两个字段:
df['DiscretePriceChange'].loc['2010-01-01':'2010-03-31'].plot(figsize=(12, 6), color='darkgray', linestyle='-', legend='DiscretePriceChange') df['PredDiscretePriceChange'].loc['2010-01-01':'2010-03-31'].plot(figsize=(12, 6), color='black', linestyle='-.', legend='PredDiscretePriceChange')
这产生了一个图表,如下截图所示,其中True和预测标签之间存在一些错位:
图 6.25 – 2010 年 Q1 的 RandomForest 分类模型原始和预测离散价格变动标签的比较
我们可以使用以下代码计算和绘制ClassificationErrors DataFrame 的分布:
df['ClassificationErrors'] = \ df['DiscretePriceChange'] - df['PredDiscretePriceChange'] df['ClassificationErrors'].plot(figsize=(12, 6), kind='kde', color='black', legend='ClassificationErrors')
这产生了以下误差分布:
图 6.26 – RandomForest 分类器模型分类错误分布图
分类错误再次没有偏差,可以忽略不计。
摘要
所有先进的交易算法都使用统计模型,无论是用于直接交易规则还是只是决定何时进入/离开交易。在本章中,我们涵盖了 Python 的四个关键统计库——statsmodels、pmdarima、fbprophet 和 scikitlearn。
在下一章中,我们将讨论如何将关键的金融和经济数据导入到 Python 中。
第三部分:Python 中的算法交易
本节教你如何在 Python 中获取市场数据,如何运行基本的算法交易回测,并详细描述了关键的算法交易算法。
本节包括以下章节:
- 第七章*,Python 中的金融市场数据访问*
- 第八章*,Zipline 和 PyFolio 简介*
- 第九章*,基础算法交易策略*
Python 金融交易实用指南(三)(3)https://developer.aliyun.com/article/1523774