*代码中的数据和文件可点击链接 股票多因子模型实战:Python核心代码解析-图书 - 博文视点 到网站的“下载资源”处自行下载。
单因子的计算
#好的因子不仅在历史数据上与股票收益率之间体现出稳定且持续的相关关系,而且因子的逻辑与因子与股票收益率的关系都可以被经济逻辑解释
#1. 因子的来源
#1.1 财务因子: 估值因子、盈利因子、营运因子、成长因子、现金流因子
#1.2 分析师一致预期因子: EPS一致预期变动、评级变化率、分析师热度变化率
#1.3 技术因子: 量价数据、动量因子、换手率因子
#1.4 其他因子:另类数据因子(投资者情绪),ESG因子
#2. 大小盘因子: 小市值策略
import pandas as pd
import numpy as np
import statsmodels.api as sm
trading_data_2019 = pd.read_csv('2019_trading_data.csv', dtype={'ind_code':str})
trading_data_2019[trading_data_2019['data_date'] == '2019-05-27']['mv'].hist(bins=100)
#对市值因子取对数可以使市值分布更加合理均匀
np.log(trading_data_2019[trading_data_2019['data_date'] == '2019-05-27']['mv']).hist(bins=100, figsize=(18,9))
#因子的处理流程
#2.1 去极值与异常值
#计算因子的上下限
sub_trading_data = trading_data_2019[trading_data_2019['data_date'] == '2019-05-27']
sub_trading_data['size'] = np.log(sub_trading_data['mv'])
size_upper = sub_trading_data['size'].mean() + 3 * sub_trading_data['size'].std()
size_lower = sub_trading_data['size'].mean() - 3 * sub_trading_data['size'].std()
sub_trading_data['size'] = sub_trading_data['size'].where(
sub_trading_data['size'] < size_upper, size_upper).where(
sub_trading_data['size'] > size_lower, size_lower)
sub_trading_data['size'].hist(bins=100, figsize=(18, 9))
#2.2 因子的标准化:
#公式: 标准化后的因子值(因子打分值) = (因子原始值 - 因子均值)/ 因子的标准差
sub_trading_data['size'] = (sub_trading_data['size'] -
sub_trading_data['size'].mean()) / sub_trading_data['size'].std()
sub_trading_data['size'].hist(bins=100, figsize=(18, 9))
#2.3 因子中性化
#所属行业会对股票的收益率产生影响,在将所有行业放在一起比较时,应进行行业因子中性化
#行业中性化:将行业因子变成哑变量再作为解释变量,处理后的市值因子作为被解释变量后进行回归
#目的是为了获得回归的残差值,即剔除了行业因素后的因子值(无法被行业因子解释的部分)
def industry_neu(factor_df, factor_name):
result = sm.OLS(factor_df[factor_name],
factor_df[list(factor_df.ind_code.unique())],
hasconst=True).fit()
return result.resid
sub_trading_data = pd.concat([sub_trading_data, pd.get_dummies(sub_trading_data['ind_code'])], axis=1)
sub_trading_data['size_factor_neu'] = industry_neu(sub_trading_data, 'size')
sub_trading_data[['data_date', 'secucode', 'ind_code', 'size', 'size_factor_neu']]
sub_trading_data[['size', 'size_factor_neu']].hist(bins=100, figsize=(18, 9))
#中性化后的银行业市值因子均值几乎为0(银行业市值普遍较大)
sub_trading_data[sub_trading_data.ind_code == 480000][['size', 'size_factor_neu']].hist(bins=100, figsize=(18, 9))
sub_trading_data[sub_trading_data.ind_code == 480000][['size', 'size_factor_neu']].mean()
#t日期的市值因子需要在收盘后才能获得,所以实际的市值使用日期为t+1
size_factor_df = sub_trading_data[['data_date', 'secucode', 'size', 'size_factor_neu']]
size_factor_df['data_date'] = pd.to_datetime('2019-05-28')
size_factor_df.rename(columns={'data_date': 'factor_date'}, inplace=True)
size_factor_df
#3. ROE因子
#公式:ROE = 净利润(TTM)/(0.5 * 最新一期财报股东权益 + 0.5 * 上年同期股东权益)
roe_factor = pd.read_hdf('./factor/roe.h5')
roe_factor
#ROE原始数据存在极大值和负值
roe_factor[roe_factor['data_date'] == '2019-05-27']['roe'].hist(bins=100, figsize=(18, 9))
#极值压缩
#实际的ROE极值处理情况需要通过主观判断进行,这里只是一个简单处理的例子
roe_factor = roe_factor[roe_factor['data_date'] == '2019-05-27']
roe_upper = roe_factor['roe'].mean() + 3 * roe_factor['roe'].std()
roe_lower = roe_factor['roe'].mean() - 3 * roe_factor['roe'].std()
roe_factor['roe'] = roe_factor['roe'].where(roe_factor['roe'] <
roe_upper, roe_upper).where(roe_factor['roe'] > roe_lower, roe_lower)
roe_factor['roe'].hist(bins=100, figsize=(18, 9))
#人工设定上下边界0-30%
roe_factor['roe'] = roe_factor['roe'].where(roe_factor['roe'] < 30).where(roe_factor['roe'] > 0,0)
roe_factor['roe'].hist(bins=100, figsize=(18,9))
#标准化
roe_factor['roe'] = (roe_factor['roe'] - roe_factor['roe'].mean()) / roe_factor['roe'].std()
roe_factor['roe'].hist(bins=100, figsize=(18,9))
#市值和行业中性化
def ind_size_neu(factor_df, factor_name):
result = sm.OLS(factor_df[factor_name], factor_df[list(factor_df.ind_code.unique()) + ['size']],
hasconst=False).fit()
return result.resid
roe_neu_df = pd.merge(sub_trading_data, roe_factor[['secucode','roe']])
roe_neu_df['roe_neu'] = ind_size_neu(roe_neu_df, 'roe')
roe_neu_df[['data_date', 'secucode','roe_neu']]
#4. RSI因子
#公式: RSI = 100 * RS/(1+RS); RS=N天内股票收盘价上涨数之和的平均值/N天内股票下跌数之和的平均值
#因子解析:RSI因子的预期是均值回归; 一般设定上下阈值,超过上限时认为市场超买,低于下限时认为市场超卖
#4.1 处理股票价格的方式:价格指数化(通过股票的收益率计算)
def RSI_cal(rolling_ser):
price_ser = 100 * pd.Series(rolling_ser).add(1).cumprod()
price_ser_diff = price_ser.diff(1).dropna()
RS = sum([item for item in price_ser_diff if item > 0]) / sum([-item for item in price_ser_diff if item < 0])
return 100 *(RS / (1 + RS))
#股票累计收益率曲线
trading_data_2019[trading_data_2019['secucode'] ==
'601318.SH'].set_index('data_date')['daily_return'].add(1).cumprod().plot(figsize=(18,9))
#股票RSI指标
trading_data_2019[trading_data_2019['secucode'] ==
'601318.SH'].set_index('data_date')['daily_return'].rolling(15).apply(RSI_cal).plot(figsize=(19,9))