DolphinDB database 是一款高性能分佈式時序數據庫(time-series database),它特別適用於投資銀行、對衝基金和交易所的定量查詢和分析,能夠用於構建基於歷史數據的策略測試。下面咱們將舉例說明如何在DolphinDB中快速構建複雜的Alpha因子。php
著名論文 101 Formulaic Alphas 給出了世界頂級量化對衝基金之一的WorldQuant所使用的101個Alpha因子公式。不少我的和機構嘗試用不一樣的語言來實現這101個Alpha因子。本文中,咱們例舉了較爲簡單的Alpha #001和較爲複雜的Alpha #098兩個因子的實現,分別使用了2行和4行DolphinDB SQL代碼,可謂史上最簡。ios
因子介紹git
Alpha#001公式:rank(Ts_ArgMax(SignedPower((returns<0?stddev(returns,20):close), 2), 5))-0.5github
Alpha #001的詳細解讀能夠參考【史上最詳細】WorldQuant Alpha 101因子系列#001研究。數據庫
Alpha#98公式:(rank(decay_linear(correlation(vwap, sum(adv5,26.4719), 4.58418), 7.18088))- rank(decay_linear(Ts_Rank(Ts_ArgMin(correlation(rank(open), rank(adv15), 20.8187), 8.62571), 6.95668) ,8.07206)))編程
這兩個因子在計算時候既用到了cross sectional的信息,也用到了大量時間序列的計算。也即在計算某個股票某一天的因子時,既要用到該股票的歷史數據,也要用到當天全部股票的信息,因此計算量很大。架構
須要的數據app
輸入數據爲包含如下字段的table:dom
symbol:股票代碼分佈式
date:日期
volume:成交量
vwap:成交量的加權平均價格
open:開盤價格
close:收盤價格
在計算Alpha #001因子時,只須要股票代碼、日期和收盤價格三個字段。
代碼實現
def alpha1(stock){ t= select date,symbol ,mimax(pow(iif(ratios(close) < 1.0, mstd(ratios(close) - 1, 20),close), 2.0), 5) as maxIndex from stock context by symbol return select date,symbol, rank(maxIndex) - 0.5 as A1 from t context by date } def alpha98(stock){ t = select symbol, date, vwap, open, mavg(volume, 5) as adv5, mavg(volume,15) as adv15 from stock context by symbol update t set rank_open = rank(open), rank_adv15 = rank(adv15) context by date update t set decay7 = mavg(mcorr(vwap, msum(adv5, 26), 5), 1..7), decay8 = mavg(mrank(9 - mimin(mcorr(rank_open, rank_adv15, 21), 9), true, 7), 1..8) context by symbol return select symbol, date, rank(decay7)-rank(decay8) as A98 from t context by date }
以上代碼使用了DolphinDB的一些內置函數:
pow:計算指數冪
iif:條件運算函數
ratios:對向量X的每一個元素,計算X(n)X(n-1)
mstd:在滑動窗口中計算標準差
mavg:在滑動窗口中計算平均值
mcorr:在滑動窗口中計算相關性
msum:在滑動窗口中求和
mrank:返回元素在滑動窗口的按升序或降序排序後的位置
mimin:返回滑動窗口中最小值的索引位置
mimax:返回滑動窗口中最大值的索引位置。
全部的核心代碼都用SQL實現,對用戶很是方便,可讀性也很好。SQL中最關鍵的功能是context by子句實現的分組計算功能。與group by每一個組產生一行記錄不一樣,context by會輸出與輸入相同行數的記錄,方便的進行多個函數嵌套。cross sectional計算時,咱們用date分組。時間序列計算時,咱們用symbol(股票代碼)分組。
性能分析
咱們使用美國證券市場從2007年到2016年總共11,711只股票進行回測。每一個股票每一個交易日產生一個因子值, 總共產生1700萬個因子值。測試機器配置以下:
CPU: Intel Core i7-9700 @ 3.0 GHz
內存: 32GB
操做系統: Ubuntu 18.04.4
使用單線程計算,Alpha #001因子耗時僅1.5秒,複雜的Alpha #098因子耗時僅4.3秒。使用pandas計算,Alpha #98耗時760.1秒,性能相差150倍以上。pandas實現代碼見文末。DolphinDB的高性能得益於它的設計思路和技術架構,詳情能夠參考揭祕高性能DolphinDB。
在計算Alpha因子時,除了要考慮性能問題,代碼的簡潔性和可讀性也不容忽視。DolphinDB實現Alpha #001因子只須要2行核心代碼,實現Alpha #098因子僅須要4行核心代碼,而其餘系統實現則須要大段代碼,能夠參考pandas實現或者其餘系統計算Alpha #001因子。爲何DolphinDB的實現如此簡潔?這得益於DolphinDB功能強大的腳本語言。在DolphinDB中,腳本語言與SQL是徹底融合在一塊兒的,SQL查詢能夠直接賦給一個變量或做爲函數的參數。DolphinDB的SQL引擎除了支持標準的SQL之外,還爲大數據分析特別是時間序列數據分析作了不少有用的擴展。好比上面使用到的context by是DolphinDB的特點之一,它至關於其餘系統(SQL Server、PostgreSQL)的窗口函數,可是它比其餘系統的窗口函數功能豐富得多,語法上也更簡潔靈活,對面板數據很是友好。DolphinDB內置了許多與時序數據相關的函數,並進行了優化,大幅度提升了計算性能,好比上面使用到的mavg、mcorr、mrank、mimin等計算滑動窗口指標的函數複雜度僅爲O(n)或O(nlogk), k爲窗口大小。若是你想了解更多DolphinDB的腳本語言,能夠參考DolphinDB腳本語言的混合範式編程。
感興趣的朋友能夠到官網下載 DolphinDB database 嘗試實現本身的Alpha因子和策略回測。
附件
pandas代碼:
from time import time import pandas as pd import numpy as np from scipy.stats import rankdata def rank(df): return df.rank(pct=True) def decay_linear(df, period=10): if df.isnull().values.any(): df.fillna(method='ffill', inplace=True) df.fillna(method='bfill', inplace=True) df.fillna(value=0, inplace=True) na_lwma = np.zeros_like(df) na_lwma[:period, :] = df.iloc[:period, :] na_series = df.as_matrix() divisor = period * (period + 1) / 2 y = (np.arange(period) + 1) * 1.0 / divisor # Estimate the actual lwma with the actual close. # The backtest engine should assure to be snooping bias free. for row in range(period - 1, df.shape[0]): x = na_series[row - period + 1: row + 1, :] na_lwma[row, :] = (np.dot(x.T, y)) return pd.DataFrame(na_lwma, index=df.index, columns=['CLOSE']) def rolling_rank(na): return rankdata(na)[-1] def ts_rank(df, window=10): return df.rolling(window).apply(rolling_rank) def ts_argmin(df, window=10): return df.rolling(window).apply(np.argmin) + 1 def correlation(x, y, window): return x.rolling(window).corr(y) def decay7(df): return rank(decay_linear(correlation(df.vwap, df.adv5, 5).to_frame(), 7).CLOSE) def decay8(df): return rank(decay_linear(ts_rank(ts_argmin(correlation(rank(df.open), rank(df.adv15), 21), 9), 7).to_frame(), 8).CLOSE) def alpha098(df): return (decay7(df) - decay8(df)).to_frame() path = 'your_path/USPrices.csv' df = pd.read_csv(path, parse_dates=[1]) df = df[df.date.between('2007.01.01', '2016.12.31')] print("loaded") df["vwap"] = df["PRC"] df["open"] = df["PRC"] + np.random.random(len(df)) df['adv5'] = df.groupby('PERMNO')['VOL'].transform(lambda x: x.rolling(5).mean()) df['adv15'] = df.groupby('PERMNO')['VOL'].transform(lambda x: x.rolling(15).mean()) df['rank_open'] = df.groupby('date')['open'].rank(method='min') df['rank_adv15'] = df.groupby('date')['adv15'].rank(method='min') print("start") start = time() df['A98'] = df.groupby('PERMNO').apply(alpha098) end = time() print(end - start)