掘金社区

多因子选股之有效因子Pinned highlighted

掘金小Q 【 论坛管理员 】 发表在策略分享 2018-08-03 11:13:10

策略分享
13110
6
0

什么是多因子选股?

玩股票的朋友的朋友应该很清楚,股市之道无外乎:选股、择时、仓控。精通任何一点都可以说在股市中所向披靡。

这次,我们从选股入手,来谈谈量化选股的基本“套路”——多因子选股。多因子选股采用一系列的因子(主要考虑使用价值、成长、质量以及市场等四大类因子)作为选股标准,将多个具有逻辑背景的因子策略相结合,选取在各个因子上综合得分较高的股票构建投资组合。通过这种方式选出来的股票通常不会在某个因子上有特别的短板,能够综合很多信息最后得出一个选股结果。同时,因为在不同的市场情况下,总有一些因子会发挥作用,因此多因子模型的表现相对来说也比较稳定。

比如高考,高校要从高中学生中录取学业优秀的学生进入大学,需要考察学生的综合成绩,对语文、数学、外语、物理、地理、化学等各门成绩进行测评,只有综合成绩高的学生,才能最终进入高校。这里,每门功课都相当于一个候选因子,每一个因子的情况——分数都十分透明,可以帮助高校非常清晰地看清学生的实力。

各种多因子模型核心的区别第一是在因子的选取上,第二是在如何用多因子综合得到一个最终的判断。

一般而言,多因子选股模型有两种判断方法,一是打分法,二是回归法。

打分法就是根据各个因子的大小对股票进行打分,然后按照一定的权重加权得到一个总分,根据总分再对股票进行筛选。回归法就是用过去的股票的收益率对多因子进行回归,得到一个回归方程,然后再把最新的因子值代入回归方程得到一个对未来股票收益的预判,然后再以此为依据进行选股。

多因子选股模型的建立过程主要分为候选因子的选取、选股因子有效性的检验、有效但冗余因子的剔除、综合评分模型的建立和模型的评价及持续改进等5个步骤。

候选因子的选取:

因子类型可以概括为9类:规模因子,估值因子,成长因子,盈利因子,动量反转因子,交投因子,波动率因子,分析师预测因子。

1.规模类因子:总市值,流通市值,自由流通市值

2.估值类因子:市盈率(TTM),市净率,市销率,市现率,企业价值倍数

3.成长类因子:营业收入同比增长率、营业利润同比增长率,归属于母公司的近利润同比增长率、经营活动产生的现金流金额同比增长率

4.盈利类因子:净资产收益率ROE、总资产报酬率ROA、销售毛利率、销售净利率

5.动量反转因子:前一个月涨跌幅,前2个月涨跌幅、前3个月涨跌幅、前6个月涨跌幅

6.交投因子:前一个月日均换手率

7.波动因子:前一个月的波动率,前一个月的振幅

8.股东因子:户均持股比例、、户均持股比例变化、机构持股比例变化

9.分析师因子:预测当年净利润增长率、主营业务收入增长率、最近一个月预测净利润上调幅度、最近一个月越策主营业务收入上调幅度,最近一个月上调评级占比

我们在掘金平台提供的因子中,选取了以下六个典型因子:

0_1533265520188_1.jpg

因子有效性的检验:

一般检验方法主要采用排序的方法检验候选因子的选股有效性。
具体而言,对于任意一个候选因子,在模型形成期的第一个月初开始计算市场中每只正常交易股票的该因子的大小, 按从小到大的顺序对样本股票进行排序,并平均分为n个组合,一直持有到月末,在下月初再按同样的方法重新构建n个组合并持有到月末,每月如此,一直重复到模型形成期末。

我们在掘金平台上进行因子有效性检验:

测试参数:
回测时间周期:2017-01-01--2018-01-01
基准指数:IT指数(SZSE.399239)
股票池:IT指数(SZSE.399239)成分股
滑点:0
手续费:0

**基本思想:**我们需要采取循环测试,每月按照因子的值升序排序,然后选取某一区间(共十个均分区间)的股票作为标的,全仓等权重买入,每月换仓,统计最终结果。

过程:
1.因子有效性测试系统建立在参数优化系统上,循环回测,每次输入不同的区间参数,从而影响每月选取股票的范围。
2.每月调仓,获取因子并排序(注意去除空值)。
3.清仓,选取股票,重新买入。
4.重复第2与第3步,直至测试周期结束。
5.输入新的区间参数,重复2-4步,直至全部参数输入完毕。

测试结果(绝对收益):

0_1632278118691_08f6c14a-1dc8-42d6-b6f3-59387be183fb-image.png

0_1632278172517_36f07ee4-a1ea-41ba-b713-e8a19f50efef-image.png

0_1632278199326_80af99c2-809b-4131-b8a7-d077bd25791a-image.png

