量化选股——基于动量因子的行业风格轮动策略(第1部分—因子测算)

news/2024/11/2 2:23:47/

文章目录

  • 动量因子与行业轮动概述
    • 动量因子的理解
    • 投资视角下的行业轮动现象
    • 投资者视角与奈特不确定性
  • 动量因子在行业风格上的效果测算
    • 动量因子效果测算流程概述
    • 1. 行业选择:申万一级行业
    • 2. 动量因子选择:阿隆指标(Aroon)
    • 3. 测算方法
      • 1.选择特定的时间区间
      • 2.计算阿隆指标(Arron)
      • 3. 统计收益率&胜率
    • 4. 测算结论

【量化选股——基于动量因子的行业风格轮动策略】分为两部分:

  1. 第1部分—因子测算
  2. 第2部分—策略回测

动量因子与行业轮动概述

动量因子的理解

动量,可以理解为“势头”,“强势的程度”。汽车遇到红灯时,不是一下子停下,而是滑行一段再停。滑行的这一段就解释为“动量”造成的。动量因子表示,即便情况发生了变化,这些因子所代表的势头仍会持续一段时间。

动量因子一直饱受争议,因为它的前提假设是股票会表现出马太效应。意思是:股票的相对强弱趋势会延续,并且表现出“强者恒强,弱者恒弱”的态势,除非有意外情况发生才会导致强弱之势逆转。动量因子表现出两种效应:

  1. 动量效应:股票的收益率会延续
  2. 反转效应:经过较长时间后,收益会翻转

挖掘动量因子的过程往往是先算出结果,再找一个逻辑来解释。因此动量因子其实没有太多的基础理论支撑,是在实践中被摸索出来的。使用动量因子的风险也很高,因为对动量因子的解释性的逻辑往往超脱了我们过去所知的对市场理解的框架体系,有一种“唯数据论”的感觉。这里博主希望大家能够辩证的看待动量因子,有自己的看法。

通常学界对动量因子的理解有以下几种:

  1. 投资者的决策行为,导致了动量因子的产生。如中国人个人炒股比例高,而且由于缺乏背景知识,缺乏对基本面的了解,同时好大喜功求快,导致短线操作居多,跟风者居多;

    但反对者认为这种猜测无法定量评估,而这样的解释是“为了发展一个理论模型而寻找不合理的逻辑假设”

  2. 因为每个市场的参与者,接收同一个信息的时间都不同,因此即便某个事件发生了,依旧会因为接收信息的时间差导致参与者在操作上存在时间差,所以时间差导致了动量这种“效应”的产生。同样,也可能因为存在一些信息差,不对称的信息差会随着时间流逝慢慢对称,这个过程也会造成动量。

    但反对者同样认为:既然存在操作的时间差,那么利好和利空的消息都应该存在时间差,而往往动量因子只能体现出某一边的动量。(如,某些代表利好的动量因子无法解释利空的情况;或者说,动量因子本身就不应该被分为利好或利空,否则就代表它只验证了对一遍有效)

  3. 市场的参与者都有自己入场的动机与离场的目的,不同参与者也是在市场中不断博弈与进化的(市场具有“奈特不确定性”)。按照书本所述,股票现在合理的价格,就是对未来现金流的折现。那么不同的参与者因为伴随有不同的入场与离场的目的,因此站在各自的立场上,对“未来现金流折现”的估值也不同,而这种行为伴随着市场的进化与发展,导致“动量”作为一个观测现象而产生。

    这就意味着“动量”是一种表面现象,它不是因为固定的几个逻辑直接导致的结果,而是博弈造成的一种被观测出来的现象。

目前动量因子在个股上是作为多因子模型的一部分存在,而如果将视野扩大到行业层面,就可以单独拿动量因子进行建模测算,此时因子的动量效应较为明显。通常认为是因为暴露的因子动量会随着个股传递到整个行业组合上,因此观测较为明显。

我们常见的动量因子通常包含:

  1. 过去一段时间(1周、10天、一个月、3个月)的收益率
  2. 5、10、20、30、60天收盘价均线
  3. 过去一段时间板块、行业的高开低收等数据

投资视角下的行业轮动现象

