量化投資(ETF輪動)

雪球上看到一個大V的ETF輪動策略,回測效果還不錯,比較適合個人投資者使用。

【策略思想】
針對多只指數(shù)基金,以等權重方式持有符合買入條件的基金,最高同時持有3只基金。沒有基金符合要求時空倉
【策略理論依據(jù)】
輪動策略的理論基礎是動量效應,也就是處于上漲狀態(tài)的基金會在一定時間內(nèi)保持上漲趨勢。
【買入條件】(兩個條件全部滿足才買入)
1、近13個交易日漲幅排名前三(設置漲幅閾值為0.1%),選擇最強勢的基金;
2、當前價大于13日均線,主要用于過濾假突破信號。
【賣出條件】(三個條件滿足一個就賣出)
1、近13個交易日漲幅排名未入前三(先剔除不符合買入條件的基金再排序);
2、近13個交易日漲幅不足0.1%;
3、當前價小于近13個交易日均線
4、上證指數(shù)連續(xù)6日不過7日量線,無條件賣出,提前出場等待

基于動量的輪動是一種偏進攻型的策略,不追求高勝率,核心邏輯在于“多賺少虧”,整體盈利。

輪動策略的“少虧”是通過輪動換倉實現(xiàn)的,但是我們發(fā)現(xiàn)基礎策略的回撤幅度仍然是非常大的(超過35%),通過同時持有多個標的分散風險,我們把回撤控制在了25%以內(nèi)。

這個策略如果改了運行時間的話,回測結果差異非常大,把交易時間設置在下午14:30以后會比較好。難道是因為我們的市場在收盤前半個小時經(jīng)常出現(xiàn)逆趨勢的波動?例如處于上漲周期的,經(jīng)常尾盤跳水,處于下降周期的,又經(jīng)常尾盤拉高,這樣一買一賣,差價就出來了。這應該有一個統(tǒng)計學上的解釋。但如果真是如此,這就是一個值得注意的策略鈍化的潛在風險,沒有辦法保證我們的市場風格會一直如此。

不同時間段的回測收益曲線如下:

image.png
image.png
image.png

ETF輪動
'''信號判斷:價格不低于13日均線,且價格相對于13日前上漲,綜合評分排名第一或者低于第一不超過閾值
止損信號:收益率峰值下跌20%點位,賣出全部,冷卻3天
確認信號:11:30
交易時間:14:40
均線周期:13
標題:滬深寬基ETF輪動策略低回撤
'''

from jqdata import *

=================================================
總體回測前設置參數(shù)和回測
=================================================

def initialize(context):
set_params()    #1設置策參數(shù)
set_variables() #2設置中間變量
set_backtest()  #3設置回測條件
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ù)

def set_params():
# 設置基準收益
set_benchmark('000300.XSHG')
g.returnsRate = 0  #峰值收益率初始化
g.CoolingOff = 0  #冷卻期
g.signal = 'KEEP'  #交易信號初始化
g.lag = 13  #均線周期
g.shift = 0.2  #設置漲幅%偏差過濾閾值
g.last = '0' #持倉股票代碼初始化
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'], #國債ETF
    #['518880.XSHG','518880.XSHG'], #黃金ETF
    ['399932.XSHE','159928.XSHE'] #消費ETF
])

2 設置中間變量

def set_variables():
return

3 設置回測條件

def set_backtest():
set_option('use_real_price', True) #用真實價格交易
log.set_level('order', 'error')

=================================================
每日交易時
=================================================

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("交易信號:持倉不變")
else:
    sell_the_stocks(context)    
    buy_the_stocks(context,g.signal)

5 獲取信號

def get_signal(context):
if KeepReturns(context): # 達到止損條件后發(fā)出空倉信號
    if g.last == '0':# 持倉為空
        log.info("交易信號:冷卻期保持空倉狀態(tài)")
        return 'KEEP'# 持倉保持不變
    else:# 持倉不為空
        log.info("交易信號:收益率下跌超20%,空倉止損")
        g.last = '0'
        return 'sell_the_stocks'

i=0 # 計數(shù)器初始化
# dapan_stoploss() # 調(diào)用大盤止損函數(shù)設置均線周期
# 創(chuàng)建保持計算結果的DataFrame
df = pd.DataFrame()
for row in g.ETFList:
    security = row[1]
# 獲取股票的收盤價
    close_data = attribute_history(security, g.lag, '1d', ['close'],df=False)
