Quantopian 入門系列二 - 流水線 (上)

  • 2019 年 11 月 28 日
  • 筆記

本文含 8045 字,18 圖表截屏

建議閱讀 42 分鐘

引言

在上貼〖Quantopian 系列一〗我們初探了的流水線(pipeline),本帖我們就把它揉碎了講。

很多交易演算法都需要重複的做以下幾個步驟

  1. 對於已知集合中的每個資產,計算它們在不同移動窗口下的 N 個統計量
  2. 根據『1』中計算的值選擇可交易的子資產集(subset)
  3. 根據『2』中選擇的資產集上計算所需的投資組合權重
  4. 根據『3』中計算的權重下訂單

能高效地執行上述操作存在幾個技術難點,包括:

  • 要高效的查詢大量資產
  • 要在大量資產上進行計算
  • 要能處理調整(拆分和股息)和資產退市

流水線就是來解決這些難點的。

在上貼中,我們了解到 Quantopian 有研究環境(research)和回測環境(backtest),我們可以在前者中快速迭代不同的交易策略,然後再後者構建下訂單對其策略進行回測。

兩個環境都需要流水線,好消息是,在兩個環境中,構造流水線是相同的,唯一不同的是其運行方式

  • 研究環境:需要設定起始日和終止日來運行流水線
  • 回測環境:不需要設定起始日和終止日來運行流水線,因此在每個回測日都要跑一次流水線

目錄如下:

  1. 簡介
  2. 因子
  3. 篩選器
  4. 分類器
  5. 蒙面法
  6. 數據集
  7. 自定義因子
  8. 回測

由於內容較多,本帖分上下兩貼,上貼講 1 至 4 節,下帖講 5 至 8 節。

1

簡介

在流水線中,我們可以在同時在多個資產中的多維特徵上定義一系列運算,而這些計算可分為三大類:

  • 因子(factor)
  • 篩選器(filter)
  • 分類器(classifer)

三者的相同點:都是從資產和時點產生值的函數

三者的不同點:產生值的類型不同

因子

因子是從「資產和時點」到數值的函數。

關於因子的兩個簡單示例:

  1. 資產的最新價格,給定資產和特定時點,得到的最新價格是個數值。
  2. 資產的 10 天平均交易量,給定資產和特定時點(10 個時點),得到的平均交易量是個數值。

因子最常用於以下幾種方式:

  • 計算目標權重
  • 產生交易訊號
  • 構造更複雜的因子
  • 構造篩選器

篩選器

篩選器是從「資產和時點」到布爾值的函數。

關於篩選器的一個示例:判斷資產價格是否低於 10 美元,在給定資產和時點的情況下,此結果只能為真(True)或假(False),是個布爾值。

篩選器最常用於包括或剔除的某些資產集。

分類器

分類器是從「資產和時點」到分類值的函數。

具體來講,分類器產生的分類值可以是字元串(string)或整數(integer)。注意這裡整數是指整數標籤,例如行業程式碼(sector code)。

關於分類器的一個示例:正在交易的資產所在交易所的代號。

分類器最常用於對資產進行分組。

數據集

流水線可以在多種數據上進行計算,比如 OHLC 數據、交易量數據、基本面數據和情緒數據等。我們將在後面會介紹每類數據集。


接下來第 2, 4, 6, 10 節討論因子,第 3, 5, 7 節討論篩選器,第 8 節討論分類器,第 9 節討論數據集,第 11 節結合所有內容構建一個完整的策略做回測。

2

因子

因子本質上是一個函數,將資產和時點兩個自變數轉化成一個數值型變數(numeric variable)。抽象形式如下:

F(asset, timestamp) -> float

其中 asset 是一列數據,timestamp 是一個窗口長度。

Quantopian 裡面有一組內置因子(built-in factors)。比如在計算最近 10 天的平均收盤價時,我們可以使用 SimpleMovingAverage 內置因子來計算指定窗口長度(10天)內輸入數據(收盤價)的平均值。為此,我們需要導入內置的 SimpleMovingAverage 因子和 USEquityPricing 數據集。

