【研报复现】方正金工:(1)适度冒险 因子
【研报复现】方正金工:(1)适度冒险 因子
- 原始因子构建思路
- 1. 引言
- 2. “适度冒险”因子定义逻辑
- 3. “适度冒险”因子构建
- 3.1) 成交量激增的定义
- 3.2) 激增时刻的*价格波动*
- 3.3) 激增时刻的*价格变动*
- 3.4) 适度冒险因子合成
- 3.5) 参数敏感性测试
- 4. 代码实现
- 5. 因子表现
- 6. 评价
- 7. 不足分析(金融意义视角)
原始因子构建思路
1. 引言
在股票市场中,成交量的边际变化隐含着非常重要的信息,特别是在技术分析领域,成交量被认为是股票市场的原动力。俗语“量在价先”深刻地反应了成交量的变化对于股票价格波动的预测具有指示性作用。
成交量的大小,可以衡量股票市场或者个股的活跃程度,并由此来观察买卖双方进入或退出市场的状况。本文我们将尝试从成交量的边际变动出发,挖掘其对股票收益的潜在影响。
2. “适度冒险”因子定义逻辑
以利好信息为例:当一个利好信息公布后,可能会引起相应个股成交量的突然放大。
- 如果在成交量激增的同时,价格却未发生变动,或者未能引起价格的波动,则表明这一利好消息没能得到市场广泛的认可。
- 相反,如果成交量激增的同时,价格出现大幅上涨,则表明市场对于此利好信息反应过于趋同,有可能出现反应过度。
因此,当市场获得新的利好信息后,一方面我们希望此信息可以被市场广泛的认可和接受,推动股票价格稳步上涨;而另一方面我们不希望成交量的激增引起的价格变动太过剧烈,这样可能代表了投资者反应过度,导致股票短时间内涨幅过大,或者风险大幅加剧。正所谓,“那些吸引人们注意的股票,更容易诱发人们去承担过度的风险”。
综合以上两个方面,我们希望成交量激增能带来股票价格的适度变化与波动。或者说,我们希望那些认可利好消息的人,去适度地承担风险。
3. “适度冒险”因子构建
3.1) 成交量激增的定义
我们观察个股分钟频成交量的边际变化来定义成交量是否激增,具体如下:
- 剔除开盘和收盘数据,仅考虑日内分钟频数据,我们首先计算个股每分钟的成交量相对于上一分钟的成交量的差值,作为该分钟成交量的增加量。
- 计算每天每只个股分钟频成交量的增加量的均值meanmeanmean和标准差stdstdstd。
- 我们定义那些分钟频成交量增加量大于“均值+一倍标准差”的时刻为成交量激增的时刻,我们将对应的时刻统称为“激增时刻”。举例来说,假设股票A在某天t的第9分钟、第10分钟、第88分钟、第200分钟的成交量增加量大于当天的mean+stdmean+stdmean+std,那么我们将第9、10、88、200这四个分钟统称为A股票在t日的“激增时刻”。
对于A股市场整体而言,投资者交易行为存在较为明显的时间特征,开盘之后成交量一般逐步下降,临近收盘再逐步提升。但对于个股而言,成交量的变化并不严格与此同步,成交量出现大幅变动的情况较为频繁。
3.2) 激增时刻的价格波动
首先来考察成交量激增引起的价格波动,进而构造“月耀眼波动率”因子,具体过程如下:
- 我们定义“激增时刻”的这一分钟及其随后的4分钟,是因成交量激增而引起投资者关注的5分钟。投资者对成交量激增的反应,在这5分钟里表现得最充分最强烈,我们将这5分钟称为“耀眼5分钟”。
- 在上例中,如果第9、10、88、200分钟为“激增时刻”,那么对应的“耀眼5分钟”分别为第9~13分钟、第10~14分钟、第88~92分钟、第200~204分钟。
- 我们使用分钟收盘价,计算每分钟的收益率,进而可以把得到的每个“耀眼5分钟”里收益率的标准差作为成交量激增引起的价格波动率,我们将其称为“耀眼波动率”。
- 我们计算A股票在ttt日内所有“耀眼波动率”的均值,作为ttt日A股票对成交量的激增在波动层面上反应的代理变量,记为“日耀眼波动率”。
- 根据前述分析,我们希望“日耀眼波动率”不要太大,也不要太小,适度最好,为了不引入其他参数,我们此处选取“日耀眼波动率”的截面均值作为最“适度”的水平。因此我们将每日的“日耀眼波动率”减去截面的均值再取绝对值,表示个股的“日耀眼波动率”与市场平均水平的距离,并将其记为日频因子“适度日耀眼波动率”。
- 我们分别计算最近20个交易日的“适度日耀眼波动率”的平均值和标准差,记为“月均耀眼波动率”因子和“月稳耀眼波动率”因子。
- 将“月均耀眼波动率”与“月稳耀眼波动率”等权合成,得到“月耀眼波动率”因子。
3.3) 激增时刻的价格变动
接下来考察成交量激增引起的价格变动,进而构造“月均耀眼收益率”和“月稳耀眼收益率”因子,具体过程如下:
- 我们通过股票的每分钟收盘价,计算股票每分钟的收益率。
- 找到“激增时刻”对应的分钟收益率,例如上例中,第9、10、88、200分钟为激增时刻,那么分别对应着第9、10、88、200分钟的分钟收益率,我们将其称为“耀眼收益率”。
- 对A股票在t日内所有的“耀眼收益率”求均值,作为股票对成交量激增在收益率层面反应的代理变量,记为“日耀眼收益率”。
- 根据上述分析,我们同样希望“耀眼收益率”不要太大,也不要太小,适度最好。因此我们将每日的“日耀眼收益率”减去截面的均值再取绝对值,表示个股的“日耀眼收益率”与市场平均水平的距离,并将其记为“适度日耀眼收益率”。
- 我们分别计算最近20个交易日的“适度日耀眼收益率”的平均值和标准差,记为“月均耀眼收益率”和“月稳耀眼收益率”因子。
- 将“月均耀眼收益率”与“月稳耀眼收益率”因子等权合成,得到“月耀眼收益率”因子。
3.4) 适度冒险因子合成
以上我们分别从波动率以及收益率角度构造了两个衡量“适度冒险”的因子,并发现其均具备非常强势的选股能力。
我们将上述“月耀眼波动率”因子与“月耀眼收益率”因子合并,等权合成为最终的“适度冒险”因子。
3.5) 参数敏感性测试
我们将上述“耀眼5分钟”,改为“耀眼3分钟”或“耀眼4分钟”,即取“激增时刻”及其随后的2分钟或3分钟来重新计算因子,可以看到因子表现依然非常出色,参数敏感性相对较低。
4. 代码实现
def factor(df, window = 96):"""计算适度冒险因子(基于成交量激增的价格波动和价格变动)步骤:1. 计算成交量变化量(当前bar与前一个bar的成交量差)2. 按日分组计算成交量变化的均值和标准差3. 识别激增时刻(成交量变化 > 均值 + 标准差)4. 计算激增时刻的波动率(振幅)和收益率5. 按日聚合计算日耀眼波动率和日耀眼收益率6. 计算适度日耀眼波动率和适度日耀眼收益率7. 计算月均/月稳耀眼波动率和月均/月稳耀眼收益率8. 合成月耀眼波动率和月耀眼收益率因子9. 最终合成适度冒险因子参数:df: 包含OHLCV数据的DataFrame"""# 创建副本避免修改原始数据data = df.copy()# 添加日期列data['date'] = data.index.date# 1. 计算成交量变化量(当前bar与前一个bar的成交量差)data['volume_diff'] = data.groupby('date')['volume'].diff()# 2. 按日分组计算成交量变化的均值和标准差daily_mean = data.groupby('date')['volume_diff'].transform('mean')daily_std = data.groupby('date')['volume_diff'].transform('std')# 3. 识别激增时刻(成交量变化 > 均值 + 标准差)data['surge'] = (data['volume_diff'] > (daily_mean + daily_std)) & (data['volume_diff'].notna())# 4. 计算激增时刻的波动率(振幅)和收益率data['amplitude'] = (data['high'] - data['low']) / data['open'] # 波动率代理data['return'] = (data['close'] - data['open']) / data['open'] # 收益率# 5. 按日聚合计算日耀眼波动率和日耀眼收益率daily_amp = data[data['surge']].groupby('date')['amplitude'].mean().rename('daily_amp')daily_ret = data[data['surge']].groupby('date')['return'].mean().rename('daily_ret')# 合并回原始数据data = data.merge(daily_amp, left_on='date', right_index=True, how='left')data = data.merge(daily_ret, left_on='date', right_index=True, how='left')# 6. 计算适度日耀眼波动率和适度日耀眼收益率(使用过去window天的历史均值)# 创建日频DataFrame用于滚动计算daily_df = data.groupby('date').first()[['daily_amp', 'daily_ret']]# 计算过去20天的历史均值daily_df['past_20d_mean_amp'] = daily_df['daily_amp'].rolling(window, min_periods=10).mean()daily_df['past_20d_mean_ret'] = daily_df['daily_ret'].rolling(window, min_periods=10).mean()# 计算适度日耀眼波动率和收益率daily_df['mod_daily_amp'] = (daily_df['daily_amp'] - daily_df['past_20d_mean_amp']).abs()daily_df['mod_daily_ret'] = (daily_df['daily_ret'] - daily_df['past_20d_mean_ret']).abs()# 7. 计算月均/月稳耀眼波动率和月均/月稳耀眼收益率daily_df['monthly_avg_amp'] = daily_df['mod_daily_amp'].rolling(window, min_periods=10).mean()daily_df['monthly_std_amp'] = daily_df['mod_daily_amp'].rolling(window, min_periods=10).std()daily_df['monthly_avg_ret'] = daily_df['mod_daily_ret'].rolling(window, min_periods=10).mean()daily_df['monthly_std_ret'] = daily_df['mod_daily_ret'].rolling(window, min_periods=10).std()# 8. 合成月耀眼波动率和月耀眼收益率因子daily_df['monthly_star_vol'] = (daily_df['monthly_avg_amp'] + daily_df['monthly_std_amp']) / 2daily_df['monthly_star_ret'] = (daily_df['monthly_avg_ret'] + daily_df['monthly_std_ret']) / 2# 9. 最终合成适度冒险因子daily_df['moderate_adventure'] = (daily_df['monthly_star_vol'] + daily_df['monthly_star_ret']) / 2# 将日频因子值映射回原始数据data = data.merge(daily_df[['moderate_adventure']], left_on='date', right_index=True, how='left')# 使用滞后一天的因子值(避免前视偏差)data['moderate_adventure'] = data.groupby('date')['moderate_adventure'].transform('first').shift(1)return data['moderate_adventure']
5. 因子表现
📊 单币种 (single) 详细评估结果:
--------------------------------------------------
📈 平稳性检验 (ADF):p_value: 0.010378是否平稳: 是
🔗 相关性分析:IC (Pearson): 0.008211Rank_IC (Spearman): 0.007968
📊 信息比率:IR: -0.110466
6. 评价
- IC, Rank_IC数值难看(遑论中后段回撤)
- 收益率看上去似乎有修的希望。。
- 分布 & 散点图贼丑!
7. 不足分析(金融意义视角)
-
激增时刻识别单一性
- 仅依赖成交量变化,未考虑主动买卖力量(taker_buy数据)
- 可能将被动抛售误判为积极冒险行为
-
维度覆盖不全面
- 只考虑价格波动和收益率,忽略交易行为特征
- 未利用turnover、trade_count等微观结构信息
-
窗口机制僵化
- 固定96天窗口(约3个月)无法适应市场状态变化
- 高波动期应缩短窗口,低波动期应延长窗口
-
因子合成简单化
- 波动率和收益率简单平均,未考虑不同维度的信息含量差异
- 未进行标准化处理,不同量纲特征直接组合
-
日内信息利用不足
- 日频聚合丢失激增时刻的日内模式信息
- 未考虑激增发生的时段特征(如亚洲/欧美交易时段)