我们在前几篇指南里面分别讲解了几个常用的量化选股模型,主要内容是数据处理和回归方法。其实,对于构建一个模型而言,最困难的不是检验和回归本身,而是因子的构建。
更进一步说,因子降维的处理甚至也不过是另一种层次上的线性回归(只有通过有效性检验的因子才能参与到降维中去,而且降维的原理正是最小二乘方法。)
这一篇指南是本系列的收官之作吧。我们想要从综合步骤的角度再一次审视如何搭建多因子线性模型这个问题。因为我们想要更加强调python实操里面的细节技术,所以我们不会提及特定的模型,代码附在每个步骤的超链接里,我们把它们拆成了多个文章。如果想了解,可以从这一篇推送的对应链接进入。
请一定要看到最后!!!
在Anaconda3 Jupyter Notebook中进行操作:
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
#data.xlsx文件是假定的股票数据统称,没有特定意义,在各个模块里面可能假设特定意义,里面有8个工作表
#df是一般操作的dataframe数据
数据读取和整合
基本步骤
股票数据的获取一般来自金融终端,是excel文件或是csv文件,这两种文件在Python中的处理方法是一致的。对于excel文件而言,一般获取后会储存为一个文件簿中的多个工作表,比如2017年数据,2018年数据···等等。所以,一般在读取数据之后还要进行整合。
我们一般需要把多个格式一致的工作表统一到一个DataFrame里面,才能进行时间维度或是行业维度上的分析。这里的拼接一般是纵向拼接。
方式一:工作表不多并且sheetname没有统一格式,使用循环读取所有工作表。
#读取数据temp=pd.read_excel('stock.xlsx',sheet_name=i,header=1)
for i in range(8)]
#拼接数据
df=pd.concat([temp[0],temp[1],temp[2],temp[3],temp[4],
temp[5],temp[6],temp[7]],
axis=0,ignore_index = True)
#axis=0纵向拼接,多个表拼接成为一个表
方式二:工作表名字有统一格式,为了方便,生成工作表的名称列表,使用列表生成式整合。这里假设工作表储存格式为:Y12Q1···Y20Q1,Y20Q2,Y20Q3,Y20Q4。(12年到20年分季度财报披露数据)
sname = ['Y'+str(i)+'Q'+str(j) for i in list(range(12,20))
for j in list(range(1,5))]
sname = sname[:-3]
#没有20年的后三个数据所以取sname[:-3]
temp = [pd.read_excel('data.xlsx',
sheet_name = '财报披露'+ i,
header = 1 ,skipfooter = 2 ) for i in sname]
report = pd.concat(temp,axis=0,igonre_index = True)
分组处理利器——groupby
基本步骤
实际操作中常常需要对数据进行筛选和创造新的指标。
比如,对于一些股票数据,你想要给先给每个行业分组,在各个行业里面对各个板块(风险程度不同)再次分组,以此来查看你这种分组安排下的收益率差别。那么我们推荐一个很好的分组方式 groupby() ,它对 DataFrame 数据进行操作。
常规做法是 groupby() 分组之后再对你分好的组进行各种想要的操作,可以计算指标或是添加tag···
公式如下:
df[选出的列].groupby(用来分组的列名称)(分组后用于计算的列).函数或操作
注意:
计算时,如果使用 apply() ,就要点明使用的函数来源,不使用便不用说明。
推荐 apply()+lambda 搭配食用,numpy的函数不推荐使用apply(因为比较麻烦,需要说明来源是np)。
即如下案例:
df.groupby(['A','B','C'])['M','N'].sum()
按照 A B C 一次分组,对 M N 列指标分别进行加和操作。
函数来源是 numpy,可以对 DataFrame 数据直接操作,不使用apply()。
df.groupby(['A','B','C'])['M','N'].apply(lambda x:(x['M']-x['N']).mean())
按照 A B C一次分组,对 M N 列指标施加匿名函数 lambda 构建新的指标 M-N,并按照分组取平均值。
函数来源自定义,需要使用 apply(),并点明是 lambda 函数。
看不懂没关系,直接进入实操。
分组后选择列进行运算
单类分组
df.groupby("block")
#按照一个属性"block"分组
df.groupby("block")["close"].describe().unstack()
#按照一个属性板块"block"分组后
#["close"].describe() 提取各个分组一个特定指标收盘价"close"的基本统计量
#unstack() 索引重排,易于对比
多类分组
只要在 groupby() 的步骤多写几个分组标准就可以(请用列表格式!),Python 会自动按照顺序分组,下面就是按照先考虑 "block" 分组,再再已经有的分组里面各自按照 "industry" 分组。
强烈推荐一下 agg() 方法,无论是多类分组还是单类分组,你都可以对不同指标选择不同的操作。注意,冒号之前是你想要操作的列,冒号之后是你想要进行的操作。
df.groupby(["block","industry"])
.agg({'close':'mean', volume':'sum'})
# agg()表示针对不同的列应用多种不同的统计方法
#对收盘价计算平均数,对交易量求和
按照 groupby() 的参数进行分组,按照第二个列表进行计算。
df = data.groupby(['code','date','price','ratio','buyside','sellside'])['volume','amount'].sum()
df.reset_index(inplace=True)
#进行groupby得到多层数据
#reset_index可以按照你操作好的数据重新排序,只有inplace = true才能保存在你的df中。
常用的时间提取和文本切片
这一块还是离不开 groupby() ,在了解基本分组之后我们会发现很多需要的指标是需要我们自己创造的。最常用的两个操作就是基于时间和文本构建指标。
比如你现在有了2020年疫情期间的所有股票交易数据(每一条交易是一条数据)。你想要分析本事件区间的股票收益率和去年有什么变化,那么就为所有交易数据添加月份标签,季度标签····等等。
这些标签都是从你的交易时间里面提取的,但是时间数据一般是如下格式:
2020-02-03 14:30
2020年2月3日
03/02/2020
...
从中提取我们想要的年,月,季度等就是操作的目标。
对于文本切片来说也是一样,比如想要从股票代码里面获得板块信息。(虽然一般没有人这么做,但我还是想附上提取股票代码的一个小函数···)
#板块识别函数
def bk(s):
if s[:3]=='300':
return '创业板'
if s[:3]=='002':
return '中小板'
if s[:3]=='000' or s[:3]=='001':
return '深圳主板'
if s[:2]=='60':
return '上海主板'
#操作
df['板块']=[bk(x) for x in df['code']]
下面进入时间的提取实操~
时间提取
时间序列可以直接作为 index ,一般金融终端给你提供的数据都是使用了标准时间格式,但有的时候也会拿到文本格式的时间序列,所以在提取之前需要用 .dtype 方法判断。
如果是标准时间格式,那么就可以用 .year 类方法直接提取你想要的信息。标准日期格式里面提取年月日可以用列表生成式或是 lambda 函数法。
如果是文本格式就要分两种情况。一种是格式化的文本,我们可以用 .to_date 变成时间格式,另一种纯文本只能切片。
df.date.dtype
#查看时间列是否为时间格式,如果不是时间格式就需要转换或者切片
# 格式化的非时间数据转化为标准时间数据
df["date"] = pd.to_datetime(df["date"],format ="%Y/%m/%d")
#标准时间格式的数据:
#方法一 列表生成式方法
df['year']=[i.year for i in df['date']]
#提取年份
df['month']=[i.month for i in df['date']]
#提取月份
df['quarter']=[i.quarter for i in df['date']]
#提取季度,对于季度报告而言很有用
#方法二 lambda函数法,一般结合groupby和后续操作
df.groupby(df['date'].apply(lambda x: x.year))
#提取年份
非格式化的文本时间数据比如:
2020年3月2日至2020年3月8日
想要提取开始时间和截止时间就必须用 “至” 字进行前后切片。
split() 函数是字符串切片函数,它把切出来的信息放在列表里,如果只有两块切片,那么索引 [0] 表示的就是前面的,索引 [1] 表示的就是后面的。
假如“累计区间”列储存的数据都是 “2020年3月2日至2020年3月8日 ” 这种类型的。我们用这个方法可以进行以下操作提取开始时间和结束时间,就有了格式化的文本时间。
#非格式化的非时间类型数据转化为字符串切片提取特定时间
data['开始时间'] = [ str(i).split('至')[0] for i in data['累计区间'] ]
data['截止时间'] =
[ str(i).split('至') [len(str(i).split('至'))-1 ] for i in data['累计区间'] ]
#提取‘至’字之前的切片作为开始时间
#提取‘至’字最后一个切片作为截止时间
按照已有时间数据操作
操作比较多的时候,你可以依据需要构建季度 or 月份列之类的时间tag,也可以不构建,每次使用 lambda 函数就可以分组。
下面是一些很好用的操作,直接上代码。
df.sort_values("date", inplace=True)
# 按时间排序并保存,如果你想要直接进行时序回归或者折线图
#计算各个年份的交易次数
df.groupby('year').count()
#如果已经通过date.year 创建了年份列,就可以按照年份分组 并计算各个年份的数据个数
df.groupby(df['date'].apply(lambda x: x.year)).count()
#如果没有year一列可以直接把date列转化为year列,应用在轻量计算中
注意,上个code block 里面出现了 count() 方法,这个方法在金融里面很常用。但 size() 方法也能计算数据的个数,它们之间的区别是: size计数时包含NaN值,而count不包含NaN值。
量化选股中一般使用 count(),而且不能把NaN替换成0。
#截取每组前后几个数据
#常用first(),tail(),filter()选取满足特定条件的分组
#本操作一般用来打tag,所以常常带有参数as_index = False,保持原来的数据索引结果不变
df.groupby(df["date"].apply(lambda x:x.year),as_index=False).first()
#按照年份分组,本年度内时间最早数据的提取出来.
#应用:提取本年度第一笔交易。
df.groupby(df["date"].apply(lambda x:x.year),as_index=False).Tail(n=1)
#按照年份分组,本年度内时间最后一次数据的提取出来.
A.groupby(A["date"].apply(lambda x:x.month),as_index=False).filter(lambda x: len(x)==1)
#filter()保留满足作为参数条件的分组
相 关 阅 读
指南 | 量化选股模型 with Python (1)
指南 | 量化选股模型 with Python (2)
指南 | 量化选股模型 with Python (3)
指南 | 量化选股模型 with Python (4)
Python 量化选股系列到这里就结束啦,断断续续更新了将近一个月,也存在一些经验不足之处,实在不好意思!(手动抱歉)
其实大家都知道,对于建模和回归来说,Python并不擅长,可能Stata或是Spss几个指令就出来的东西,Python却要操作好久,而且人家能自动识别个各种index和columns···
那我们为什么要使用Python呢?因为那种可以把DataFrame当做橡皮泥一样的感觉吗? :)
在我心里,Python依然是最自由的编程方式。
用它量化选股最重要的不是为了最后的回归,而是能够按照自己的思维构建指标,按照自己的思维进行操作。现在我们看到的指标构建不过是基于数据的。但是设想一下,如果我们导入了人的情感,如纯文本评论或是人的表情,我们是否有机会从中获取量化信息?继续进行我们的选股?
我可以使用机器学习的 TextBlob 情绪分析对新浪财经某个板块的当日网友评论进行情绪打分,对
“半导体诚不坑我!!!”
打 -0.45 分,那么我们就可以拥有新的所谓市场情绪指标,而这些在Stata等软件里面都是不可实现的!
(ps 仅仅是举栗)
疑似下一篇:
爬虫可以用来抢课???
—责编:衣诺—
—推送:衣诺—