掘金社区

行业轮动策略Pinned highlighted

掘金小Q 【 论坛管理员 】 发表在策略研究 2018-03-24 10:48:50

策略研究
2245
1
0

行业轮动策略简介

什么是行业轮动策略?

​ 行业轮动是利用市场趋势获利的一种主动交易策略,其本质是利用不同投资品种强势时间的错位对行业品种进行切换以达到投资收益最大化的目的。通俗点讲就是根据不同行业的区间表现差异性进行轮动配置,力求能够抓住区间内表现较好的行业、剔除表现不佳的行业,在判断市场不佳的时候,权益类仓位降低,提升债券或货币的比例。

行业轮动的具体内容及特点

​ 我们可以将行业分为以下几类:

​ 周期性:当增长加速时,股票和大宗商品表现好,周期性行业例如汽车、房地产、钢铁股战胜市场;当增长放缓,债券、现金和防守型股票战胜市场。

成长性:当通胀率下降,贴现率下降,金融资产表现好,成长型股票战胜市场;当通胀率上升,大宗商品等实物资产和现金表现最好,定价权变强,价值型股票战胜市场。

利率敏感性:银行和消费类股票是利率敏感型的“早周期”表现者,在衰退和复苏阶段,当中央银行放松政策和经济增长率开始回升时表现最好。

资产类品种:一些行业和其拥有的资产的表现紧密相关,银行保险经常对债券和股票价格敏感,在衰退和复苏阶段表现好;采矿业股票对金属价格敏感,在过热阶段表现好;石油和天然气行业对油价敏感,在滞胀阶段战胜市场。

板块

​ 上图来自“通达信板块地图”界面,结合近几年我国A股市场行业轮动的基本情况来看,周期循环通常是沿着产业链按一定的顺序地依次发生:住房和汽车消费是引领经济景气周期启动的主要和先导动力,这些行业在经济上升阶段(复苏期)的良好表现,将逐步带动机械设备、化工、建材等中游制造业的兴起,并传导到有色、钢铁、煤炭、石油等上游资源品行业,此时经济到达过热期。衰退也是由下游行业开始,逐步传导到上游行业。在经济景气的最高峰,商业一片繁荣,此时的主角就是非必需的消费品,如轿车、高档服装、奢侈品、消费类电子产品和旅游等行业,享受最后的经济周期盛宴。当经济下滑(滞胀期、衰退期),周期性行业业绩受到影响,而医药、必选消费等非周期性行业由于需求弹性小,受宏观经济影响不大,表现相对较好;银行价格相对较稳定,需求波动也较小,且作为利率敏感型的“早周期”表现者,兴起于衰退后期。当然,考虑到历史背景不同,行业周期轮动并非简单的重复,不能直接套用历史经验判断周期拐点,而应根据经验具体情况具体分析。

行业轮动

阶段 经济 通胀 最佳大类资产 最佳股票行业
收缩 债券 公共事业
复苏 股票 金融、有色
扩张 商品 钢铁
滞胀 现金 食品饮料、农林牧渔

行业轮动策略实现(基于掘金量化平台)

策略思想

  • 每隔1个月定时触发计算“300工业”、“300材料”、“300可选”、“300消费”、“300医药”、“300金融”这几个行业指数过去20个交易日的收益率。
  • 随后选取了收益率最高的指数的成份股中流通市值最小的5只股票。
  • 对不在股票池的股票平仓并等权配置股票池的标的。

策略主要步骤实现

获取当前交易日日期

now = context.now

​ 直接调用context.now函数,返回“datetime.datetime”格式

获取上一交易日日期

last_day = get_previous_trading_date(exchange='SHSE', date=now)

​ 获取上一交易日可调用get_previous_trading_date函数,返回值为字符串格式:

  • exchang需要设置交易市场代码。
  • date需要设置指定日期。

固定月初调仓

schedule(schedule_func=algo, date_rule='1m', time_rule='09:40:00')

