Py学习  »  Python

【中金 · 固收+】风格轮动:规律、策略与Python实现

中金固定收益研究 • 2 年前 • 376 次点击  


当固收+的投资者们提到“选结构”,风格的倾向几乎是首要的问题。从去年以来,市场至少经历过大盘成长转向小盘(2021年1月末)、小盘转向大盘价值(2021年10月)两次体感较为明显的风格转向。虽然个股、转债层面还有诸多值得讨论的细节,但风格上站错位置,几乎很难拿回好的回报。而在近期,投资者更加集中关注的,是成长是否有抄底价值。在此,我们基于历史情况和数据分析,给出一些可行的思路。


一、A股特征:趋势与边界并存


两个月窗口,正负20%是关键边际值

我们对近10年的风格相对跑赢幅度进行统计,以上证50和中证1000分别代表大\小盘,国证成长和国证价值代表成长\价值。相对稳定的结论是,两个月窗口下,市场整体的轮动偏移幅度基本在正负20%以内(不考虑2015年那一次过大的波动)。这意味着在统计意义上,正负20%是风格偏移的边际值,若触及则后续大概率会出现均值回归。若进一步细看,正负10%已经具备较大约束力。同时我们发现成长价值的分化较大小盘而言更弱,即使不考虑2014-2015年。但值得注意的是,在来到边界值之前,市场倾向于延续原来的方向。


图表1:股市风格轮动情况(40天涨跌幅差值,单位%)

资料来源:万得资讯,中金公司研究部,注:蓝线代表大票跑赢小票幅度,橙线代表价值跑赢成长幅度



轮动从两象限变为四象限

以2020年为节点,我们进一步观测到在此以前,风格相对跑赢幅度的散点具备较明显的正斜率,这意味着市场整体呈现着“小盘≈成长”、“大盘≈价值”的格局,即轮动是一个两象限互动。而随着2020年以来创业板企业体量明显增长,大盘成长股和小盘价值股的结构性机遇,上图的散点的分散度加剧,市场风格轮动的可能性增多。成为典型的四象限运动,也就是大盘不再与价值挂钩,小盘也不一定就意味着成长——而就实际样本来看,“小盘价值”多为一些小周期股,而这些标的可能股票的覆盖度不高,但却常常作为转债的发行人。


图表2:分年度股市风格轮动情况(2012-2019)

资料来源:万得资讯,中金公司研究部


图表2(续):分年度股市风格轮动情况(2020-2022YTD)


资料来源:万得资讯,中金公司研究部,注:2022年数据截至2022年3月24日



长期趋势偏好成长

而如果我们拉长窗口,能看到成长更容易在长时间窗口占据优势。也即,上述“40交易日20%”的约束关系聚焦于中短期,与更长期限视角下,风格的优劣势具备趋势性不矛盾。但在大小盘中,这样的趋势性则更不明显。


下图则试图说明这个问题:横轴为观测窗口,纵轴为99%的概率下,成长\价值或大盘\小盘的极限跑赢幅度。可以看到,就成长与价值而言,存在时间窗口越大,可能累计的分化越大的情况。而对于大小盘而言,极限分化度不稳定地随窗口扩大而扩大(而图上小盘在100日左右的占优主要来自行情整体强势后,小盘弹性更大,并非严格的风格问题)。


图表3:板块轮动偏离阈值情况(大小盘)


资料来源:万得资讯,中金公司研究部


图表3(续):板块轮动偏离阈值情况(价值成长)

资料来源:万得资讯,中金公司研究部



二、转债与风格轮动的适配度


考虑边界和趋势的转债轮动策略


固收+投资者的一个显著特征是可以用转债补足一些“深挖个股”触及不到的风格。而我们经过测算,风格轮动型策略也与转债的策略相适配。延续上述观察,市场的风格是一个趋势与约束并存的变量,我们很容易得到如下的一个策略:当换仓时,风格偏离未接近阈值时,我们遵循市场风格偏移的势能;若突破阈值,则做反向处理。


具体实现方式上,我们需要下面的辅助函数,以得到当前风格偏移的状态,程序如下。这里相当于生成一个1.a中的图表。实现上没有其他需要注意之处,但为避免后续编号混乱,在设定风格与对应代码的字典时选择了OrderedDict。


图表4:观测板块轮动的基础程序


def getDictPairs():
    return OrderedDict({"BigSmall": ["000016.SH", "000852.SH"],
            "ValueGrowth": ["399371.SZ", "399370.SZ"]})

def divergence(dictPairs, start, end, lstDay=None):
    if lstDay is None:
        lstDay = [40]
    
    lstCodes = []
    for v in dictPairs.values():
        lstCodes += v

    _, dfPct = w.wsd(",".join(lstCodes), "Close", dayOffset(start, -max(lstDay) - 1),
                     end, usedf=True)

    dfRet = pd.DataFrame(index=dfPct.index)

    for n in lstDay:
        for k, v in dictPairs.items():
            dfPctRolling = dfPct.rolling(n).apply(lambda x: 100 * (x[-1] / x[0] - 1))
            dfRet[k + ":{_n} days".format(_n=n)] = dfPctRolling[v[0]] - dfPctRolling[v[1]]
            
    dfRet.index = map(lambda x: x.strftime("%Y/%m/%d"), pd.to_datetime(dfRet.index))
    
    return dfRet.dropna(how="any")

