【譯】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閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容