首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >因子评估——双重排序

因子评估——双重排序

作者头像
量化小白
发布于 2020-02-24 05:49:45
发布于 2020-02-24 05:49:45
6.7K31
代码可运行
举报
运行总次数:1
代码可运行

对于因子的评估,之前的文章中总结了单因子测试的回归法、分层法以及多因子评估的Fama-MacBeth回归(链接见底部)。本文给出因子分析中的双重排序法(double sorting or bivariate sorting) 的原理及代码实现。

双重排序可以评估两个因子叠加使用是否会优于单个因子,即分析两个因子的信息重叠程度以及否有信息增益。

双重排序法的原理与Fama-French三因子中的SMB和HML构造方法一致。具体来说,对于两个因子X、Y,同时按照X、Y排序分组,即双重排序,构建投资组合,分析投资组合的表现。

双重排序在实施时特别需要注意的细节是进行独立排序还是条件排序,独立排序即分别按照X、Y进行排序,取交集得到最终的组合。条件排序则先按照一个因子X排序分层,在X的每个类别内对Y进行排序分层,得到最终的投资组合。

这两种排序的区别在于,如果使用独立排序,未考虑X、Y之间的相关性,如果X、Y之间的相关性很高,分层出来的结果差不多,得到的投资组合会集中在对角线上,会导致非对角线的组合包含的股票数目非常少。这样的不平衡情况下,对组合收益的分析意义不大。因此可以用独立排序的方法评估X、Y之间的相关性程度。

如果使用条件排序,需要考虑是先按X排序还是先按Y排序,研究的是在控制了一个因子后,另一个因子的表现。因此可以分析一个因子相比另一个因子是否有信息增益。如果有信息增益,在控制因子的每一层内,另一个因子都依然会是单调的,有明显超额收益,如果信息增益不多,在控制了一个因子之后,另一个因子的分层表现可能会没有什么差异。同时条件排序下每个组合中的数目都是相同的,不会出现不平衡情况。

这两种排序都是有用的,接下来给一个代码实现的例子。

取A股市场的市值因子和市净率因子,数据从2010年-2018年。对这两个因子做双重排序,数据和代码在后台回复“双重排序”获取。

首先对这两个因子做单因子测试,用到的函数如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import os
os.chdir('E:\\quant\\doublesort\\')

import numpy as np
import pandas as pd
import datetime
import matplotlib.pyplot as plt
import seaborn as sns

def getRet(price,freq ='d',if_shift = True):
    price = price.copy()
   
    if freq == 'w':
        price['weeks'] = price['tradedate'].apply(lambda x:x.isocalendar()[0]*100 + x.isocalendar()[1])
        ret = price.groupby(['weeks','stockcode']).last().reset_index()
        del ret['weeks']
    
    elif freq =='m':
        price['ym'] = price.tradedate.apply(lambda x:x.year*100 + x.month)
        ret = price.groupby(['ym','stockcode']).last().reset_index()
        del ret['ym']
    
    ret = ret[['tradedate','stockcode','price']]
    if if_shift:
        ret = ret.groupby('stockcode').apply(lambda x:x.set_index('tradedate').price.pct_change(1).shift(-1))
    else:
        ret = ret.groupby('stockcode').apply(lambda x:x.set_index('tradedate').price.pct_change(1))
    
    ret = ret.reset_index()
    ret = ret.rename(columns = {ret.columns[2]:'ret'})
    return ret


def getdate(x):
    if type(x) == str:
        return pd.Timestamp(x).date()
    else:
        return datetime.date(int(str(x)[:4]),int(str(x)[4:6]),int(str(x)[6:]))


def getICSeries(factors,ret,method):
    # method = 'spearman';factors = fall.copy();

    icall = pd.DataFrame()
    fall = pd.merge(factors,ret,left_on = ['tradedate','stockcode'],right_on = ['tradedate','stockcode'])
    icall = fall.groupby('tradedate').apply(lambda x:x.corr(method = method)['ret']).reset_index()
    icall = icall.drop(['ret'],axis = 1).set_index('tradedate')

    return icall


def ifst(x):
    if pd.isnull(x.entry_dt):
        return 0
    elif (x.tradedate < x.entry_dt) |(x.tradedate > x.remove_dt):
        return 0
    else:
        return 1
 