首先引用它們,加上定義流水線的 Pipeline 和運行流水線的 run_pipeline。

創建因子

當創建「移動均值」的因子時,我們只用調用 SimpleMovingAverage 構造函數,來實例化該因子。構造函數需要設定兩個參數:

  1. input - 一組數據對象的列表
  2. window_length - 一個整數,表示移動平均值計算應用多少天的數據

下行程式碼創建了用於計算「10 天美股平均收盤價」的因子。

mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close],                       window_length=10)

把因子加入流水線

定義好因子後,只需在傳給 Pipeline 裡面的 columns 參數,程式碼如下:

看看流水線在 2019-11-25 上運行的結果(需要將起始日和終止日都設成 2019-11-25),列印其首尾五行:

result = run_pipeline(make_pipeline(), '2019-11-25', '2019-11-25')result.head().append(result.tail())

流水線的產出就是個多層的數據幀,第 0 層的行標籤是時間,第 1 層的行標籤是資產代號,列標籤就是上面 Pipeline 裡面賦值給 columns 參數的字典的鍵。

上面結果只有一天,因此第 0 層只顯示了 2019-11-25,接下來我們看看流水線在 2019-11-25 到 2019-11-27 上運行的結果,並列印其首尾五行:

result = run_pipeline(make_pipeline(), '2019-11-25', '2019-11-27')result.head().append(result.tail())

除了在 Pipeline 裡面用 columns 參數加因子,我們還可以用 Pipeline.add 方法,語法如下:

>>> my_pipe = Pipeline()

>>> f1 = SomeFactor(...)

>>> my_pipe.add(f1, 'f1')

Latest

Quanopian 里最常見的內置因子是 latest,就是獲取數據序列中最新的值。由於該因子用的太頻繁了,因此我們不是構建函數來實例化它,而是用 .latest 方法來實例化。程式碼如下:

看看結果:

result = run_pipeline(make_pipeline(), '2019-11-25', '2019-11-25')result.head().append(result.tail())

這是返回的數據幀裡面有兩個列標籤,一個是 10 天平均值,一個是當日最新值。

需要注意的是,.latest 方法不僅僅只用到因子上,還可以用到其它方面,接下來會介紹。

默認輸入

對於有默認輸入(default input)的因子,我們可以不指定輸入。

比如在計算以交易量加權平均價格(VWAP)因子時,我們需要收盤價和交易量,分別從USEquityPricing.close 和 USEquityPricing.volume 獲取,它們都是默認因子,因此我們在計算 VWAP 時可以不指定它們。程式碼如下:

from quantopian.pipeline.factors import VWAPvwap = VWAP(window_length=10)

回顧一下並對比 SimpleMovingAverage 的程式碼,

mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close],                       window_length=10)

在調用 VWAP 時,沒有設置 inputs。

組合因子

多個因子可以組合成新的因子,通過任何內置的數學運算符(+, -, * 等)。例如,構造一個可以計算其他兩個因子的平均值的因子很簡單:

    >>> f1 = SomeFactor(...)      >>> f2 = SomeOtherFactor(...)      >>> average = (f1 + f2) / 2.0

接下來,我們來創建一個 relative_difference 的因子,來計算 10 天平均和30 天平均的相對差。

首先定義兩者,注意 window_length 設置為 10 和 30。

mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close],                        window_length=10)mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close],                       window_length=30)

在計算出兩者的相對差。

percent_difference = (mean_close_10 - mean_close_30) / mean_close_30

顯然 percent_difference 還是一個因子,一個組合因子。只要是因子,都可以寫到流水線中,程式碼如下:

列印結果。

result = run_pipeline(make_pipeline(), '2019-11-25', '2019-11-25')result.head().append(result.tail())

這時輸出的數據幀的列標籤就是 percent_difference 了。

3

篩選器

篩選器(filter)本質上是一個函數,將資產和時點兩個自變數轉化成一個布爾型變數(boolean variable)的函數。抽象形式如下:

F(asset, timestamp) -> boolean

在流水線中,篩選器用於縮小資產範圍。有兩種常見的創建篩選器的方法:

  1. 比較運算符方法
  2. 因子對象中的方法

