共计 4518 个字符,预计需要花费 12 分钟才能阅读完成。
DolphinDB database 是一款高性能分布式时序数据库(time-series database),它特地实用于投资银行、对冲基金和交易所的定量查问和剖析,能够用于构建基于历史数据的策略测试。上面咱们将举例说明如何在 DolphinDB 中疾速构建简单的 Alpha 因子。
驰名论文 101 Formulaic Alphas 给出了世界顶级量化对冲基金之一的 WorldQuant 所应用的 101 个 Alpha 因子公式。很多集体和机构尝试用不同的语言来实现这 101 个 Alpha 因子。本文中,咱们例举了较为简单的 Alpha #001 和较为简单的 Alpha #098 两个因子的实现,别离应用了 2 行和 4 行 DolphinDB SQL 代码,堪称史上最简。
因子介绍
Alpha#001 公式:rank(Ts_ArgMax(SignedPower((returns<0?stddev(returns,20):close), 2), 5))-0.5
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 的信息,也用到了大量工夫序列的计算。也即在计算某个股票某一天的因子时,既要用到该股票的历史数据,也要用到当天所有股票的信息,所以计算量很大。
须要的数据
输出数据为蕴含以下字段的 table:
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)