def GroupTestAllFactors(factors,ret,groups):
    # factors = f1_norm[['tradedate','stockcode','pb']].copy();ret = ret=.copy();groups = 10
    """
    一次性测试多个因子
    """
    fnames = factors.columns
    fall = pd.merge(factors,ret,left_on = ['stockcode','tradedate'],right_on = ['stockcode','tradedate'])
    Groupret = []
 
    for f in fnames: # f = fnames[2]
        if ((f != 'stockcode')&(f != 'tradedate')):
            fuse = fall[['stockcode','tradedate','ret',f]]
# fuse['groups'] = pd.qcut(fuse[f],q = groups,labels = False)
            fuse['groups'] = fuse[f].groupby(fuse.tradedate).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))
            result = fuse.groupby(['tradedate','groups']).apply(lambda x:x.ret.mean())
            result = result.unstack().reset_index()
            result.insert(0,'factor',f)
            Groupret.append(result)
 

    Groupret = pd.concat(Groupret,axis = 0).reset_index(drop = True)
 
    Groupnav = Groupret.iloc[:,2:].groupby(Groupret.factor).apply(lambda x:(1 + x).cumprod())
    Groupnav = pd.concat([Groupret[['tradedate','factor']],Groupnav],axis = 1)
 
    return Groupnav


def plotnav(Groupnav):
    """
    GroupTest作图
    """
    for f in Groupnav.factor.unique(): # f = Groupnav.factor.unique()[0]
        fnav = Groupnav.loc[Groupnav.factor ==f,:].set_index('tradedate').iloc[:,1:]
        groups = fnav.shape[1]
        lwd = [2]*groups
        ls = ['-']*groups
        
        plt.figure(figsize = (10,5))
        for i in range(groups):
            plt.plot(fnav.iloc[:,i],linewidth = lwd[i],linestyle = ls[i])
        plt.legend(list(range(groups)))
        plt.title('factor group test: ' + f ,fontsize = 20)

读取数据,计算IC

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mktcap = pd.read_excel('mktcap.xlsx',encoding = 'gbk')
pb = pd.read_excel('PB_monthly.xlsx',encoding = 'gbk')
price = pd.read_excel('price.xlsx',encoding = 'gbk')
ST = pd.read_excel('ST.xlsx',encoding = 'gbk')


startdate = datetime.date(2010,12,31)
enddate = datetime.date(2018,12,31)

pb = pb.fillna(0)
price = price.loc[(price.tradedate >= startdate) & (price.tradedate <= enddate)].reset_index(drop = True)
pb = pb.loc[(pb.tradedate >= startdate) & (pb.tradedate <= enddate)].reset_index(drop = True)
mktcap = mktcap.loc[(mktcap.tradedate >= startdate) & (mktcap.tradedate <= enddate)].reset_index(drop = True)
  

ret_m = getRet(price,freq ='m',if_shift = True)


fall = pd.merge(mktcap,pb,left_on = ['tradedate','stockcode'],right_on = ['tradedate','stockcode'])
 



# 剔ST
fall = pd.merge(fall,ST,left_on = 'stockcode',right_on = 'stockcode',how = 'left')
fall['if_st'] = fall.apply(ifst,axis = 1)
fall = fall.loc[fall.if_st == 0].reset_index(drop = True)
fall = fall.drop(['if_st','entry_dt','remove_dt'],axis = 1)



# 计算IC
ics = getICSeries(fall,ret_m,'spearman')
ics.cumsum().plot(title = 'cum IC',figsize = (8,4))
ics.mean()
ics.mean()/ics.std()*np.sqrt(12)

累计IC

IC、年化ICIR如下

分层测试如下,分五层

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 分层测试
groups = 5
Groupnav = GroupTestAllFactors(fall,ret_m,groups)
plotnav(Groupnav)

接下来对这两个因子进行双重排序,生成5x5的投资组合。首先做独立排序,统计每个分组中的股票占比如下,横轴为市净率分组,纵轴为市值分组,1-5因子值逐渐增大。

