雪球上看到一個(gè)大V的ETF輪動(dòng)策略,回測(cè)效果還不錯(cuò)骂维,比較適合個(gè)人投資者使用蚌父。
【策略思想】
針對(duì)多只指數(shù)基金哮兰,以等權(quán)重方式持有符合買入條件的基金,最高同時(shí)持有3只基金苟弛。沒(méi)有基金符合要求時(shí)空倉(cāng)
【策略理論依據(jù)】
輪動(dòng)策略的理論基礎(chǔ)是動(dòng)量效應(yīng)喝滞,也就是處于上漲狀態(tài)的基金會(huì)在一定時(shí)間內(nèi)保持上漲趨勢(shì)。
【買入條件】(兩個(gè)條件全部滿足才買入)
1嗡午、近13個(gè)交易日漲幅排名前三(設(shè)置漲幅閾值為0.1%)囤躁,選擇最強(qiáng)勢(shì)的基金;
2、當(dāng)前價(jià)大于13日均線狸演,主要用于過(guò)濾假突破信號(hào)言蛇。
【賣出條件】(三個(gè)條件滿足一個(gè)就賣出)
1、近13個(gè)交易日漲幅排名未入前三(先剔除不符合買入條件的基金再排序)宵距;
2腊尚、近13個(gè)交易日漲幅不足0.1%;
3满哪、當(dāng)前價(jià)小于近13個(gè)交易日均線
4婿斥、上證指數(shù)連續(xù)6日不過(guò)7日量線,無(wú)條件賣出哨鸭,提前出場(chǎng)等待
基于動(dòng)量的輪動(dòng)是一種偏進(jìn)攻型的策略民宿,不追求高勝率,核心邏輯在于“多賺少虧”像鸡,整體盈利活鹰。
輪動(dòng)策略的“少虧”是通過(guò)輪動(dòng)換倉(cāng)實(shí)現(xiàn)的,但是我們發(fā)現(xiàn)基礎(chǔ)策略的回撤幅度仍然是非常大的(超過(guò)35%)只估,通過(guò)同時(shí)持有多個(gè)標(biāo)的分散風(fēng)險(xiǎn)志群,我們把回撤控制在了25%以內(nèi)。
這個(gè)策略如果改了運(yùn)行時(shí)間的話蛔钙,回測(cè)結(jié)果差異非常大锌云,把交易時(shí)間設(shè)置在下午14:30以后會(huì)比較好。難道是因?yàn)槲覀兊氖袌?chǎng)在收盤前半個(gè)小時(shí)經(jīng)常出現(xiàn)逆趨勢(shì)的波動(dòng)吁脱?例如處于上漲周期的桑涎,經(jīng)常尾盤跳水,處于下降周期的豫喧,又經(jīng)常尾盤拉高石洗,這樣一買一賣,差價(jià)就出來(lái)了紧显。這應(yīng)該有一個(gè)統(tǒng)計(jì)學(xué)上的解釋讲衫。但如果真是如此,這就是一個(gè)值得注意的策略鈍化的潛在風(fēng)險(xiǎn)孵班,沒(méi)有辦法保證我們的市場(chǎng)風(fēng)格會(huì)一直如此涉兽。
不同時(shí)間段的回測(cè)收益曲線如下:
ETF輪動(dòng)
'''信號(hào)判斷:價(jià)格不低于13日均線,且價(jià)格相對(duì)于13日前上漲篙程,綜合評(píng)分排名第一或者低于第一不超過(guò)閾值
止損信號(hào):收益率峰值下跌20%點(diǎn)位枷畏,賣出全部,冷卻3天
確認(rèn)信號(hào):11:30
交易時(shí)間:14:40
均線周期:13
標(biāo)題:滬深寬基ETF輪動(dòng)策略低回撤
'''
from jqdata import *
=================================================
總體回測(cè)前設(shè)置參數(shù)和回測(cè)
=================================================
def initialize(context):
set_params() #1設(shè)置策參數(shù)
set_variables() #2設(shè)置中間變量
set_backtest() #3設(shè)置回測(cè)條件
set_slippage(FixedSlippage(0))
set_order_cost(OrderCost(open_tax=0, close_tax=0, \
open_commission=0.0005, close_commission=0.0005,\
close_today_commission=0, min_commission=5), type='fund')
run_daily(ETFtrade1, time='11:30')
run_daily(ETFtrade2, time='14:40')
1 設(shè)置參數(shù)
def set_params():
# 設(shè)置基準(zhǔn)收益
set_benchmark('000300.XSHG')
g.returnsRate = 0 #峰值收益率初始化
g.CoolingOff = 0 #冷卻期
g.signal = 'KEEP' #交易信號(hào)初始化
g.lag = 13 #均線周期
g.shift = 0.2 #設(shè)置漲幅%偏差過(guò)濾閾值
g.last = '0' #持倉(cāng)股票代碼初始化
g.ETFList = np.array([
['399006.XSHE','159915.XSHE'], #創(chuàng)業(yè)板
['000300.XSHG','510300.XSHG'], #滬深300
['000905.XSHG','510500.XSHG'], #中證500
#['399330.XSHE','159901.XSHE'], #深證100
['510880.XSHG','510880.XSHG'], #紅利ETF
#['511010.XSHG','511010.XSHG'], #國(guó)債ETF
#['518880.XSHG','518880.XSHG'], #黃金ETF
['399932.XSHE','159928.XSHE'] #消費(fèi)ETF
])
2 設(shè)置中間變量
def set_variables():
return
3 設(shè)置回測(cè)條件
def set_backtest():
set_option('use_real_price', True) #用真實(shí)價(jià)格交易
log.set_level('order', 'error')
=================================================
每日交易時(shí)
=================================================
def ETFtrade1(context):
g.signal = get_signal(context)
def ETFtrade2(context):
for stock in context.portfolio.positions.keys():
if stock not in g.last:
log.info("正在賣出遺留基金 %s" % stock)
order_target_value(stock, 0)
if g.signal == 'sell_the_stocks':
sell_the_stocks(context)
elif g.signal == 'KEEP':
log.info("交易信號(hào):持倉(cāng)不變")
else:
sell_the_stocks(context)
buy_the_stocks(context,g.signal)
5 獲取信號(hào)
def get_signal(context):
if KeepReturns(context): # 達(dá)到止損條件后發(fā)出空倉(cāng)信號(hào)
if g.last == '0':# 持倉(cāng)為空
log.info("交易信號(hào):冷卻期保持空倉(cāng)狀態(tài)")
return 'KEEP'# 持倉(cāng)保持不變
else:# 持倉(cāng)不為空
log.info("交易信號(hào):收益率下跌超20%虱饿,空倉(cāng)止損")
g.last = '0'
return 'sell_the_stocks'
i=0 # 計(jì)數(shù)器初始化
# dapan_stoploss() # 調(diào)用大盤止損函數(shù)設(shè)置均線周期
# 創(chuàng)建保持計(jì)算結(jié)果的DataFrame
df = pd.DataFrame()
for row in g.ETFList:
security = row[1]
# 獲取股票的收盤價(jià)
close_data = attribute_history(security, g.lag, '1d', ['close'],df=False)
# 獲取股票現(xiàn)價(jià)
current_data = get_current_data()
current_price = current_data[security].last_price
# 獲取股票的階段收盤價(jià)漲幅
cp_increase = (current_price/close_data['close'][0]-1)*100
# 取得過(guò)去 g.lag 天的平均價(jià)格
ma_n1 = close_data['close'].mean()
# 計(jì)算前一收盤價(jià)與均值差值
pre_price = (current_price/ma_n1-1)*100
df.loc[i,'股票代碼'] = row[1] # 把標(biāo)的股票代碼添加到DataFrame
df.loc[i,'股票名稱'] = get_security_info(row[1]).display_name # 把標(biāo)的股票名稱添加到DataFrame
df.loc[i,'周期漲幅%'] = cp_increase # 把計(jì)算結(jié)果添加到DataFrame
df.loc[i,'均線差值%'] = pre_price # 把計(jì)算結(jié)果添加到DataFrame
i=i+1
# 刪除不符合要求的標(biāo)的
for t in df.index:
if df.loc[t,'周期漲幅%'] < 0 or df.loc[t,'均線差值%'] < 0:
#if df.loc[t,'均線差值'] < 0:
df=df.drop(t)
# 對(duì)計(jì)算結(jié)果表格進(jìn)行從大到小排序
df.sort_values(by='周期漲幅%',ascending=False,inplace=True) # 按照漲幅排序
df.reset_index(drop=True, inplace=True) # 重新設(shè)置索引
df['周期漲幅%'].apply(lambda x:'%.2f' %x)
df['均線差值%'].apply(lambda x:'%.2f' %x)
log.info("行情統(tǒng)計(jì)結(jié)果表:\n%s" % (df))
if df.empty: # 表為空
if g.last == '0':# 持倉(cāng)為空
log.info("交易信號(hào):繼續(xù)保持空倉(cāng)狀態(tài)")
return 'KEEP'# 持倉(cāng)保持不變
else:# 持倉(cāng)不為空
log.info("交易信號(hào):空倉(cāng)")
g.last = '0'
return 'sell_the_stocks' # 當(dāng)前價(jià)格低于均線賣出股票
elif g.last == '0': # 表不為空,持倉(cāng)為空,購(gòu)買排名第一股
stockcode = str(df.iloc[0,0])
g.last = stockcode
log.info("交易信號(hào):買入 %s" % (stockcode))
return stockcode
elif g.last != '0': # 表不為空 持倉(cāng)不為空
if g.last not in df['股票代碼'].values: # 如果持倉(cāng)股不在表中,購(gòu)買排名第一股
stockcode = str(df.iloc[0,0])
g.last = stockcode
log.info("交易信號(hào):買入 %s" % (stockcode))
return stockcode
if g.last in df['股票代碼'].values:# 如果持倉(cāng)股在表中
for t in df.index: # 取得持倉(cāng)股漲幅
if df.loc[t,'股票代碼'] == g.last:
temp = df.loc[t,'周期漲幅%']
if df.iloc[0,2] - temp < g.shift: # 排名第一股漲幅差距低于閾值,返回繼續(xù)持倉(cāng)
return 'KEEP' # 持倉(cāng)保持不變
else: # 排名第一股漲幅差距大于閾值,換股
stockcode = str(df.iloc[0,0])
g.last = stockcode
log.info("交易信號(hào):買入 %s" % (stockcode))
return stockcode
賣出股票
def sell_the_stocks(context):
for stock in context.portfolio.positions.keys():
return (log.info("正在賣出 %s" % stock), order_target_value(stock, 0))
買入股票
def buy_the_stocks(context,signal):
return (log.info("正在買入 %s"% signal
),order_value(signal,context.portfolio.cash))
收益止損函數(shù)
def KeepReturns(context):
if g.CoolingOff > 0:
g.CoolingOff = g.CoolingOff - 1
return True
else:
current_returns = context.portfolio.returns
if current_returns > g.returnsRate:
g.returnsRate = current_returns
log.info("最高收益更新:{:.2%}".format(current_returns))
return False
elif current_returns - g.returnsRate > -0.2:
return False
else:
current_returns - g.returnsRate-1 <= -0.2
g.returnsRate = current_returns
log.info("最高收益更新:{:.2%}".format(current_returns))
g.CoolingOff = 3
return True
=================================================
每日收盤后
=================================================
def after_trading_end(context):
log.info('今日持倉(cāng)情況:%s',context.portfolio.positions.keys())
print("總權(quán)益:{:.2f}萬(wàn)".format(context.portfolio.total_value/10000))
return