diff --git "a/B-\345\233\240\345\255\220\346\236\204\345\273\272\347\261\273/\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220/20200223_\344\270\234\345\220\264\350\257\201\345\210\270_\342\200\234\346\212\200\346\234\257\345\210\206\346\236\220\346\213\245\346\212\261\351\200\211\350\202\241\345\233\240\345\255\220\342\200\235\347\263\273\345\210\227\347\240\224\347\251\266\357\274\210\344\270\200\357\274\211\357\274\232\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220.pdf" "b/B-\345\233\240\345\255\220\346\236\204\345\273\272\347\261\273/\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220/20200223_\344\270\234\345\220\264\350\257\201\345\210\270_\342\200\234\346\212\200\346\234\257\345\210\206\346\236\220\346\213\245\346\212\261\351\200\211\350\202\241\345\233\240\345\255\220\342\200\235\347\263\273\345\210\227\347\240\224\347\251\266\357\274\210\344\270\200\357\274\211\357\274\232\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220.pdf" new file mode 100644 index 0000000..a6f9652 Binary files /dev/null and "b/B-\345\233\240\345\255\220\346\236\204\345\273\272\347\261\273/\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220/20200223_\344\270\234\345\220\264\350\257\201\345\210\270_\342\200\234\346\212\200\346\234\257\345\210\206\346\236\220\346\213\245\346\212\261\351\200\211\350\202\241\345\233\240\345\255\220\342\200\235\347\263\273\345\210\227\347\240\224\347\251\266\357\274\210\344\270\200\357\274\211\357\274\232\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220.pdf" differ diff --git "a/B-\345\233\240\345\255\220\346\236\204\345\273\272\347\261\273/\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220/CPV\345\233\240\345\255\220.html" "b/B-\345\233\240\345\255\220\346\236\204\345\273\272\347\261\273/\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220/CPV\345\233\240\345\255\220.html" new file mode 100644 index 0000000..b6196f3 --- /dev/null +++ "b/B-\345\233\240\345\255\220\346\236\204\345\273\272\347\261\273/\351\253\230\351\242\221\344\273\267\351\207\217\347\233\270\345\205\263\346\200\247\357\274\214\346\204\217\346\203\263\344\270\215\345\210\260\347\232\204\351\200\211\350\202\241\345\233\240\345\255\220/CPV\345\233\240\345\255\220.html" @@ -0,0 +1,14597 @@ + + +
+ +价量相关性的平均数因子:平均数因子利用成交量的信息,修正了传统 反转因子对股票价格涨跌的判断,即价格涨跌的反转不完全由价格自己 决定,还需看成交量的信息,若有量的确认,则月度行情的反转效应更强;若没有量的确认,则月度行情更容易呈现动量效应。
+价量相关性的波动性因子:波动性因子则从价量形态稳定性的角度,对 反转因子进行了改进,即无论股票价格过去的涨跌,只要每日价量关系 维持某种稳定形态,下个月就更有可能上涨;而价量关系在多种形态间频繁切换的股票,下个月更有可能下跌。
+价量相关性的趋势因子:股票价量相关性的变化趋势中也包含了有效的 选股信号。回测结果显示,价量相关系数随时间推移变小的股票,下个月的收益倾向于越高。
+ +# 引入库
+import sys
+
+sys.path.append('../..')
+
+from Hugos_tools.Tdays import (Tdaysoffset, get_trade_period)
+from Hugos_tools.BuildStockPool import Filter_Stocks
+from Hugos_tools.Stragegy_performance import (get_performance_table,
+ get_information_table,
+ calc_mono_score)
+import functools
+import warnings
+from typing import (List, Tuple, Dict, Callable, Union)
+from tqdm import tqdm_notebook
+
+import alphalens as al
+import alphalens.performance as perf
+import statsmodels.api as sm
+from scipy.stats import pearsonr
+import pandas as pd
+import numpy as np
+import empyrical as ep
+
+from jqdata import *
+from jqfactor import (calc_factors, Factor)
+
+import seaborn as sns
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+
+plt.rcParams['font.sans-serif'] = ['SimHei'] #用来正常显示中文标签
+plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
+
经过探索,我们找到一种提炼有效信息的方案,以全体A股为研究样本(剔除其中的ST股、停牌股以及上市不足60个交易日的次新股),以 2014/01/01-2021/11/30为回测时间段,实施以下操作:
+先来看平均数因子 PV_corr_avg,它衡量了股票过去 20 日价量相关性的平均水平, 回测结果显示,价量相关系数越小的股票,未来收益倾向于越高。经过反复推敲,我们认为该因子其实是对传统反转因子做了修正。入下图,传统反转因子认为,股票的月度行情存在反转效应,过去 20 日上涨的股票下个月更倾向于下跌,应该归于空头; 而过去 20 日下跌的股票未来更有可能上涨,下个月应该归为多头。价量相关性的平均数因子 PV_corr_avg 则加入量的信息,细化了反转因子对股票价格形态的分类。
+
对于价格上涨,可以进一步分为放量上涨和缩量上涨,前者的价量相关系数较大, 后者的价量相关系数较小,在 PV_corr_avg 因子看来,两者不可混为一谈。过去放量上 涨的股票,就好比武侠小说中的“末路英雄”,强弩之末,难穿鲁缟,耗尽了内力,下 个月便倾向于下跌,这一点与反转因子一致;而过去缩量上涨的股票,好比有绵绵内力 不断缓缓释放,上个月还未涨到尽头,仍有力量支撑,下个月便能延续之前的行情,继续上涨,这一点正好与反转因子相反。
+对于价格下跌,也可以进一步分为放量下跌和缩量下跌,前者的价量相关系数小, 后者的价量相关系数大,PV_corr_avg 因子对这两类股票也持有不同的态度。放量下跌 的股票,PV_corr_avg 的判断与反转因子一致,认为下个月行情更容易反转,归为多头; 而过去平均来看缩量下跌的股票,由于还未经历放量见底的过程,下个月的反转行情相对较弱,仍然归于空头,这一点与反转因子不同。
+综上所述,平均数因子 PV_corr_avg 对传统反转因子的修正逻辑可总结为:价格涨跌的反转不完全由价格自己决定,还需看成交量的信息,若有量的确认,则月度行情的反转效应更强;若没有量的确认,则月度行情的动量效应更强。
+ +再来看衡量股票过去价量相关性波动情况的 PV_corr_std 因子,回测结果显示,因 子值越小,股票未来收益越高。我们也分 4 种价量关系分析该因子的选股逻辑。
+对于缩量上涨和放量下跌的情形,若股票的价量相关系数波动较小,则意味着股票在过去 20 个交易日,每日都稳定呈现缩量上涨或放量下跌的形态,自然与 PV_corr_avg因子的判断一致,将股票归为多头。
+而对另外两种情形的分析,PV_corr_std因子则与PV_corr_avg不同。对于放量上涨, 若价量相关系数波动较小,则意味着股票过去 20 个交易日,每日都呈现放量上涨的状 态,此时它不再是“末路英雄”,而是“绝顶高手”,比如受到了众多利好信息的持续刺 激,这类股票下个月应该仍然归为多头,期待上涨行情得以延续;对于缩量下跌,若股 票每日都稳定呈现此状态,则 PV_corr_std 因子对该股的判断与反转因子相同,认为下个月更有可能反转,出现上涨行情。
+其实,波动性因子 PV_corr_std 包含的选股信息也可以看做是对反转因子的一种修 正:无论股票过去是上涨还是下跌,只要每日价量关系维持一种稳定的形态, PV_corr_std 因子就把这只股票归为未来的多头;而价量关系在多种形态间频繁转换的股票,就会被 PV_corr_std 因子归为空头。
+小结
+总结了前两部分的分析,价量相关性因子可以看做是对传统反转的修正。
+
由上,pv_corr可以看作是对传统反转因子的修正,价量相关性因子的选股信息必然与传统反转存在重叠。我们可进一步精炼选股信息,先将两个子因子PV_corr_avg和PV_corr_std 分别剔除反转因子,再各自横截面标准化,等权线性相加构建综合因子,将得到的新因子记为 PV_corr_deRet20。
+ +在前文研究的基础上,我们额外发现股票价量相关性的变化趋势也能带来一些增量 信息,因此本节内容做进一步探索,挖掘新信息,并在最后呈现包含价量相关性3个维度信息的最终因子。
+本节介绍的选股因子计算过程如下:
+经检验,上一小节介绍的趋势因子 PV_corr_trend 在剔除原来的综合因子 PV_corr_deRet20 后,仍然具有一定的选股能力,因此我们将 PV_corr_trend 带来的增量 信息叠加到 PV_corr_deRet20 之上,最终因子命名为CPV因子(Correlation of Price and Volume),涵盖了本篇报告提出的价量相关性 3 个维度上的综合信息:
+$$CPV = \frac{PV\_corr\_deRet20+mean(PV\_corr\_deRet20)}{std(PV\_corr\_deRet20)} + \frac{PV\_corr\_trend-mean(PV\_corr\_trend)}{std(PV\_corr\_trend)}$$"""因子构建相关"""
+# 修饰器-分钟及数据数量大用次部分进行拆分
+
+
+def split_securities(Limit: int = 500):
+ def decorator(func: Callable):
+ @functools.wraps(func)
+ def wrapper(*args, **kw):
+
+ if 'securities' not in kw:
+
+ securities = args[0]
+
+ args = args[1:]
+
+ else:
+
+ securities = kw['securities']
+
+ del kw['securities']
+
+ if isinstance(securities, str):
+
+ securities = [securities]
+
+ size = len(securities)
+
+ if size > Limit:
+
+ return pd.concat((func(securities[i:i + Limit], *args, **kw)
+ for i in range(0, size, Limit)))
+
+ else:
+
+ return func(securities, *args, **kw)
+
+ return wrapper
+
+ return decorator
+
+# 获取日内数据
+
+
+@split_securities()
+def get_intraday_price(securities: Union[str, List], end_date: str,
+ count: int) -> pd.DataFrame:
+ """获取日内分钟数据
+
+ Args:
+ securities (Union[str, List]): 标的
+ end_date (str): 观察日
+ count (int): 天数
+
+ Returns:
+ pd.DataFrame: 数据
+ """
+ return get_price(securities,
+ end_date=end_date + ' 15:00:00',
+ count=240 * count,
+ frequency='1m',
+ fields=['close', 'volume'],
+ panel=False)
+
+
+# 计算相关系数
+def calc_corr(price: pd.DataFrame) -> pd.Series:
+ """计算量价相关系数
+
+ Args:
+ price (pd.DataFrame): 量价数据,index-datetime
+
+ Returns:
+ pd.Series: 相关系数
+ """
+ group = price.index.normalize()
+
+ return price.groupby(price.index.normalize()).apply(
+ lambda x: pearsonr(x['close'], x['volume'])[0])
+
+
+# 计算标准分
+def scaling_z_score(ser: pd.Series) -> pd.Series:
+ """标准分
+
+ Args:
+ ser (pd.Series): 因子值
+
+ Returns:
+ pd.Series: 标准化后的因子
+ """
+ return (ser - ser.mean()) / ser.std()
+
+
+# 计算残差
+def calc_ols(x: pd.Series, y: pd.Series,
+ method: str = 'resid') -> pd.Series:
+ """计算回归
+
+ Args:
+ x (pd.Series): 自变量
+ y (pd.Series): 应变量
+
+ Returns:
+ pd.Series: 残差
+ """
+
+ result = sm.OLS(y.fillna(0), sm.add_constant(np.nan_to_num(x))).fit()
+
+ return getattr(result, method)
+
+
+def Culling_factor(factor_ser: pd.Series,
+ other_factor: Union[pd.Series, pd.DataFrame],
+ scaling: bool = True) -> pd.Series:
+ """计算剔除其他因子
+
+ 再各自横截面标准化,等权线性相加构建综合因子
+ Args:
+ factor_ser (pd.Series): 因子
+ other_factor (pd.Series|pd.DataFrame): 反转因子
+ scaling (bool):是否标准化
+ Returns:
+ pd.DataFrame: 结果
+ """
+
+ # 去除反转因子的影响
+ ser = calc_ols(other_factor, factor_ser, 'resid')
+
+ if scaling:
+ # 标准化
+ scaling_ser = scaling_z_score(ser)
+
+ else:
+
+ scaling_ser = ser
+
+ return scaling_ser
+
+
+@split_securities()
+def calc_pv_corr(securities: Union[list, str], end_date: str, count: int,
+ market_cap: pd.Series) -> pd.DataFrame:
+ """计算因子
+
+ Args:
+ securities (Union[list, str]): 标的
+ end_date (str): 观察日
+ count (int): 周期
+ market_cap (pd.Series): 市值数据
+
+ Returns:
+ pd.DataFrame: 因子
+ """
+ # 获取分钟数据
+ price = get_intraday_price(securities, end_date=end_date, count=count)
+ # 获取量价相关系数
+ pv_corr = (price.set_index('time').groupby('code').apply(calc_corr).T)
+
+ # 计算pv_corr_trend所需的回归系数
+
+ pv_beta = pv_corr.apply(lambda x: calc_ols(np.arange(1,
+ len(x) + 1), x,
+ 'params')[1]) # 这里只是中间过程
+
+ # 平均数因子
+ pv_corr_avg = pv_corr.mean()
+ # 波 动性因子
+ pv_corr_std = pv_corr.std()
+
+ # 市值-标准化处理
+ market_cap = scaling_z_score(market_cap.loc[securities])
+ # 市值中性化
+ pv_corr_avg = Culling_factor(pv_corr_avg, market_cap, False)
+ pv_corr_std = Culling_factor(pv_corr_std, market_cap, False)
+ pv_beta = Culling_factor(pv_beta, market_cap, False)
+
+ pv_corr = scaling_z_score(pv_corr_avg) + scaling_z_score(pv_corr_std)
+
+ df = pd.concat((pv_corr_avg, pv_corr_std, pv_corr, pv_beta), axis=1)
+ df.columns = ['pv_corr_avg', 'pv_corr_std', 'pv_corr', 'pv_corr_trend']
+
+ return df
+
+
+class PV_corr(Factor):
+
+ warnings.filterwarnings("ignore")
+ name = 'PV_corr'
+ max_window = 20 # 可做敏感分析
+ # ROC20-20日动量
+ # VOL20-20日换手率
+ # 聚宽居然木有波动率....
+ dependencies = ['market_cap', 'ROC20', 'VOL20', 'close']
+
+ def calc(self, data: Dict) -> pd.Series:
+
+ codes = data['market_cap'].columns.tolist()
+ tradeDate = data['market_cap'].index[-1].strftime('%Y-%m-%d')
+ market_cap = data['market_cap'].iloc[-1]
+
+ # 获取反转因子
+ volatility: pd.Series = data['close'].pct_change().std() # 计算20日波动率
+ roc: pd.Series = data['ROC20'].iloc[-1]
+ vol: pd.Series = data['VOL20'].iloc[-1]
+ other_factor: pd.DataFrame = pd.concat((roc, vol, volatility), axis=1)
+ # 反转因子标准化
+ other_factor = other_factor.apply(scaling_z_score)
+
+ df = calc_pv_corr(securities=codes,
+ end_date=tradeDate,
+ count=self.max_window,
+ market_cap=market_cap)
+
+ # 计算pv_corr_deret20
+ pv_corr_avg_: pd.Series = Culling_factor(df['pv_corr_avg'],
+ other_factor, True)
+
+ pv_corr_std_: pd.Series = Culling_factor(df['pv_corr_std'],
+ other_factor, True)
+
+ df['pv_corr_deret20'] = pv_corr_avg_ + pv_corr_std_
+
+ # 计算pv_corr_trend
+ df['pv_corr_trend'] = Culling_factor(df['pv_corr_trend'], other_factor,
+ False)
+
+ df['CPV'] = scaling_z_score(df['pv_corr_deret20']) + scaling_z_score(
+ df['pv_corr_trend'])
+
+ self.market_cap: pd.Series = market_cap
+ self.other_factor: pd.DataFrame = other_factor
+ self.pv_corr_factor: pd.DataFrame = df
+
+
+"""因子获取"""
+
+
+def get_factor(symbol: str, tradeDt) -> pd.DataFrame:
+ """获取因子
+
+ Args:
+ symbol (str): 股票池范围 A为全A
+ startDate (str): 起始日期
+ endDate (str): 结束日期
+ freq (str): 频率
+
+ Returns:
+ pd.DataFrame: 因子
+ """
+
+ for tradeDt in tqdm_notebook(periods, desc='因子获取'):
+
+ stock_pool_func = Filter_Stocks(symbol, tradeDt)
+ stock_pool_func.filter_paused(21, 20) # 过滤21日停牌超过20日的股票
+ stock_pool_func.filter_st() # 过滤st
+ stock_pool_func.filter_ipodate(60) # 过滤次新
+
+ PV_CORR = PV_corr()
+ codes = stock_pool_func.securities
+ calc_factors(codes, [PV_CORR], start_date=tradeDt, end_date=tradeDt)
+ yield PV_CORR.pv_corr_factor
+
+
+def get_freq_price(security: Union[List, str], periods: List) -> pd.DataFrame:
+ """获取对应频率价格数据
+
+ Args:
+ security (Union[List, str]): 标的
+ periods (List): 频率
+
+ Yields:
+ Iterator[pd.DataFrame]
+ """
+ for trade in tqdm_notebook(periods, desc='获取收盘价数据'):
+
+ yield get_price(security,
+ end_date=trade,
+ count=1,
+ fields='close',
+ fq='post',
+ panel=False)
+
+
+def get_pricing(factor_df: pd.DataFrame, last_periods: str = None) -> pd.DataFrame:
+ """获取价格数据
+
+ Args:
+ factor_df (pd.DataFrame): 因子数据 MultiIndex levels-0 date levels-1 code
+ last_periods (str, optional): 最后一期数据. Defaults to None.
+
+ Returns:
+ pd.DataFrame
+ """
+ if last_periods is not None:
+ periods = factor_df.index.levels[0].tolist(
+ ) + [pd.to_datetime(last_periods)]
+ else:
+ periods = factor_df.index.levels[0]
+
+ securities = factor_df.index.levels[1].tolist()
+
+ # 获取收盘价
+ price_list = list(get_freq_price(securities, periods))
+ price_df = pd.concat(price_list)
+ pivot_price = pd.pivot_table(price_df,
+ index='time',
+ columns='code',
+ values='close')
+ return pivot_price
+
+
+class get_factor_returns(object):
+
+ def __init__(self, factors: pd.DataFrame, factor_name: str, max_loss: float) -> None:
+ '''
+ 输入:factors MuliIndex level0-date level1-asset columns-factors
+ '''
+ self.factors = factors
+ self.factor_name = factor_name
+ self.name = self.factor_name
+ self.max_loss = max_loss
+
+ def get_calc(self, pricing: pd.DataFrame, periods: Tuple = (1,), quantiles: int = 5) -> pd.DataFrame:
+
+ factor_ser: pd.Series = self.factors[self.factor_name]
+ preprocessing_factor = al.utils.get_clean_factor_and_forward_returns(factor_ser,
+ pricing,
+ periods=periods,
+ quantiles=quantiles,
+ max_loss=self.max_loss)
+
+ # 预处理好的因子
+ self.factors_frame = preprocessing_factor
+
+ # 分组收益
+ self.group_returns = pd.pivot_table(preprocessing_factor.reset_index(
+ ), index='date', columns='factor_quantile', values=1)
+
+ # 分组累计收益
+ self.group_cum_returns = ep.cum_returns(
+ self.group_returns)
+
+ def long_short(self, lower: int = 1, upper: int = 5) -> pd.Series:
+ '''
+ 获取多空收益
+ 默认地分组为1,高分组为5
+ '''
+ try:
+ self.group_returns
+ except NameError:
+ raise ValueError('请先执行get_calc')
+
+ self.long_short_returns = self.group_returns[upper] - \
+ self.group_returns[lower]
+ self.long_short_returns.name = f'{self.name}_excess_ret'
+
+ self.long_short_cum = ep.cum_returns(self.long_short_returns)
+ self.long_short_cum.name = f'{self.name}_excess_cum'
+
# 因子获取
+periods = get_trade_period('2014-01-01', '2021-10-31', 'ME')
+factor_dfs = list(get_factor('A', periods))
+
+# 因子值
+factor_df = pd.concat({tradeDt: df for tradeDt, df in zip(
+ periods, factor_dfs)}, names=['date', 'asset'])
+
+# 数据储存
+factor_df.to_csv('cpv.csv')
+
# 读取
+factor_df = pd.read_csv('cpv.csv',index_col=[0,1],parse_dates=['date'])
+
+# 获取收盘价数据
+pricing = get_pricing(factor_df, '2021-11-30')
+
# 数据结构如下
+factor_df.tail()
+
# 查看因子分布
+fig, axes = plt.subplots(2, 3, figsize=(18, 9))
+
+axes = [i for x in axes for i in x]
+for ax, (name, ser) in zip(axes, factor_df.items()):
+
+ ax.set_title(name)
+ ser.hist(ax=ax)
+
# 获取因子回测数据
+factor_name = factor_df.columns.tolist()
+res_dic = {}
+
+for ax, name in zip(axes, factor_name):
+
+ res = get_factor_returns(factor_df, name, 0.4)
+ res.get_calc(pricing)
+ res.long_short(5, 1)
+ res_dic[name] = res
+
# 因子分组收益
+fig, axes = plt.subplots(2, 3, figsize=(19, 9))
+axes = [i for x in axes for i in x]
+for ax,(k,v) in zip(axes,res_dic.items()):
+ v.group_cum_returns.plot(
+ ax=ax,
+ title=k,
+ color=['Navy', 'LightGrey', 'DimGray', 'DarkKhaki', 'LightSteelBlue'])
+ res.long_short_cum.plot(ax=ax, ls='--', color='red')
+
+plt.subplots_adjust(hspace=0.5)
+
# 因子分组平均收益
+fig, axes = plt.subplots(2, 3, figsize=(19, 9))
+axes = [i for x in axes for i in x]
+for ax, (k, v) in zip(axes, res_dic.items()):
+
+ mono_score = calc_mono_score(v.group_returns)
+ ax.yaxis.set_major_formatter(
+ mpl.ticker.FuncFormatter(lambda x, pos: '%.2f%%' % (x * 100)))
+ ax.text(0.65,0.95,"单调性得分为:%.3f"%mono_score,
+ fontsize=10,
+ bbox={'facecolor': 'white', 'alpha': 1, 'pad': 5},
+ transform=ax.transAxes,
+ verticalalignment='top')
+ v.group_returns.mean().plot.bar(ax=ax, title=k, color='#1f77b4')
+
+plt.subplots_adjust(hspace=0.5)
+
由于 A股市场中做空手段相对有限,所以我们将重心放在多头组的分析中,从上图中可以看到第一组的收益相对较好,分别查看第一组每年的收益情况:
+ +# report
+ret_by_y = pd.DataFrame(columns=list(res_dic.keys()))
+ic_df = pd.DataFrame(columns=list(res_dic.keys()))
+risk_df = pd.DataFrame(columns=list(res_dic.keys()))
+for k, v in res_dic.items():
+
+ ret_by_y[k] = v.group_returns[1].groupby(pd.Grouper(
+ level=0, freq='Y')).apply(lambda x: ep.cum_returns(x)[-1])
+
+ ic_df[k] = get_information_table(
+ perf.factor_information_coefficient(v.factors_frame)).iloc[:, 0]
+ risk_df[k] = get_performance_table(v.group_returns[[1]],
+ periods='monthly').iloc[:, 0]
+
+ret_by_y.index = ret_by_y.index.strftime('%Y')
+ret_by_y.index.names = ['年度']
+
+ret_by_y.style.format('{:.2%}').bar(align='mid', color=['#5fba7d', '#d65f5f'])
+
risk_df.style.format('{:.2%}').highlight_max(axis=1, color='#d65f5f')
+
# IC统计
+ic_df
+
本篇报告从技术分析中最简单的价量关系入手,在计算股票每日高频价量相关系数 的基础上,逐步挖掘了其中蕴藏的选股因子。
+首先,我们定义了价量相关性的平均数因子和波动性因子。研究发现,两者其实都可以看做是对传统反转因子的修正。平均数因子认为,股票的月度行情是否反转需要看成交量的信息,若有量的确认,则月度行情的反转效应更强;而若没有量的确认,则月度行情更容易呈现动量效应。波动性因子则不论股票过去价格的涨跌,只要每日价量关 系维持稳定形态,下个月就更有可能上涨;而价量关系在多种形态间频繁转换的股票,下个月更有可能下跌。
+其次,我们发现股票价量相关性的变化趋势也具有不错的选股能力,因此定义了趋势因子。回测结果显示,上个月的趋势因子值越小,即价量相关系数随时间推移逐渐变小的股票,下个月的收益倾向于越高,这与平均数因子的选股逻辑相呼应。
+最后,本篇报告综合上述3个维度的信息,构建了最终的价量相关性因子CPV,其各项回测指标均大幅优于传统反转因子。即使剔除市场常用风格和行业的干扰,纯净CPV 因子仍然具备良好的选股能力。
+ +\n", + " | \n", + " | pv_corr_avg | \n", + "pv_corr_std | \n", + "pv_corr | \n", + "pv_corr_trend | \n", + "pv_corr_deret20 | \n", + "CPV | \n", + "
---|---|---|---|---|---|---|---|
date | \n", + "asset | \n", + "\n", + " | \n", + " | \n", + " | \n", + " | \n", + " | \n", + " |
2021-10-29 | \n", + "688799.XSHG | \n", + "-0.040364 | \n", + "-0.059105 | \n", + "-2.062111 | \n", + "0.003719 | \n", + "-1.530446 | \n", + "-0.588403 | \n", + "
688800.XSHG | \n", + "-0.044931 | \n", + "0.062279 | \n", + "0.593672 | \n", + "0.004973 | \n", + "-1.193098 | \n", + "-0.217781 | \n", + "|
688819.XSHG | \n", + "0.022162 | \n", + "0.007419 | \n", + "0.567500 | \n", + "0.006496 | \n", + "0.947565 | \n", + "1.386607 | \n", + "|
688981.XSHG | \n", + "-0.035503 | \n", + "-0.117590 | \n", + "-3.293693 | \n", + "-0.002650 | \n", + "-2.185112 | \n", + "-1.765053 | \n", + "|
689009.XSHG | \n", + "-0.017115 | \n", + "0.057875 | \n", + "0.996562 | \n", + "-0.003718 | \n", + "0.413937 | \n", + "-0.155996 | \n", + "
\n", + " | pv_corr_avg | \n", + "pv_corr_std | \n", + "pv_corr | \n", + "pv_corr_trend | \n", + "pv_corr_deret20 | \n", + "CPV | \n", + "
---|---|---|---|---|---|---|
年度 | \n", + "\n", + " | \n", + " | \n", + " | \n", + " | \n", + " | \n", + " |
2014 | \n", + "53.73% | \n", + "68.62% | \n", + "65.39% | \n", + "55.99% | \n", + "57.22% | \n", + "59.41% | \n", + "
2015 | \n", + "19.73% | \n", + "37.41% | \n", + "34.34% | \n", + "19.30% | \n", + "41.24% | \n", + "37.34% | \n", + "
2016 | \n", + "30.70% | \n", + "35.17% | \n", + "34.28% | \n", + "28.65% | \n", + "32.54% | \n", + "33.58% | \n", + "
2017 | \n", + "-10.25% | \n", + "-9.13% | \n", + "-8.39% | \n", + "-14.62% | \n", + "-12.38% | \n", + "-11.76% | \n", + "
2018 | \n", + "-22.41% | \n", + "-22.85% | \n", + "-21.41% | \n", + "-29.72% | \n", + "-24.58% | \n", + "-25.94% | \n", + "
2019 | \n", + "30.71% | \n", + "28.41% | \n", + "31.11% | \n", + "38.70% | \n", + "30.40% | \n", + "32.82% | \n", + "
2020 | \n", + "8.34% | \n", + "12.23% | \n", + "12.18% | \n", + "5.41% | \n", + "10.65% | \n", + "8.14% | \n", + "
2021 | \n", + "24.46% | \n", + "35.39% | \n", + "32.15% | \n", + "24.95% | \n", + "29.57% | \n", + "25.27% | \n", + "
\n", + " | pv_corr_avg | \n", + "pv_corr_std | \n", + "pv_corr | \n", + "pv_corr_trend | \n", + "pv_corr_deret20 | \n", + "CPV | \n", + "
---|---|---|---|---|---|---|
年化收益率 | \n", + "14.82% | \n", + "20.41% | \n", + "20.01% | \n", + "13.11% | \n", + "17.91% | \n", + "17.08% | \n", + "
累计收益 | \n", + "195.24% | \n", + "328.40% | \n", + "317.44% | \n", + "162.43% | \n", + "263.53% | \n", + "243.90% | \n", + "
波动率 | \n", + "29.51% | \n", + "28.45% | \n", + "29.27% | \n", + "28.22% | \n", + "30.25% | \n", + "29.53% | \n", + "
夏普 | \n", + "61.59% | \n", + "79.62% | \n", + "77.07% | \n", + "57.74% | \n", + "69.47% | \n", + "68.08% | \n", + "
最大回撤 | \n", + "-44.81% | \n", + "-36.93% | \n", + "-38.73% | \n", + "-50.95% | \n", + "-41.85% | \n", + "-42.17% | \n", + "
\n", + " | pv_corr_avg | \n", + "pv_corr_std | \n", + "pv_corr | \n", + "pv_corr_trend | \n", + "pv_corr_deret20 | \n", + "CPV | \n", + "
---|---|---|---|---|---|---|
IC Mean | \n", + "-0.046 | \n", + "-0.069 | \n", + "-0.076 | \n", + "-0.018 | \n", + "-0.050 | \n", + "-0.048 | \n", + "
IC Std. | \n", + "0.068 | \n", + "0.076 | \n", + "0.068 | \n", + "0.042 | \n", + "0.054 | \n", + "0.050 | \n", + "
Risk-Adjusted IC | \n", + "-0.680 | \n", + "-0.908 | \n", + "-1.113 | \n", + "-0.431 | \n", + "-0.918 | \n", + "-0.957 | \n", + "
t-stat(IC) | \n", + "-6.589 | \n", + "-8.800 | \n", + "-10.794 | \n", + "-4.180 | \n", + "-8.905 | \n", + "-9.274 | \n", + "
p-value(IC) | \n", + "0.000 | \n", + "0.000 | \n", + "0.000 | \n", + "0.000 | \n", + "0.000 | \n", + "0.000 | \n", + "
IC Skew | \n", + "-0.491 | \n", + "0.437 | \n", + "0.267 | \n", + "0.114 | \n", + "-0.169 | \n", + "0.405 | \n", + "
IC Kurtosis | \n", + "1.192 | \n", + "0.667 | \n", + "0.008 | \n", + "0.216 | \n", + "0.782 | \n", + "0.333 | \n", + "