基金评价专题6:单因子构建及rankic评价

目录

1. 因子构建

1.1 因子解释

1.2 因子作用

1.3 因子刻画

2. 因子评价

2.1 Rank IC 解释

2.2 计算Rank IC值

2.3 Rank ICIR 解释

 2.4 计算 Rank ICIR 值


免责声明:本文由作者参考相关资料,并结合自身实践和思考独立完成,对全文内容的准确性、完整性或可靠性不作任何保证。同时,文中提及的基金仅作为举例使用,不构成推荐;文中所有观点均不构成任何投资建议。请读者仔细阅读本声明,若读者阅读此文章,默认知晓此声明。

1. 因子构建

1.1 因子解释

    因子,实际上就是一个由计算公式构建的指标(用于预测的一个变量)。通过抽象化的思维,来刻画描述对象存在的特征和规律,根据对应的规律可以挖掘出有用的信息。比如,常听说的动量因子,反转因子,市值因子,流动性因子等。从理论上理解起来相对有些抽象,接下来通过实际的案例来分析。因子主要

1.2 因子作用

   因子主要有两个作用:1.通过因子来遴选标的,这个是量化的常用方法。以本文为例,测算基金的某个因子值,通过因子值来遴选相应的基金。2.通过因子来进行风险识别和控制,这个是风控常用的方法。以Barra模型为例,通过测算投资组合的风险因子值,来识别当前组合在各个因子上的风险暴露,对于暴露超出预期的因子,需要在组合内进行相应的控制。

1.3 因子刻画

    夏普比率自身作为一个指标,因此可以直接作为一个因子。选取10只偏股型基金(此处需要注意,由于本文仅是举例,所以样本量控制的较小,在实际使用中,样本量应该足够大,在初筛或者没有其他约束条件的情况下最好是接近于全量,否则测算的结果很可能是无效的),计算其在2021年整年的夏普比率(无风险利率假设2%)。

import akshare as ak
import pandas as pd
import numpy as np
from scipy.stats import spearmanr


def get_data(code, year, dt=None):
    # 获取基金一整年的收益率或者夏普比率
    # code:str,基金代码,例如'000001'
    # year:int,年份,格式如2022
    # dt:str,输出数据的选择
    # 由于可能出现数据缺失的情况,因此添加异常处理
    try:
        data = ak.fund_open_fund_info_em(fund=code, indicator="累计净值走势")
        data['year'] = data['净值日期'].apply(lambda x: pd.to_datetime(x).year)
        new_data = data.loc[data['year'] == year]
        new_data = new_data.sort_values(['净值日期'], ascending=True)
        new_data['return'] = new_data['累计净值'].pct_change().fillna(0)
        annual_volatility = new_data['return'].std() * np.sqrt(252)
        annual_return = new_data['累计净值'].values[-1] / new_data['累计净值'].values[0] - 1
        rf = 0.02
        sharpe_ratio = (annual_return - rf) / annual_volatility
    except:
        sharpe_ratio = 9999
        annual_return = 9999
    if dt == None:
        one_df = pd.DataFrame({'基金代码': [code], str(year) + '收益率': [annual_return]})
    elif dt == 'sharpe':
        one_df = pd.DataFrame({'基金代码': [code], str(year) + '夏普比率': [sharpe_ratio]})
    return one_df


if __name__ == '__main__':
    code_list = ['001552', '001726', '001628', '001736', '001643',
                 '001556', '001521', '001528', '001541', '001542']
    year = 2021
    all_df = pd.DataFrame()
    for code in code_list:
        print(code)
        one_df = get_data(code, year, 'sharpe')
        all_df = all_df.append(one_df)

    由于数据可能存在缺失,为避免报错,添加异常处理:将出现异常后的结果设置为9999,便于后续清洗过滤。 

  得到的结果下:

2. 因子评价

