【研报复现】年化27.1%,人工智能多因子大类资产配置策略之benchmark

我们的研报得现工作,用了两篇文章讲数据准备:

【研报复现】年化16.19%,人工智能多因子大类资产配置策略

【研报复现】年化27.1%,人工智能多因子大类资产配置策略之benchmark

今天我们来整理因子集。

研报里没有给出因子细节,我的解读是“根本不重要”。

因此,我就用qlib的Alpha158,结合部分WorldQuant101,还有常用的Ta-lib的技术分析指标来构建因子集。

我把函数集统一了命名,补充了一些函数:Alpha158可以正常工作了:

图片

from datafeed.factor.alpha import AlphaBase


class Alpha158(AlphaBase):

    def get_fields_names(self):
        # ['CORD30', 'STD30', 'CORR5', 'RESI10', 'CORD60', 'STD5', 'LOW0',
        # 'WVMA30', 'RESI5', 'ROC5', 'KSFT', 'STD20', 'RSV5', 'STD60', 'KLEN']
        fields = []
        names = []

        # kbar
        fields += [
            "(close-open)/open",
            "(high-low)/open",
            "(close-open)/(high-low+1e-12)",
            "(high-greater(open, close))/open",
            "(high-greater(open, close))/(high-low+1e-12)",
            "(less(open, close)-low)/open",
            "(less(open, close)-low)/(high-low+1e-12)",
            "(2*close-high-low)/open",
            "(2*close-high-low)/(high-low+1e-12)",
        ]
        names += [
            "KMID",
            "KLEN",
            "KMID2",
            "KUP",
            "KUP2",
            "KLOW",
            "KLOW2",
            "KSFT",
            "KSFT2",
        ]

        # =========== price ==========
        feature = ["OPEN", "HIGH", "LOW", "CLOSE"]
        windows = range(5)
        for field in feature:
            field = field.lower()
            fields += ["shift(%s, %d)/close" % (field, d) if d != 0 else "%s/close" % field for d in windows]
            names += [field.upper() + str(d) for d in windows]

        # ================ volume ===========
        fields += ["shift(volume, %d)/(volume+1e-12)" % d if d != 0 else "volume/(volume+1e-12)" for d in windows]
        names += ["VOLUME" + str(d) for d in windows]

        # ================= rolling ====================

        windows = [5, 10, 20, 30, 60]
        fields += ["shift(close, %d)/close" % d for d in windows]
        names += ["ROC%d" % d for d in windows]

        fields += ["mean(close, %d)/close" % d for d in windows]
        names += ["MA%d" % d for d in windows]

        fields += ["std(close, %d)/close" % d for d in windows]
        names += ["STD%d" % d for d in windows]

        #fields += ["slope(close, %d)/close" % d for d in windows]
        #names += ["BETA%d" % d for d in windows]

        fields += ["ts_max(high, %d)/close" % d for d in windows]
        names += ["MAX%d" % d for d in windows]

        fields += ["ts_min(low, %d)/close" % d for d in windows]
        names += ["MIN%d" % d for d in windows]

        fields += ["quantile(close, %d, 0.8)/close" % d for d in windows]
        names += ["QTLU%d" % d for d in windows]

        fields += ["quantile(close, %d, 0.2)/close" % d for d in windows]
        names += ["QTLD%d" % d for d in windows]

        #fields += ["ts_rank(close, %d)" % d for d in windows]
        #names += ["RANK%d" % d for d in windows]

        fields += ["(close-ts_min(low, %d))/(ts_max(high, %d)-ts_min(low, %d)+1e-12)" % (d, d, d) for d in windows]
        names += ["RSV%d" % d for d in windows]

        fields += ["ts_argmax(high, %d)/%d" % (d, d) for d in windows]
        names += ["IMAX%d" % d for d in windows]

        fields += ["ts_argmin(low, %d)/%d" % (d, d) for d in windows]
        names += ["IMIN%d" % d for d in windows]

        fields += ["(ts_argmax(high, %d)-ts_argmin(low, %d))/%d" % (d, d, d) for d in windows]
        names += ["IMXD%d" % d for d in windows]

        fields += ["correlation(close, log(volume+1), %d)" % d for d in windows]
        names += ["CORR%d" % d for d in windows]

        fields += ["correlation(close/shift(close,1), log(volume/shift(volume, 1)+1), %d)" % d for d in windows]
        names += ["CORD%d" % d for d in windows]

        fields += ["mean(close>shift(close, 1), %d)" % d for d in windows]
        names += ["CNTP%d" % d for d in windows]

        fields += ["mean(close<shift(close, 1), %d)" % d for d in windows]
        names += ["CNTN%d" % d for d in windows]

        fields += ["mean(close>shift(close, 1), %d)-mean(close<shift(close, 1), %d)" % (d, d) for d in windows]
        names += ["CNTD%d" % d for d in windows]

        fields += [
            "sum(greater(close-shift(close, 1), 0), %d)/(sum(abs(close-shift(close, 1)), %d)+1e-12)" % (d, d)
            for d in windows
        ]
        names += ["SUMP%d" % d for d in windows]

        fields += [
            "sum(greater(shift(close, 1)-close, 0), %d)/(sum(abs(close-shift(close, 1)), %d)+1e-12)" % (d, d)
            for d in windows
        ]
        names += ["SUMN%d" % d for d in windows]

        fields += [
            "(sum(greater(close-shift(close, 1), 0), %d)-sum(greater(shift(close, 1)-close, 0), %d))"
            "/(sum(abs(close-shift(close, 1)), %d)+1e-12)" % (d, d, d)
            for d in windows
        ]
        names += ["SUMD%d" % d for d in windows]

        fields += ["mean(volume, %d)/(volume+1e-12)" % d for d in windows]
        names += ["VMA%d" % d for d in windows]

        fields += ["std(volume, %d)/(volume+1e-12)" % d for d in windows]
        names += ["VSTD%d" % d for d in windows]

        fields += [
            "std(abs(close/shift(close, 1)-1)*volume, %d)/(mean(abs(close/shift(close, 1)-1)*volume, %d)+1e-12)"
            % (d, d)
            for d in windows
        ]
        names += ["WVMA%d" % d for d in windows]

        fields += [
            "sum(greater(volume-shift(volume, 1), 0), %d)/(sum(abs(volume-shift(volume, 1)), %d)+1e-12)"
            % (d, d)
            for d in windows
        ]
        names += ["VSUMP%d" % d for d in windows]

        fields += [
            "sum(greater(shift(volume, 1)-volume, 0), %d)/(sum(abs(volume-shift(volume, 1)), %d)+1e-12)"
            % (d, d)
            for d in windows
        ]
        names += ["VSUMN%d" % d for d in windows]

        fields += [
            "(sum(greater(volume-shift(volume, 1), 0), %d)-sum(greater(shift(volume, 1)-volume, 0), %d))"
            "/(sum(abs(volume-shift(volume, 1)), %d)+1e-12)" % (d, d, d)
            for d in windows
        ]
        names += ["VSUMD%d" % d for d in windows]

