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