入门指南

基本概念

Zipline 是一个用 Python 开发的开源的算法交易框架。

源代码可以在此查看: https://github.com/quantopian/zipline

主要优点有:

  • 模拟现实:滑点、手续费、延时成交。
  • 流式处理:单独处理每个事件,避免未来函数。
  • 丰富的库:技术指标(如移动平均)、风险评估(如夏普比率)。
  • Quantopian 开发和持续维护, 为 Zipline 提供易于使用的网络界面。Quantopian 还提供 10 年的分钟级美国股市数据和实时交易功能。本教程针对不使用 Quantopian 的 Zipline 用户。如果想了解 Quantopian,请参阅 此处

本教程假设您已正确安装了 Zipline,如果尚未安装 Zipline ,请参阅 安装指南

每个 zipline 策略都必须包含以下两个函数:

  • initialize(context)
  • handle_data(context, data)

在执行策略之前, zipline 调用 initialize() 函数并传入一个 context 变量。context 是一个持久变量,用于存储策略迭代中需要访问的数据。

策略被初始化之后,zipline 为每个事件调用一次 handle_data() 函数。 每次调用时,都会传入相同的 context 变量和 data 事件框架,data 中含有当前开盘价、最高价、最低价、收盘价(OHLC)和成交量的数据。有关这些函数的更多信息,请参阅 Quantopian 相关文档.

第一个策略

让我们看下 examples 文件夹中的一个简单例子,buyapple.py

from zipline.api import order, record, symbol


def initialize(context):
    pass


def handle_data(context, data):
    order(symbol('AAPL'), 10)
    record(AAPL=data.current(symbol('AAPL'), 'price'))

可以看到,首先我们引入一些需要的函数。策略中所有常用的函数可以在 zipline.api 中找到。 这里的 order() 函数接受两个参数:一个股票代码,一个数量(为负则为卖出或做空)。 在例子中,每次迭代我们买入 10 股 APPL 股票。更多 order() 信息,可参阅 Quantopian 文档

record() 函数能在每一次迭代的时候记录一些临时数据。 第一个参数接受 变量名=变量值 的这样参数。回测结束进行分析的时候,能够通过变量名访问到 这些记录的数据(下面会有示例)。您还可以看到如何在 data 事件框架中访问之前记录的 AAPL 股票的价格数据(更多信息请访问 这里 )。

运行策略

现在要在价格数据上面运行这个策略, zipline 提供三种接口:命令行、IPython Notebookrun_algorithm()

获取数据

如果您还没有拿到数据,您将需要 Quandl 的 API key 来获取默认数据。然后运行:

$ QUANDL_API_KEY=<yourkey> zipline ingest [-b <bundle>]

<bundle> 是要获取数据的名称,默认是 quandl

您可以查看 获取数据 获取更多信息。

命令行

安装 Zipline 之后,您就可以在命令行中运行以下命令了(在 Windows 中使用 cmd.exe ,在 macOS 中使用 Terminal App):

$ zipline run --help
Usage: zipline run [OPTIONS]

Run a backtest for the given algorithm.

Options:
 -f, --algofile FILENAME         The file that contains the algorithm to run.
 -t, --algotext TEXT             The algorithm script to run.
 -D, --define TEXT               Define a name to be bound in the namespace
                                 before executing the algotext. For example
                                 '-Dname=value'. The value may be any python
                                 expression. These are evaluated in order so
                                 they may refer to previously defined names.
 --data-frequency [daily|minute]
                                 The data frequency of the simulation.
                                 [default: daily]
 --capital-base FLOAT            The starting capital for the simulation.
                                 [default: 10000000.0]
 -b, --bundle BUNDLE-NAME        The data bundle to use for the simulation.
                                 [default: quandl]
 --bundle-timestamp TIMESTAMP    The date to lookup data on or before.
                                 [default: <current-time>]
 -s, --start DATE                The start date of the simulation.
 -e, --end DATE                  The end date of the simulation.
 -o, --output FILENAME           The location to write the perf data. If this
                                 is '-' the perf will be written to stdout.
                                 [default: -]
 --trading-calendar TRADING-CALENDAR
                                 The calendar you want to use e.g. LSE. NYSE
                                 is the default.
 --print-algo / --no-print-algo  Print the algorithm to stdout.
 --help                          Show this message and exit.