# 獲取股票現(xiàn)價
    current_data = get_current_data()
    current_price = current_data[security].last_price
# 獲取股票的階段收盤價漲幅
    cp_increase = (current_price/close_data['close'][0]-1)*100
# 取得過去 g.lag 天的平均價格
    ma_n1 = close_data['close'].mean()
# 計算前一收盤價與均值差值    
    pre_price = (current_price/ma_n1-1)*100
    df.loc[i,'股票代碼'] = row[1] # 把標的股票代碼添加到DataFrame
    df.loc[i,'股票名稱'] = get_security_info(row[1]).display_name # 把標的股票名稱添加到DataFrame
    df.loc[i,'周期漲幅%'] = cp_increase # 把計算結果添加到DataFrame
    df.loc[i,'均線差值%'] = pre_price # 把計算結果添加到DataFrame
    i=i+1

# 刪除不符合要求的標的
for t in df.index:
    if df.loc[t,'周期漲幅%'] < 0 or df.loc[t,'均線差值%'] < 0:
    #if df.loc[t,'均線差值'] < 0:    
        df=df.drop(t)


# 對計算結果表格進行從大到小排序
df.sort_values(by='周期漲幅%',ascending=False,inplace=True) # 按照漲幅排序
df.reset_index(drop=True, inplace=True) # 重新設置索引
df['周期漲幅%'].apply(lambda x:'%.2f' %x)
df['均線差值%'].apply(lambda x:'%.2f' %x)
log.info("行情統(tǒng)計結果表:\n%s" % (df))

if df.empty: # 表為空
    if g.last == '0':# 持倉為空
        log.info("交易信號:繼續(xù)保持空倉狀態(tài)")
        return 'KEEP'# 持倉保持不變
    else:# 持倉不為空
        log.info("交易信號:空倉")
        g.last = '0'
        return 'sell_the_stocks' # 當前價格低于均線賣出股票

elif g.last == '0': # 表不為空,持倉為空,購買排名第一股
    stockcode = str(df.iloc[0,0])
    g.last = stockcode
    log.info("交易信號:買入 %s" % (stockcode))
    return stockcode
    
elif g.last != '0': # 表不為空 持倉不為空
    if g.last not in df['股票代碼'].values: # 如果持倉股不在表中,購買排名第一股
        stockcode = str(df.iloc[0,0])
        g.last = stockcode
        log.info("交易信號:買入 %s" % (stockcode))
        return stockcode
    if g.last in df['股票代碼'].values:# 如果持倉股在表中
        for t in df.index: # 取得持倉股漲幅
            if df.loc[t,'股票代碼'] == g.last:
                temp = df.loc[t,'周期漲幅%']
        if df.iloc[0,2] - temp < g.shift:  # 排名第一股漲幅差距低于閾值,返回繼續(xù)持倉
            return 'KEEP' # 持倉保持不變
        else: # 排名第一股漲幅差距大于閾值,換股
            stockcode = str(df.iloc[0,0])
            g.last = stockcode
            log.info("交易信號:買入 %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('今日持倉情況:%s',context.portfolio.positions.keys())
print("總權益:{:.2f}萬".format(context.portfolio.total_value/10000))
return
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 摘要 隨著中國股票市場的發(fā)展,市場中開始出現(xiàn)了越來越多的股票,各種行業(yè)概念的劃分開始不斷出現(xiàn),而市場中最為流行的個...
    段公子閱讀 5,717評論 0 51
  • 綜述 我是一個投機客。我的交易體系的核心為:價值選股,技術擇時,大波段操作。 以下是我整理的一些前輩的心得,對于我...
    KIKO_軟糖閱讀 4,809評論 0 4
  • 算法技術解構 1、Python基礎知識 (1)IPythonIPython的開發(fā)者吸收了標準解釋器的基本概念,在此...
    shenciyou閱讀 5,893評論 0 10
  • 2017.10.06 今天是國慶節(jié)假期,但是產(chǎn)科一樣要值班。一上午收了兩個病人,等到中午一點才吃到了午飯。還好下午...
    陪太醫(yī)度過漫長歲月閱讀 309評論 0 0
  • 一、轉印芝士蛋糕 1.放松心情描邊,保持線條流暢。 2.鉤邊裱花袋出口適宜,鉤邊線條粗一點。 3.填充顏色時裱花袋...
    一點甜Plus閱讀 1,252評論 5 1

友情鏈接更多精彩內(nèi)容