0_1632278210529_5cb7813d-6cfe-43d3-8883-a95d0d98c6d4-image.png

0_1632278228785_7a78559d-ff8d-40ad-984e-2692b15d9f48-image.png

0_1632278241628_8dc04bca-f4e6-4fba-99a4-2069fb857698-image.png
总结:
由上面几幅图可以看出,PB、ROEANNUAL、TAGRT都具有很强的有效性,因子的值从低到高排序,股票组合的超额收益率也是从低到高排列。

我们再看另外三个因子,PELFYNPAAEI、NEGOTIABLEMV、EVEBITDA。因子的大小与股票组合的超额收益相关性并不明显。
从而我们得到了PB、ROEANNUAL、TAGRT等因子具有一定有效性。

后续:
因子的有效性也会跟随市场的变化而变化,如之前的小市值因子。我们所做的因子的有效性仅仅是作为参考,在大多数情况是有效的,而市场的魅力就是在于它的不确定性。
最后分享一张因子有效性的图。

0_1533265854966_8.jpg

数据来源:豆瓣

作者:经纬量化 宋瑞迪

附策略源码:

# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
import multiprocessing
import numpy as np
import pandas as pd
from gm.api import *
import matplotlib.pyplot as plt


def init(context):
            # 每月第一个交易日09:40:00的定时执行algo任务
            schedule(schedule_func=algo, date_rule='1m', time_rule='09:40:00')

def algo(context):
            # 获取当前时刻
            now = context.now
            # 获取上一个交易日
            last_day = get_previous_trading_date(exchange='SHSE', date=now)
            # 获取IT指数成份股
            stock300 = get_history_constituents(index='SZSE.399239', start_date=last_day,
                                                end_date=last_day)[0]['constituents'].keys()

            # 获取当天有交易的股票
            not_suspended_info = get_history_instruments(symbols=stock300, start_date=now, end_date=now)
            not_suspended_symbols = [item['symbol'] for item in not_suspended_info if not item['is_suspended']]
            fin = get_fundamentals(table='trading_derivative_indicator', symbols=not_suspended_symbols,
                                   start_date=now, end_date=now, fields='PELFYNPAAEI',
                                   filter='PELFYNPAAEI >0 or PELFYNPAAEI <0', order_by='PELFYNPAAEI', df=True)
            long=len(fin)
            if context.stockrange==0.1:
                stockpool = list(fin.symbol)[0:int(long * context.stockrange)-1]
            else:
                stockpool = list(fin.symbol)[int(long*(context.stockrange-0.1))-1:int(long*context.stockrange)-1]
            #清仓
            order_close_all()

            # 获取股票的权重
            percent = 1.0 / len(stockpool)
            # 买在标的池中的股票
            for symbol in stockpool:
                order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market,
                                     position_side=PositionSide_Long)

# 获取每次回测的报告数据
def on_backtest_finished(context, indicator):

    data = [indicator['pnl_ratio'],indicator['pnl_ratio']+0.189, indicator['pnl_ratio_annual'], indicator['sharp_ratio'], indicator['max_drawdown'],
            context.stockrange]
    # 将回测报告加入全局list,以便记录
    context.list.append(data)


def run_strategy(stockrange, a_list):
    from gm.model.storage import context
    # 用context传入参数
    context.stockrange = stockrange
    # a_list一定要传入
    context.list = a_list
    run(strategy_id='xxxxxxxxxxxxxxxx',
        filename='有效因子.py',
        mode=MODE_BACKTEST,
        token='xxxxxxxxxxxxxxxx',
        backtest_start_time='2017-01-01 08:00:00',
        backtest_end_time='2018-01-01 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=50000,
        backtest_commission_ratio=0,
        backtest_slippage_ratio=0)


if __name__ == '__main__':
    # 生成全局list
    manager = multiprocessing.Manager()
    a_list = manager.list()
    # 循环输入参数数值回测
    for stockrange in np.arange(0.1, 1.1, 0.1):
            print(stockrange)
            process = multiprocessing.Process(target=run_strategy, args=(stockrange, a_list))
            process.start()
            process.join()
    # 回测报告转化成DataFrame格式
    a_list = np.array(a_list)
    final = pd.DataFrame(a_list,columns=['pnl_ratio', 'Alpha','pnl_ratio_annual', 'sharp_ratio', 'max_drawdown', 'stockrange'])
    fig = plt.figure(figsize=(12, 6))
    fig.set_facecolor('white')
    plt.bar(final.loc[final.Alpha>0].index, final.loc[final.Alpha>0].Alpha, align='center',color='r', width=0.3)
    plt.bar(final.loc[final.Alpha < 0].index, final.loc[final.Alpha < 0].Alpha, align='center',color='g', width=0.3)
    plt.title('PELFYNPAAEI')
    plt.show()
    print(final)

    
评论: 6

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