从结果来看,各组的股票数占比差异不大,表明两个因子相关性不高。计算这25个投资组合的净值曲线结果如下

不是非常容易观察, 计算每个投资组合的平均月度收益率,做5x5的热力图如下

可以看出,不论纵轴还是横轴,随着一个因子组别的上升,投资组合的平均收益率下降,表明因子独立且有信息增益,更细致的话还可以关注波动率、夏普。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 独立排序

f1 = 'mktcap'
f2 = 'pb'


f = fall[['tradedate','stockcode',f1,f2]].copy()

f[f1] = f[f.columns[2]].groupby(f.tradedate).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))
f[f2] = f[f.columns[3]].groupby(f.tradedate).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))


res = f.groupby([f1,f2]).count()
res = res.iloc[:,1].reset_index()
res = res.pivot(index = f1,columns = f2,values = 'stockcode')
res = res/f.shape[0]


# 基本独立,均匀分布
plt.figure(figsize = (8,8))
sns.heatmap(res,cmap = 'YlGnBu', annot=True,square = True)
plt.show()


f = pd.merge(f,ret_m,left_on = ['tradedate','stockcode'],right_on = ['tradedate','stockcode'])

f['groups'] = f.apply(lambda x:str(int(x[f1])) + '-' + str(int(x[f2])),axis = 1)

res = f.groupby(['tradedate','groups']).apply(lambda x:x.ret.mean())
res = res.unstack().reset_index()

res.set_index('tradedate').cumsum().plot(figsize = (8,6))


yret = res.iloc[:,1:].mean()
yret = yret.reset_index()
yret.columns = ['groups','ret']
yret[f1] = yret.groups.apply(lambda x:x[0])
yret[f2] = yret.groups.apply(lambda x:x[2])


plt.figure(figsize = (8,8))
sns.heatmap(yret.pivot(index = f1,columns = f2,values = 'ret'),cmap = 'YlGnBu',annot = True,square = True)
plt.show()

接下来分别按两个因子进行条件排序,重复上述过程。

市净率按市值排序

市值按市净率排序

因子分层表现很好,表明有有信息增益。代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 先按f1分组,再按f2分组 doublesorts


f1 = 'mktcap'
f2 = 'pb'


f = fall[['tradedate','stockcode',f1,f2]].copy()

f[f1] = f[f.columns[2]].groupby(f.tradedate).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))
f[f2] = f[f.columns[3]].groupby([f.tradedate,f[f1]]).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))
f = pd.merge(f,ret_m,left_on = ['tradedate','stockcode'],right_on = ['tradedate','stockcode'])

f['groups'] = f.apply(lambda x:str(int(x[f1])) + '-' + str(int(x[f2])),axis = 1)

res = f.groupby(['tradedate','groups']).apply(lambda x:x.ret.mean())
res = res.unstack().reset_index()

res.iloc[:,1:].cumsum().plot(figsize = (8,6))


yret = res.iloc[:,1:].mean()
yret = yret.reset_index()
yret.columns = ['groups','ret']
yret[f1] = yret.groups.apply(lambda x:x[0])
yret[f2] = yret.groups.apply(lambda x:x[2])

plt.figure(figsize = (8,8))
sns.heatmap(yret.pivot(index = f1,columns = f2,values = 'ret'),cmap = 'YlGnBu',annot = True,square = True)
plt.show()
f1 = 'pb'
f2 = 'mktcap'

f = fall[['tradedate','stockcode',f1,f2]].copy()

f[f1] = f[f.columns[2]].groupby(f.tradedate).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))
f[f2] = f[f.columns[3]].groupby([f.tradedate,f[f1]]).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))
f = pd.merge(f,ret_m,left_on = ['tradedate','stockcode'],right_on = ['tradedate','stockcode'])

f['groups'] = f.apply(lambda x:str(int(x[f1])) + '-' + str(int(x[f2])),axis = 1)

res = f.groupby(['tradedate','groups']).apply(lambda x:x.ret.mean())
res = res.unstack().reset_index()

res.iloc[:,1:].cumsum().plot(figsize = (8,6))


yret = res.iloc[:,1:].mean()
yret = yret.reset_index()
yret.columns = ['groups','ret']
yret[f1] = yret.groups.apply(lambda x:x[0])
yret[f2] = yret.groups.apply(lambda x:x[2])

