股票多因子模型实战:Python核心代码解析.笔记03(单因子的计算)

本文介绍了如何通过财务、分析师一致预期和技术因子来构建股票投资组合的因子,包括估值、盈利等财务因子,分析师预期变动等,以及市值和大小盘效应。通过数据处理如去极值、标准化和行业中性化,确保因子的合理性和有效性。
摘要由CSDN通过智能技术生成

*代码中的数据和文件可点击链接 股票多因子模型实战: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))

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值