资料来源:万得资讯,中金公司研究部


在选择风格时,我们首先判定其偏移度距离阈值是否较远,然后判定是否超出阈值,按照下图逻辑:


图表5:板块轮动策略逻辑示意

资料来源:万得资讯,中金公司研究部


由于已经设定好偏移数据,这里可以直接进入择券程序,择券框架请见《是时候,选出优秀的策略了》[1]。具体实现较为简单,没有值得特别注意之处。


图表6:具体择券程序


def rotation(obj, codes, date, tempCodes, dfAssetBook):
    # 获取市场风格分化情况.
    if not hasattr(obj, 'divergence'):
        obj.divergence = divergence(dictPairs, start, end)

    # 获取转债标的风格数据,得到dictStyle为字典,共有size和style两个字段,各保存一个表格以注明大小盘、成长价值风格
    if not hasattr(obj, 'dictStyle'):
        obj.dictStyle = getStyle(obj)

    sizeScore, styleScore = obj.divergence.loc[date, ["BigSmall:40 days","ValueGrowth:40 days"]]

    # 获取阈值的相关数据,这里我们采取动态把控,而非定量的阈值
    drSize, drStyle = 1 if sizeScore > 0 else -1,1 if styleScore > 0 else -1
    idx = obj.divergence.index.get_loc(date)
    # theta代表该风格过去1年在40交易日内累计偏移度的95%分位数,epsilon则是这一偏移度绝对值的0.5倍标准差
    # 下面判断当前状态与阈值距离时,则以 score - theta的绝对值是否大于epsilon为准
    thetaSize, thetaStyle = obj.divergence.iloc[idx - 250:idx].abs().quantile(0.95)
    epsilonSize, epsilonStyle = obj.divergence.iloc[idx - 250:idx].abs().std() / 2.
    
    # 经过计算后,下面stylePick的choice将显示为所选风格的字符
    stylePicks = {'size': {'score': sizeScore, 'direction': drSize, 'theta': thetaSize, 'epsilon': epsilonSize,
                           'choice': 0, 'category': ['large', 'small']},
                  'style': {'score': styleScore, 'direction': drStyle, 'theta': thetaStyle, 'epsilon': epsilonStyle,
                            'choice': 0, 'category': ['value', 'growth']}}

    for k, v in stylePicks.items():
        # 判定:当前状态距离阈值是否较远
        if abs(v['score'] - v['theta'] * v['direction']) >= v['epsilon']:
            
            v["choice"] = v["direction"] if abs(v["score"]) < v["theta"] else -v["direction"]

        else:
            v['choice'] = -v['direction']
            
        v['choice'] = v['category'][0] if v['choice' ] == 1 else v['category'][1]
    # 这里选择对应风格的品种,取交集
    srs = obj.dictStyle['size'].loc[date, tempCodes] == stylePicks['size']['choice']
    srs *= obj.dictStyle['style'].loc[date, tempCodes] == stylePicks['style']['choice']

    return srs[srs].index

资料来源:万得资讯,中金公司研究部



上述策略的效果我们测算如下:


图表7:策略具体情况


资料来源:万得资讯,中金公司研究部,回测数据自2017年12月29日至2022年4月6日



图表8:策略净值情况

资料来源:万得资讯,中金公司研究部,回测数据自2017年12月29日至2022年4月6日


同时这一策略的“生产率”也相对比较稳定,没有出现某一风格长时间占据主导地位的情况:直观角度来看大票仍然占比不低,因此我们认为加权策略的稳定性具备一定保证。


图表9:策略实际选择板块情况

资料来源:万得资讯,中金公司研究部



三:固收+择股的风格轮动


《再看固收+的绝对收益》[2]中,我们谈到固收+择股普遍具有分散、低估值、近似复制宽基的特性。我们希望借助风格轮动这个话题,再进一步推拟固收+择股还有什么可能性。根据我们测算,按照上述方式直接则股,亦可在250日的滚动窗口下,以接近76%的胜率战胜万得全A。而与债券结合后,在例如简单的股二债八分配下,亦可得到明显的增强效果。投资者可以考虑将股票、转债策略与债券结合,得到更加适合自己的风险回报分布(以下债券市场以债券指数为替代)。


图表10:固收+策略具体情况

资料来源:万得资讯,中金公司研究部,回测数据自2016年12月29日至2022年4月6日



图表11:固收+策略净值情况

资料来源:万得资讯,中金公司研究部,回测数据自2016年12月29日至2022年4月6日




[1] https://research.cicc.com/document/detail?id=221571

[2] https://research.cicc.com/document/detail?id=260562



文章来源

本文摘自:2022年4月8日已经发布的《固收+与风格轮动:规律、策略与Python实现


杨 冰 SAC执业证书编号:S0080515120002;SFC CE Ref: BOM868

房 铎 SAC执业证书编号:S0080519110001

罗 凡 SAC执业证书编号:S0080120070107

陈健恒 SAC执业证书编号:S0080511030011;SFC CE Ref: BBM220




法律声明

向上滑动参见完整法律声明及二维码



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/131436
 
376 次点击