您可以看到,由很多可以设置的参数,如 -f 设置使用的策略, -b 设置使用的数据(默认为 quandl)。 也可以用 --start--end 指定回测时间区间。 您还可以保存回测结果以便后续分析,方法是用 --output 参数指定文件名,结果将以 DateFrame 的格式保存到 Python 的 pickle 文件内。您还可以将配置写到配置文件里,然后用 -c 指定配置文件的路径,这样就不用每次都指定各种参数了(请参阅示例目录中的 .conf 文件)。

要执行上面的算法,并把结果保存到 buyapple_out.pickle ,可以像这样调用 zipline run

zipline run -f ../../zipline/examples/buyapple.py --start 2016-1-1 --end 2018-1-1 -o buyapple_out.pickle
AAPL
[2018-01-03 04:30:50.150039] WARNING: Loader: Refusing to download new benchmark data because a download succeeded at 2018-01-03 04:01:34+00:00.
[2018-01-03 04:30:50.191479] WARNING: Loader: Refusing to download new treasury data because a download succeeded at 2018-01-03 04:01:35+00:00.
[2018-01-03 04:30:51.843465] INFO: Performance: Simulated 503 trading days out of 503.
[2018-01-03 04:30:51.843598] INFO: Performance: first open: 2016-01-04 14:31:00+00:00
[2018-01-03 04:30:51.843672] INFO: Performance: last close: 2017-12-29 21:00:00+00:00

run 首先调用 initialize() 函数,然后逐条将股票价格传入 handle_data() 。 每次调用 handle_data() 后,都会买入 10 股 AAPL 。 调用 order() 后, Zipline 记录下单信息并送入盘口。 在 handle_data() 函数结束后, Zipline 查找未成交订单,并进行撮合交易。 如果市场容量足够容纳您的订单,则会在添加佣金并应用滑点模型后成交订单。 滑点模型会模拟您的订单对市场价格的影响,因此收取的费用不仅仅是股票价格 * 10 。 (您可以更改 Zipline 使用的佣金和滑点模型,请参阅 Quantopian 文档 获取更多信息)。

让我们看一下保存的 DataFrame 结果。我们在 IPython Notebook 中用 pandas 加载结果并打印前 10 行。 Zipline 大量使用 pandas ,特别是对于数据输入和输出这部分,所以值得花一些时间来学习。