return fields, names

计算出来Qlib的159个因子,

图片

其实因子就是原始数据的数学变形。

在线性模型里还需要分析“多重共线性”的问题,但在机器学习里,反正就是一股脑进去,树模型还能把重要的特征筛选出来。

接下来就是数据集做一个量纲的统一,预处理之类的。

Qlib里有类似的预处理函数:

图片

def __call__(self, df):

def normalize(x, min_val=self.min_val, max_val=self.max_val):

return (x – min_val) / (max_val – min_val)

这里的预处理需要格外小心,不能引入未来函数。

本质是是做归一化,避免量纲不同,模型训练失真。

研报结论是CSMinMax效果最好,所谓CSMinMax就是在截面(时间,即calc_by_date),也就是每天对因子数据进行MinMax的归一化。——这一点上符合逻辑,从机器学习的角度,每天的数据是一个样本,而样本进行minmax,相对大小没有发生改变,只是“归一化”到0-1之间,更符合特定分布。

def cs_minmax(se: pd.Series):
    return (se - se.min()) / (se.max() - se.min())

图片

后续可以引入lightGBM机器学习模型,进行训练和策略开发。

阅读

《特斯拉传-万物皆我》,这本书读完了。

与其说读完了,不如说翻完了。

这本书确实写得一般,不同于传统传记,他的写作,更像是要与特斯拉融入一体,那种半梦半醒,活在自我构建的世界,那种感觉。

一个不为名,不为钱的怪才,狂热的科学实验爱好者。

注定是孤独的。

普通人无法成为特斯拉、图灵,我想也不愿意身边的人,成为他们。

但我们有可能“成为”爱迪生。——实用主义,解决问题,有美满的家庭,儿女成群,世人拥戴。

我想,只是欲望不过度,不伤害其他人,一定的财富与名利是好的。

本周要的书,估计是《点亮黑夜——爱迪生传》。

由于有这一次教训,我特意在电子书平台看了半章,确保内容不会再出现这样的问题。

