首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
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.6K3
Fama-French三因子回归A股实证(附源码)
基于高阶矩的行业轮动
大量研究表明,A股行业有明显的轮动现象,并且与A股相反,行业指数通常呈现动量特征,即前期涨幅高的行业,会延续上涨的趋势,比前期涨幅低的行业有明显超额收益,这一现象之前的文章中也探究过,具体可以参考《研报复制(六):行业轮动的黄金律》。
量化小白
2019/12/31
1.3K0
基于高阶矩的行业轮动
Fama-Macbeth 回归和Newey-West调整
Fama Macbeth是一种通过回归方法做因子检验,并且可以剔除残差截面上自相关性的回归方法,同时为了剔除因子时序上的自相关性,可以通过Newey West调整对回归的协方差进行调整。
量化小白
2019/12/30
14.3K25
研报复制(七):A股行业动量的精细结构
其中关于动量效应和行业动量纵向切割的部分,已经在上一篇中复制过,本文复制报告关于行业动量的横向切割部分。
量化小白
2023/04/03
5020
研报复制(七):A股行业动量的精细结构
用python输出stata一样的标准化回归结果
如果你经常用stata写论文,会了解stata有个outreg2的函数,可以把回归的结果输出成非常规范的论文格式,并且可以把多个回归结果并在一起,方便对比。例如下图
量化小白
2020/11/03
5.9K0
用python输出stata一样的标准化回归结果
因子评估全流程详解
首先,这是一篇值得收藏的干货文。基本上覆盖到了因子评估的每个方面每个细节,小白友好型,很长,慢慢看。
量化小白
2023/04/03
5.1K1
因子评估全流程详解
研报复制(六):行业轮动的黄金律
原文是对申万一级行业做的,这里对申万、中信都测了一下, 频率上原文是月频,这里分别测了月频和周频,时间区间同研报
量化小白
2019/10/21
1.7K0
研报复制(六):行业轮动的黄金律
Carhart四因子模型A股实证(附源码)
接上一篇《Fama-French三因子回归A股实证》,继续写Carhart四因子模型,整个过程比较容易,还是基于Fama三因子的框架,多加进去一个动量因子进行回归。全文的代码数据论文获取请在后台回复“C4"。
量化小白
2020/11/17
4.4K0
Carhart四因子模型A股实证(附源码)
单因子测试(下)——回归测试法
之前两篇分别总结了因子数据的预处理和单因子测试的分层测试法,本篇总结回归测试法,相较于分层测试法,回归测试法更简洁。
量化小白
2019/01/22
6.2K1
【Pandas教程】像写SQL一样用Pandas~
Python在数据分析领域有三个必须需要熟悉的库,分别是pandas,numpy和matplotlib,如果排个优先级的话,我推荐先学pandas。
Awesome_Tang
2019/06/17
2.4K0
Optiver波动率预测大赛系列解读二:LightGBM模型及特征工程
量化投资与机器学习微信公众号,是业内垂直于量化投资、对冲基金、Fintech、人工智能、大数据等领域的主流自媒体。公众号拥有来自公募、私募、券商、期货、银行、保险、高校等行业20W+关注者,连续2年被腾讯云+社区评选为“年度最佳作者”。 前言 Optiver波动率预测大赛于上个月27号截止提交,比赛终于告一段落,等待着明年1月份的最终比赛结果。Kaggle上,由财大气粗的对冲基金大佬主办的金融交易类预测大赛,总能吸引大量的人气。在过去3个月的比赛中,也诞生了很多优秀的开源代码,各路神仙应用各种模型算法,在竞争激烈的榜单你追我赶。 关于这个比赛,网络上陆陆续续也有很多参赛经验的分享。但为了充分吸收大神们的精髓,公众号还是决定从0到1解读各种不同类型的开源比赛代码,方便小伙伴们学习归纳,并应用到实际研究中去。本系列大概安排内容如下:
量化投资与机器学习微信公众号
2021/10/22
2.8K0
Optiver波动率预测大赛系列解读二:LightGBM模型及特征工程
首次公开,用了三年的 pandas 速查表!
导读:Pandas 是一个强大的分析结构化数据的工具集,它的使用基础是 Numpy(提供高性能的矩阵运算),用于数据挖掘和数据分析,同时也提供数据清洗功能。
IT阅读排行榜
2022/04/14
7.8K0
首次公开,用了三年的 pandas 速查表!
baseline来啦!第三届厦门国际银行数创金融杯金融营销建模大赛
厦门国际银行数创金融杯已经举办第三届了,是专门针对金融机构痛点专门设立的金融风控竞赛。很多初学者不知道如何快速入门数据挖掘,其实最好的方式就是自己动手做一遍竞赛,这其中有业务理解,也有技术技巧上的提升,对于个人进步都是一次难得的机会。
Python数据科学
2022/03/11
8430
基于机器学习的IC电商数据挖掘-数据探索篇
从描述统计信息中发现price字段的最小值是0,判定位异常;我们选择price大于0的信息:
皮大大
2023/08/25
3370
基于机器学习的IC电商数据挖掘-数据探索篇
Pandas之实用手册
Pandas作为大数据分析最流行的框架之一。用好Pandas就像大数据工程师用好SQL用好Excel一样重要。如果你打算学习 Python 中的数据分析、机器学习或数据科学工具,大概率绕不开Pandas库。Pandas 是一个用于 Python 数据操作和分析的开源库。
mariolu
2024/02/02
7500
pandas技巧6
可根据⼀个或多个键将不同DataFrame中的⾏连接起来,它实现的就是数据库的join操作 ,就是数据库风格的合并
皮大大
2021/03/02
2.9K0
Pandas三百题
pd.set_option('display.max_columns',None)
SingYi
2022/07/13
5.3K0
Pandas三百题
13个Pandas实用技巧,有点香 !
归纳整理了一些工作中常用到的pandas使用技巧,方便更高效地实现数据分析。文章很短,不用收藏就能Get~
Python数据科学
2020/06/22
1.1K0
用python绘制有效前沿
投资中最关心的两个问题是预期收益与风险,当对多个资产进行投资时,如何测定组合的风险与收益,如何根据这两项指标进行资产权重配置? 马科维茨理论给出了解决这一问题的框架,被认为是现代金融学的开端。本文首先给出马科维茨均值方差模型的理论说明,随后用股票指数数据绘制组合的有效前沿,最后给出一种应用方法,获取代码和数据请在后台回复“代码”。
量化小白
2019/03/06
7.7K8
用python绘制有效前沿
利用python实现地理加权回归(GWR)与网约车订单数据挖掘
说到地理加权回归,相信大家肯定不会陌生。作为一种先进的空间数据分析技术,地理加权回归能够充分捕捉空间关系的非平稳性。举个简单的不恰当的例子,我们要对中国各个城市的奢侈品消费量与人均收入进行建模。正常的的理解是人均收入越高,奢侈品消费量就越大,在全国各个城市都应该是这种关系(这也正是全局模型的前提假设)。但事实真的是这样吗?现实情况可能是在一些比较张扬的地方(比如我们大东百
DataCharm
2021/02/22
5.1K5
利用python实现地理加权回归(GWR)与网约车订单数据挖掘
相关推荐
Fama-French三因子回归A股实证(附源码)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档