第二篇:二八轮动择时策略

导语:作为策略锦集第二篇,我们以沪深A股市场出了名的“二八轮动”现象为基础,向大家介绍经典择时策略:二八轮动策略。

一、策略阐述

  在介绍二八轮动策略之前,我们首先来了解一下择时交易,择时交易是指利用某种方法来判断后市走势,如果判断是上涨,则买入持有;如果判断是下跌,则卖出清仓;如果判断是震荡,则进行高抛低吸,这样可以获得远远超越简单买入持有策略的收益率,所以择时交易是收益率最高的一种交易方式。通俗的讲,择时即为选择交易时机,投资者总是希望通过择时,在上涨前买入,在下跌前卖出。

  “二八轮动”现象就是大小盘轮动现象,“二”代表数量占比20%左右的大盘权重股,“八”代表数量占比80%左右的中小盘股票,二八轮动策略就是指在大盘股与小盘股中间不断切换,轮流持有,在大小盘都看跌的时候,则买入债券或者货币基金。二八轮动策略的本质是择时,从大小盘指数中选择最佳交易时机。

  二八轮动是如何轮动的呢?

  择时方法:对比当前交易日收盘数据与二十个交易日前的收盘数据,选择沪深300指数和中证500指数中涨幅较大的一个,于下一个交易日切换为持有该指数ETF,若两个指数均为下跌,则于下个交易日切换为空仓状态。调仓日为每周第一个交易日。

  以下为策略实现的基本信息:

  策略实现难度:1
  实现过程中所需要用到的API函数,ps:通过SuperMind量化交易平台API文档快速掌握:

需要用到的API函数 功能
run_weekly() 按周定时运行函数
context.portfolio.stock_account.positions 获取账户持仓信息
context.portfolio.available_cash 获取账户当前资金
history() 获取多只股票多属性的历史行情数据

二、代码示意图

三、编写释义

  本策略的编写难点在于根据信号值来调整持仓。以下是持仓调整逻辑梳理:

  第一步:根据信号值,只可能会出现三种情况:分别是空仓、沪深300、中证500,其中空仓是两者信号值都小于0,否则持仓为信号值大的那个。

  第二步:以目标持仓为导向,根据当前持仓情况作出调仓操作:

目标持仓 当前持仓 操作
空仓 无持仓 无操作
空仓 有持仓 卖出持仓
沪深300 无持仓 买入沪深300
沪深300 沪深300 不操作
沪深300 中证500 卖出中证500,随后买入沪深300
中证500 无持仓 买入中证500
中证500 沪深300 卖出沪深300,买入中证500
中证500 中证500 无操作

四、最终结果

策略回测区间:2021.01.01-2022.12.31
回测资金:1000000
回测频率:日级
回测结果:红色曲线为策略收益率曲线,蓝色曲线为对应的基准指数收益率曲线

策略源代码:

In [ ]:
import pandas as pd 
import numpy as np 
# 初始化函数,全局只运行一次
def init(context):
    context.day = 20 #设置数据获取长度为20
    context.security = ['000300.SH','000905.SH']#设沪深300指数,中证500指数
    context.ETF300 = '510300.OF' #沪深300ETF基金
    context.ETF500 = '510500.OF' #中证500ETF基金
    run_weekly(handle_bar,date_rule=1)

## 开盘时运行函数
def handle_bar(context, bar_dict):
    #获取信号值
    signal=get_signal(context,bar_dict)
    hs300=signal[0]
    zz500=signal[1]
    #根据信号值,来调整至相应仓位
    #空仓
    if hs300 <= 0 and zz500 <= 0:
        if len(context.portfolio.stock_account.positions) > 0:
            for stock in list(context.portfolio.stock_account.positions):
                order_target(stock, 0)
    #沪深300
    elif hs300 > zz500:
        if context.ETF500 in list(context.portfolio.stock_account.positions):
            order_target(context.ETF500, 0)
        if len(context.portfolio.stock_account.positions) < 1:
            order_target_value(context.ETF300, context.portfolio.available_cash)
    #中证500
    elif hs300 < zz500:
        if context.ETF300 in list(context.portfolio.stock_account.positions):
            order_target(context.ETF300, 0)
        if len(context.portfolio.stock_account.positions) < 1:
            order_target_value(context.ETF500, context.portfolio.available_cash)
#信号获取函数
def get_signal(context,bar_dict):
    #获取沪深300与中证500过去20日的收盘价
    close=history(context.security, ['close'], 20, '1d', False, 'pre', is_panel=1)['close']
    #计算涨跌幅
    h=(close.iloc[-1]-close.iloc[0])/close.iloc[0]
    h300=h['000300.SH']
    h500=h['000905.SH']
    return h300,h500