导语:作为策略锦集第二篇,我们以沪深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
回测频率:日级
回测结果:红色曲线为策略收益率曲线,蓝色曲线为对应的基准指数收益率曲线
策略源代码:
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