译】Python 金融:算法交易 (2)常见的金融分析方法
2019-04-16 16:41:14
本文翻译自2018年最热门的Python金融教程 Python For Finance: Algorithmic Trading。
本教程由以下五部分内容构成:
常见的金融分析方法
这是该教程的第二部分,介绍常见的金融分析方法,包括以下内容:
现在,你已经了解了所使用的股票数据,什么是时间序列数据,以及如何使用 Pandas 快速探索数据,是时候深入到常见的金融分析中了,以便可以真正开始交易策略的开发。
在本节中,你将了解更多关于收益率、移动窗口、波动率计算和普通最小二乘回归(OLS)的知识。
收益率
简单的每日百分比变化不考虑红利和其他因素,而是指在一天的交易中股票价值变化的百分比。每日百分比变化的计算很简单,只需使用 Pandas 包中的 pct_change()
函数。
# 导入apple公司股票数据import pandas_datareader as pdrimport datetimeaapl = pdr.get_data_yahoo('AAPL',start=datetime.datetime(2006, 10, 1),end=datetime.datetime(2012, 1, 1))
# 导入numpy和pandasimport numpy as npimport pandas as pd# 将aapl数据框中`Adj Close`列数据赋值给变量`daily_close`daily_close = aapl[['Adj Close']]# 计算每日收益率daily_pct_change = daily_close.pct_change()# 用0填补缺失值NAdaily_pct_change.fillna(0, inplace=True)# 查看每日收益率的前几行print(daily_pct_change.head())
Adj CloseDate 2006-10-02 0.0000002006-10-03 -0.0104192006-10-04 0.0175482006-10-05 -0.0072962006-10-06 -0.008152
# 计算每日对数收益率daily_log_returns = np.log(daily_close.pct_change()+1)# 查看每日对数收益率的前几行print(daily_log_returns.head())
Adj CloseDate 2006-10-02 NaN2006-10-03 -0.0104742006-10-04 0.0173962006-10-05 -0.0073232006-10-06 -0.008186
注意,计算对数收益率可以使你更好地了解回报随时间的增长。
很好,你知道了如何计算每日百分比变化,但如果想要知道月度或季度的收益率,该怎么办呢?在这种情况下,让我们回顾本教程第一部分中介绍的 resample()
函数。
# 按营业月对 `aapl` 数据进行重采样,取每月最后一项monthly = aapl.resample('BM').apply(lambda x: x[-1])# 计算每月的百分比变化,并输出前几行print(monthly.pct_change().head())
High Low Open Close Volume Adj CloseDate 2006-10-31 NaN NaN NaN NaN NaN NaN2006-11-30 0.134672 0.134987 0.132106 0.130488 0.735854 0.1304892006-12-29 -0.078550 -0.084560 -0.089578 -0.074405 0.236584 -0.0744062007-01-31 0.007026 0.011876 0.010840 0.010490 -0.204714 0.0104902007-02-28 -0.004651 -0.016005 -0.021918 -0.013064 0.074066 -0.013064
# 按季度对`aapl`数据进行重采样,将均值最为每季度的数值quarter = aapl.resample("3M").mean()# 计算每季度的百分比变化,并输出前几行print(quarter.pct_change().head())
High Low Open Close Volume Adj CloseDate 2006-10-31 NaN NaN NaN NaN NaN NaN2007-01-31 0.130579 0.124445 0.129591 0.126389 0.452239 0.1263892007-04-30 0.034003 0.039993 0.035453 0.037156 -0.256056 0.0371562007-07-31 0.357706 0.347572 0.354985 0.353929 0.429955 0.3539292007-10-31 0.215316 0.211005 0.213942 0.215416 0.044101 0.215416
使用 pct_change()
相当方便,但也让人困惑到底每日的百分比是如何计算的。这也是为什么人们会使用 Pandas 的 shift()
函数来代替 pct_change()
。用daily_close
除以 daily_close.shift(1)
,然后再减 1
,就得到了每日百分比变化。然而,使用这一函数会使计算得到的数据框的开头存在缺失值 NA
。
提示:将以下代码的结果和之前计算的每日百分比变化相比较,查看这两种方法的差异。
# 每日收益率daily_pct_change = daily_close / daily_close.shift(1) - 1# 输出 `daily_pct_change`的前几行print(daily_pct_change.head())
Adj CloseDate 2006-10-02 NaN2006-10-03 -0.0104192006-10-04 0.0175482006-10-05 -0.0072962006-10-06 -0.008152
提示:在IPython控制台中尝试使用 Pandas 的 shift()
函数计算每日对数收益率。(如果你找不到答案,试一下这行代码:daily_log_returns_shift = np.log(daily_close / daily_close.shift(1))
)。
做为参考,每日百分比变化的计算公式为:
,其中
是价格,
是时间(这里是天),
是收益率。
此外,让我们来绘制每日百分比变化 daily_pct_change
的分布。
# 导入 matplotlibimport matplotlib.pyplot as plt# 绘制直方图daily_pct_change.hist(bins=50)# 显示图plt.show()# 输出daily_pct_change的统计摘要print(daily_pct_change.describe())
Adj Closecount 1322.000000mean 0.001566std 0.023992min -0.17919525% -0.01067250% 0.00167775% 0.014306max 0.139050
以上分布看起来非常对称且像中心在0.00附近的正态分布。尽管如此,你还是需要对daily_pct_change
使用describe()
函数,以确保正确解读了直方图。从统计摘要的结果中知道,其均值非常接近0.00,标准差是0.02。同时,查看百分位数,了解有多少数据点落在-0.010672, 0.001677 和 0.014306之间。
累积日收益率有助于定期确定投资价值。可以使用每日百分比变化的数值来计算累积日收益率,只需将其加上1
并计算累积的乘积。
# 计算累积日收益率cum_daily_return = (1 + daily_pct_change).cumprod()# 输出 `cum_daily_return` 的前几行print(cum_daily_return.head())
Adj CloseDate 2006-10-02 NaN2006-10-03 0.9895812006-10-04 1.0069462006-10-05 0.9996002006-10-06 0.991451
注意仍旧可以使用 Matplotlib 快速绘制 cum_daily_return
的曲线;只需对其加上 plot()
函数即可,可选择使用参数 figsize
设置图片大小。
# 绘制累积日收益率曲线cum_daily_return.plot(figsize=(12,8))# 显示绘图plt.show()
非常简单,不是吗?现在,如果你不想使用日回报率,而是用月回报率,那么对 cum_daily_return
使用 resample()
函数就可轻松实现月度水平的统计:
# 将累积日回报率转换成累积月回报率cum_monthly_return = cum_daily_return.resample("M").mean()# 输出 `cum_monthly_return` 的前几行print(cum_monthly_return.head())
Adj CloseDate 2006-10-31 1.0317102006-11-30 1.1400582006-12-31 1.1551102007-01-31 1.1873032007-02-28 1.145176
知道如何计算回报率是一项非常有用的技能,但是如果没有将其与其他股票进行比较,就没有太大的意义。这就是为什么案例中经常会比较多只股票。在本节接下来的内容中,我们将从雅虎财经中获取更多的数据以便能比较不同股票的日收益率。
注意,接下来的工作需要你对Pandas有更深入的理解以及知道如何使用Pandas操作数据。
让我们开始吧!首先从雅虎财经中获取更多的数据。通过创建一个 get()
函数可以轻松地实现这一点。该函数将股票代码列表 tickers
以及开始和结束日期作为输入参数。第二个函数 data()
将 ticker
作为输入,用于获取 startdate
和 enddate
日期之间的股票数据并将其返回。将 tickers
列表中的元素通过 map()
函数映射,获取所有股票的数据并将它们合并在一个数据框中。
以下代码获取了 Apple、Microsoft、IBM 和 Google 的股票数据,并将它们合并在一个大的数据框中。
import pandas_datareader as pdrimport datetimedef get(tickers, startdate, enddate):def data(ticker):return (pdr.get_data_yahoo(ticker, start=startdate, end=enddate))datas = map (data, tickers)return(pd.concat(datas, keys=tickers, names=['Ticker', 'Date']))tickers = ['AAPL', 'MSFT', 'IBM', 'GOOG']all_data = get(tickers, datetime.datetime(2006, 10, 1), datetime.datetime(2012, 1, 1))
注意这一代码源自 “Mastering Pandas for Finance” 一书,并且在本教程中根据新的标准进行了升级。还是要注意,因为开发人员仍在研究从雅虎财经API中获取数据的更持久的修复方案,你可能还是需要导入 fix_yahoo_finance
包。可以从此处找到安装说明或者查看这篇教程的Jupyter notebook。
现在,查看以上获取的数据:
print(all_data.head())
High Low Open Close Volume \Ticker DateAAPL 2006-10-02 10.838572 10.614285 10.728572 10.694285 178159800.0 2006-10-03 10.707143 10.455714 10.635715 10.582857 197677200.0 2006-10-04 10.780000 10.451428 10.585714 10.768572 207270700.0 2006-10-05 10.880000 10.590000 10.647142 10.690000 170970800.0 2006-10-06 10.720000 10.544286 10.631429 10.602858 116739700.0 Adj CloseTicker DateAAPL 2006-10-02 7.161565 2006-10-03 7.086947 2006-10-04 7.211311 2006-10-05 7.158698 2006-10-06 7.100338
接下来让我们使用这个大的数据框做一些有趣的图表:
# 选取 `Adj Close` 这一列并变换数据框daily_close_px = all_data[['Adj Close']].reset_index().pivot('Date', 'Ticker', 'Adj Close')# 对`daily_close_px` 计算每日百分比变化daily_pct_change = daily_close_px.pct_change()# 绘制分布直方图daily_pct_change.hist(bins=50, sharex=True, figsize=(12,8))# 显示绘图结果plt.show()
另一类有用的图是散点矩阵图。使用 pandas
库能够轻易实现它。在代码中加入 scatter_matrix()
函数就可以绘制散点矩阵图。将 daily_pct_change
作为参数传递给该函数,在对角线上使用核密度估计(KDE)做图。另外,使用 alpha
参数设置透明度,figsize
参数设置图片大小。
# 对 `daily_pct_change` 数据绘制散点矩阵图pd.plotting.scatter_matrix(daily_pct_change, diagonal='kde', alpha=0.1,figsize=(12,12))# 显示绘图结果plt.show()
注意如果你在本地运行代码,可能需要使用 plotting
模块来绘制散点矩阵图(例如 pd.plotting.scatter_matrix()
)。而且,最好要知道核密度估计图估算了随机变量的概率密度函数。
恭喜你成功地完成了第一项常见的金融分析:收益率探索。现在让我们进入下一个主题:移动窗口。
移动窗口
移动窗口指的是在一特定的时间窗口内计算数据的统计量,并在数据中按特定的间隔滑动窗口。这样,只要窗口在时间序列的日期内不断滑动,统计量就被连续的计算。
在Pandas中有许多函数都可以计算移动窗口,比如 rolling_mean()
、rolling_std()
…… 在这里查看这些函数。
然而,注意这些函数大多数将被弃用,所以最好将 rolling()
函数和 mean()
、std()
…… 结合使用,当然也依赖于实际要计算的移动窗口的种类。
但是,移动窗口到底意味着什么呢?
当然确切的含义取决于对数据使用的统计量。例如,移动平均值平滑了数据中的短期波动并突出了长期趋势。
# 选取调整的收盘价 adj_close_px = aapl['Adj Close']# 计算移动均值moving_avg = adj_close_px.rolling(window=40).mean()# 查看后十项结果print(moving_avg[-10:])
Date2011-12-16 37.1861612011-12-19 37.1606662011-12-20 37.1371802011-12-21 37.1340222011-12-22 37.1291192011-12-23 37.1258672011-12-27 37.1296452011-12-28 37.1245272011-12-29 37.1451192011-12-30 37.163272Name: Adj Close, dtype: float64
提示:在IPython控制台中尝试Pandas包中其他标准的移动窗口函数,比如 rolling_max()
、 rolling_var()
或者 rolling_median()
。注意也可以结合使用rolling()
和max()
、 var()
或 median()
来得到相同的结果。
当然,你可能没能真正理解这一切。也许使用 Matplotlib 做一幅简单的图,可以帮助你理解移动平均值及其实际的含义:
# 短期的移动窗口aapl['42'] = adj_close_px.rolling(window=40).mean()# 长期的移动窗口aapl['252'] = adj_close_px.rolling(window=252).mean()# 绘制调整的收盘价,同时包含短期和长期的移动窗口均值aapl[['Adj Close', '42', '252']].plot()# 显示绘图结果plt.show()
波动率计算
股票的波动率衡量了股票在特定时间内收益率的变化。常常将一只股票的波动率和另一只股票比较,以寻找风险较小的股票;或是将之与市场指数比较,来检查股票在整个市场上的波动。一般来说,波动率越高,该股票的投资风险更大,导致人们选择投资其他股票。
对数收益率的历史移动标准差,也就是历史移动波动率,可能更令人感兴趣。也可以使用 pd.rolling_std(data, window=x) * math.sqrt(window)
来计算。
# 定义最小周期 min_periods = 75 # 计算波动率vol = daily_pct_change.rolling(min_periods).std() * np.sqrt(min_periods)# 绘制波动率曲线vol.plot(figsize=(10, 8))# 显示绘图结果plt.show()
通过计算股票百分比变化的移动窗口标准差得到波动率。从上述代码中可以清楚地看到这一点。
注意窗口的大小能够改变整体的结果:如果扩大窗口(也就是让min_periods
变大),结果将变得不那么有代表性。如果缩小窗口,结果将更接近于标准差。
考虑到所有这些,你会发现基于数据采样频率得到合适的窗口大小绝对是一项技能。
普通最小二乘回归
完成了上述所有计算后,你还可以使用更传统的回归分析(比如普通最小二乘回归(OLS)),对金融数据进行更多的统计分析。
要做到这一点,你必须使用 statsmodels
库,它不仅提供了用于估算多种统计模型的类和函数,还能让你进行统计检验以及统计的数据探索。
注意你确实可以使用Pandas实现OLS回归,但是在将来的版本中其 ols
模块将被弃用。所以最明智的做法是使用 statsmodels
包。
# 导入`statsmodels` 包的 `api` 模块,设置别名 `sm`import statsmodels.api as sm# 获取调整的收盘价数据all_adj_close = all_data[['Adj Close']]# 计算对数收益率all_returns = np.log(all_adj_close / all_adj_close.shift(1))# 提取苹果公司数据 aapl_returns = all_returns.iloc[all_returns.index.get_level_values('Ticker') == 'AAPL']aapl_returns.index = aapl_returns.index.droplevel('Ticker')# 提取微软公司数据msft_returns = all_returns.iloc[all_returns.index.get_level_values('Ticker') == 'MSFT']msft_returns.index = msft_returns.index.droplevel('Ticker')# 使用 aapl_returns 和 msft_returns 创建新的数据框return_data = pd.concat([aapl_returns, msft_returns], axis=1)[1:]return_data.columns = ['AAPL', 'MSFT']# 增加常数项X = sm.add_constant(return_data['AAPL'])# 创建模型model = sm.OLS(return_data['MSFT'],X).fit()# 输出模型的摘要信息print(model.summary())
OLS Regression Results ==============================================================================Dep. Variable: MSFT R-squared: 0.281Model: OLS Adj. R-squared: 0.280Method: Least Squares F-statistic: 515.5Date: Mon, 19 Nov 2018 Prob (F-statistic): 1.32e-96Time: 09:30:16 Log-Likelihood: 3514.0No. Observations: 1322 AIC: -7024.Df Residuals: 1320 BIC: -7014.Df Model: 1 Covariance Type: nonrobust ==============================================================================coef std err t P>|t| [0.025 0.975]------------------------------------------------------------------------------const -0.0005 0.000 -1.119 0.263 -0.001 0.000AAPL 0.4407 0.019 22.704 0.000 0.403 0.479==============================================================================Omnibus: 268.593 Durbin-Watson: 2.074Prob(Omnibus): 0.000 Jarque-Bera (JB): 7029.415Skew: -0.211 Prob(JB): 0.00Kurtosis: 14.289 Cond. No. 41.6==============================================================================Warnings:[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
注意在合并 AAPL 和 MSFT 收益率数据时,使用了 [1:]
切片,这样就没有缺失值来扰乱你的模型了。
当你研究模型摘要结果时,请注意以下内容:
Dep. Variable
指出哪个变量是模型的响应。Model
是拟合中使用的模型,在本案例中是OLS
。另外,
Method
指出模型参数是如何被计算的。在本案例中被设置为Least Squares
。
目前为止,还没有出现任何新的信息,这些都已经在上述代码中设置了。然而,也有另一些令人感兴趣的项,比如:
观测量的数目(
No. Observations
)。注意你也可以使用Pandas包中的info()
函数得到它,只要在 IPython 控制台中运行return_data.info()
。残差的自由度(
DF Residuals
)DF Model
指模型参数的数目,注意它并不包括代码中定义的X
的常数项。
这基本上是左边栏的内容。右边栏给出了更多关于拟合的信息。例如你可以看到:
R-squared
是决定系数。这个分数表明回归线接近真实数据点的程度。在本例中,结果是 0.281,用百分比表示该分数是 28.1% 。当决定系数是 0% 时,表明模型完全不能解释响应数据在其均值附近的变异性。当然,当分数为 100% 时情况恰恰相反。Adj. R-squared
分数乍看上去和R-squared
的数值差不多。然而,该度量背后的计算是基于观测量的数目和残差的自由度调整了R-squared
的值。在本例中这一调整没有起到多少作用,致使两者的结果相近。F-statistic
衡量该拟合的显著性。通过将模型的均方误差除以残差的均方误差来计算。Prob (F-statistic)
指得到上述F-statistic
结果的概率,假设零假设认为它们是无关的。Log-Likelihood
指的是似然函数的对数。AIC
是赤池信息量准则,这一指标根据观测量的数目和模型的复杂性,对对数似然度进行了调整。最后,
BIC
或者是贝叶斯信息准则,类似于上述AIC
,但是它用更多的参数更严格地惩罚模型。
在模型摘要的第一部分下方,汇报了模型的每一项系数:
coef
表示系数的估计值。std err
是系数估计的标准误差。t
代表t统计量。该度量用于测量系数的统计显著性。P>|t|
表示系数等于0为真的零假设。如果该值小于置信水平(通常是0.05),则表明该系数对应的项和响应之间存在统计学上的显著关系。
最后,在模型摘要的最后部分,你将看到用于评估残差分布的其他统计检验:
Omnibus
是 Omnibus D’Angostino 检验,它为偏斜和峰度的存在提供了组合的统计检验。Prob(Omnibus)
将Omnibus
度量转变成了概率。其次,
Skew
或偏斜,测量数据关于均值的对称性。Kurtosis
给出了分布形状的指示,因为它比较了接近均值的数据量和远离均值(在尾部)的数据量。Durbin-Watson
是对自相关的存在的检验,Jarque-Bera (JB)
是另一项对偏斜和峰度(kurtosis)的检验。也可以将其结果转换成概率,即为Prob (JB)
。最后,
Cond. No
是对多重共线性的检验。
使用 Matplotlib 绘制普通最小二乘回归拟合的直线。
# 绘制 AAPL 和 MSFT 收益率的散点图plt.plot(return_data['AAPL'], return_data['MSFT'], 'r.')# 增加坐标轴ax = plt.axis()# 初始化 `x`x = np.linspace(ax[0], ax[1] + 0.01)# 绘制回归线plt.plot(x, model.params[0] + model.params[1] * x, 'b', lw=2)# 定制此图plt.grid(True)plt.axis('tight')plt.xlabel('Apple Returns')plt.ylabel('Microsoft returns')# 输出此图plt.show()
注意也可以使用收益率的移动相关性对结果进行核查。只需对滚动相关性的结果调用 plot()
函数:
# 绘制滚动相关性return_data['MSFT'].rolling(window=252).corr(return_data['AAPL']).plot()# 显示该图plt.show()
作者:鱼心DrFish
链接:https://www.jianshu.com/p/bd40080d2a69
来源:简书