​ 固定时间调仓可使用schedule函数进行定时任务配置:

  • 参数schedule_func为调用的策略函数。
  • date_rule可设为1m(一月)。
  • time_rule为开仓日的开仓时间,这里设为每月第一个交易日的09:40:00

获取过去交易日的历史信息

return_index_his = history_n(symbol=symbol, frequency='1d', count=20, fields='close,bob',fill_missing='Last', adjust=ADJUST_PREV, end_time=last_day, df=True)

​ 获取历史信息需要调用histor_n函数,默认返回值为“字典“格式,如果参数df设为True,则返回"dataframe"格式

  • symbol设置所需获取的标的代码。
  • frequency获取历史信息的频率,如日线数据设置为1d
  • count需要设置获取的bar的数量。
  • fileds设置返回值的种类。
  • fill_missing需要设置对于空值的填充方式, None- 不填充,NaN- 用空值填充, Last- 用上一个值填充,默认 None
  • adjust需要设置对于复权的处理,ADJUST_NONE or 0: 不复权, ADJUST_PREV or 1: 前复权, ADJUST_POST or 2: 后复权, 默认不复权。
  • end_time需设置获取历史信息的结束时间。

获取指数成分股

symbols = get_history_constituents(index=index_symbol, start_date=last_day, end_date=last_day)[0]['constituents'].keys()

​ 获取指数成分股可调用函数get_history_constituents或者get_constituents,返回值类型为list[dict],这里调用get_history_constituents是因为再回测时需要获取上一交易日的成分股,而get_constituents只能获取最新的成分股:

  • index需要设置获取指数的代码。
  • start_dateend_date需设置获取成分股的开始与结束日期,这里需要调成上一交易日以获取上一交易日的成分股信息。

获取当天有交易的股票

not_suspended_info = get_history_instruments(symbols=symbols, start_date=now, end_date=now)
not_suspended_symbols = [item['symbol'] for item in not_suspended_info if not item['is_suspended']]

​ 获取当天有交易的股票,即非停牌的股票,首先需获取停牌信息,这里需调用get_history_instruments函数,返回值类型为list[dict],之后就是将所提取的“字典”转换为”list“:

  • symbols需要设置订阅的标的代码。
  • start_dateend_date需设置获取成分股的开始与结束日期,这里需要调成上一交易日以获取上一交易日的成分股信息。

获取财务(市值)信息

fin = get_fundamentals(table='tq_sk_finindic', symbols=not_suspended_symbols, start_date=last_day,end_date=last_day, limit=5, fields='NEGOTIABLEMV', order_by='NEGOTIABLEMV', df=True)

获取财务数据使用get_fundamentals函数,返回值类型为list[dict]

  • table需填写所需获取指标所在表格的代码。
  • fields设置返回值的种类。
  • orde_by需设置数据的排序方式,默认 NoneTCLOSE 表示按 TCLOSE 升序排序。 -TCLOSE 表示按 TCLOSE 降序排序。TCLOSE, -NEGOTIABLEMV 表示按 TCLOSE 升序, NEGOTIABLEMV 降序综合排序。
  • limit表示返回值的个数。

策略回测分析

回测

分析

​ 我们选取了2016年5月至2016年11月作为回测周期,可以看出:

  • 胜率 (具有盈利的平仓次数与总平仓次数之比)达到了60%。
  • 卡玛比率 (年化收益率与历史最大回撤之比)是使用最大回撤率来衡量风险。**采用最大回撤率来衡量风险,关注的是最极端的情况。**卡玛比率越高表示策略承受每单位最大损失获得的报酬越高。在这里卡玛比率超过了3.5。
  • 夏普比率 (年化收益率减无风险收益率的差收益波动率之比)超过1,也即承受1单位的风险,会有超过1个单位的收益回报
  • 策略收益曲线 较为稳定,在无止损条件的情况下,最大回撤控制在承受范围,并成功跑赢沪深300指数。

策略源码

# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
import numpy as np
from gm.api import *