我们以接收信息的时间差导致参与者在操作上存在时间差,所以时间差导致了动量这种“效应”的产生为视角,就可以发现在行情走势上:

  1. 市场上有大量未接受到该信息的投资者:标的买卖双方造成的买卖供需关系并不会显著到立即把价格推向其应当达到的位置,此时表现在信息的“反应不足”

  2. 越来越多的投资者接触到了该信息并做出反应:此时价格逐渐被推至合理价格附近,但后入市场的投资者中有一部分因为各种各样的原因,错误或过分估计了该信息价值,从而将该股票价格再进一步推离了合理价格,此时“反应过度”,出现超买、超卖现象

  3. 超买超卖现象被发现:市场的投资者捕捉到反映过度带来的盈利空间,通过交易获取超额收益的同时,将股票价格再度往合理价格处推动。

在这里插入图片描述
比如:在2022年11月30日ChatGPT就已经向公众开放:

  • 11月30日OpenAI(ChatGPT的作者)在维基百科发布关于ChatGPT的百科信息
  • 12月6日开始维基百科词条开始普通用户参与修改,并且参与修改的用户账号量每日增长显著
  • 12月新闻既有“小学生击败ChatGPT”,“差点被ChatGPT骗了”,“举一千反一,人类离xxx还有多远”等负面看法,也有“爆火的ChatGPT太强了”等正面观点

在这里插入图片描述
复盘12、1、2月份到如今的ChatGPT概念(BK1126),可以发现站在投资者的视角上:

  1. 投资者无法判断信息是从何时开始反映在市场上的,也无法判断未来将持续多长时间
  2. 投资者难以评估信息的市场价值,信息带来的对预期的影响,能造成的股价涨跌幅难以度量
  3. 信息源种类、观点、立场繁多,难以主观评价
    如今,百度指数上,ChatGpt的搜索指数与资讯关注度依旧很高,但相对高点已经走弱,由于是境外资讯,而百度搜索面向国内用户,因此国内资讯会优先与国内的搜索指数呈现出增长的趋势

在这里插入图片描述

因此在行业轮动上,动量指标盈利的来源:

  1. 市场上有大量未接受到该信息的投资者
  2. 越来越多的投资者接触到了该信息并做出反应(动量指标盈利主因)
  3. 超买超卖现象被发现(动量指标亏损主因)

投资者视角与奈特不确定性

奈特把对未来的不确定性分为两种,一种称为“风险”,另一种称为“奈特不确定性”,将未知分为了两类:

  1. 风险:具有特定概率分布的不确定性
  2. 奈特不确定性:没有特定概率分布的不确定性

后续的研究证明:

  1. 人们常对奈特不确定性表现出厌恶的倾向,并愿意为避免奈特不确定性而支付溢价
  2. 厌恶奈特不确定性的人不一定厌恶风险,即“赌鬼也讨厌奈特不确定”
  3. 在人们面临奈特不确定性时,会出现群体盲从的现象,形成羊群效应,此时人们更在乎别人的想法

当信息被越来越多的人接触到的时候,投资者面临的其实就不是完全未知的“奈特不确定性”,而是可以评估盈利与亏损区间与概率的“风险”了。但相对的,不同的市场参与者对信息的敏感程度与评估是不同的,从这个角度也验证了上述的第二条:“投资者难以评估信息的市场价值,信息带来的对预期的影响,能造成的股价涨跌幅难以度量”。对于信息的动量与价格走势趋势的判断需要策略研究员进行细致的研究。

动量因子在行业风格上的效果测算

动量因子效果测算流程概述

  1. 首先我们选择“申万一级”行业指数,阿隆指标(Aroon)作为动量指标进行测算
  2. 然后根据指数数据集,选择公共的时间段(2015-01-01 至 2023-01-01)将日期分为两个部分:
    • 选择 2015-01-01 至 2020-01-01 这一段时期进行测算
    • 选择 2020-01-01 至 2023-01-01 这一段时期进行回测
  3. 选择测算的时间段进行测算
  4. 选择回测的时间段进行回测

1. 行业选择:申万一级行业

我们选取申万一级行业指数来测算

申万行业分类规则请参考:申万行业分类标准(2021版)