plt.figure(figsize = (8,8))
sns.heatmap(yret.pivot(index = f1,columns = f2,values = 'ret'),cmap = 'YlGnBu',annot = True,square = True)
plt.show()
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 量化小白躺平记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
3 条评论
热度
最新
双重排序
双重排序
回复回复点赞举报
求代码,双重排序
求代码,双重排序
回复回复点赞举报
求代码
求代码
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
Fama-French三因子回归A股实证(附源码)
Fama-French三因子回归是量化中最经典的模型之一,最早提出是在论文《Common risk factors in the returns on stocks and bonds》中,FAMA三因子回归模型可表示如下
量化小白
2023/04/03
4.7K3
Fama-French三因子回归A股实证(附源码)
因子评估全流程详解
首先,这是一篇值得收藏的干货文。基本上覆盖到了因子评估的每个方面每个细节,小白友好型,很长,慢慢看。
量化小白
2023/04/03
5.2K1
因子评估全流程详解
Carhart四因子模型A股实证(附源码)
接上一篇《Fama-French三因子回归A股实证》,继续写Carhart四因子模型,整个过程比较容易,还是基于Fama三因子的框架,多加进去一个动量因子进行回归。全文的代码数据论文获取请在后台回复“C4"。
量化小白
2020/11/17
4.4K0
Carhart四因子模型A股实证(附源码)
Fama-Macbeth 回归和Newey-West调整
Fama Macbeth是一种通过回归方法做因子检验,并且可以剔除残差截面上自相关性的回归方法,同时为了剔除因子时序上的自相关性,可以通过Newey West调整对回归的协方差进行调整。
量化小白
2019/12/30
14.4K25
基于高阶矩的行业轮动
大量研究表明,A股行业有明显的轮动现象,并且与A股相反,行业指数通常呈现动量特征,即前期涨幅高的行业,会延续上涨的趋势,比前期涨幅低的行业有明显超额收益,这一现象之前的文章中也探究过,具体可以参考《研报复制(六):行业轮动的黄金律》。
量化小白
2019/12/31
1.3K0
基于高阶矩的行业轮动
Optiver波动率预测大赛系列解读二:LightGBM模型及特征工程
量化投资与机器学习微信公众号,是业内垂直于量化投资、对冲基金、Fintech、人工智能、大数据等领域的主流自媒体。公众号拥有来自公募、私募、券商、期货、银行、保险、高校等行业20W+关注者,连续2年被腾讯云+社区评选为“年度最佳作者”。 前言 Optiver波动率预测大赛于上个月27号截止提交,比赛终于告一段落,等待着明年1月份的最终比赛结果。Kaggle上,由财大气粗的对冲基金大佬主办的金融交易类预测大赛,总能吸引大量的人气。在过去3个月的比赛中,也诞生了很多优秀的开源代码,各路神仙应用各种模型算法,在竞争激烈的榜单你追我赶。 关于这个比赛,网络上陆陆续续也有很多参赛经验的分享。但为了充分吸收大神们的精髓,公众号还是决定从0到1解读各种不同类型的开源比赛代码,方便小伙伴们学习归纳,并应用到实际研究中去。本系列大概安排内容如下:
量化投资与机器学习微信公众号
2021/10/22
2.8K0
Optiver波动率预测大赛系列解读二:LightGBM模型及特征工程
单因子测试(下)——回归测试法
之前两篇分别总结了因子数据的预处理和单因子测试的分层测试法,本篇总结回归测试法,相较于分层测试法,回归测试法更简洁。
量化小白
2019/01/22
6.2K1
用python输出stata一样的标准化回归结果
如果你经常用stata写论文,会了解stata有个outreg2的函数,可以把回归的结果输出成非常规范的论文格式,并且可以把多个回归结果并在一起,方便对比。例如下图
量化小白
2020/11/03
5.9K0
用python输出stata一样的标准化回归结果
pandas进阶
pandas使用get_dummies进行one-hot编码 import pandas as pd df = pd.DataFrame([ ['green', 'M', 10.1, 'class1'], ['red', 'L', 13.5, 'class2'], ['blue', 'XL', 15.3, 'class1']]) df.columns = ['color', 'size', 'prize', '
李智
2018/08/03
5330
研报复制(六):行业轮动的黄金律
原文是对申万一级行业做的,这里对申万、中信都测了一下, 频率上原文是月频,这里分别测了月频和周频,时间区间同研报
量化小白
2019/10/21
1.7K0
研报复制(六):行业轮动的黄金律
研报复制(七):A股行业动量的精细结构
其中关于动量效应和行业动量纵向切割的部分,已经在上一篇中复制过,本文复制报告关于行业动量的横向切割部分。
量化小白
2023/04/03
5140
研报复制(七):A股行业动量的精细结构
最详细的 Python 结合 RFM 模型实现用户分层实操案例!
1、分析目的:用户分类 2、数据获取:Excel 数据 3、清洗加工:Excel、Python 4、建立模型:RFM 5、数据可视化 6、结论与建议
杰哥的IT之旅
2021/06/01
2.1K0
最详细的 Python 结合 RFM 模型实现用户分层实操案例!
baseline来啦!第三届厦门国际银行数创金融杯金融营销建模大赛
厦门国际银行数创金融杯已经举办第三届了,是专门针对金融机构痛点专门设立的金融风控竞赛。很多初学者不知道如何快速入门数据挖掘,其实最好的方式就是自己动手做一遍竞赛,这其中有业务理解,也有技术技巧上的提升,对于个人进步都是一次难得的机会。
Python数据科学
2022/03/11
8520
基于机器学习的IC电商数据挖掘-数据探索篇
从描述统计信息中发现price字段的最小值是0,判定位异常;我们选择price大于0的信息:
皮大大
2023/08/25
3520
基于机器学习的IC电商数据挖掘-数据探索篇
因果推断与反事实预测——利用DML进行价格弹性计算(二十四)
经济学课程里谈到价格需求弹性,描述需求数量随商品价格的变动而变化的弹性。价格一般不直接影响需求,而是被用户决策相关的中间变量所中介作用。假设 Q 为某个商品的需求的数量,P 为该商品的价格,则计算需求的价格弹性为,
悟乙己
2022/05/09
4.4K0
因果推断与反事实预测——利用DML进行价格弹性计算(二十四)
用户行为分析(Python)
电商、互联网、金融这三驾马车是对数据分析应用最为广泛的行业,同时也占据了就业市场上绝大多数的数据分析岗位,只因日常业务产生的海量数据蕴含着无尽的价值。 本次就通过电商角度,选取阿里天池项目中的淘宝App用户行为数据利用Python进行数据分析。
用户8949263
2022/04/08
5.8K0
用户行为分析(Python)
【Pandas教程】像写SQL一样用Pandas~
Python在数据分析领域有三个必须需要熟悉的库,分别是pandas,numpy和matplotlib,如果排个优先级的话,我推荐先学pandas。
Awesome_Tang
2019/06/17
2.5K0
python pandas教程
#coding=utf-8 import numpy as np import pandas as pd import matplotlib.pyplot as pyplot #s=pd.Series([7,'Heisenberg',3.14,-1789710578,'Happy Eating!']) #print s #Series可以转换字典 d = {'Chicago': 1000, 'New York': 1300, 'Portland': 900, 'San Francisco': 110
李智
2018/08/03
1.6K0
数据清洗、合并、转化和重构
1、数据清洗是数据分析关键的一步,直接影响之后的处理工作 2、数据需要修改吗?有什么需要修改的吗?数据应该怎么调整才能适用于接下来的分析和挖掘? 3、是一个迭代的过程,实际项目中可能需要不止一次地执行这些清洗操作 4、处理缺失数据:pd.fillna(),pd.dropna() 1、数据连接(pd.merge) 1、pd.merge 2、根据单个或多个键将不同DataFrame的行连接起来 3、类似数据库的连接操作 示例代码: import pandas as pd import numpy as np
用户1332428
2018/03/08
9960
单细胞空间联合分析之SpatialScope
追风少年i
2024/04/15
2050
单细胞空间联合分析之SpatialScope
相关推荐
Fama-French三因子回归A股实证(附源码)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档