“FIRE与退休”

之前聊过比较多“FIRE——财务自由,提前退休”的方式,也聊过“500,10%”的财务自由逻辑。

现在把这种方式,归入ABCZ的Z计划。

因为所谓退休,是一种心态。

即你不为设想中的明天而放弃今天的生活,就是退休的状态。

比如,你能很大程度上享受当下在做的事情,或者工作,就是一个退休之状态。

历史文章:

【研报复现】年化27.1%,人工智能多因子大类资产配置策略之benchmark

AI量化实验室——2024量化投资的星辰大海

原创文章第564篇,专注“AI量化投资、世界运行的规律、个人成长与财富自由”。

昨天今天把基础数据准备完成了:

【研报复现】年化16.19%,人工智能多因子大类资产配置策略

今天把这个数据统一处理,以便后续统一调用。

有8个指数可以通过tushare获取,包括A股指数和一些国际指数。

if __name__ == '__main__':
indexes = [
'000300.SH', # 沪深300
'000905.SH', # 中证500
'399006.SZ', # 创业板指数
'000012.SH', # 国债指数
'000832.CSI', # 中证转债指数
'HSI', # 香港恒生
'N225', # 日经225
'GDAXI' # 德国DAX指数
]
from config import DATA_DIR_QUOTES
for s in indexes:
df = get_index_quotes(s, '20050101')
print(df)
df.to_csv(DATA_DIR_QUOTES.joinpath('{}.csv'.format(s)), index=False)

另外四支,通过yfiance获取。

if __name__ == '__main__':
    for s in [
        'CL', # 原油
        'TNX', # 美十年期国债
        'GOLD', # 黄金
        '^NDX', #纳指100
    ]:
        df = get_quotes(s)
        print(df)
        df.to_csv(DATA_DIR_QUOTES.joinpath('{}.csv'.format(s)), index=False)

与原研报相比,我加了一个“创业板指数“——个人认为小市值还是比较重要的。

if __name__ == '__main__':
    for s in [
        'CL', # 原油
        'TNX', # 美十年期国债
        'GOLD', # 黄金
        '^NDX', #纳指100
    ]:
        df = get_quotes(s)
        print(df)
        df.to_csv(DATA_DIR_QUOTES.joinpath('{}.csv'.format(s)), index=False)

一共12支,历史以来所有日线:

图片

下载好的数据,格式也已经统一整理好,在星球发布了:

图片

图片

图片

这就是研报的benchark,在quantlab框架下复现还是比较容易的。

图片

下一步要引入机器学习来排序。

理论与实验

如同物理学分为理论物理和实验物理。

数学也分理论数学与应用数据。

理论与工程总是交替进行,理论的突然,需要数十上百年,可能需要天才的灵光乍现和临门一脚。

——比如相对论、质能方程、麦克斯韦方程组、图灵机。

工程上的改进,往往让理论落地,并生产实际价值,且最终受益。

比如做实验的爱迪生、造计算机的埃里克,电话、电报…,原子弹…

理论到实战还有非常远之距离,比如爱因斯坦本人就认为原子弹造不出来。

当前的大模型,AIGC或者说AGI之路,没有很强的理论支撑,都是工程上的改进。

——普通人似乎有机会做点啥。

爱迪生与特斯拉

《特斯拉传》和《爱迪生传》确实应该放到一起读。

图片

近几年,随着埃隆.马斯克把他的电动车命名为特斯拉,尼古拉.特斯拉,这个天才发明家,作为一个悲情英雄登上历史舞台。

1912年,特斯拉和爱迪生都被授予诺贝尔物理学奖,但都拒绝此奖,理由是无法忍受和对方一起分享这一荣誉!

真相如何,我们已经无从得知。

我们更关心,我们能学到点什么?

几个关键的信息,特斯拉放弃了交流电的专利权,其实拿到手里,用或不用与直接放弃,有天壤之别——就像金庸可以把版权1块钱出售。

尼古拉要支持自己那么多超前的想法,没有雄厚的资金是不现实的。

尼古拉的想法过于超前,而爱迪生的发明更加务实和解决实际问题。包括特斯拉做了无线电的实验,但波波夫和马可尼则是直接把它变得实用,并应用于生活场景之中。

对于我们普通人的启示——针对应用场景去改进。而不是天马行空,一则没有特斯拉这样的天份和才华,二来,这样的生活,也许也不是你想要的。

发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/103248
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!

(0)
股市刺客的头像股市刺客
上一篇 2024 年 7 月 26 日
下一篇 2024 年 7 月 29 日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注