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

  • Post author:
  • Post category:其他


原创文章第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是个不错的指标。

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


我的开源项目及知识星球



版权声明:本文为weixin_38175458原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。