• ETF轮动+RSRS择时,加上卡曼滤波:年化48.41%,夏普比1.89


    原创文章第112篇,专注“个人成长与财富自由、世界运作的逻辑, AI量化投资”。

    昨天我们设计了一个不错的策略:etf动量轮动+大盘择时:年化30%的策略。ETF动量轮动+RSRS择时,动量其实一直都有效,如何定义动量可以有优化空间。今天我们继续来优化它。

    01 从tushare下载ETF数据

    在现实世界中,我们会交易指数对应的ETF而非指数,从理论上讲,收益只会更高,因为指数本身不包含分红之类的信息。

    如下是一些常用的ETF,包括宽基:上证50, 沪深300,创业板,创业板50,行业如医药,消费,红利,新能车等。

    etfs = ['510300.SH',  # 沪深300ETF
            '159949.SZ',  # 创业板50
            '510050.SH',  # 上证50ETF
            '159928.SZ',  # 中证消费ETF
            '510500.SH',  # 500ETF
            '159915.SZ',  # 创业板 ETF
            '512120.SH',  # 医药50ETF
            '159806.SZ',  # 新能车ETF
            '510880.SH',  # 红利ETF
            ]
    
    download_etfs(etfs)

    dowload_etfs里面调用了get_etf

    def download_etfs(etfs):
        for etf in etfs:
            print(etf)
            df = get_etf(etf)
            print(df)
            if df is None or len(df) == 0:
                print('error')
                continue
            with pd.HDFStore('{}/index.h5'.format(DATA_DIR_HDF5.resolve())) as store:
                store[symbol] = df

    这里需要特别注意一点,就是etf有“复权信息”,一般我们把复权因子直接乘到对应的OHLV数据上。

    def get_etf(code):
        # 拉取数据
        df = pro.fund_daily(**{
            "trade_date": "",
            "start_date": "",
            "end_date": "",
            "ts_code": code,
            "limit": "",
            "offset": ""
        }, fields=[
            "ts_code",
            "trade_date",
            "open",
            "high",
            "low",
            "close",
            "vol"
        ])
    
        df.rename(columns={'trade_date': 'date', 'ts_code': 'code', 'vol': 'volume'}, inplace=True)
        df.set_index('date', inplace=True)
        # 拉取数据
        df_adj = pro.fund_adj(**{
            "ts_code": code,
            "trade_date": "",
            "start_date": "",
            "end_date": "",
            "offset": "",
            "limit": ""
        }, fields=[
            "trade_date",
            "adj_factor"
        ])
        df_adj.rename(columns={'trade_date': 'date'}, inplace=True)
        df_adj.set_index('date', inplace=True)
        df = pd.concat([df, df_adj], axis=1)
        df.dropna(inplace=True)
        for col in ['open', 'high', 'low', 'close']:
            df[col] *= df['adj_factor']
        df.index = pd.to_datetime(df.index)
        df.sort_index(ascending=True, inplace=True)
        return df

    02 换成真实的ETF回测

    symbols = ['510300.SH', '159915.SZ']
    names = ['order_by']
    fields = ['Slope($close,20)']
    # fields = ['$close/Ref($close,20)-1']
    
    from engine.bt_engine import BacktraderEngine
    from datetime import datetime
    
    e = BacktraderEngine(init_cash=1000000, benchmark='399006.SZ', start=datetime(2014, 1, 1))
    e.add_features(symbols, names, fields)
    e.add_extra('000300.SH', fields=['RSRS($high,$low,18,600)', '$RSRS_beta<0.8'], names=['RSRS', 'signal'])
    
    from engine.strategy.algos import SelectTopK, PickTime, WeightEqually
    
    e.run_algo_strategy([SelectTopK(K=1), PickTime(), WeightEqually()])
    e.analysis(pyfolio=False)

    年化31.11%, 回撤降为31.06%。

    若把RSRS择时标准更换为ETF本身,即510500.SH。

    年化提升到38.17%, 回撤降至23.57%,夏普比达到1.55

    symbols = [
        '510050.SH',  # 上证50ETF
        #'159928.SZ',  # 中证消费ETF
        '510300.SH',  # 沪深300ETF
        '159915.SZ',  # 创业板50
        #'512120.SH',  # 医药50ETF
        #'159806.SZ',  # 新能车ETF
        #'510880.SH',  # 红利ETF
    ]

    添加行业进来轮动,会降低收益率,原因待分析。

    03 卡尔曼过滤

    添加一个卡尔曼滤波的函数:

    from pykalman import KalmanFilter
    
    
    class KF(Rolling):
    
        def __init__(self, feature, damping=1, N=1):
            super(KF, self).__init__(feature, N, "kalman")
            self.damping = damping
    
        def _load_internal(self, instrument):
            series = self.feature.load(instrument)
            series = series.fillna(0.0)
            observation_covariance = 0.15
            initial_value_guess = 1
            transition_matrix = 1
            transition_covariance = 0.1
    
            kf = KalmanFilter(transition_matrices=[1],
                              observation_matrices=[1],
                              initial_state_mean=0,
                              initial_state_covariance=1,
                              observation_covariance=1,
                              transition_covariance=.01)
            pre, _ = kf.smooth(np.array(series))
            pre = pre.flatten()
            series = pd.Series(pre, index=series.index)
            return series
    

    对昨天的“动量信号”进行滤波etf动量轮动+大盘择时:年化30%的策略

    年化提升到48.41%,回撤降至21.48%,夏普比达到1.89!

    names = ['order_by']
    fields = ['KF(Slope($close,20))']

    明天继续可以对RSRS进行标准分的优化。

    小结:

    ETF动量轮动是个好策略,卡尔曼滤波是一个好策略,RSRS是个不错的指标。

    代码、数据已经上传到星球-量化专栏。

    我的开源项目及知识星球

  • 相关阅读:
    web期末网站设计大作业 奶茶店网站美食餐饮网站设计与实现(HTML+CSS+JavaScript)
    docker基础命令 docker镜像和docker容器的操作基础命令的思维导图
    一篇文章带你搞懂前端Cookie
    Element类型【2】
    【Linux】常见指令(下)
    (总结一)Halcon基础之寻找目标特征+转正
    关于 KL 散度和变分推断的 ELBO
    进程与线程的区别
    思腾云计算
    Linux内核详解与内核优化方案
  • 原文地址:https://blog.csdn.net/weixin_38175458/article/details/127983968