2.1 Rank IC 解释

    Rank IC (信息系数)是一个统计量,用以刻画预测变量和实际结果之间的关系(评价因子的预测效果)。Rank IC 的计算方式是预测变量的排序值和实际结果排序值之间的相关关系(通常是spaerman秩相关系数)。

  关于Rank IC的值的使用方法,类似于皮尔逊相关系数,主要如下:

  1.Rank IC的值位于-1和1之间。

  2.Rank IC值为正,表明预测结果和实际结果之间是正向关系;值为负则表示为反向关系。

  3.Rank IC的绝对值越大,说明预测结果和实际结果之间的关系越强烈,预测的准确度越高。

  4.一般地,Rank IC的值大于5%,则说明因子是有效的(前提需要建立在大量样本的统计下)。

2.2 计算Rank IC值

   此处的预测变量指的就是我们的因子,而实际结果一般是使用未来的收益率(由于因子的统计区间是2021年一整年,因此本文设置2022年的收益率作为实际结果来检验评价因子的预测效果)。

   接下来,实现获取2022年收益率这一步。

    next_year = year + 1
    next_df = pd.DataFrame()
    for code in code_list:
        print(code)
        next_year_df = get_data(code, next_year)
        next_df = next_df.append(next_year_df)
    me_df = pd.merge(all_df, next_df, how='inner', on='基金代码').fillna(0)
    # 数据清洗
    new_df = me_df.loc[me_df[str(year) + '夏普比率'] != 9999]
    new_df = new_df.loc[new_df[str(next_year) + '收益率'] != 9999]

    此处代码续接前文。这里需要特别注意,必须要保证数据的完整性,即某只基金对应的因子值和实际结果都必须是存在的,如果是二者之间出现异常值,则需要过滤,避免影响后续IRank IC 的计算结果。

  得到的结果为:

   下一步,根据因子值(2021年夏普值)和实际结果(2022年收益率)对应的排序值(降序排列,即值越大,排序值越低),计算出对应的Rank IC的值。

    out_df = new_df.sort_values([str(year) + '夏普比率'], ascending=False)
    out_df['因子排序值'] = [x+1 for x in range(len(out_df))]
    out_df = out_df.sort_values([str(next_year) + '收益率'], ascending=False)
    out_df['实际结果排序值'] = [x+1 for x in range(len(out_df))]
    rank_ic = spearmanr(out_df['因子排序值'],out_df['实际结果排序值'])[0]

   out_df对应的结果为:

    对应得到的Rank IC的值为-0.38。因此在不考虑样本量较低的情况下,所选的样本中以及对应时间区间中,夏普比率是一个有效的负向因子,即2021年的夏普比率越高,整体在2022年的表现越差。

2.3 Rank ICIR 解释

   Rank ICIR(信息比率),计算的方式是Rank IC的均值除以Rank IC的标准差。单期的Rank IC值稳定性不足,因此需要关注多期Rank IC值的情况,以便更好的评价因子的效果。Rank ICIR是对Rank IC的进一步补充,其实用方法和Rank IC一样,此处不再进行重复叙述。 

 2.4 计算 Rank ICIR 值

    首先,获取多期的Rank IC值。为了代码的简洁,封装一个单期Rank IC的函数--get_Rank_Ic,然后根的循环年份来获取相应每期的Rank IC值。

import akshare as ak
import pandas as pd
import numpy as np
from scipy.stats import spearmanr


def get_data(code, year, dt=None):
    # 获取基金一整年的收益率或者夏普比率
    # code:str,基金代码,例如'000001'
    # year:int,年份,格式如2022
    # dt:str,输出数据的选择
    # 由于可能出现数据缺失的情况,因此添加异常处理
    try:
        data = ak.fund_open_fund_info_em(fund=code, indicator="累计净值走势")
        data['year'] = data['净值日期'].apply(lambda x: pd.to_datetime(x).year)
        new_data = data.loc[data['year'] == year]
        new_data = new_data.sort_values(['净值日期'], ascending=True)
        new_data['return'] = new_data['累计净值'].pct_change().fillna(0)
        annual_volatility = new_data['return'].std() * np.sqrt(252)
        annual_return = new_data['累计净值'].values[-1] / new_data['累计净值'].values[0] - 1
        rf = 0.02
        sharpe_ratio = (annual_return - rf) / annual_volatility
    except:
        sharpe_ratio = 9999
        annual_return = 9999
    if dt == None:
        one_df = pd.DataFrame({'基金代码': [code], str(year) + '收益率': [annual_return]})
    elif dt == 'sharpe':
        one_df = pd.DataFrame({'基金代码': [code], str(year) + '夏普比率': [sharpe_ratio]})
    return one_df


