本文翻譯自2018年最熱門的Python金融教程 Python For Finance: Algorithmic Trading。
本教程由以下五部分內(nèi)容構(gòu)成:
本文是該教程的第四部分。
既然你手頭已經(jīng)有了一項(xiàng)交易策略,最好對(duì)其進(jìn)行回溯測(cè)試并計(jì)算其性能。但是,在進(jìn)行深入研究之前,你可能想多了解一些相關(guān)知識(shí),比如回溯測(cè)試中的陷阱,回測(cè)器的組成,以及可以用來(lái)回測(cè)算法的Python工具。
然而,如果你已經(jīng)掌握了這些,可以跳過(guò)以下內(nèi)容,直接開(kāi)始實(shí)現(xiàn)你的回測(cè)器。
回溯測(cè)試的陷阱
回溯測(cè)試,不僅僅是“測(cè)試交易策略”,而是在相關(guān)的歷史數(shù)據(jù)中測(cè)試策略,以確保該策略在你開(kāi)始操作之前是實(shí)際可行的。利用回溯測(cè)試,交易人員能夠模擬并分析在一段時(shí)間內(nèi),使用特定策略進(jìn)行交易的風(fēng)險(xiǎn)和收益。然而,在你進(jìn)行回測(cè)時(shí),最好牢記一些剛開(kāi)始可能并不起眼的陷阱。
例如,有一些外部事件肯定會(huì)影響回溯測(cè)試,如市場(chǎng)體制的轉(zhuǎn)變,這是監(jiān)管的變化或是宏觀經(jīng)濟(jì)事件。此外,流動(dòng)性約束,如禁止賣空,可能會(huì)嚴(yán)重影響回溯測(cè)試。
其次,你自己也可能引入陷阱。比如當(dāng)你過(guò)擬合模型時(shí)(優(yōu)化偏差),當(dāng)你認(rèn)為這樣更好而忽略策略規(guī)則時(shí)(干擾),或當(dāng)你偶然將信息引入過(guò)去的數(shù)據(jù)時(shí)(前視偏差)。
在你學(xué)完本教程,開(kāi)始制定自己的策略并進(jìn)行回測(cè)時(shí),需要重點(diǎn)考慮這些陷阱。
回溯測(cè)試的構(gòu)成
除了這些陷阱, 最好要知道構(gòu)成回測(cè)器的四項(xiàng)不可或缺的組件:
- 數(shù)據(jù)處理器,是數(shù)據(jù)集的接口。
- 策略,基于數(shù)據(jù)產(chǎn)生做多或做空的信號(hào)。
- 投資組合,生成訂單并管理利潤(rùn)和損失(也稱為“PnL”)。
- 執(zhí)行處理程序,向經(jīng)紀(jì)人發(fā)送訂單,并接收股票已被買入或賣出的信號(hào)。
除了這四個(gè)組件外,根據(jù)系統(tǒng)的復(fù)雜性,還可以向回測(cè)器中添加更多的東西。你絕對(duì)可以做的更多,而不僅僅局限于這四個(gè)組件。然而,在這篇初學(xué)者教程中,你只需專注于讓這些基本組件在代碼中順利運(yùn)行。
Python 工具
為了實(shí)現(xiàn)回溯測(cè)試,除了 Pandas 外還可以使用一些其它工具,在本教程的第一部分對(duì)數(shù)據(jù)進(jìn)行金融分析時(shí),你其實(shí)已經(jīng)大量使用了這些工具。除了 Pandas,還有如 NumPy 和 SciPy,它們提供了向量化、優(yōu)化和線性代數(shù)的程序,可以在開(kāi)發(fā)交易策略時(shí)使用。
此外,當(dāng)開(kāi)發(fā)預(yù)測(cè)策略時(shí),Python 的機(jī)器學(xué)習(xí)庫(kù) Scikit-Learn 也能派上用場(chǎng),因?yàn)樗峁┝藙?chuàng)建回歸和分類模型所需的一切。DataCamp 的 Supervised Learning With Scikit-Learn 課程,提供了該庫(kù)的介紹。然而,如果你想使用統(tǒng)計(jì)庫(kù)進(jìn)行諸如時(shí)間序列的分析,statsmodels 庫(kù)是一個(gè)理想的選擇。在本教程中執(zhí)行普通最小二乘回歸(OLS)時(shí),你其實(shí)已經(jīng)簡(jiǎn)單使用過(guò)這個(gè)庫(kù)了。
最后,還有 IbPy 和 ZipLine 庫(kù)。前者為 Interactive Brokers 在線交易系統(tǒng)提供了Python接口:你將獲得連接到 Interactive Brokers 的所有功能,如請(qǐng)求股票報(bào)價(jià)器數(shù)據(jù),提交股票訂單,…… ZipLine 是一個(gè)集成的 Python 回測(cè)框架,在本教程中你將使用的 Quantopian 就是基于它的。
實(shí)現(xiàn)簡(jiǎn)單的回測(cè)器
如前所述,一個(gè)簡(jiǎn)單的回測(cè)器包括策略、數(shù)據(jù)處理器、投資組合以及執(zhí)行處理程序。在之前你已經(jīng)實(shí)現(xiàn)了一項(xiàng)策略,并且也能訪問(wèn)數(shù)據(jù)處理器,即 pandas-datareader 庫(kù)或者是從Excel讀取數(shù)據(jù)的Pandas庫(kù)。仍需實(shí)現(xiàn)的組件就剩執(zhí)行處理程序和投資組合了。
但是,作為初學(xué)者,你目前還無(wú)需要專注于實(shí)現(xiàn)執(zhí)行處理程序。相反,接下來(lái)你將看到如何開(kāi)始創(chuàng)建用于生成訂單并管理盈虧的投資組合。
在開(kāi)始之前,先獲取 apple 公司的股票數(shù)據(jù),參考本教程的第一部分:基礎(chǔ)入門。
# 獲取apple公司股票數(shù)據(jù)
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))
然后是創(chuàng)建均線交叉策略,參考本教程的第三部分:用Python構(gòu)建交易策略。
# 導(dǎo)入pandas,numpy
import pandas as pd
import numpy as np
# 初始化短期和長(zhǎng)期窗口
short_window = 40
long_window = 100
# 初始化 `signals` 數(shù)據(jù)框,增加 `signal` 列
signals = pd.DataFrame(index=aapl.index)
signals['signal'] = 0.0
# 創(chuàng)建短期簡(jiǎn)單移動(dòng)均值
signals['short_mavg'] = aapl['Close'] \
.rolling(window=short_window, min_periods=1, center=False) \
.mean()
# 創(chuàng)建長(zhǎng)期簡(jiǎn)單移動(dòng)均值
signals['long_mavg'] = aapl['Close'] \
.rolling(window=long_window, min_periods=1, center=False) \
.mean()
# 生成信號(hào)
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()
現(xiàn)在讓我們開(kāi)始創(chuàng)建回溯測(cè)試中的投資組合(portfolio)。
- 首先,設(shè)置變量
initial_capital來(lái)存儲(chǔ)初始資金。再創(chuàng)建一個(gè)新的數(shù)據(jù)框positions,并拷貝數(shù)據(jù)框signals的索引給它,這是為了獲取信號(hào)生成的時(shí)間。
- 接著,在
positions數(shù)據(jù)框中創(chuàng)建新的列AAPL。當(dāng)信號(hào)(signal)為1,短期移動(dòng)均線超過(guò)了長(zhǎng)期移動(dòng)均線(針對(duì)大于最短移動(dòng)均值窗口的時(shí)期),這時(shí)買入100股。而對(duì)于信號(hào)為0的時(shí)段,運(yùn)算100*signals['signal']的結(jié)果是0。
- 創(chuàng)建新的數(shù)據(jù)框
portfolio,存儲(chǔ)購(gòu)買股票的市場(chǎng)價(jià)值。
- 然后,創(chuàng)建數(shù)據(jù)框
pos_diff存儲(chǔ)股票數(shù)目的差值。
- 接下來(lái)開(kāi)始真正的回溯測(cè)試:在數(shù)據(jù)框
portfolio中創(chuàng)建新的一列holdings,存儲(chǔ)買入股票的數(shù)量與調(diào)整的收盤價(jià)的乘積。
- 另外
portfolio中還包含一列cash,指剩余的資金:用initial_capital減去用于買股票的錢。
- 在
portfolio數(shù)據(jù)框中再增加一列total,包括了現(xiàn)金和所持股票總的價(jià)值。
- 最后,在
portfolio中增加returns列,存儲(chǔ)獲得的收益率。
# 設(shè)置初始資金
initial_capital= float(100000.0)
# 創(chuàng)建數(shù)據(jù)框 `positions`
positions = pd.DataFrame(index=signals.index).fillna(0.0)
# 當(dāng)signal為1時(shí),買入100股
positions['AAPL'] = 100*signals['signal']
# 用擁有的價(jià)值初始化 portfolio
portfolio = positions.multiply(aapl['Adj Close'], axis=0)
# 存儲(chǔ)股票數(shù)目的差值
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
作為回測(cè)的最后一項(xiàng)練習(xí),利用 Matplotlib 和回測(cè)的結(jié)果,可視化投資組合的價(jià)值,即 portfolio['total'] 隨時(shí)間變化的情況。
# 導(dǎo)入`pyplot`模塊
import matplotlib.pyplot as plt
# 創(chuàng)建一幅圖
fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(111, ylabel='Portfolio value in $')
# 繪制資產(chǎn)曲線
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')
# 顯示繪圖結(jié)果
plt.show()

