【译】Python 金融:算法交易 (4)回测交易策略

本文翻译自2018年最热门的Python金融教程 Python For Finance: Algorithmic Trading

本教程由以下五部分内容构成:

本文是该教程的第四部分。


既然你手头已经有了一项交易策略,最好对其进行回溯测试并计算其性能。但是,在进行深入研究之前,你可能想多了解一些相关知识,比如回溯测试中的陷阱,回测器的组成,以及可以用来回测算法的Python工具。

然而,如果你已经掌握了这些,可以跳过以下内容,直接开始实现你的回测器。

回溯测试的陷阱

回溯测试,不仅仅是“测试交易策略”,而是在相关的历史数据中测试策略,以确保该策略在你开始操作之前是实际可行的。利用回溯测试,交易人员能够模拟并分析在一段时间内,使用特定策略进行交易的风险和收益。然而,在你进行回测时,最好牢记一些刚开始可能并不起眼的陷阱。

例如,有一些外部事件肯定会影响回溯测试,如市场体制的转变,这是监管的变化或是宏观经济事件。此外,流动性约束,如禁止卖空,可能会严重影响回溯测试。

其次,你自己也可能引入陷阱。比如当你过拟合模型时(优化偏差),当你认为这样更好而忽略策略规则时(干扰),或当你偶然将信息引入过去的数据时(前视偏差)。

在你学完本教程,开始制定自己的策略并进行回测时,需要重点考虑这些陷阱。

回溯测试的构成

除了这些陷阱, 最好要知道构成回测器的四项不可或缺的组件:

  • 数据处理器,是数据集的接口。
  • 策略,基于数据产生做多或做空的信号。
  • 投资组合,生成订单并管理利润和损失(也称为“PnL”)。
  • 执行处理程序,向经纪人发送订单,并接收股票已被买入或卖出的信号。

除了这四个组件外,根据系统的复杂性,还可以向回测器中添加更多的东西。你绝对可以做的更多,而不仅仅局限于这四个组件。然而,在这篇初学者教程中,你只需专注于让这些基本组件在代码中顺利运行。

Python 工具

为了实现回溯测试,除了 Pandas 外还可以使用一些其它工具,在本教程的第一部分对数据进行金融分析时,你其实已经大量使用了这些工具。除了 Pandas,还有如 NumPy 和 SciPy,它们提供了向量化、优化和线性代数的程序,可以在开发交易策略时使用。

此外,当开发预测策略时,Python 的机器学习库 Scikit-Learn 也能派上用场,因为它提供了创建回归和分类模型所需的一切。DataCamp 的 Supervised Learning With Scikit-Learn 课程,提供了该库的介绍。然而,如果你想使用统计库进行诸如时间序列的分析,statsmodels 库是一个理想的选择。在本教程中执行普通最小二乘回归(OLS)时,你其实已经简单使用过这个库了。

最后,还有 IbPyZipLine 库。前者为 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() 函数,以确定进行何种交易操作。该函数包含 contextdata 两项参数: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中)继续使用该库。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容