原創(chuàng)文章第112篇,專注“個人成長與財富自由、世界運(yùn)作的邏輯, AI量化投資”。
昨天我們設(shè)計了一個不錯的策略:etf動量輪動+大盤擇時:年化30%的策略。ETF動量輪動+RSRS擇時,動量其實一直都有效,如何定義動量可以有優(yōu)化空間。今天我們繼續(xù)來優(yōu)化它。
01 從tushare下載ETF數(shù)據(jù)
在現(xiàn)實世界中,我們會交易指數(shù)對應(yīng)的ETF而非指數(shù),從理論上講,收益只會更高,因為指數(shù)本身不包含分紅之類的信息。
如下是一些常用的ETF,包括寬基:上證50, 滬深300,創(chuàng)業(yè)板,創(chuàng)業(yè)板50,行業(yè)如醫(yī)藥,消費,紅利,新能車等。
etfs = ['510300.SH', # 滬深300ETF
'159949.SZ', # 創(chuàng)業(yè)板50
'510050.SH', # 上證50ETF
'159928.SZ', # 中證消費ETF
'510500.SH', # 500ETF
'159915.SZ', # 創(chuàng)業(yè)板 ETF
'512120.SH', # 醫(yī)藥50ETF
'159806.SZ', # 新能車ETF
'510880.SH', # 紅利ETF
]
download_etfs(etfs)
dowload_etfs里面調(diào)用了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有“復(fù)權(quán)信息”,一般我們把復(fù)權(quán)因子直接乘到對應(yīng)的OHLV數(shù)據(jù)上。
def get_etf(code):
# 拉取數(shù)據(jù)
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)
# 拉取數(shù)據(jù)
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擇時標(biāo)準(zhǔn)更換為ETF本身,即510500.SH。
年化提升到38.17%, 回撤降至23.57%,夏普比達(dá)到1.55。
symbols = [
'510050.SH', # 上證50ETF
#'159928.SZ', # 中證消費ETF
'510300.SH', # 滬深300ETF
'159915.SZ', # 創(chuàng)業(yè)板50
#'512120.SH', # 醫(yī)藥50ETF
#'159806.SZ', # 新能車ETF
#'510880.SH', # 紅利ETF
]
添加行業(yè)進(jìn)來輪動,會降低收益率,原因待分析。
03 卡爾曼過濾
添加一個卡爾曼濾波的函數(shù):
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
對昨天的“動量信號”進(jìn)行濾波etf動量輪動+大盤擇時:年化30%的策略。
年化提升到48.41%,回撤降至21.48%,夏普比達(dá)到1.89!
names = ['order_by']
fields = ['KF(Slope($close,20))']
明天繼續(xù)可以對RSRS進(jìn)行標(biāo)準(zhǔn)分的優(yōu)化。
小結(jié):
ETF動量輪動是個好策略,卡爾曼濾波是一個好策略,RSRS是個不錯的指標(biāo)。
代碼、數(shù)據(jù)已經(jīng)上傳到星球-量化專欄。