注意,在本教程中,回測(cè)器以及交易策略的 Pandas 代碼都是以能夠輕松交互的方式編寫的。在實(shí)際應(yīng)用中,你可能會(huì)選擇使用類這種更面向?qū)ο蟮脑O(shè)計(jì),因?yàn)樗怂械倪壿?。?a target="_blank" rel="nofollow">這里能夠找到相同的移動(dòng)均線交叉策略在使用面向?qū)ο笤O(shè)計(jì)時(shí)的示例,查看這篇演示文稿,并且千萬(wàn)不要忘了DataCamp的Python Functions Tutorial教程。
使用 ZipLine 和 Quantopian 進(jìn)行回測(cè)
現(xiàn)在你已經(jīng)了解了如何使用 Python 中流行的數(shù)據(jù)處理包 Pandas 實(shí)現(xiàn)回溯測(cè)試。然而,你會(huì)發(fā)現(xiàn)這么做很容易出錯(cuò),也可能不是每次使用時(shí)最安全的選擇:盡管已利用了 Pandas 來(lái)獲取結(jié)果,也需要你每次從頭開(kāi)始構(gòu)建大部分組件。
這就是為什么人們普遍使用諸如 Quantopian 這樣的回測(cè)平臺(tái)來(lái)進(jìn)行回溯測(cè)試。Quantopian 是一個(gè)免費(fèi)的、以社區(qū)為中心的托管平臺(tái),用于構(gòu)建和執(zhí)行交易策略。它由 zipline 驅(qū)動(dòng) ,這是一個(gè)用于算法交易的Python庫(kù)。你可以在本地使用該庫(kù),但是在這篇初學(xué)者教程中,你將使用 Quantopian 來(lái)編寫并回測(cè)你的算法。不過(guò)在使用它之前,確保你已經(jīng)注冊(cè)并登錄了該平臺(tái)。
接下來(lái),你就可以很輕松地開(kāi)始了。點(diǎn)擊“新算法”按鈕來(lái)開(kāi)始編寫你的交易算法,或者選擇已經(jīng)編寫好的實(shí)例代碼之一,以便更好地了解其使用方法。

