Alphalens和Jqfactor_analyzer单因子分析解读
摘要:
Alphalens和Jqfactor_analyzer都是一款开源的Python 的工具包,Alphalens是Quantopian公司(成立9年的知名量化平台于今年10月正式宣告关门)旗下,是较早的开源回测框架,因其接口简单易用被广泛的量化分析师所青睐,不过alphalens 作为国外的开源量化模块,在使用时应该注意美股与A股的不同(比如A股是单向交易一般散户不能做空,采用的是T+1的交易制度等),同时考虑到Quantopian公司的倒闭之后的维护性也是一个问题。Jqfactor_analyzer是国内聚宽旗下的,最早的版本发布于2019年5月,提供的主要回测功能和Alphalens比较相似:因子收益、因子 IC、因子换手以及事件研究的基础上做了功能的完善,不过在框架上采用了面向对象的思想具有很好的封装性,在后续使用中也可以利用其继承特性进行个性化功能定制,同时由于其可以通过对象直接访问各个因子分析结果的值,所以具备更好的可理解性、可测试性和可修改性。在使用方式上和Alphalens也略有些不同,需要建立连接在线获取prices、行业、权重数据,耗费时间,整体上Jqfactor_analyzer性能劣于Alphalens,但是在单个功能尤其是数据清洗部分Jqfactor_analyzer较Alphalens有所提升。下面就围绕他们的主要回测功能进行介绍。
- 在介绍之前,首先给出我对因子和单因子分析的理解:因子就是以交易对象(股票)和交易日期为自变量的二元函数,通过这个函数得到交易对象在某个交易日的因子值。聚宽上提供了一些经典单因子的类别、概念、回测等,详见经典因子分析。
接口使用
- 获取因子数据
股票:[‘000001.XSHE’,‘000002.XSHE’,‘000004.XSHE’,‘000006.XSHE’]
因子:Skewness60 (个股收益的60日偏度)
时间:‘2020-09-01’ - ‘2020-10-01’
# 导入函数库
from jqdatasdk import get_factor_values
import jqdatasdk
import jqfactor_analyzer as ja
jqdatasdk.auth("username","password")
factor_data = get_factor_values(securities=['000001.XSHE','000002.XSHE','000004.XSHE','000006.XSHE'], factors=['Skewness60','DEGM','quick_ratio'], start_date='2020-09-01', end_date='2020-10-01')
# 将字典类型数据转换为DataFrame
factor_data = pd.DataFrame(factor_data['Skewness60'])
#
# 将 index 转换为 DatetimeIndex
factor_data.index = pd.to_datetime(factor_data.index)
print(factor_data)
- 对因子进行分析
# 对因子进行分析,参数使用默认值
far = ja.analyze_factor(factor=factor_data,industry='jq_l1',weight_method='mktcap',quantiles=2,periods=(1,5))
# 展示全部分析
far.create_full_tear_sheet(demeaned=False, group_adjust=False, by_group=False, turnover_periods=None,
avgretplot=(5, 15), std_bar=False)
主要功能解读
- 1. 因子收益表
• • (2020-09-02 close - 2020-09-01 close)/ 2020-09-01 close
=(15.32-15.14)÷15.14= 0.0118890357
5期收益率计算为
(2020-09-08 close - 2020-09-01 close)/ 2020-09-01 close
(15.43-15.14)÷15.14=0.01915
- 1.1收益计算
以2020-09-01,000001.XSHE为例: - 1期收益率计算为:
- 1.2 因子值
factor为所选择因子Skewness60的值 - 1.3分组
group这里按照聚宽1类jq_l1分的 - 1.4权重
聚宽增加weights,本例选用的是市值权重mktcap,即根据同一quantile下市值进行权重分配(市值和权重成正比)
另外也可以选择 'avg’等权重(默认);‘mktcap’: 按总市值加权;‘ln_mktcap’: 按总市值的对数加权;‘cmktcap’: 按流通市值加权; ‘ln_cmktcap’: 按流通市值的对数加权 - 2.alpha、 beta计算
def factor_alpha_beta(factor_data, demeaned=True, group_adjust=False):
"""
计算因子的alpha(超额收益),
alpha t-统计量 (alpha 显著性)和 beta(市场暴露).
使用每期平均远期收益作为自变量(视为市场组合收益)
因子值加权平均的远期收益作为因变量(视为因子收益), 进行回归.
Parameters
----------
factor_data : pd.DataFrame - MultiIndex
一个 DataFrame, index 为日期 (level 0) 和资产(level 1) 的 MultiIndex,
values 包括因子的值, 各期因子远期收益, 因子分位数,
因子分组(可选), 因子权重(可选)
demeaned : bool
因子分析是否基于一个多空组合? 如果是 True, 则计算权重时因子值需要去均值
group_adjust : bool
因子分析是否基于一个分组(行业)中性的组合?
如果是 True, 则计算权重时因子值需要根据分组和日期去均值
Returns
-------
alpha_beta : pd.Series
一个包含 alpha, beta, a t-统计量(alpha) 的序列
"""
returns = factor_returns(factor_data, demeaned, group_adjust)
universe_ret = factor_data.groupby(level='date')[
get_forward_returns_columns(factor_data.columns)] \
.mean().loc[returns.index]
if isinstance(returns, pd.Series):
returns.name = universe_ret.columns.values[0]
returns = pd.DataFrame(returns)
alpha_beta = pd.DataFrame()
for period in returns.columns.values:
x = universe_ret[period].values
y = returns[period].values
x = add_constant(x)
period_int = int(period.replace('period_', ''))
reg_fit = OLS(y, x).fit()
alpha, beta = reg_fit.params
alpha_beta.loc['Ann. alpha', period] = \
(1 + alpha) ** (250.0 / period_int) - 1
alpha_beta.loc['beta', period] = beta
return alpha_beta
公式:因子值加权组合每日收益 = beta * 市场组合每日收益 + alpha
- 2.1计算因子值加权组合每日收益和市场组合每日收益
def factor_returns(factor_data, demeaned=True, group_adjust=False):
"""
计算按因子值加权的投资组合的收益
权重为去均值的因子除以其绝对值之和 (实现总杠杆率为1).
参数
----------
factor_data : pd.DataFrame - MultiIndex
一个 DataFrame, index 为日期 (level 0) 和资产(level 1) 的 MultiIndex,
values 包括因子的值, 各期因子远期收益, 因子分位数,
因子分组(可选), 因子权重(可选)
demeaned : bool
因子分析是否基于一个多空组合? 如果是 True, 则计算权重时因子值需要去均值
group_adjust : bool
因子分析是否基于一个分组(行业)中性的组合?
如果是 True, 则计算权重时因子值需要根据分组和日期去均值
返回值
-------
returns : pd.DataFrame
每期零风险暴露的多空组合收益
"""
def to_weights(group, is_long_short):
if is_long_short:
demeaned_vals = group - group.mean()
return demeaned_vals / demeaned_vals.abs().sum()
else:
return group / group.abs().sum()
grouper = [factor_data.index.get_level_values('date')]
if group_adjust:
grouper.append('group')
weights = factor_data.groupby(grouper)['factor'] \
.apply(to_weights, demeaned)
if group_adjust:
weights = weights.groupby(level='date').apply(to_weights, False)
weighted_returns = \
factor_data[get_forward_returns_columns(factor_data.columns)] \
.multiply(weights, axis=0)
returns = weighted_returns.groupby(level='date').sum()
return returns
以2020-09-01period1为例(使用数据见1因子收益表):
市场组合每日收益
=各股票收益/股票数量
=( 0.011890+ 0.011021-0.012414 -0.018979)/4= -0.002120
因子值加权组合每日收益
= 因子权值weight收益
= 0.1958070.011890+0.3041930.011021+(-0.144020)(-0.012414)+(-0.35598)*(-0.018979)
=0.014225
其中因子权值weight
=(因子值-因子值.mean())/( ∑|因子值- 因子值.mean()| )
=[0.195807, 0.304193,- 0.144020,- 0.35598]
Eg. 0.195807 = (1.381-0.842)/ [(1.381-0.842)+(1.679-0.842)+(0.842-0.445)+(0.138+0.842)]
其中0.842 是因子值的均值(1.381+1.679+0.445-0.138)/4
- 2.2拟合
通过OLS(y, x) 拟合计算alpha和 beta
alpha, beta = OLS(y, x).fit().params
- 2.3年化alpha
(一年250个交易日)
alpha = (1 + alpha) ** (250.0 / period_int) – 1
- 3. 行业中性收益
cols = get_forward_returns_columns(factor_data.columns)
factor_data[cols] = factor_data.groupby(
grouper, as_index=False
)[cols.append(pd.Index(['weights']))].apply(
lambda x: x[cols].subtract(
np.average(x[cols], axis=0, weights=x['weights'].fillna(0.0).values),
axis=1
)
)
- 3.1公式
行业中性收益 = 收益-行业收益
Alphalens | Jqfactor_analyzer | |
行业收益 | 每日各个行业股票收益均值 | 每日各个行业股票收益*weights |
- 3.2分组 按照行业、时间groupby分组
• grouper=
[DatetimeIndex([‘2020-09-01’, ‘2020-09-01’, ‘2020-09-01’, ‘2020-09-01’,
‘2020-09-02’, ‘2020-09-02’, ‘2020-09-02’, ‘2020-09-02’,
… …
‘2020-09-30’, ‘2020-09-30’, ‘2020-09-30’, ‘2020-09-30’],
dtype=‘datetime64[ns]’, name=‘date’, freq=None), ‘group’]
- 3.3计算行业中性收益
只有房地产行业出现了一只以上股票,其他行业就一只股票,减完均值余0
以2020-09-01period1为例,房地产行业的两只股票行业中性收益为:
实际代码验证如下: - 4.IC计算(因子值与因子远期收益计算信息系数)
def factor_information_coefficient(factor_data,
group_adjust=False,
by_group=False):
"""
Computes the Spearman Rank Correlation based Information Coefficient (IC)
between factor values and N period forward returns for each period in
the factor index.
Parameters
----------
factor_data : pd.DataFrame - MultiIndex
A MultiIndex DataFrame indexed by date (level 0) and asset (level 1),
containing the values for a single alpha factor, forward returns for
each period, the factor quantile/bin that factor value belongs to, and
(optionally) the group the asset belongs to.
- See full explanation in utils.get_clean_factor_and_forward_returns
group_adjust : bool
Demean forward returns by group before computing IC.
by_group : bool
If True, compute period wise IC separately for each group.
Returns
-------
ic : pd.DataFrame
Spearman Rank correlation between factor and
provided forward returns.
"""
def src_ic(group):
f = group['factor']
_ic = group[utils.get_forward_returns_columns(factor_data.columns)] \
.apply(lambda x: stats.spearmanr(x, f)[0])
return _ic
factor_data = factor_data.copy()
grouper = [factor_data.index.get_level_values('date')]
if group_adjust:
factor_data = utils.demean_forward_returns(factor_data,
grouper + ['group'])
if by_group:
grouper.append('group')
ic = factor_data.groupby(grouper).apply(src_ic)
return ic
- 4.1公式
IC = 因子值y(factor)排序和因子远期收益x(period_i)排序之间的相关系数 - 4.2相关系数计算
Pearson相关系数:
Spearman相关系数:根据原始数据的排序位置进行求解:
以2020-09-01 period1为例(使用数据见1因子收益表):
x(period_1) = [0.011890, 0.011021, -0.012414, -0.018979]
y(factor) = [1.380870, 1.679204, 0.445487, -0.137936]
对它们按照大小排序后
x = [1, 2, 3, 4]
y = [2, 1, 3, 4]
n = len(x)
total = 0
for i in range(n):
total += (x[i]-y[i])**2
spearman = 1 - float(6 * total) / (n * (n ** 2 - 1)) = 0.8
- 5.换手率
def quantile_turnover(quantile_factor, quantile, period=1):
"""
Computes the proportion of names in a factor quantile that were
not in that quantile in the previous period.
Parameters
----------
quantile_factor : pd.Series
DataFrame with date, asset and factor quantile.
quantile : int
Quantile on which to perform turnover analysis.
period: int, optional
Number of days over which to calculate the turnover.
Returns
-------
quant_turnover : pd.Series
Period by period turnover for that quantile.
"""
quant_names = quantile_factor[quantile_factor == quantile]
quant_name_sets = quant_names.groupby(level=['date']).apply(
lambda x: set(x.index.get_level_values('asset')))
name_shifted = quant_name_sets.shift(period)
new_names = (quant_name_sets - name_shifted).dropna()
quant_turnover = new_names.apply(
lambda x: len(x)) / quant_name_sets.apply(lambda x: len(x))
quant_turnover.name = quantile
return quant_turnover
- 5.1公式
某quantile换手率 = - 5.2日换手率
以2020-09-28数据为例:
2020-09-28 000001.XSHE相较于09-25 factor_quantile由2变为1
2020-09-28 000006.XSHE相较于09-25 factor_quantile都是1
2020-09-28 quantile位1的换手率为 - 5.3最终换手率
最终平均换手率为
这里由于在9月这四只股票只在09-28一天发生了因子换手,故
Quantile 1 Mean Turnover= 0.5/21=0.024 - 6因子自相关性
def factor_rank_autocorrelation(factor_data, period=1):
"""
Computes autocorrelation of mean factor ranks in specified time spans.
We must compare period to period factor ranks rather than factor values
to account for systematic shifts in the factor values of all names or names
within a group. This metric is useful for measuring the turnover of a
factor. If the value of a factor for each name changes randomly from period
to period, we'd expect an autocorrelation of 0.
Parameters
----------
factor_data : pd.DataFrame - MultiIndex
A MultiIndex DataFrame indexed by date (level 0) and asset (level 1),
containing the values for a single alpha factor, forward returns for
each period, the factor quantile/bin that factor value belongs to, and
(optionally) the group the asset belongs to.
- See full explanation in utils.get_clean_factor_and_forward_returns
period: int, optional
Number of days over which to calculate the turnover.
Returns
-------
autocorr : pd.Series
Rolling 1 period (defined by time_rule) autocorrelation of
factor values.
"""
grouper = [factor_data.index.get_level_values('date')]
ranks = factor_data.groupby(grouper)['factor'].rank()
asset_factor_rank = ranks.reset_index().pivot(index='date',
columns='asset',
values='factor')
asset_shifted = asset_factor_rank.shift(period)
autocorr = asset_factor_rank.corrwith(asset_shifted, axis=1)
autocorr.name = period
return autocorr
- 6.1公式
因子自相关性=corr(因子值本期排名, 因子值上期排名)
asset_factor_rank.corrwith(asset_factor_rank.shift(period), axis=1)
- 6.2相关性计算
两个框架都用的pandas中的corrwith函数
比如09-28四只股票的排名是2,3,4,1,而在09-28的上一期也就是09-25是3,4,2,1,
因此在09-28因子自相关性=cor([2,3,4,1],[3,4,2,1])=