行业代码行业名称成份个数静态市盈率TTM(滚动)市盈率市净率静态股息率
0801010.SI农林牧渔9947.1347.642.840.62
1801030.SI基础化工34315.9215.022.482.11
2801040.SI钢铁447.2712.031.065.65
3801050.SI有色金属12824.5515.512.691.21
4801080.SI电子30823.8827.292.941.21
5801880.SI汽车24029.2828.172.271.31
6801110.SI家用电器7917.6016.002.822.97
7801120.SI食品饮料11940.7336.847.841.77
8801130.SI纺织服饰11316.8217.741.973.48
9801140.SI轻工制造14821.6725.742.381.81
10801150.SI医药生物36029.3426.323.571.08
11801160.SI公用事业12321.3219.211.902.28
12801170.SI交通运输1249.878.481.284.62
13801180.SI房地产1159.5512.150.983.40
14801200.SI商贸零售10426.1031.572.601.51
15801210.SI社会服务7362.3462.043.570.55
16801780.SI银行424.974.660.555.79
17801790.SI非银金融8813.6416.671.382.56
18801230.SI综合2457.8330.572.310.78
19801710.SI建筑材料749.3412.551.483.83
20801720.SI建筑装饰1588.507.910.892.35
21801730.SI电力设备26540.1929.254.070.60
22801890.SI机械设备39824.0727.192.341.69
23801740.SI国防军工9855.9150.133.740.49
24801750.SI计算机26839.4943.273.890.88
25801760.SI传媒14018.9321.971.882.49
26801770.SI通信10717.8915.991.454.08
27801950.SI煤炭3810.126.641.426.61
28801960.SI石油石化4710.618.141.006.81
29801970.SI环保10916.6018.721.581.75
30801980.SI美容护理2843.9542.615.690.61

这里申万一级的行情数据不在之前的股票数据里,这里提供获取代码:

import akshare as ak# 申万一级行业信息
sw_index_first_info_df = ak.sw_index_first_info()
for _, sw_series in sw_index_first_info_df.iterrows():sw_symbol = sw_series["行业代码"].split(".")[0]_ak_df = ak.index_hist_sw(symbol=sw_symbol, period="day")_ak_df.to_csv("../data/select_factor_data/sw_{}.csv".format(sw_symbol),index=False)

2. 动量因子选择:阿隆指标(Aroon)

阿隆(Aroon)指标是由图莎尔·钱德(Tushar Chande)1995 年发明的,它通过计算当前价格达到近期最高值和最低值以来所经过的天数,帮助投资者预测证券价格趋势或反转的变化

阿隆指标计算步骤:

  1. 确定滑动窗口的长度,比如25个工作日);获取这个窗口中日线的最高价与最低价
  2. 用最高价计算AroonUp = [(计算期天数-达到最高价后的天数)/计算期天数]*100,即:
    Aroonup = [ ( 25 - 到达最高价后的天数 ) / 25] * 100
  3. 用最低价计算AroonDown = [(计算期天数-达到最低价后的天数)/计算期天数]*100,即:
    Arrondown = [ ( 25 - 达到最低价后的天数 ) / 25 ] * 100

根据公式我们可以推算出:

  1. 最高价屡创新高时,arronup=1;最低价屡创新低时,arrondown=1;
  2. 最高价不断走低时,arronup=0;最低价不断走高时,arrondown=0;
  3. arronup越小,代表离上一次创新高的时间越久;arrondown越小,代表离上一次创新低的时间越久;
  4. arronup=1且arrondown=1,代表最高价屡创新高的同时,最低价也屡创新低(柱子拉长)

在这里插入图片描述

3. 测算方法

1.选择特定的时间区间

我们删除数据不足的“石油石化”,“环保”,“美容护理”,这三个指数,然后划分公共数据区间为两段:

  • 统计测算:2015-01-01 至 2020-01-01
  • 回测:2020-01-01 至 2023-01-01
#(部分代码)
train_data_dict = {}
test_data_dict = {}
for _sw_key, _sw_df in sw_data_dict.items():train_data_dict[_sw_key] = _sw_df[_sw_df["日期"].between("2015-01-01", "2020-01-01")]test_data_dict[_sw_key] = _sw_df[_sw_df["日期"].between("2020-01-01", "2023-01-01")]

2.计算阿隆指标(Arron)

选择时间区间:2015-01-01 至 2020-01-01,所有指数单独计算,以25天为滑动窗口长度,计算aroonup与aroondown指标

规定交易规则:当arronup>arrondown时,以当天收盘价买入;arronup<arrondown时以当天收盘价卖出

def measure_aroon(dataframe:pd.DataFrame):dataframe.columns = ["code","date",'close','open','high','low','volume','business_volume']dataframe.set_index(["date"], inplace=True)dataframe.index.name = ""dataframe['aroondown'], dataframe['aroonup'] = talib.AROON(dataframe['high'], dataframe['low'], timeperiod=14)dataframe = dataframe.dropna()return dataframe

