Quantopian 入門系列二 - 流水線 (上)
- 2019 年 11 月 28 日
- 筆記

本文含 8045 字,18 圖表截屏
建議閱讀 42 分鐘
引言
在上貼〖Quantopian 系列一〗我們初探了的流水線(pipeline),本帖我們就把它揉碎了講。
很多交易演算法都需要重複的做以下幾個步驟
- 對於已知集合中的每個資產,計算它們在不同移動窗口下的 N 個統計量
- 根據『1』中計算的值選擇可交易的子資產集(subset)
- 根據『2』中選擇的資產集上計算所需的投資組合權重
- 根據『3』中計算的權重下訂單
能高效地執行上述操作存在幾個技術難點,包括:
- 要高效的查詢大量資產
- 要在大量資產上進行計算
- 要能處理調整(拆分和股息)和資產退市
流水線就是來解決這些難點的。
在上貼中,我們了解到 Quantopian 有研究環境(research)和回測環境(backtest),我們可以在前者中快速迭代不同的交易策略,然後再後者構建下訂單對其策略進行回測。
兩個環境都需要流水線,好消息是,在兩個環境中,構造流水線是相同的,唯一不同的是其運行方式
- 研究環境:需要設定起始日和終止日來運行流水線
- 回測環境:不需要設定起始日和終止日來運行流水線,因此在每個回測日都要跑一次流水線
目錄如下:
- 簡介
- 因子
- 篩選器
- 分類器
- 蒙面法
- 數據集
- 自定義因子
- 回測
由於內容較多,本帖分上下兩貼,上貼講 1 至 4 節,下帖講 5 至 8 節。
1
簡介
在流水線中,我們可以在同時在多個資產中的多維特徵上定義一系列運算,而這些計算可分為三大類:
- 因子(factor)
- 篩選器(filter)
- 分類器(classifer)
三者的相同點:都是從資產和時點產生值的函數
三者的不同點:產生值的類型不同
因子
因子是從「資產和時點」到數值的函數。
關於因子的兩個簡單示例:
- 資產的最新價格,給定資產和特定時點,得到的最新價格是個數值。
- 資產的 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 構造函數,來實例化該因子。構造函數需要設定兩個參數:
- input - 一組數據對象的列表
- 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
在流水線中,篩選器用於縮小資產範圍。有兩種常見的創建篩選器的方法:
- 比較運算符方法
- 因子對象中的方法
比較運算符
例一:創建一個篩選器 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 個資產。
上貼把流水線里因子、篩選器和分類器三大核心講清楚了,幾個重點要記住:
- 因子(數值)和篩選器(布爾值)可以是單個的,也可以是組合的。
- 組合因子用的 +, -, *, / 等運算符號,組合篩選器用的是 &, | 等條件符號。
- 分類器(分類值)可以用來構建篩選器,用
eq(),isnull(),startwith()等方法。 - 因子可以用來構建分類器,但最終分類器還是用來構建篩選器。
- 因子是你最終想收集的數據,篩選器是幫助你得到你想要的資產集合。
一個高度抽象構建流水線的偽程式碼長成下面這個樣子:
def make_pipeline():
計算一堆因子
列出一堆篩選器
生成一堆分類器
分類器生成一堆篩選器
return Pipeline(
columns={'因子字元串':因子}
screen={'篩選器字元串':篩選器}
)
我瘋狂喜歡高度抽象的東西,具體的東西要用的時候再查資料。人腦不是電腦,記不住那麼東西!
下帖繼續講流水線。Stay Tuned!

