本文翻譯自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中)繼續使用該庫。