3. 统计收益率&胜率

统计每一笔完整的交易(从买到卖的完整交易)的年化收益率,并且逐笔统计,以年化收益率>2%记为胜,否则为负

# (部分代码)
total_measure_record = {} # 测算结果for _train_lable,_train_df in train_data_dict.items():measure_record = {} # 测算结果if _train_df.shape[0] ==0:continuemea_df = measure_aroon(_train_df.copy())# 开始测算trade_record_list = []this_trade = {"close_record":[],}for index,series in tqdm(mea_df.iterrows(),total=mea_df.shape[0]):if series['aroondown'] < series['aroonup']:mea_df.loc[index,"label"] = "sell"if "buy_date" not in this_trade.keys():continuethis_trade['sell_date'] = index.to_pydatetime()trade_record_list.append(this_trade)this_trade = this_trade = {"close_record":[],}else:mea_df.loc[index,"label"] = "buy"this_trade['buy_date'] = index.to_pydatetime()this_trade['close_record'].append(series['close'])if "buy_date" in this_trade.keys():this_trade['close_record'].append(series['close'])trade_record_df = pd.DataFrame(trade_record_list)for _i,_trade_series in trade_record_df.iterrows():_trade_record_year_rate = (_trade_series['close_record'][-1] - _trade_series['close_record'][0])/_trade_series['close_record'][0]/(_trade_series['sell_date'] - _trade_series['buy_date']).days * 365 # 年化收益if _trade_record_year_rate > 0.02:trade_record_df.loc[_i,'victory'] = 1else:trade_record_df.loc[_i,'victory'] = 0trade_record_df.loc[_i,'年化收益率']  = _trade_record_year_rate# trade_record_df 即为每一个行业真实的测算结果measure_record['胜率'] = round(sum(trade_record_df['victory']) / trade_record_df.shape[0], 4)measure_record['胜率详情'] = "{}/{}".format(round(sum(trade_record_df['victory']),3), trade_record_df.shape[0])measure_return = trade_record_df['年化收益率'].describe()measure_record['收益率均值'] = measure_return['mean']measure_record['收益率方差'] = measure_return['std']measure_record['25%'] = measure_return["25%"]measure_record['75%'] = measure_return["75%"]measure_record['中位数'] = measure_return['50%']total_measure_record[_train_lable] = measure_record

4. 测算结论

按照上述的测算方法,测算结论如下:

  1. 综合测算下来,28个申万一级行业中,有15个行业的综合胜率>=50%,有18个年化收益率中位数>=0。
  2. 没有一个行业可以每年的胜率都达到50%以上
胜率胜率详情“年化收益率均值”“年化收益率方差”“年化收益率25%”“年化收益率75%”“年化收益率中位数”
801210.SI0.6526.0/401.10132813.854513-3.567328.3831933.277827
801110.SI0.62520.0/32-0.43676116.563576-7.3248557.5505731.551451
801750.SI0.605323.0/38-2.00612122.928017-6.9598759.2676092.499013
801120.SI0.589723.0/39-0.54581212.693933-3.1803855.8975451.632406
801890.SI0.588220.0/34-3.36067722.453205-14.6205418.865592.704538
801080.SI0.588220.0/34-5.75448225.290329-8.0245436.4357951.120838
801200.SI0.575819.0/33-5.14804120.819803-12.9407574.3527821.052707
801140.SI0.575819.0/33-4.73596818.721499-10.9718666.1674021.135974
801160.SI0.571420.0/35-4.53512617.491513-7.791965.7381180.732191
801730.SI0.558819.0/34-2.88049719.378822-12.309228.1740410.882168
801010.SI0.555620.0/36-2.93808817.975981-11.3953038.5876680.848525
801130.SI0.545518.0/33-4.97715521.158295-8.8583126.2438431.500513
801760.SI0.531217.0/32-10.96552527.899573-16.3725745.7345610.556964
801770.SI0.515217.0/33-4.68487724.414547-10.70240610.3028370.887308
801050.SI0.516.0/32-4.53906122.368268-16.5867359.2855820.261408
801040.SI0.487219.0/39-1.24763415.297324-10.7822486.7690930.011439
801180.SI0.487219.0/39-0.80418516.592557-3.9955525.568170
801720.SI0.486518.0/37-3.74782419.616045-15.0682356.5086590
801710.SI0.473718.0/38-3.01019717.654178-7.4173625.759873-0.061242
801030.SI0.468815.0/32-1.80782217.684925-6.3255938.010072-0.742137
801880.SI0.468815.0/32-3.9287619.513424-9.6945966.653341-1.287274
801170.SI0.4518.0/40-0.84516113.940707-5.7243374.644774-0.482324
801790.SI0.447417.0/38-5.05709720.558703-15.5258314.800522-0.754778
801150.SI0.406213.0/32-6.39906418.282151-14.5345014.495543-1.321404
801230.SI0.406213.0/32-7.86619327.87652-13.1456536.940749-1.782862
801740.SI0.333311.0/33-9.56387620.308132-17.155551.213517-6.112185
801950.SI0.28574.0/14-15.70347737.098001-25.4346440.683044-0.548628
801780.SI0.268311.0/41-3.81688410.615774-6.9792130.250093-0.675727

