原创文章第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是个不错的指标。
代码、数据已经上传到星球-量化专栏。
我的开源项目及知识星球