def get_Rank_Ic(year, code_list):
    # 获取rankic的值
    all_df = pd.DataFrame()
    next_df = pd.DataFrame()
    next_year = year + 1
    for code in code_list:
        print(year, code)
        one_df = get_data(code, year, 'sharpe')

        next_year_df = get_data(code, next_year)
        next_df = next_df.append(next_year_df)
        all_df = all_df.append(one_df)

    me_df = pd.merge(all_df, next_df, how='inner', on='基金代码').fillna(0)
    # 数据清洗
    new_df = me_df.loc[me_df[str(year) + '夏普比率'] != 9999]
    new_df = new_df.loc[new_df[str(next_year) + '收益率'] != 9999]

    # 计算排序值
    out_df = new_df.sort_values([str(year) + '夏普比率'], ascending=False)
    out_df['因子排序值'] = [x + 1 for x in range(len(out_df))]
    out_df = out_df.sort_values([str(next_year) + '收益率'], ascending=False)
    out_df['实际结果排序值'] = [x + 1 for x in range(len(out_df))]
    rank_ic = spearmanr(out_df['因子排序值'], out_df['实际结果排序值'])[0]
    return rank_ic


if __name__ == '__main__':
    code_list = ['001552', '001726', '001628', '001736', '001643',
                 '001556', '001521', '001528', '001541', '001542']
    ic_df = pd.DataFrame()
    year_list = [2015 + x for x in range(0, 7)]
    for year in year_list:
        one_ic = get_Rank_Ic(year, code_list)
        one_df = pd.DataFrame({'年份': [year], 'ic值': [one_ic]})
        ic_df = ic_df.append(one_df)

  对应得到的结果是:

  接下来,分别求Rank IC的均值和标准差,然后计算Rank ICIR的值。

    ic_mean = ic_df['ic值'].mean()
    ic_std = ic_df['ic值'].std()
    Rank_Ic_Ir = ic_mean / ic_std

  对应的结果如下: 

  为了结果更加直观,对所得的结果进行可视化的展示。

    # 作图
    import matplotlib.pyplot as plt

    year, ic = ic_df['年份'], ic_df['ic值']
    plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
    plt.rcParams['axes.unicode_minus'] = False
    plt.title('因子评价' + '(' + 'IC均值:' + str(round(ic_mean, 4)) +
              ';IC标准差:' + str(round(ic_std, 4)) + ';IR值:' + str(round(Rank_Ic_Ir, 4)) + ')')
    x = np.arange(len(year))
    plt.bar(x, ic, 0.5, label='逐年ic', tick_label=year)
    for i in range(len(ic)):
        plt.text(x[i], ic[i] + 0.01, round(ic[i], 4))
    plt.legend()
    plt.show()

  得到的结果如下:

   从结果来看:尽管Rank ICIR的值大于5%,但是Rank IC的标准差达到了32.7%;说明该因子具备较强的预测能力,但是稳定性不足,极端值较多。当然,出现这种结果是因为样本的数量较少。在实际的使用过程,除了需要较大的样本容量,还需要多个因子的组合来增加准确性。因为单个因子的预测作用有限,使用多因子组合可以有效提高预测的稳定性,后续将继续分享多因子组合。

本期分享结束,有何问题欢迎交流。

免责声明:本文由作者参考相关资料,并结合自身实践和思考独立完成,对全文内容的准确性、完整性或可靠性不作任何保证。同时,文中提及的基金仅作为举例使用,不构成推荐;文中所有观点均不构成任何投资建议。请读者仔细阅读本声明,若读者阅读此文章,默认知晓此声明。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值