不同指数分年的胜率统计图(0.5为纯白色,越偏红胜率越高,越偏蓝胜率越低):

在这里插入图片描述


http://www.ppmy.cn/news/24585.html

相关文章

Spring Boot HTTP 400排查

背景 前段时间朋友咨询他们公司某个HTTP接口偶现400错误&#xff0c;有没有什么好的分析方法和解决方案&#xff0c;使用的是Spring Cloud体系。最近有时间总结下这个问题的处理过程。 为了分析问题&#xff0c;笔者使用 Spring Boot 3.0.2还原报错场景进行讲解。 问题分析 …

R语言读取Excel表格数据并绘制多系列柱状图、条形图

本文介绍基于R语言中的readxl包与ggplot2包&#xff0c;读取Excel表格文件数据&#xff0c;并绘制具有多个系列的柱状图、条形图的方法。 首先&#xff0c;我们配置一下所需用到的R语言readxl包与ggplot2包&#xff1b;其中&#xff0c;readxl包是用来读取Excel表格文件数据的&…

基于VS调试分析 + 堆栈观察问题代码段

文章目录问题代码段1 —— 阶乘之和问题代码段2 —— 越界的危害① 发现问题② 分析问题③ 思考问题【⭐堆栈原理⭐】④ 解决问题【DeBug与Release】&#x1f468;程序员与测试人员&#x1f469;✒总结与提炼问题代码段1 —— 阶乘之和 先来看一道C语言中比较基础的题目&#x…

Linux(Linux各目录结构详解)

我们知道Linux系统是一个文件系统&#xff0c;它的文件系统就类似windows系统下的磁盘文件系统。 我们连接上一台linux系统的服务器。 输入命令 &#xff1a; ls / 我们可以看到 linux系统的根目录下有这些目录 bin boot data dev etc hbr home lib lib64 lostfoun…

分享微信点餐小程序搭建步骤_微信点餐功能怎么做

线下餐饮实体店都开始摸索发展网上订餐服务。最多人选择的是入驻外卖平台&#xff0c;但抽成高&#xff0c;推广还要另买流量等问题&#xff0c;也让不少商家入不敷出。在这种情况下&#xff0c;建立自己的微信订餐小程序&#xff0c;做自己的私域流量是另一种捷径。那么&#…

ArcGIS API for JavaScript 4.15系列(5)——Dojo中的query查询器

1、前言 在之前的博客中&#xff0c;我们一直通过dom.byId方法来获取dom元素。但在实际开发过程中&#xff0c;单凭id获取dom元素是无法满足开发需求的&#xff0c;例如根据css样式名称获取对应的dom元素集合、获取某个div下所有的超链接<a>元素等。Dojo中提供了dojo/qu…

软件工程(4)--螺旋模型

前言 这是基于我所学习的软件工程课程总结的第四篇文章。 在软件开发过程中必须及时识别和分析风险&#xff0c;并且采取适当措施以消除或减少风险的危害。构建原型是一种能使某些类型的风险降至最低的方法。为了降低交付给用户的产品不能满足用户需要的风险&#xff0c;一种行…

linux的三权分立设计思路和用户创建(安全管理员、系统管理员和审计管理员)

目录 一、三权分立设计思路 1、什么是三权 2、三员及权限的理解 3、三员之三权 4、权限划分 5、“三员”职责 6、“三员”配置要求 二、linux三权分立的用户创建 1、系统管理员 2、安全管理员 3、审计管理员 一、三权分立设计思路 1、什么是三权 三权指的是配置、…