比較運算符

例一:創建一個篩選器 close_price_filter,當最新收盤價高於 $20 時,返回 True。

last_close_price = USEquityPricing.close.latestclose_price_filter = last_close_price > 20

例二:創建一個篩選器 mean_crossover_filter,當 10 天均值低於30 天均值時,返回 True。

mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close],                      window_length=10)mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close],                      window_length=30)mean_crossover_filter = mean_close_10 < mean_close_30

在使用篩選器時,每個資產在每個時點都會得到自己對應的 True 和 False。

因子對象中的方法

以 top 函數舉例,factor.top(n) 方法產生一個篩選器,它針對給定因子的前n 個資產返回 True。下例的篩選器每天針對正好 200 種資產返回 True,表示這些資產的最新收盤價在所有已知資產的最新收盤價中排名前 200。

last_close_price = USEquityPricing.close.latesttop_close_price_filter = last_close_price.top(200)

具體篩選實例

在下例中,我們想篩選出「過去 30 天平均交易額大於 $10,000,000」的資產,首先引入 AverageDollarVolume

from quantopian.pipeline.factors import AverageDollarVolume

再實例化「平均交易額」這個因子,注意AverageDollarVolume 用 USEquityPricing.close 和 USEquityPricing.volume 作為默認參數,因此不用特意設定(還記得第 2 章講的默認輸入嗎?)。

dollar_volume = AverageDollarVolume(window_length=30)

再創建布爾變數類型的篩選條件:

high_dollar_volume = (dollar_volume > 10000000)

全部寫到流水線中的程式碼如下:

看看結果:

result = run_pipeline(make_pipeline(), '2019-11-25', '2019-11-25')print('Number of securities: %d' % len(result))result.head().append(result.tail())

返回的數據幀多了 high_dollar_volume 的列標籤,注意到我們列印出所有資產的數目,8880 個。


默認情況下,流水線每天會為 Quantopian 資料庫中的每個資產生成各種計算值。但很多時候,我們只關心滿足特定條件的一部分資產(比如我們只關心日交易量大過某個閾值的股票)。這是可以通過 screen 關鍵字將篩選器傳遞給流水線,相當於告訴流水線忽略掉篩選器產生 False 的資產。

要篩選管道輸出的 30 天平均金額大於 $10,000,000 的股票,我們可以把 high_dollar_volume 過濾器傳給 screen 參數,見以下程式碼並注意高亮部分。

看看結果:

result = run_pipeline(make_pipeline(), '2019-11-25', '2019-11-25')print('Number of securities that passed the filter: %d' % len(result))result.head().append(result.tail())

注意篩選過後資產的數目只有 2041 個了,遠少於沒篩選過的 8880 個。

反轉篩選器

符號 ~ 用於反轉帥選器,即把原來 True 變成 False 和原來的 False 變成 True。比如

low_dollar_volume = ~high_dollar_volume

這個篩選器 low_dollar_filter 在「過去 30 天的平均交易額小於等於 $1,000,000」的時候返回 True。

組合篩選器

和因子相同的是,篩選器也可以組合起來用;和因子不同的是,我們使用 &, | 運算符而不是 +, -, * 運算符。

比如說我們想篩選出「過去 30 天的平均交易額」排前 10% 和「最新收盤價」高於 $20 的資產,我們首先分別定義出這兩個篩選器

篩選器一:過去 30 天的平均交易額排 10%,用 percentile_between 方法。

dollar_volume = AverageDollarVolume(window_length=30)high_dollar_volume = dollar_volume.percentile_between(90, 100)

篩選器二:「最新收盤價」高於 $20,用 AverageDollarVolume 因子。

latest_close = USEquityPricing.close.latestabove_20 = latest_close > 20

將篩選器一 high_dollar_volume 和篩選器二 above_20 合併以來,用 & 操作符,組合成新的篩選器 tradeable_filter。

tradeable_filter = high_dollar_volume & above_20

把 tradeable_filter過濾器傳給 screen 參數,見以下程式碼並注意高亮部分。

看看結果:

