头条 量化选基系列(十八):我似乎找到了量化选基因子的正确打开方式!

作者: 量化拯救散户

改进的牛市弹性+熊市韧性因子

在量化选基系列的第十六篇文章中,笔者介绍了一个由 Deepseek 推荐的因子,叫做牛市弹性-熊市韧性因子。如果您对这个因子感兴趣的话可以阅读量化选基系列(十六):牛市弹性+熊市韧性,Deepseek给了我一个看起来很高端的因子!

这个因子的表现看起来还可以,但是在量化选基系列整整十六篇文章中,它的表现并不是最好的。表现最好的因子应该是那两个研发失败了的均值回归因子,分别在量化选基系列(十五):一个对均值回归走火入魔的博主,整了个新活!结果在FOF基金上效果炸裂!量化选基系列(十四):精心设计了一个均值回归因子,结果却变成了强者恒强!这两篇文章中有介绍。

于是,对均值回归走火入魔的笔者,决定参考这两篇文章的思想来对牛市弹性+熊市韧性这个因子进行改造。看看,会不会有提升。

计算思想和代码

计算思想和之前的两篇介绍均值回归的思想是一致的,都是拿最近一个自然年的因子值减去该自然年之前所有数据计算的因子值。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def cal_factor(self, data):
    data['year'] = data.index.year
    data['month'] = data.index.month
    mom_factor = data.groupby(['year', 'month'], as_index=False, group_keys=False).apply(lambda x: x.sort_index().ffill().iloc[[-1]])
    mom_factor.reset_index(inplace=True)
    mom_factor = mom_factor.drop(columns=['month'])
    mom_factor.set_index(['index', 'year'], inplace=True)
    mom_factor = mom_factor.pct_change()
    mom_factor = mom_factor.replace(0.0, np.nan)
    mkt_rtn = (mom_factor.median(axis=1)).values.reshape(-1, 1)
    flag = mkt_rtn > 0
    mom_factor = mom_factor - mkt_rtn
    bull_elasticity = (mom_factor * np.where(flag, 1.0, np.nan))
    bear_resilience = (mom_factor * np.where(flag, np.nan, 1.0))
    res = []
    for i in tqdm(range(bull_elasticity.shape[1])):
        res.append(self.__cal_factors__(bull_elasticity.iloc[:, i:i+1], bear_resilience.iloc[:, i:i+1]))
    res = pd.concat(res, axis=1)
    return res

这段代码的前 15 行,和该系列第十六篇文章中是一致的,主要是计算每日的牛市弹性和熊市韧性这两个因子。

第 16-19 行,通过遍历的方式计算每一个标的当前自然年的因子值与该年之前的因子值之差。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@staticmethod
def __cal_factors__(bull, bear):
    col = bull.columns.tolist()[0]
    bull.reset_index(inplace=True)
    bear.reset_index(inplace=True)
    res = []
    idx_list = []
    for year in range(2017, 2025):
        history_bull = bull[bull['year'] < year]
        his_bull = history_bull[col].mean()
        cur_bull = bull[bull['year'] == year]
        day = cur_bull['index'].tolist()[-1]
        cur_bull = cur_bull[col].mean()
        if np.isnan(cur_bull):
            cur_bull = 0
        history_bear = bear[bear['year'] < year]
        his_bear = history_bear[col].mean()
        cur_bear = bear[bear['year'] == year]
        cur_bear = cur_bear[col].mean()
        if np.isnan(cur_bear):
            cur_bear = 0
        res.append(cur_bear + cur_bull - (his_bear + his_bull))
        idx_list.append(day)
    res = pd.Series(res, index=idx_list, name=col)
    return res
  • 第 3 行,获取当前标的的列名。
  • 第 4-5 行,通过 reset_index() 方法得到 indexyear 两列。
  • 第 9-10 行,计算历史牛市弹性因子值。
  • 第 13-15 行,计算当前自然年的牛市弹性因子值。
  • 第 16-17 行,获取历史熊市韧性因子值。
  • 第 18-21 行,获取当前自然年熊市韧性因子值。

实证分析

代码中,笔者去掉了历史上熊市韧性或者牛市弹性中任一个因子为 nan 的标的。所以在实证分析的时候,可能存在偏差。

所以,笔者将对每个类型的标的展示两个不同的实证分析结果。每个小标题下的第一张图是去掉了历史上有一个因子为 nan 的情形,而第二张图则是将 nan 置为 0 的情形(但是,历史牛市弹性和熊市韧性这两个因子中必须有一个不为 nan)。

01 股票型

这两张图有那么一丁点儿区别,但是区别不大。

和改进前的因子相比,这两张图都有了不错的提升,因子值最大的一组收益率更大了,而因子值最小的一组收益率更小了。

02 债券型

这个因子在债基上的表现并不是很好。

不过,有一点需要注意的是,这个因子的分层回测曲线和改进前的因子可以说是完全不同,改进前 group_0 收益率最高,改进后变成了 group_9

03 混合型

这个因子在混合型上的表现比改进之前好的,但是还是不行。

04 FOF型

FOF 应该是唯一一个改进后表现更差的基金类别了。从这个改进型因子的表现,笔者似乎觉得自己找到了量化选基因子的正确打开方式了,但是还需要更多的因子来验证以下,是不是用当前自然年的因子值减去历史的因子值就能获得更好的回测结果。

最后,感谢各位大佬一直以来的支持,也希望大家能够继续支持,笔者也将努力为大家提供更过优质的内容。所以,希望各位大佬能动动小手指,点点赞关注分享和喜欢。

- END -


来源: 微信公众号

目录

×