讓我們從簡(jiǎn)單的開(kāi)始,來(lái)實(shí)現(xiàn)一個(gè)新算法,但仍然延續(xù)移動(dòng)均線交叉策略的例子,這也是zipline 快速入門指南中的標(biāo)準(zhǔn)案例。碰巧這個(gè)例子非常類似于你在前一節(jié)中實(shí)現(xiàn)的簡(jiǎn)單交易策略。不過(guò),如你所見(jiàn),下方的代碼塊和上方的屏幕截圖的結(jié)構(gòu)與本教程之前所示有所不同,也就是說(shuō),從 initialize() 和 handle_data() 這兩個(gè)函數(shù)定義開(kāi)始。
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)
當(dāng)程序開(kāi)始運(yùn)行并執(zhí)行一次性的啟動(dòng)邏輯時(shí),調(diào)用第一個(gè)函數(shù) initialize()。其中的 context 參數(shù),用于存儲(chǔ)回溯測(cè)試或在線交易中的狀態(tài),并且可以在算法的不同地方被調(diào)用;如接下來(lái)的代碼所示,在第一個(gè)移動(dòng)均值窗口的定義中,context 參數(shù)又出現(xiàn)了。通過(guò)有價(jià)證券(如股票)的符號(hào)(如AAPL)將其查詢結(jié)果賦值給 context.security。
在模擬或在線交易中,每分鐘調(diào)用一次 handle_data() 函數(shù),以確定進(jìn)行何種交易操作。該函數(shù)包含 context 和 data 兩項(xiàng)參數(shù):context 和剛才所說(shuō)的一樣,data 對(duì)象包含多種API函數(shù),比如 current() 函數(shù)用于獲取給定資產(chǎn)給定字段的最新數(shù)據(jù),history() 函數(shù)用于獲取歷史價(jià)格和成交量的移動(dòng)窗口數(shù)據(jù)。這些API函數(shù)超出了本教程的范圍,因此沒(méi)有在代碼中顯示出來(lái)。
注意輸入Quantopian控制臺(tái)的代碼只能在該平臺(tái)中工作,不能用于像Jupyter Notebook這樣的本地程序中。
data 對(duì)象使你能夠獲取向前填充的 price 價(jià)格,如果有的話返回最后一個(gè)已知的價(jià)格,否則返回 NaN 空值。
order_target() 下訂單來(lái)調(diào)整目標(biāo)股份數(shù)量。如果資產(chǎn)中沒(méi)有頭寸,用完整的目標(biāo)數(shù)下訂單。如果資產(chǎn)中有頭寸,用目標(biāo)股份數(shù)和當(dāng)下持有量的差值下訂單。負(fù)目標(biāo)訂單將導(dǎo)致特定負(fù)數(shù)的欠缺頭寸。
提示: 如果對(duì)函數(shù)或?qū)ο笥腥魏纹渌鼏?wèn)題,請(qǐng)查看 Quantopian 的幫助頁(yè)面, 它涵蓋的內(nèi)容比本教程多得多。
當(dāng)你在界面左手邊的控制臺(tái)中使用 initialize() 和 handle_data() 函數(shù)創(chuàng)建了策略(或者粘貼-復(fù)制上述代碼),然后只要點(diǎn)擊 “Build Algorithm” 按鈕即可編譯代碼來(lái)運(yùn)行回溯測(cè)試。如果點(diǎn)擊 “Run Full Backtest” 按鈕,就會(huì)運(yùn)行一項(xiàng)完全的回溯測(cè)試,基本上和之前的 “Build Algorithm” 相同,只是返回更多的細(xì)節(jié)。無(wú)論是簡(jiǎn)單還是完全的回溯測(cè)試,都需要運(yùn)行一段時(shí)間,一定要注意頁(yè)面頂部的進(jìn)度條!

從這里能獲取更多 Quantopian 的入門知識(shí)。
注意 Quantopian 可以讓你輕松上手 zipline,但是你總可以在本地(比如 Jupyter Notebook中)繼續(xù)使用該庫(kù)。