import pandas as pd
perf = pd.read_pickle('buyapple_out.pickle') # read in perf DataFrame
perf.head()
AAPL algo_volatility algorithm_period_return alpha benchmark_period_return benchmark_volatility beta capital_used ending_cash ending_exposure ending_value excess_return gross_leverage long_exposure long_value longs_count max_drawdown max_leverage net_leverage orders period_close period_label period_open pnl portfolio_value positions returns sharpe short_exposure short_value shorts_count sortino starting_cash starting_exposure starting_value trading_days transactions treasury_period_return
2016-01-04 21:00:00+00:00 105.35 NaN 0.000000e+00 NaN -0.013983 NaN NaN 0.0 10000000.0 0.0 0.0 0.0 0.000000 0.0 0.0 0 0.000000e+00 0.0 0.000000 [{\'dt\': 2016-01-04 21:00:00+00:00, \'reason\': N... 2016-01-04 21:00:00+00:00 2016-01 2016-01-04 14:31:00+00:00 0.0 10000000.0 [] 0.000000e+00 NaN 0 0 0 NaN 10000000.0 0.0 0.0 1 [] 0.0
2016-01-05 21:00:00+00:00 102.71 0.000001 -1.000000e-07 -0.000022 -0.012312 0.175994 -0.000006 -1028.1 9998971.9 1027.1 1027.1 0.0 0.000103 1027.1 1027.1 1 -1.000000e-07 0.0 0.000103 [{\'dt\': 2016-01-05 21:00:00+00:00, \'reason\': N... 2016-01-05 21:00:00+00:00 2016-01 2016-01-05 14:31:00+00:00 -1.0 9999999.0 [{\'sid\': Equity(8 [AAPL]), \'last_sale_price\': ... -1.000000e-07 -11.224972 0 0 0 -11.224972 10000000.0 0.0 0.0 2 [{\'order_id\': \'4011063b5c094e82a5391527044098b... 0.0
2016-01-06 21:00:00+00:00 100.70 0.000019 -2.210000e-06 -0.000073 -0.024771 0.137853 0.000054 -1008.0 9997963.9 2014.0 2014.0 0.0 0.000201 2014.0 2014.0 1 -2.210000e-06 0.0 0.000201 [{\'dt\': 2016-01-06 21:00:00+00:00, \'reason\': N... 2016-01-06 21:00:00+00:00 2016-01 2016-01-06 14:31:00+00:00 -21.1 9999977.9 [{\'sid\': Equity(8 [AAPL]), \'last_sale_price\': ... -2.110000e-06 -9.823839 0 0 0 -9.588756 9998971.9 1027.1 1027.1 3 [{\'order_id\': \'3bf9fe20cc46468d99f741474226c03... 0.0
2016-01-07 21:00:00+00:00 96.45 0.000064 -1.081000e-05 0.000243 -0.048168 0.167868 0.000300 -965.5 9996998.4 2893.5 2893.5 0.0 0.000289 2893.5 2893.5 1 -1.081000e-05 0.0 0.000289 [{\'dt\': 2016-01-07 21:00:00+00:00, \'reason\': N... 2016-01-07 21:00:00+00:00 2016-01 2016-01-07 14:31:00+00:00 -86.0 9999891.9 [{\'sid\': Equity(8 [AAPL]), \'last_sale_price\': ... -8.600019e-06 -10.592737 0 0 0 -9.688947 9997963.9 2014.0 2014.0 4 [{\'order_id\': \'6af6aed9fbb44a6bba17e802051b94d... 0.0
2016-01-08 21:00:00+00:00 96.96 0.000063 -9.380000e-06 0.000466 -0.058601 0.145654 0.000311 -970.6 9996027.8 3878.4 3878.4 0.0 0.000388 3878.4 3878.4 1 -1.081000e-05 0.0 0.000388 [{\'dt\': 2016-01-08 21:00:00+00:00, \'reason\': N... 2016-01-08 21:00:00+00:00 2016-01 2016-01-08 14:31:00+00:00 14.3 9999906.2 [{\'sid\': Equity(8 [AAPL]), \'last_sale_price\': ... 1.430015e-06 -7.511729 0 0 0 -7.519659 9996998.4 2893.5 2893.5 5 [{\'order_id\': \'18f64975732449a18fca06e9c69bf5c... 0.0

可以看到,从 2016 年第一个交易日起,每天都有一条记录。在每列中可以找到策略表现的各类信息。 第一列的 AAPL 的值由 record() 写入,方便后续绘制图线,比较策略收益与股票价格的表现。

%pylab inline
figsize(12, 12)
import matplotlib.pyplot as plt

ax1 = plt.subplot(211)
perf.portfolio_value.plot(ax=ax1)
ax1.set_ylabel('Portfolio Value')
ax2 = plt.subplot(212, sharex=ax1)
perf.AAPL.plot(ax=ax2)
ax2.set_ylabel('AAPL Stock Price')
Populating the interactive namespace from numpy and matplotlib
<matplotlib.text.Text at 0x10c48c198>
_images/tutorial_11_2.png

portfolio_value 表示我们策略的收益,可以看到和 AAPL 的价格走势很接近。 这很好理解,因为只是简单买入。

IPython Notebook

IPython Notebook 是一个非常强大的、 基于浏览器界面的 Python 解释器(本教程就是基于它而写)。它已经是大多数宽客的事实上的开发环境, Zipline 提供了一种不使用命令行就能在 Notebook 中运行策略的方法。

使用时,将策略写在 Notebook 的一个 cell 中,并且让 Zipline 知道将要运行这个策略。 方法是在 import zipline 使用后 %%zipline 这个 IPython 魔术方法。 这个魔术方法采用与上面命令行相同的参数。为了和上面命令行的例子保持一致,在 import zipline 之后,运行下面的 cell 。

%load_ext zipline
%%zipline --start 2016-1-1 --end 2018-1-1
from zipline.api import symbol, order, record

def initialize(context):
    pass

def handle_data(context, data):
    order(symbol('AAPL'), 10)
    record(AAPL=data[symbol('AAPL')].price)

注意我们并没有指定输入文件,因为魔术方法将使用 cell 内的内容,并在那里找到你的策略算法。 此外,我们也没有定义输出文件,而是指定了 -o 参数,他将在环境创建一个包含像上面那样 结果的 DataFrame 变量。

_.head()
AAPL algo_volatility algorithm_period_return alpha benchmark_period_return benchmark_volatility beta capital_used ending_cash ending_exposure ending_value excess_return gross_leverage long_exposure long_value longs_count max_drawdown max_leverage net_leverage orders period_close period_label period_open pnl portfolio_value positions returns sharpe short_exposure short_value shorts_count sortino starting_cash starting_exposure starting_value trading_days transactions treasury_period_return
2016-01-04 21:00:00+00:00 105.35 NaN 0.000000e+00 NaN -0.013983 NaN NaN 0.00 10000000.00 0.0 0.0 0.0 0.000000 0.0 0.0 0 0.000000e+00 0.0 0.000000 [{\'created\': 2016-01-04 21:00:00+00:00, \'reaso... 2016-01-04 21:00:00+00:00 2016-01 2016-01-04 14:31:00+00:00 0.00 10000000.00 [] 0.000000e+00 NaN 0 0 0 NaN 10000000.00 0.0 0.0 1 [] 0.0
2016-01-05 21:00:00+00:00 102.71 1.122497e-08 -1.000000e-09 -2.247510e-07 -0.012312 0.175994 -6.378047e-08 -1027.11 9998972.89 1027.1 1027.1 0.0 0.000103 1027.1 1027.1 1 -9.999999e-10 0.0 0.000103 [{\'created\': 2016-01-04 21:00:00+00:00, \'reaso... 2016-01-05 21:00:00+00:00 2016-01 2016-01-05 14:31:00+00:00 -0.01 9999999.99 [{\'amount\': 10, \'cost_basis\': 102.711000000000... -1.000000e-09 -11.224972 0 0 0 -11.224972 10000000.00 0.0 0.0 2 [{\'dt\': 2016-01-05 21:00:00+00:00, \'order_id\':... 0.0
2016-01-06 21:00:00+00:00 100.70 1.842654e-05 -2.012000e-06 -4.883861e-05 -0.024771 0.137853 5.744807e-05 -1007.01 9997965.88 2014.0 2014.0 0.0 0.000201 2014.0 2014.0 1 -2.012000e-06 0.0 0.000201 [{\'created\': 2016-01-05 21:00:00+00:00, \'reaso... 2016-01-06 21:00:00+00:00 2016-01 2016-01-06 14:31:00+00:00 -20.11 9999979.88 [{\'amount\': 20, \'cost_basis\': 101.706000000000... -2.011000e-06 -9.171989 0 0 0 -9.169708 9998972.89 1027.1 1027.1 3 [{\'dt\': 2016-01-06 21:00:00+00:00, \'order_id\':... 0.0
2016-01-07 21:00:00+00:00 96.45 6.394658e-05 -1.051300e-05 2.633450e-04 -0.048168 0.167868 3.005102e-04 -964.51 9997001.37 2893.5 2893.5 0.0 0.000289 2893.5 2893.5 1 -1.051300e-05 0.0 0.000289 [{\'created\': 2016-01-06 21:00:00+00:00, \'reaso... 2016-01-07 21:00:00+00:00 2016-01 2016-01-07 14:31:00+00:00 -85.01 9999894.87 [{\'amount\': 30, \'cost_basis\': 99.9543333333335... -8.501017e-06 -10.357397 0 0 0 -9.552189 9997965.88 2014.0 2014.0 4 [{\'dt\': 2016-01-07 21:00:00+00:00, \'order_id\':... 0.0
2016-01-08 21:00:00+00:00 96.96 6.275294e-05 -8.984000e-06 4.879306e-04 -0.058601 0.145654 3.118401e-04 -969.61 9996031.76 3878.4 3878.4 0.0 0.000388 3878.4 3878.4 1 -1.051300e-05 0.0 0.000388 [{\'created\': 2016-01-07 21:00:00+00:00, \'reaso... 2016-01-08 21:00:00+00:00 2016-01 2016-01-08 14:31:00+00:00 15.29 9999910.16 [{\'amount\': 40, \'cost_basis\': 99.2060000000002... 1.529016e-06 -7.215497 0 0 0 -7.301134 9997001.37 2893.5 2893.5 5 [{\'dt\': 2016-01-08 21:00:00+00:00, \'order_id\':... 0.0

使用 history 回看价格数据

示例:双移动平均线交叉

双移动平均线交叉(DMA)一个经典的动量策略。要求较高的交易者可能已经不再使用它了, 但它仍旧有一些启发性。基本思想是引入两条移动平均线(mavg),一条长周期的捕捉长期趋势, 一条短周期的捕获短期趋势。短线上穿长线,我们认为将持续上涨,因此做多。下穿的时候我们 认为还将下跌,因此卖出。

因为计算移动平均线需要用到历史数据,因此引入一个新概念:History 。

data.history() 函数可以让你很方便的获取历史数据。第一个参数是你需要获取的数据条数, 第二个是时间周期(如 1d1m,使用 1m 时要提供分钟级数据)。有关 history() 更多说明,请参阅 Quantopian 文档 。 让我们看一个策略的例子:

%%zipline --start 2014-1-1 --end 2018-1-1 -o dma.pickle


from zipline.api import order_target, record, symbol
import matplotlib.pyplot as plt

def initialize(context):
    context.i = 0
    context.asset = symbol('AAPL')


def handle_data(context, data):
    # Skip first 300 days to get full windows
    context.i += 1
    if context.i < 300:
        return

    # Compute averages
    # data.history() has to be called with the same params
    # from above and returns a pandas dataframe.
    short_mavg = data.history(context.asset, 'price', bar_count=100, frequency="1d").mean()
    long_mavg = data.history(context.asset, 'price', bar_count=300, frequency="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.asset, 100)
    elif short_mavg < long_mavg:
        order_target(context.asset, 0)

    # Save values for later inspection
    record(AAPL=data.current(context.asset, 'price'),
           short_mavg=short_mavg,
           long_mavg=long_mavg)


def analyze(context, perf):
    fig = plt.figure()
    ax1 = fig.add_subplot(211)
    perf.portfolio_value.plot(ax=ax1)
    ax1.set_ylabel('portfolio value in $')

    ax2 = fig.add_subplot(212)
    perf['AAPL'].plot(ax=ax2)
    perf[['short_mavg', 'long_mavg']].plot(ax=ax2)

    perf_trans = perf.ix[[t != [] for t in perf.transactions]]
    buys = perf_trans.ix[[t[0]['amount'] > 0 for t in perf_trans.transactions]]
    sells = perf_trans.ix[
        [t[0]['amount'] < 0 for t in perf_trans.transactions]]
    ax2.plot(buys.index, perf.short_mavg.ix[buys.index],
             '^', markersize=10, color='m')
    ax2.plot(sells.index, perf.short_mavg.ix[sells.index],
             'v', markersize=10, color='k')
    ax2.set_ylabel('price in $')
    plt.legend(loc=0)
    plt.show()
_images/tutorial_22_1.png

这里我们定义了一个 analyze() 函数,在回测结束后它将自动被调用 (在 Quantopian 这个功能还不可用)。

虽然收益变化不明显,但 history() 的作用不容小觑,因为大部分策略都是基于历史数据的。 很容易使用 scikit-learn 设计一个分类器, 基于历史数据来预测未来市场的走向(注意,大部分 scikit-learn 函数不使用 pandas.DataFrame ,而是使用 numpy.ndarray,所以你可以直接使用 DataFrame 的原始值 .values ,它是 ndarray 格式的)。

上面我还使用了 order_target() 函数。这类函数能使订单管理和收益再平衡变得容易。 更多信息,请参阅 order 函数文档

结论

希望这个教程能让你大致了解 Zipline 的结构、API 和功能。下一步,可以查看一些 示例

可以在 邮件列表 中提问,在 GitHub issueQuantopian 中提交问题。