本文翻译自2018年最热门的Python金融教程 Python For Finance: Algorithmic Trading。
本教程由以下五部分内容构成:
- Python金融入门
- 常见的金融分析方法
- 简单的动量策略开发
- 回溯测试策略
- 评估交易策略
本文是该教程的第四部分。
既然你手头已经有了一项交易策略,最好对其进行回溯测试并计算其性能。但是,在进行深入研究之前,你可能想多了解一些相关知识,比如回溯测试中的陷阱,回测器的组成,以及可以用来回测算法的Python工具。
然而,如果你已经掌握了这些,可以跳过以下内容,直接开始实现你的回测器。
回溯测试的陷阱
回溯测试,不仅仅是“测试交易策略”,而是在相关的历史数据中测试策略,以确保该策略在你开始操作之前是实际可行的。利用回溯测试,交易人员能够模拟并分析在一段时间内,使用特定策略进行交易的风险和收益。然而,在你进行回测时,最好牢记一些刚开始可能并不起眼的陷阱。
例如,有一些外部事件肯定会影响回溯测试,如市场体制的转变,这是监管的变化或是宏观经济事件。此外,流动性约束,如禁止卖空,可能会严重影响回溯测试。
其次,你自己也可能引入陷阱。比如当你过拟合模型时(优化偏差),当你认为这样更好而忽略策略规则时(干扰),或当你偶然将信息引入过去的数据时(前视偏差)。
在你学完本教程,开始制定自己的策略并进行回测时,需要重点考虑这些陷阱。
回溯测试的构成
除了这些陷阱, 最好要知道构成回测器的四项不可或缺的组件:
- 数据处理器,是数据集的接口。
- 策略,基于数据产生做多或做空的信号。
- 投资组合,生成订单并管理利润和损失(也称为“PnL”)。
- 执行处理程序,向经纪人发送订单,并接收股票已被买入或卖出的信号。
除了这四个组件外,根据系统的复杂性,还可以向回测器中添加更多的东西。你绝对可以做的更多,而不仅仅局限于这四个组件。然而,在这篇初学者教程中,你只需专注于让这些基本组件在代码中顺利运行。
Python 工具
为了实现回溯测试,除了 Pandas 外还可以使用一些其它工具,在本教程的第一部分对数据进行金融分析时,你其实已经大量使用了这些工具。除了 Pandas,还有如 NumPy 和 SciPy,它们提供了向量化、优化和线性代数的程序,可以在开发交易策略时使用。
此外,当开发预测策略时,Python 的机器学习库 Scikit-Learn 也能派上用场,因为它提供了创建回归和分类模型所需的一切。DataCamp 的 Supervised Learning With Scikit-Learn 课程,提供了该库的介绍。然而,如果你想使用统计库进行诸如时间序列的分析,statsmodels
库是一个理想的选择。在本教程中执行普通最小二乘回归(OLS)时,你其实已经简单使用过这个库了。
最后,还有 IbPy 和 ZipLine 库。前者为 Interactive Brokers 在线交易系统提供了Python接口:你将获得连接到 Interactive Brokers 的所有功能,如请求股票报价器数据,提交股票订单,…… ZipLine 是一个集成的 Python 回测框架,在本教程中你将使用的 Quantopian 就是基于它的。
实现简单的回测器
如前所述,一个简单的回测器包括策略、数据处理器、投资组合以及执行处理程序。在之前你已经实现了一项策略,并且也能访问数据处理器,即 pandas-datareader
库或者是从Excel读取数据的Pandas库。仍需实现的组件就剩执行处理程序和投资组合了。
但是,作为初学者,你目前还无需要专注于实现执行处理程序。相反,接下来你将看到如何开始创建用于生成订单并管理盈亏的投资组合。
在开始之前,先获取 apple 公司的股票数据,参考本教程的第一部分:基础入门。
# 获取apple公司股票数据
import pandas_datareader as pdr
import datetime
aapl = pdr.get_data_yahoo('AAPL',
start=datetime.datetime(2006, 10, 1),
end=datetime.datetime(2012, 1, 1))
然后是创建均线交叉策略,参考本教程的第三部分:用Python构建交易策略。
# 导入pandas,numpy
import pandas as pd
import numpy as np
# 初始化短期和长期窗口
short_window = 40
long_window = 100
# 初始化 `signals` 数据库,增加 `signal` 列
signals = pd.DataFrame(index=aapl.index)
signals['signal'] = 0.0
# 创建短期简单移动均值
signals['short_mavg'] = aapl['Close'] \
.rolling(window=short_window, min_periods=1, center=False) \
.mean()
# 创建长期简单移动均值
signals['long_mavg'] = aapl['Close'] \
.rolling(window=long_window, min_periods=1, center=False) \
.mean()
# 生成信号
signals['signal'][short_window:] = np.where(signals['short_mavg'][short_window:]
> signals['long_mavg'][short_window:], 1.0, 0.0)
# 生成交易命令
signals['positions'] = signals['signal'].diff()
现在让我们开始创建回溯测试中的投资组合(portfolio)。
- 首先,设置变量
initial_capital
来存储初始资金。再创建一个新的数据框positions
,并拷贝数据框signals
的索引给它,这是为了获取信号生成的时间。
- 接着,在
positions
数据框中创建新的列AAPL
。当信号(signal)为1,短期移动均线超过了长期移动均线(针对大于最短移动均值窗口的时期),这时买入100股。而对于信号为0的时段,运算100*signals['signal']
的结果是0。
- 创建新的数据框
portfolio
,存储购买股票的市场价值。
- 然后,创建数据框
pos_diff
存储股票数目的差值。
- 接下来开始真正的回溯测试:在数据框
portfolio
中创建新的一列holdings
,存储买入股票的数量与调整的收盘价的乘积。
- 另外
portfolio
中还包含一列cash
,指剩余的资金:用initial_capital
减去用于买股票的钱。
- 在
portfolio
数据框中再增加一列total
,包括了现金和所持股票总的价值。
- 最后,在
portfolio
中增加returns
列,存储获得的收益率。
# 设置初始资金
initial_capital= float(100000.0)
# 创建数据框 `positions`
positions = pd.DataFrame(index=signals.index).fillna(0.0)
# 当signal为1时,买入100股
positions['AAPL'] = 100*signals['signal']
# 用拥有的价值初始化 portfolio
portfolio = positions.multiply(aapl['Adj Close'], axis=0)
# 存储股票数目的差值
pos_diff = positions.diff()
# 在 portfolio 中增加 `holdings` 列
portfolio['holdings'] = (positions.multiply(aapl['Adj Close'], axis=0)) \
.sum(axis=1)
# 在 portfolio 中增加`cash`列
portfolio['cash'] = initial_capital \
- (pos_diff.multiply(aapl['Adj Close'], axis=0)) \
.sum(axis=1).cumsum()
# 在 portfolio 中增加`total`列
portfolio['total'] = portfolio['cash'] + portfolio['holdings']
# 在 portfolio 中增加`returns` 列
portfolio['returns'] = portfolio['total'].pct_change()
# 输出`portfolio`的前几行
print(portfolio.head())
AAPL holdings cash total returns
Date
2006-10-02 0.0 0.0 100000.0 100000.0 NaN
2006-10-03 0.0 0.0 100000.0 100000.0 0.0
2006-10-04 0.0 0.0 100000.0 100000.0 0.0
2006-10-05 0.0 0.0 100000.0 100000.0 0.0
2006-10-06 0.0 0.0 100000.0 100000.0 0.0
作为回测的最后一项练习,利用 Matplotlib 和回测的结果,可视化投资组合的价值,即 portfolio['total']
随时间变化的情况。
# 导入`pyplot`模块
import matplotlib.pyplot as plt
# 创建一幅图
fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(111, ylabel='Portfolio value in $')
# 绘制资产曲线
portfolio['total'].plot(ax=ax1, lw=2.)
ax1.plot(portfolio.loc[signals.positions == 1.0].index,
portfolio.total[signals.positions == 1.0],
'^', markersize=10, color='m')
ax1.plot(portfolio.loc[signals.positions == -1.0].index,
portfolio.total[signals.positions == -1.0],
'v', markersize=10, color='k')
# 显示绘图结果
plt.show()
注意,在本教程中,回测器以及交易策略的 Pandas 代码都是以能够轻松交互的方式编写的。在实际应用中,你可能会选择使用类这种更面向对象的设计,因为它包含了所有的逻辑。在这里能够找到相同的移动均线交叉策略在使用面向对象设计时的示例,查看这篇演示文稿,并且千万不要忘了DataCamp的Python Functions Tutorial教程。
使用 ZipLine 和 Quantopian 进行回测
现在你已经了解了如何使用 Python 中流行的数据处理包 Pandas 实现回溯测试。然而,你会发现这么做很容易出错,也可能不是每次使用时最安全的选择:尽管已利用了 Pandas 来获取结果,也需要你每次从头开始构建大部分组件。
这就是为什么人们普遍使用诸如 Quantopian 这样的回测平台来进行回溯测试。Quantopian 是一个免费的、以社区为中心的托管平台,用于构建和执行交易策略。它由 zipline 驱动 ,这是一个用于算法交易的Python库。你可以在本地使用该库,但是在这篇初学者教程中,你将使用 Quantopian 来编写并回测你的算法。不过在使用它之前,确保你已经注册并登录了该平台。
接下来,你就可以很轻松地开始了。点击“新算法”按钮来开始编写你的交易算法,或者选择已经编写好的实例代码之一,以便更好地了解其使用方法。
让我们从简单的开始,来实现一个新算法,但仍然延续移动均线交叉策略的例子,这也是zipline 快速入门指南中的标准案例。碰巧这个例子非常类似于你在前一节中实现的简单交易策略。不过,如你所见,下方的代码块和上方的屏幕截图的结构与本教程之前所示有所不同,也就是说,从 initialize()
和 handle_data()
这两个函数定义开始。
def initialize(context):
context.sym = symbol('AAPL')
context.i = 0
def handle_data(context, data):
# Skip first 300 days to get full windows
context.i += 1
if context.i < 300:
return
# Compute averages
# history() has to be called with the same params
# from above and returns a pandas dataframe.
short_mavg = data.history(context.sym, 'price', 100, '1d').mean()
long_mavg = data.history(context.sym, 'price', 300, '1d').mean()
# Trading logic
if short_mavg > long_mavg:
# order_target orders as many shares as needed to
# achieve the desired number of shares.
order_target(context.sym, 100)
elif short_mavg < long_mavg:
order_target(context.sym, 0)
# Save values for later inspection
record(AAPL=data.current(context.sym, "price"),
short_mavg=short_mavg,
long_mavg=long_mavg)
当程序开始运行并执行一次性的启动逻辑时,调用第一个函数 initialize()
。其中的 context
参数,用于存储回溯测试或在线交易中的状态,并且可以在算法的不同地方被调用;如接下来的代码所示,在第一个移动均值窗口的定义中,context
参数又出现了。通过有价证券(如股票)的符号(如AAPL
)将其查询结果赋值给 context.security
。
在模拟或在线交易中,每分钟调用一次 handle_data()
函数,以确定进行何种交易操作。该函数包含 context
和 data
两项参数:context
和刚才所说的一样,data
对象包含多种API函数,比如 current()
函数用于获取给定资产给定字段的最新数据,history()
函数用于获取历史价格和成交量的移动窗口数据。这些API函数超出了本教程的范围,因此没有在代码中显示出来。
注意输入Quantopian控制台的代码只能在该平台中工作,不能用于像Jupyter Notebook这样的本地程序中。
data
对象使你能够获取向前填充的 price
价格,如果有的话返回最后一个已知的价格,否则返回 NaN
空值。
order_target()
下订单来调整目标股份数量。如果资产中没有头寸,用完整的目标数下订单。如果资产中有头寸,用目标股份数和当下持有量的差值下订单。负目标订单将导致特定负数的欠缺头寸。
提示: 如果对函数或对象有任何其它问题,请查看 Quantopian 的帮助页面, 它涵盖的内容比本教程多得多。
当你在界面左手边的控制台中使用 initialize()
和 handle_data()
函数创建了策略(或者粘贴-复制上述代码),然后只要点击 “Build Algorithm” 按钮即可编译代码来运行回溯测试。如果点击 “Run Full Backtest” 按钮,就会运行一项完全的回溯测试,基本上和之前的 “Build Algorithm” 相同,只是返回更多的细节。无论是简单还是完全的回溯测试,都需要运行一段时间,一定要注意页面顶部的进度条!
从这里能获取更多 Quantopian 的入门知识。
注意 Quantopian 可以让你轻松上手 zipline,但是你总可以在本地(比如 Jupyter Notebook中)继续使用该库。
网友评论
initial_capital= float(100000.0)
可能我复制代码时,少复制了这一行,后面运行时说initial_capital未定义。因为我英文不好,所以把原文整段复制后可以运行了,以为译文也少。不好意思。
能给出你的出错信息吗?以及少了哪一段代码呢?