result = run_pipeline(make_pipeline(), '2019-11-25', '2019-11-25')print('Number of securities that passed the filter: %d' % len(result))result.head().append(result.tail())

用組合篩選器將資產範圍進一步縮小到 787 個。

4

分類器

分類器(classifier)本質上是一個函數,將資產和時點兩個自變數轉化成一個分類型變數(categorical variable)的函數。抽象形式如下:

F(asset, timestamp) -> category

類別 category 可以是一個字元串標籤,也可以是一個整數標籤。

分類器常見的例子包括交易所的 ID 和行業代號(sector code),如下:

from quantopian.pipeline.data import Fundamentalsexchange = Fundamentals.exchange_id.latestexchange
Latest([Fundamentals<US>.exchange_id], 1)
from quantopian.pipeline.classifiers.fundamentals import Sector  morningstar_sector = Sector()morningstar_sector 
Sector([Fundamentals<US>.morningstar_sector_code], 1)

很明顯,這兩個分類型變數都是整數標籤。

用分類器構建篩選器

這個邏輯很好懂,分類器包含很多類別,比如類 1、類 2、...、類 n。篩選器就是一個條件,可以選擇分類器的若干類別來做篩選。

舉個例子,如果我們想篩選出在紐約股票交易所(NYSE)上交易的證券,我們可以用分類器 exchange 中的 eq() 方法,程式碼如下:

nyse_filter = exchange.eq('NYS')

除了 eq() 方法,還有幾個常見的用分類器構建篩選器的方法:

  • 篩選器 = 分類器.isnull()
  • 篩選器 = 分類器.notnull()
  • 篩選器 = 分類器.startwith(字元串)
  • 篩選器 = 分類器.endwith(字元串)

用因子構建分類器

分類器也可以由因子構建,其中最常見的是分位數方法(quantiles)。該方法把箱數(bin counts)記做 n,並為因子輸出中的每個非 NaN 數據點分配從 0 到 n-1 的標籤,並返回帶有這些標籤的分類器(NaN數據標記為 -1)。

首先了解分位數中一些中英文名詞和函數的映射如下:

  • 四分位數,quartiles (quantiles(4))
  • 五分位數,quintiles (quantiles(5))
  • 十分位數,deciles (quantiles(10))

下面程式碼第一行用 AverageDollarVolume 因子加上 deciles() 方法構建一個含十類的分類器。第二行用 eq() 方法篩選出第 9 類,即選取 ADV 排前 10% 的資產。

dollar_volume_decile = AverageDollarVolume(window_length=10).deciles()top_decile = (dollar_volume_decile.eq(9))

將兩個篩選器(一個交易所的 nyse_filter 和一個 ADV 前 10% 的 top_decile)傳給 screen 參數,注意下面程式碼高亮部分。

看看結果:

result = run_pipeline(make_pipeline(), '2019-11-25', '2019-11-25')print('Number of securities that passed the filter: %d' % len(result))result.head().append(result.tail())

篩選出 491 個資產。

上貼把流水線里因子、篩選器和分類器三大核心講清楚了,幾個重點要記住:

  1. 因子(數值)和篩選器(布爾值)可以是單個的,也可以是組合的。
  2. 組合因子用的 +, -, *, / 等運算符號,組合篩選器用的是 &, | 等條件符號。
  3. 分類器(分類值)可以用來構建篩選器,用 eq(), isnull(), startwith() 等方法。
  4. 因子可以用來構建分類器,但最終分類器還是用來構建篩選器。
  5. 因子是你最終想收集的數據,篩選器是幫助你得到你想要的資產集合。

一個高度抽象構建流水線的偽程式碼長成下面這個樣子:

def make_pipeline():

計算一堆因子

列出一堆篩選器

生成一堆分類器

分類器生成一堆篩選器

return Pipeline(

columns={'因子字元串':因子}

screen={'篩選器字元串':篩選器}

)

我瘋狂喜歡高度抽象的東西,具體的東西要用的時候再查資料。人腦不是電腦,記不住那麼東西!

下帖繼續講流水線。Stay Tuned!

Tags: