量化投资策略与技术学习PART2:量化选股之风格轮动

市场上的投资者是有偏好的,有时候偏好于价值股,有时候偏好于成长股,有时偏于大盘,有时又偏于小盘,由于投资者的这种不同的交易行为,形成了市场风格,本节主要研究如何判断市场风格,以及如何利用风格的轮动构建投资策略以获取超额收益。

一、基本概念

投资风格是针对股票市场而言,只投资于某类具有共同收益特征或共同价格行为的股票,由于投资风格的存在,从而产生一种叫做风格动量的效应,即在过去较短时期内收益率较高的股票,在未来的中短期收益也较高。
市场的有效性程度不是一成不变的,会随着时间不断变化,也就是说,追逐这些市场失效现象能获取超额投资收益。所以风格投资从本质上来说是通过执行各种投资决策,从某些特定分割的市场或从某类错误定价的股票中获得超额收益。

1、风格鉴别方法

国外投资风格鉴别技术一般可分为两种,一种是持股特征基础的投资风格鉴别方法,包括晨星公司的风格箱法、罗素公司的风格分类系统等;另一种是收益率基础的投资风格鉴别法,如夏普的鉴别方法等。

(1)持股基础鉴别法

晨星风格箱法是一个3*3的矩阵,从大盘和小盘、价值型和成长型来对基金风格进行划分,介于大盘和小盘之间的为中盘,介于价值型和成长型之间的为混合型,共有9种风格

价值型混合型成长型
大盘大盘价值大盘混合大盘成长
中盘中盘价值中盘混合中盘成长
小盘小盘价值小盘混合小盘成长

(1)规模指标:市值。通过比较基金持有股票的市值中值来划分,市值中值小于10亿美金为小盘;大于50亿美金为大盘,10~50亿美金为中盘。
(2)估值指标:平均市盈率、平均市净率。基金所持有股票的市盈率、市净率用基金投资于该股票的比例加权求平均,然后把两个加权指标和标普500成分股的市盈率、市净率的相对比值相加,对于标普500来说,这个比值和是2.如果最后所得比值和小于1.75,则为价值型,大于2.25为成长型,介于1.75~2.25之间为混合型。

(2)夏普收益率基础的投资风格鉴别

(1)将标普500指数成分股按净市比(B/P)排序分为两类,分界点是两类股票的总市值大小一样,高B/P的股票为价值股,其余为成长股,更新频次是6个月。
(2)将非标普500指数成分股按照市值高低分为两类,从高到底排序后总市值前80%的股票为中市值股,剩下的则为小市值股

股票风格
标普500成分股价值股/成长股
非标普500成分股中市值股/小市值股

二、策略模型

(1)传统的风格预测方法

实施风格轮换策略,在不同的风格类别之间进行切换,需要对各类风格的收益特征有较好的把握和对未来走势有较准确的判断。风格评估和预测的方法可分为相对价值法和场景预测法两类。

(2)风格轮动的定量预测

由于市场风格轮动,保持单一的投资风格并不一定是最佳的投资策略,积极的风格转换策略有助于提高投资业绩,风格轮动主要涉及两个问题,即在何时进行风格转换,以及风格转换能够弥补交易成本。

3、中国市场风格投资的特点

(1)积极的风格管理能创造出超额收益

1、如果一个投资者能偶准确进行风格选时,就能创造出显著的超额收益;
2、进行大盘/小盘风格选时潜在获利能力强于价值/成长;
3、对大盘/小盘轮动的选时频率可以频繁进行,一年内可以多次进行,但价值/成长轮动频繁转换意义不大,适合进行年度或者更长时间周期的选时即轮动;

(2)建立风格选时的量化模型,操作难度较大

虽然通过运用支持向量机方法可以进行风格选时的预测,但结果差强人意,主要原因如下:
1、中国股市与宏观经济指标的关联性差;
2、数据来源受限,无法得到一些风格指数的成分数据。

(3)风格动量效应不明显,持续时间短
(4)中期风格反转效应较明显

3个月的风格效应容易出现反转,即密切观测以3个月为一个周期的风格动量具有比较强的现实意义。对于积极风格管理者来说,不妨以三个月为周期进行风格反转操作,建议持有期在3个月以上。

(5)积极风格管理的适用对象为中短期投资者,长期的风格收益差,而且风格动量并不十分明显。

三、实战演习

掘金量化终端中有现成的程序,其策略如下:
以上证50、沪深300、中证500作为市场三个风格的代表,每次选取表现做好的一种风格,买入其成分股中最大市值的N只股票,每月月初进行调仓换股。
我们便以此为基础看一下风格轮动策略的收益如何
测试代码如下:

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

import datetime
import numpy as np
import pandas as pd

'''
示例策略仅供参考,不建议直接实盘使用。

风格轮动策略
逻辑:以上证50、沪深300、中证500作为市场三个风格的代表,每次选取表现做好的一种风格,买入其成分股中最大市值的N只股票,每月月初进行调仓换股
'''


def init(context):
    # 待轮动的风格指数(分别为:上证50、沪深300、中证500)
    context.index = ['SHSE.000016', 'SHSE.000300', 'SZSE.399625']
    # 用于统计数据的天数
    context.days = 20
    # 持股数量
    context.holding_num = 10

    # 每日定时任务
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')


def algo(context):
    # 当天日期
    now_str = context.now.strftime('%Y-%m-%d')
    # 获取上一个交易日
    last_day = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]
    # 判断是否为每个月第一个交易日
    if context.now.month != pd.Timestamp(last_day).month:
        return_index = pd.DataFrame(columns=['return'])
        # 获取并计算指数收益率
        for i in context.index:
            return_index_his = history_n(symbol=i, frequency='1d', count=context.days + 1, 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.loc[i, 'return'] = return_index_his[-1] / return_index_his[0] - 1

        # 获取指定数内收益率表现最好的指数
        sector = return_index.index[np.argmax(return_index)]
        print('{}:最佳指数是:{}'.format(now_str, sector))

        # 获取最佳指数成份股
        symbols = list(stk_get_index_constituents(index=sector, trade_date=last_day)['symbol'])

        # 过滤停牌的股票
        stocks_info = get_symbols(sec_type1=1010, symbols=symbols, trade_date=now_str, skip_suspended=True,
                                  skip_st=True)
        symbols = [item['symbol'] for item in stocks_info if
                   item['listed_date'] < context.now and item['delisted_date'] > context.now]
        # 获取最佳指数成份股的市值,选取市值最大的N只股票
        fin = stk_get_daily_mktvalue_pt(symbols=symbols, fields='tot_mv', trade_date=last_day, df=True).sort_values(
            by='tot_mv', ascending=False)
        to_buy = list(fin.iloc[:context.holding_num]['symbol'])

        # 计算权重
        percent = 1.0 / len(to_buy)
        # 获取当前所有仓位
        positions = get_position()

        # 平不在标的池的股票(注:本策略交易以开盘价为交易价格,当调整定时任务时间时,需调整对应价格)
        for position in positions:
            symbol = position['symbol']
            if symbol not in to_buy:
                # 开盘价(日频数据)
                new_price = \
                history_n(symbol=symbol, frequency='1d', count=1, end_time=now_str, fields='open', adjust=ADJUST_PREV,
                          adjust_end_time=context.backtest_end_time, df=False)[0]['open']
                # # 当前价(tick数据,免费版本有时间权限限制;实时模式,返回当前最新 tick 数据,回测模式,返回回测当前时间点的最近一分钟的收盘价)
                # new_price = current(symbols=symbol)[0]['price']
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit,
                                     position_side=PositionSide_Long, price=new_price)

        # 买入标的池中的股票(注:本策略交易以开盘价为交易价格,当调整定时任务时间时,需调整对应价格)
        for symbol in to_buy:
            # 开盘价(日频数据)
            new_price = \
            history_n(symbol=symbol, frequency='1d', count=1, end_time=now_str, fields='open', adjust=ADJUST_PREV,
                      adjust_end_time=context.backtest_end_time, df=False)[0]['open']
            # # 当前价(tick数据,免费版本有时间权限限制;实时模式,返回当前最新 tick 数据,回测模式,返回回测当前时间点的最近一分钟的收盘价)
            # new_price = current(symbols=symbol)[0]['price']
            order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Limit,
                                 position_side=PositionSide_Long, price=new_price)


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 目标仓位
    target_percent = order['target_percent']
    # 查看下单后的委托状态,等于3代表委托全部成交
    status = order['status']
    # 买卖方向,1为买入,2为卖出
    side = order['side']
    # 开平仓类型,1为开仓,2为平仓
    effect = order['position_effect']
    # 委托类型,1为限价委托,2为市价委托
    order_type = order['order_type']
    if status == 3:
        if effect == 1:
            if side == 1:
                side_effect = '开多仓'
            else:
                side_effect = '开空仓'
        else:
            if side == 1:
                side_effect = '平空仓'
            else:
                side_effect = '平多仓'
        order_type_word = '限价' if order_type == 1 else '市价'
        print('{}:标的:{},操作:以{}{},委托价格:{},委托数量:{}'.format(context.now, symbol, order_type_word, side_effect,
                                                                      price, volume))


def on_backtest_finished(context, indicator):
    print('*' * 50)
    print('回测已完成,请通过右上角“回测历史”功能查询详情。')


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回测滑点比例
    backtest_match_mode市价撮合模式,以下一tick/bar开盘价撮合:0,以当前tick/bar收盘价撮合:1
    '''
    run(strategy_id='自己的策略ID',
        filename='fengge_lundong_test.py',
        mode=MODE_BACKTEST,
        token='自己的token码',
        backtest_start_time='2021-03-01 09:00:00',
        backtest_end_time='2023-03-01 15:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=10000000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001,
        backtest_match_mode=1)

在这里插入图片描述
从回测的数据看,整体效果并不是很好,超额收益率竟然是-4.27%,同时回撤也达到了-38%以上。
我们再看看我们选择的这几个指数在2021年3月到2023年3月的整体运行情况:

上证50沪深300中证500
在这里插入图片描述在这里插入图片描述在这里插入图片描述

从数据看,三个都是单边下跌的市场,怪不得数据这么差。
我们再重新选择一个时间段吧,这三个指数起伏各不相同的时候,看是否能获得不错的收益
在这里插入图片描述
所以我又尝试了一下2018年3月到2020年3月的数据,看起来还是跑赢了3个指数,说明这个还是需要在有指数上升有指数下降的时候才有一定效果,而A股市场有的时候完全无轮动,都是一起上升一起下降,该策略发挥不出来用处,所以在下一节中我们将尝试一些行业轮动策略,A股毕竟是一个炒题材炒概念的市场,也许行业轮动策略能发挥不错的效果。

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值