'''
本策略每隔1个月定时触发计算SHSE.000910.SHSE.000909.SHSE.000911.SHSE.000912.SHSE.000913.SHSE.000914
(300工业.300材料.300可选.300消费.300医药.300金融)这几个行业指数过去
20个交易日的收益率,随后选取了收益率最高的指数的成份股中流通市值最小的5只股票
对不在股票池的股票平仓并等权配置股票池的标的
回测数据为:SHSE.000910.SHSE.000909.SHSE.000911.SHSE.000912.SHSE.000913.SHSE.000914和他们的成份股
回测时间为:2017-07-01 08:00:00到2017-10-01 16:00:00
'''


def init(context):
    # 每月第一个交易日的09:40 定时执行algo任务
    schedule(schedule_func=algo, date_rule='1m', time_rule='09:40:00')
    # 用于筛选的行业指数
    context.index = ['SHSE.000910', 'SHSE.000909', 'SHSE.000911', 'SHSE.000912', 'SHSE.000913', 'SHSE.000914']
    # 用于统计数据的天数
    context.date = 20
    # 最大下单资金比例
    context.ratio = 0.8


def algo(context):
    # 获取当天的日期
    today = context.now
    # 获取上一个交易日
    last_day = get_previous_trading_date(exchange='SHSE', date=today)
    return_index = []
    # 获取并计算行业指数收益率

    for i in context.index:
        return_index_his = history_n(symbol=i, frequency='1d', count=context.date, fields='close,bob',
                                     fill_missing='Last', adjust=ADJUST_PREV, end_time=last_day, df=True)
        return_index_his = return_index_his['close'].values
        return_index.append(return_index_his[-1] / return_index_his[0] - 1)
    # 获取指定数内收益率表现最好的行业
    sector = context.index[np.argmax(return_index)]
    print('最佳行业指数是: ', sector)
    # 获取最佳行业指数成份股
    symbols = get_history_constituents(index=sector, start_date=last_day, end_date=last_day)[0]['constituents'].keys()
    # 获取当天有交易的股票
    not_suspended_info = get_history_instruments(symbols=symbols, start_date=today, end_date=today)
    not_suspended_symbols = [item['symbol'] for item in not_suspended_info if not item['is_suspended']]

    # 获取最佳行业指数成份股的市值,从小到大排序并选取市值最大的5只股票
    fin = get_fundamentals(table='tq_sk_finindic', symbols=not_suspended_symbols, start_date=last_day,
                           end_date=last_day, limit=5, fields='NEGOTIABLEMV', order_by='NEGOTIABLEMV', df=True)
    fin.index = fin['symbol']
    # 计算权重
    percent = 1.0 / len(fin.index) * context.ratio
    # 获取当前所有仓位
    positions = context.account().positions()
    # 如标的池有仓位,平不在标的池的仓位
    for position in positions:
        symbol = position['symbol']
        if symbol not in fin.index:
            order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
                                 position_side=PositionSide_Long)
            print('市价单平不在标的池的', symbol)
    # 对标的池进行操作
    for symbol in fin.index:
        order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market,
                             position_side=PositionSide_Long)
        print(symbol, '以市价单调整至仓位', percent)


if __name__ == '__main__':
    '''
    strategy_id策略ID,由系统生成
    filename文件名,请与本文件名保持一致
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
    token绑定计算机的ID,可在系统设置-密钥管理中生成
    backtest_start_time回测开始时间
    backtest_end_time回测结束时间
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
    backtest_initial_cash回测初始资金
    backtest_commission_ratio回测佣金比例
    backtest_slippage_ratio回测滑点比例
    '''
    run(strategy_id='b6df095c-d592-11e7-90ca-9cd21ef04ea9',
        filename='the industrial shift.py',
        mode=MODE_BACKTEST,
        token='your token',
        backtest_start_time='2016-05-01 08:00:00',
        backtest_end_time='2016-11-01 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=10000000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001)

评论: 1

Looks like your connection to 掘金量化社区 - 量化交易者的策略交流学习社区 was lost, please wait while we try to reconnect.