导语:本文给出以BP(账面市值比)为例的因子缩尾处理和对市值与行业中性化的处理实现代码,展现单因子不同分位处在不同处理方法下的表现。
本文是一系列因子研究中的第四篇文章。本系列的文章有:
【量化课堂】因子研究系列之一 估值和资本结构因子
【量化课堂】因子研究系列之二 成长因子
【量化课堂】因子研究系列之三 技术因子
【量化课堂】因子研究系列之四 市值与行业的中性化
因子系列文章首先展示了不同因子的构建与端点处的股票策略回测结果,本文借助【量化课题】多回测运行和参数分析框架中给出的分析方法和框架,展示对因子按照市值和行业进行标准化和去极值化的方法和回测结果。
如因子研究系列一~三篇中所示,在单因子策略中我们把股票池中股票按照某个因子从小到大排序,因为我们认为因子指标跟高或更低的股票会取得超额的收益。但很多时候用两支股票的同一因子指标在未经处理的情况下缺乏可比性。一个非常明显的例子就是 BP 值(账面市值比),银行股的 BP 一般平均大于 1 ,而互联网相关服务行业近一年平均 0.1。所以如果只是认为高 BP 就是估值过低,那么就只会买进银行股而不买科技股,但这很明显不是合理的投资思路。同理的,一般大盘股 BP 值都比小盘股要高,但我们并不能根据 BP 的估值方法来断论小市值股票的收益率低于大市值股票,毕竟事实是恰好相反的。
对单个因子进行分析,我们会发现有一些额外的因素影响了该因子的取值,比如在 BP 值的例子中,投资者对不同行业和规模的上市公司的成长预期以及市场的热点影响了 BP 的取值。我们要做的是把受到影响因素相同的股票归为一堆,然后在每一堆上进行中性化和去极值的处理,这样受到影响不同的股票之间就可以进行相互比较。接下来我们就以 BP 值为例介绍具体的处理方法,并可通过回测看出处理后的因子策略更为有效。
首先,我们注意到【量化课题】多回测运行和参数分析框架中给出的 BP 因子分位回测结果中,虽然从十年回测结果上看 BP 因子不同分位回报率还是比较有区分度,但是查看具体的历史走势显得凌乱混杂。下图是对比等权全指为基准的 BP 分位回报率超额收益(2006 年 2 月 1 日到 2016 年 11 月 1 日,两百万,持仓一个月每月月初调整,具体见链接)。
正如上面提到的,对于 BP 因子来说,常见的现象就是其分布在不同行业和不同市值的公司存在差异。下图展现的是从2005年到2016年每年年底按照总市值大小分成三种后每组的 BP 均值,其中 group1 是小市值股票,group2 是中市值,group3 是大市值股票。跨年比较的数据表明 BP 指标跟大盘的变动也挺相似,在牛市和熊市变动间不断转换,牛市平均 BP 低于熊市平均 BP。 同时我们可以看出,三个市值组合的平均 BP 相对排名在不同年份中是有变动的,在早年市场对于大盘股较为追捧,估值高 BP 相对小市值较低。从 2008 年之后,趋势发生了变动,小盘股更被关注,估值相对较高 BP 相对较低。感兴趣的朋友可以从相对变动的排名和 BP 值中挖掘处更多的信息。
以 2016 年为例,2016 年年底的 BP 按照不同的市值分组的分布情况,根据分布情况我们不止能得到均值的信息,而且能够分析出更多内容。根据分布情况我们可以直观的了解到不同市值分组的 BP 一、二、三、四阶矩都不同,反应为均值、方差、峰度和偏度都存在差异。看起来像是对数正态分布,但实际上我们试过对数转换后发现起始看起来离正态分布也差很远,因此没有使用对数值为基础进行随后的处理,而是使用 BP 的原始值。下图是使用默认设置的核函数绘制的不同市值水平 BP 的分布图。
分布的对数处理后效果
我们接下来要在大、中、小市值股票上分别对 BP 因子进行检测,为此应该分别对大、中、小市值的走势有一个评价标准,所以要构建三个分市值上证全股的回测作为基准。具体回测策略如下:按照当天的市值进行排序分三组,每月初进行调仓持有相同金额一个月上月没有停牌的该组内所有股票,从 2006 年 2 月 1 日开始到 2016 年 11 月 1 日,初始资金 2000000。下图我们可以看出三种不同市值股票的变动情况。从图形中三种策略开始区分我们对应到上图中几个组间 BP 相对排序变化的时点,在 2008 年之后开始,与普遍观测较为一致,小市值带来了很高的收益,尤其是相对于中等市值和大市值股票而言。
根据上图的三个回测结果为新基准,我们引入【量化课题】多回测运行和参数分析框架进一步分析。首先给出一个总体的表现汇总,每个市值的不同分位回报率和最大回撤的结果。策略为:按照当天的市值进行排序分三组,在每个总市值分组中再对 BP 按大小分10组,每月初进行调仓持有相同金额一个月上月没有停牌的该组内所有股票,从 2006 年 2 月 1 日开始到 2016 年 11 月 1 日,初始资金 2000000,使用的是上证的股票。下图中 Max_drawdown 和 Annual_return 分别表示最大回撤和年化收益率,group1、group2 和 group3 分别表示小、中和大市值回测结果。我们可以看出各种表现里小市值具有明显高于另外两组的收益,无论从哪一个 BP 分位而言,同时其也具有相对于其他市值分组更小的最大回撤。
结合之前等权的分市值回测结果新基准,超额收益和超额收益的最大回撤率分别如下图所示。其中 Excess_max_drawdown 和 Excess_annual_return 分别表示超额收益率的最大回撤和年化超额收益率,group1、group2 和 group3 分别表示小、中和大市值回测结果。首先,可以看出不同市值水平下,BP 因子还是具有相似的逻辑,BP 因子越大回报率相对较高。其次,高 BP 股票结合新基准对冲之后的超额收益率具有较低的最大回撤,这是我们很高兴看到的。第三,结合不同市值分组的高 BP 因子分位表现,我们可以发现小市值结合 BP 后并没有特别出色的超额收益表现。这说明小市值股票整体具有较好的走势,而小市值中的 BP 因子却不能带来更好的超额收益。相反,在大市值股票中,BP 具有很好的超额收益。
下图的回报率曲线仅展示针对自定义基准超额收益对数轴的图例(超额收益和对数轴的细节请查看【更新说明】新超额收益 和 对数轴)。从上到下分别是小市值、中市值和大市值。
小市值
中市值
大市值
通过三个市值对数轴的超额收益我们可以看出,虽然不同市值最后的年化收益因子分位区分度较为明显趋同,但是具体的时序表现而言,不同市值因子表现差异巨大。BP 本质是估值因子,高 BP 分位策略就是持有 BP 因子较高股票,剔除持仓中 BP 因子较低股票,整体反映了价值投资的逻辑。对数轴超额收益最直观的解读是其数值大于零表示超额收益为正,超额收益对数值变大(变小)表示相对于基准收益差异变大(变小)。那么三个市值的超额对数收益率的表现如下:在大市值中高 BP 策略收益相对于大市值等权收益持续扩大;小市值高 BP 策略对于小市值等权收益的超额收益主要产生于 2011 年之前,后来没有扩大的趋势反倒发生了回撤,并且 BP 最大的小市值股票完全没有产生任何的超额收益;中等市值中大小市值中存在的现象并存。结合时间趋势可以看出,市场早期(2011 年以及之前)对于高 BP 的高额回报存在与大中小市值中,但中小市值的回报更多。这体现了早期市场中对于 BP 估值因子体现的公司价值投资的认可;在 2011 年之后的市场对于 BP 的高额回报更多体现在大市值中,小市值股票体现出了炒作影响,高 BP 策略已经难以带来持续的超过小市值等权带来的平均水平收益了。
单因子结合行业的分析是另一种常见的分析框架。在聚宽平台中,我们分别有证监会行业分类和聚宽的行业分类。我们提取了 2014 年到 2016 年年底各行业的 BP 数据生成了下图,其中每个字母表示我们根据证监会行业分位的首字母把细分行业合并成上级行业后的行业分类(例如 A01 到 A05 五个次级行业归为一个上级行业 A)。这样处理后,我们给出了 14 个一级行业,分别为 A 农业,B 开采,C 制造业,D 能源,E 建筑,F 零售,G 交通运输,H 餐饮住宿,I IT相关,J 金融,K 房产,L 商务租赁,M 研发,N 公共产品,P 教育,R 文化,S 综合。删除没有行业给定的股票(以 2016 年年底为例,删除约占总股票数目的 9%),删除只有一只股票的行业(因为限制在上证上市股票可能最后出现这样的情况),同时删除同时具有两个行业标签的股票。
通过上图可以看出,在对行业区别对待 BP 值这种方法上,确实不同行业的 BP 体现了行业估值逻辑的差异性。简单总结图中的几点:第一,行业的估值相对水平基本上比较稳定,例如无论哪年金融都比别的行业 BP 高,而 IT 相关的行业相对 BP 较低;第二 BP 在不同行业都体现了大盘的波动,普遍大部分行业的 BP 水平在 2015 年都低于该行业 2014 年和 2016 年的行业平均水平;第三点是 BP 不遵守上述两点规律的表现体现了市场的投资风向变动,这同时源于不同行业在当时经济发展和制度政策的条件。
因为行业数量比较多,这里就不分别为每个行业进行分 BP 回测。在后面我们会展示按行业处理后的整体 BP 回测结果。接下来将介绍缩尾和中性化两个处理方法。
缩尾和截尾是两种处理极值处指标的方法。所谓的 尾 是指上边分布图中拖拉很长但是每个横轴对应的分布概率又很小的部分。缩尾就是把超过阈值的指标值用阈值替换,比如大于 1.5 的 BP 值在后续处理中转化成 1.5 处理。截尾处理一般是把超过阈值的样本删除,然后在保留的样本中进行处理。
缩尾和截尾一般对应两种阈值生成方法,一种是指定分位,一种是利用概率分布的均值方差处理。分位的方式很直观,当一个指标最大的 1% 和最小的 1% (或其他百分位)被认为属于极值,那么直接设定该值为阈值就可以了。这样的一个阈值选取需要基于既有的经验或习惯。另一种可以称为数据驱动型,就是阈值是由于数据本身的属性生成的而非人为给定,例如均值标准差方法。结合概率论的表述也很好理解,我们使用均值和标准差构建了一个样本值范围,该范围为所有样本中样本在一选定样本的概率(例如本研究中使用均值上下两个标准差的概率选定为 95%)。
我们根据 2016 年底上证股票 BP 均值上下两个标准差进行缩尾处理,即把出现概率小于 5% 的极端值转化为阈值处的指标水平,然后按照市值分组画出处理前后的对比图。由于原始值的分布尾巴明显往右拖,因此缩尾处理后对于右部影响明显,明显把阈值外部分挤向中间。
这两者对于尾的处理除了主要关注的值本身,还有对于样本的影响。即使不结合对于样本分布的影响讨论,样本量本身的变动差异可以看出,断尾处理与缩尾处理相比,减少了备选的可行股票池,具体减少多少根据预设处理参数决定。在实际的单因子策略中缩尾处理如果指定的标准影响的原始值数量不多,处理与不处理得到的结果影响不大,因为按照排序选取,择股只与相对大小有关而与原始值没关系了。此处就是缩尾与截尾对于结果影响的简单例子,如果使用其他比较复杂方法或模型,两者的差别会更加复杂。
中性化一般的处理方法是把原始值减去均值除以标准差得到,转化后新的分布均值为 0,大于原始值一个标准差的值变为 1,表示与均值一个标准差距离。有的时候研究中给出了中性化、标准化或规范化三个定义,实际上差异主要存在于表述中。简单总结,中性化体现了针对某个标准(例如行业或者市值)规范化/标准化的结果。
转化的意义在于怎么把鸡和鸭一起比,比如说 3 苹果和 2 橘子哪个好?庸俗点转化成人民币标准就可以比了。不同行业(市值)的 BP 水平不一样,但我们假设每个行业最大 BP 的公司可能存在着相同的估值偏低水平,那么标准化就可以实现这样的效果了。上一幅图展现了数据在是否使用缩尾带来的差异,那么下图展现 2016 年年底 BP 数据在缩尾处理后是否对市值行业中心化的差别,使用默认核函数生成分布图。经过转化的图形看起来就比较正态相似了,那么可能每个不同市值组中提取相同 BP 值对应的股票,具有相似的可比性。
首先看一下市值中性化的结果,在此之前先回忆一下不处理市值中性化前 BP 分位回测结果。
对市值中性化处理后 BP 因子策略的分位回测结果显得具有明显区分度,并且在时序变化表现中具有较好的一致趋势。高 BP 策略针对于等权全指策略的超额收益率持续放大。超额收益率的最大回撤也在下面展示了,最小的最大回撤(好拗口,就是第二、三分位的最大回撤)都在 10% 左右,已经还不错了。
不同的行业中性化 BP 分位回测对比等权全指的对数超额回报率。相对于等权全指的,行业中性化的结果和市值中性化的结果都明显好于没有处理的结果,但是行业和市值中性化结果对比而言,不同 BP 回测结果的超额收益率对数表明其区分度更好。反而言之,对于 BP 不能在不处理时具有很好的区分度这一问题,同时包含了市值影响和行业影响:不同市值的 BP 在均值和分布上都有差异,不同行业的估值逻辑导致其 BP 水平具有较大差异。结合对比的回测结果可以看出,市值和行业的 BP 干扰中行业的问题更大。
行业中性处理和市值中性处理的回报率与回撤。
行业中性处理和市值中性处理的超额回报率与超额回报最大回撤。
本文是对于之前调参框架的实用案例,也是对于因子数据处理方法的一个介绍。具体的股票分类和中性标准寻找,需要结合研究的因子而变,因为每个因子受到的影响因素存在差异。本文给出的市值和行业是因子分析中常用的特征指标,但并不是所有因子都这样。当找到合适的标准后,根据上文给出的分析方法,可以进行分组研究并进行中性化处理已得到更加合理科学的结果。
本文由JoinQuant量化课堂推出,版权归JoinQuant所有,商业转载请联系我们获得授权,非商业转载请注明出处。v1.0,2017-01-19,文章上线
#1 先导入所需要的程序包import datetimeimport numpy as npimport pandas as pdimport timefrom jqdata import *from pandas import Series, DataFrameimport matplotlib.pyplot as pltimport seaborn as snsimport itertoolsimport copyimport pickle# 选择上证股票stocks_index = '000001.XSHG'
有个不影响结果的warning很难看,下面这个就把warning屏蔽
import warningswarnings.filterwarnings("ignore")
w11 的中性化的研究的代码有修改,源代码下线处理有问题,同时在该函数中加入是否包括负值的功能
def winsorize(df, factor, std, h*e_negative = True):''' df为DataFrame化的数据 factor为strings的因子 std为几倍的标准查 输出Series '''s = pd.Series(df[factor].values, index=df.index)r=s.dropna().copy()if h*e_negative == False:r = r[r>=0]else:pass#取极值edge_up = r.mean()+std*r.std()edge_low = r.mean()-std*r.std()# print edge_lowr[r>edge_up] = edge_upr[r<edge_low] = edge_lowreturn r
生成多年 BP 分组均值指标合并列表,需要输入一个包含 datetime.date 内容格式的 list
生成每年最后一天日期,输入其实和结束年份数字
def get_days_list(star, end):days_list = []for x in xrange(star, end + 1):str_tmp = str(x) + '-12-31'days_list.append(datetime.datetime.strptime(str_tmp, '%Y-%m-%d').date())return days_listdays_list = get_days_list(2005, 2016)print days_list
[datetime.date(2005, 12, 31), datetime.date(2006, 12, 31), datetime.date(2007, 12, 31), datetime.date(2008, 12, 31), datetime.date(2009, 12, 31), datetime.date(2010, 12, 31), datetime.date(2011, 12, 31), datetime.date(2012, 12, 31), datetime.date(2013, 12, 31), datetime.date(2014, 12, 31), datetime.date(2015, 12, 31), datetime.date(2016, 12, 31)]
BP 与 MC 指标获取,需要一个 get_fundamentals 认识的日期格式的日期
可选是否进行缩尾和是否包含负值
def get_df_BP(date, winsor = True, h*e_negative = True):df_BP = get_fundamentals(query(valuation.code, 1/valuation.pb_ratio, valuation.market_cap ).filter(valuation.code.in_(get_index_stocks(stocks_index))), date)# 设定 indexdf_BP.index = df_BP.code# 删除多余列del df_BP['code']# 重新命名df_BP.columns = ['BP', 'MC']# 市值越大排名越高df_BP['MC_sorted_rank'] = df_BP['MC'].rank(ascending = True, method = 'dense')# 生成 MC 的 log 值df_BP['MC_log'] = log(df_BP['MC'])# 如果不包括非负因子if h*e_negative == False:# 删除 BP 小于0的,删除极端值df_BP = df_BP[df_BP['BP'] >= 0]else:pass# 生成 BP 的 log 值df_BP['BP_log'] = log(df_BP['BP'])a,b = (int(len(df_BP['MC'])/3), int(len(df_BP['MC'])/3 *2))# print a,bdf_BP['MC_group'] = 'group2'df_BP['MC_group'][df_BP['MC_sorted_rank'] < a] = 'group1'df_BP['MC_group'][df_BP['MC_sorted_rank'] > b] = 'group3'if winsor == True:# 获得 winsor 数据df_BP['BP_win'] = pd.DataFrame(winsorize(df_BP, 'BP', 2, h*e_negative)) # 生成均值数据df_BP['BP_mean'] = df_BP['BP_win'][df_BP['MC_group'] == 'group1'].mean()df_BP['BP_mean'][df_BP['MC_group'] == 'group2'] = df_BP['BP_win'][df_BP['MC_group'] == 'group2'].mean()df_BP['BP_mean'][df_BP['MC_group'] == 'group3'] = df_BP['BP_win'][df_BP['MC_group'] == 'group3'].mean()# 生成方差数据df_BP['BP_std'] = df_BP['BP_win'][df_BP['MC_group'] == 'group1'].std()df_BP['BP_std'][df_BP['MC_group'] == 'group2'] = df_BP['BP_win'][df_BP['MC_group'] == 'group2'].std()df_BP['BP_std'][df_BP['MC_group'] == 'group3'] = df_BP['BP_win'][df_BP['MC_group'] == 'group3'].std()# neturalizedf_BP['BP_ne'] = (df_BP['BP_win'] - df_BP['BP_mean'])/df_BP['BP_std'] else:# 生成均值数据df_BP['BP_mean'] = df_BP['BP'][df_BP['MC_group'] == 'group1'].mean()df_BP['BP_mean'][df_BP['MC_group'] == 'group2'] = df_BP['BP'][df_BP['MC_group'] == 'group2'].mean()df_BP['BP_mean'][df_BP['MC_group'] == 'group3'] = df_BP['BP'][df_BP['MC_group'] == 'group3'].mean()# 生成方差数据df_BP['BP_std'] = df_BP['BP'][df_BP['MC_group'] == 'group1'].std()df_BP['BP_std'][df_BP['MC_group'] == 'group2'] = df_BP['BP'][df_BP['MC_group'] == 'group2'].std()df_BP['BP_std'][df_BP['MC_group'] == 'group3'] = df_BP['BP'][df_BP['MC_group'] == 'group3'].std()# neturalizedf_BP['BP_ne'] = (df_BP['BP'] - df_BP['BP_mean'])/df_BP['BP_std']return df_BP# 先不用,后边优化使用# gourp_list = ['group1', 'group2', 'group3']df_BP = get_df_BP('2016-12-31', winsor = False)df_BP = df_BP.sort('BP', ascending = True)df_BP.tail(10)# df_BP
BP | MC | MC_sorted_rank | MC_log | BP_log | MC_group | BP_mean | BP_std | BP_ne | |
---|---|---|---|---|---|---|---|---|---|
code | |||||||||
600019.XSHG | 1.136364 | 1034.7300 | 1089 | 6.941896 | 0.127833 | group3 | 0.448274 | 0.276692 | 2.486840 |
601939.XSHG | 1.149425 | 13575.5996 | 1135 | 9.516029 | 0.139262 | group3 | 0.448274 | 0.276692 | 2.534046 |
600269.XSHG | 1.176471 | 117.4700 | 663 | 4.766183 | 0.162519 | group2 | 0.325658 | 0.201996 | 4.212016 |
600020.XSHG | 1.204819 | 101.1300 | 570 | 4.616407 | 0.186330 | group2 | 0.325658 | 0.201996 | 4.352359 |
601398.XSHG | 1.250000 | 15610.5898 | 1137 | 9.655705 | 0.223144 | group3 | 0.448274 | 0.276692 | 2.897536 |
600015.XSHG | 1.282051 | 1159.3800 | 1101 | 7.055641 | 0.248461 | group3 | 0.448274 | 0.276692 | 3.013373 |
601288.XSHG | 1.298701 | 10036.1396 | 1133 | 9.213948 | 0.261365 | group3 | 0.448274 | 0.276692 | 3.073548 |
601818.XSHG | 1.369863 | 1815.8199 | 1113 | 7.504292 | 0.314711 | group3 | 0.448274 | 0.276692 | 3.330735 |
601988.XSHG | 1.388889 | 10068.0596 | 1134 | 9.217123 | 0.328504 | group3 | 0.448274 | 0.276692 | 3.399497 |
601328.XSHG | 1.449275 | 4262.6802 | 1128 | 8.357653 | 0.371064 | group3 | 0.448274 | 0.276692 | 3.617742 |
生成多年 BP 分组均值指标合并列表,需要输入一个包含 datetime.date 内容格式的 list,这里选取2005年到2016年年底日期, win 为 False 对应没有缩尾的 BP 原始值
def get_df_BP_mean_by_MC(days_list):df_BP_mean_by_MC = pd.DataFrame()for day in days_list:df_BP = get_df_BP(day, winsor = False)df_BP_mean_by_MC[str(day.year)] = df_BP['BP'].groupby(df_BP['MC_group']).mean()df_BP_mean_by_MC = df_BP_mean_by_MC.T# CMC 分组1最小,分组3最大# df_BP_mean_by_MC.columns = ['分组1', '分组2', '分组3']return df_BP_mean_by_MCdf_BP_mean_by_MC = get_df_BP_mean_by_MC(days_list)df_BP_mean_by_MC
MC_group | group1 | group2 | group3 |
---|---|---|---|
2005 | 0.614339 | 0.685655 | 0.554900 |
2006 | 0.453592 | 0.461613 | 0.355264 |
2007 | 0.189421 | 0.176684 | 0.152372 |
2008 | 0.425661 | 0.517045 | 0.484497 |
2009 | 0.179158 | 0.244595 | 0.284146 |
2010 | 0.213431 | 0.297929 | 0.332776 |
2011 | 0.349186 | 0.460912 | 0.501538 |
2012 | 0.401752 | 0.490248 | 0.558868 |
2013 | 0.429592 | 0.505657 | 0.594667 |
2014 | 0.304982 | 0.379761 | 0.409139 |
2015 | 0.232777 | 0.261563 | 0.355914 |
2016 | 0.249160 | 0.325658 | 0.448274 |
生成分市值2005到2016年每年年底BP均值柱状图
def show_graph():fig = plt.figure(figsize = (20, 8))# 图1位置ax = fig.add_subplot(110)# 生成图超额收益率的最大回撤ax.bar([x-0.2 for x in range(2005,2017)], df_BP_mean_by_MC['group1'], color = '#880000', width = 0.2, label = 'group1')ax.bar([x for x in range(2005,2017)], df_BP_mean_by_MC['group2'], color = '#B22222', width = 0.2, label = 'group2')ax.bar([x+0.2 for x in range(2005,2017)], df_BP_mean_by_MC['group3'], color = '#F08080', width = 0.2, label = 'group3')# 设置图例样式ax.legend(loc='best',fontsize=15)# 设置y标签样式ax.set_ylabel('mean of BP', fontsize=15)# 设置x标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置图片标题样式ax.set_title("mean of BP for different MC level", fontsize=15)# 设定x轴长度plt.xlim(2004,2017) show_graph()
不同的 MC_group 的 BP 分布图
df_BP = get_df_BP('2016-12-31',winsor = False, h*e_negative = True)fig = plt.figure(figsize = (20, 8))plt.hist(df_BP['BP'][df_BP['MC_group'] == 'group1'], bins = 50, normed=0, alpha = 0.5, label = 'group1')plt.hist(df_BP['BP'][df_BP['MC_group'] == 'group2'], bins = 50, normed=0, alpha = 0.5, label = 'group2')plt.hist(df_BP['BP'][df_BP['MC_group'] == 'group3'], bins = 50, normed=0, alpha = 0.5, label = 'group3')plt.legend(loc='best',fontsize=15)
<matplotlib.legend.Legend at 0x7f6b695c5310>
市值分组的BP分布原始值,不winsor,包括负值
df_BP = get_df_BP('2016-12-31',winsor = False, h*e_negative = True)fig = plt.figure(figsize = (20, 8))sns.kdeplot(df_BP['BP'][df_BP['MC_group'] == 'group1'], label = 'group1', color="#FF0000", alpha=.6, shade=True)sns.kdeplot(df_BP['BP'][df_BP['MC_group'] == 'group2'], label = 'group2', color="#C1F320", alpha=.6, shade=True)sns.kdeplot(df_BP['BP'][df_BP['MC_group'] == 'group3'], label = 'group3', color="#888888", alpha=.6, shade=True)
<matplotlib.axes._subplots.AxesSubplot at 0x7f6b6aa47110>
是指分组的BP log 值分布,不winsor,不包括负值
df_BP = get_df_BP('2016-12-31',winsor = False, h*e_negative = False)fig = plt.figure(figsize = (20, 8))sns.kdeplot(df_BP['BP_log'][df_BP['MC_group'] == 'group1'], label = 'group1', color="#FF0000", alpha=.6, shade=True)sns.kdeplot(df_BP['BP_log'][df_BP['MC_group'] == 'group2'], label = 'group2', color="#C1F320", alpha=.6, shade=True)sns.kdeplot(df_BP['BP_log'][df_BP['MC_group'] == 'group3'], label = 'group3', color="#888888", alpha=.6, shade=True)
<matplotlib.axes._subplots.AxesSubplot at 0x7f6b68272810>
分市值,组内股票均权购买回测结果
def plot_three_returns():# 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(110)benchmark_id = ['d8fcbe2bea9e544734705aa6290e7d21', '592a279067ced02aa34fc2dffbc0d1dc', '211b17a00d619d069a2f1a8a35601a70']benchmark_returns1 = [x['returns'] for x in get_backtest(benchmark_id[0]).get_results()]benchmark_returns2 = [x['returns'] for x in get_backtest(benchmark_id[1]).get_results()]benchmark_returns3 = [x['returns'] for x in get_backtest(benchmark_id[2]).get_results()]dates = [x['time'] for x in get_backtest(benchmark_id[2]).get_results()]# 作图ax.plot(range(len(benchmark_returns1)), benchmark_returns1, color = 'b', label = 'group1')ax.plot(range(len(benchmark_returns2)), benchmark_returns2, color = 'g', label = 'group2')ax.plot(range(len(benchmark_returns3)), benchmark_returns3, color = 'r', label = 'group3')ticks = [int(x) for x in np.linspace(0, len(dates)-1, 11)]plt.xticks(ticks, [dates[i] for i in ticks])# 设置图例样式ax.legend(loc = 2, fontsize = 10)# 设置y标签样式ax.set_ylabel('returns',fontsize=20)# 设置x标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置图片标题样式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(benchmark_returns1)) plot_three_returns()
BP 与 MC 指标获取,需要一个 get_fundamentals 认识的日期格式的日期,被注释掉的内容包括识别重复index,补充市值分组信息
最后查看处为指定某行业进行查看
def get_df_BP_re(date, winsor = True, h*e_negative = True):industries=[u'A01', u'A02', u'A03', u'A04', u'A05', u'B06', u'B07', u'B08', u'B09', u'B11', u'C13', u'C14', u'C15', u'C17', u'C18', u'C19', u'C20', u'C21', u'C22', u'C23',u'C24', u'C25', u'C26', u'C27', u'C28', u'C29', u'C30', u'C31', u'C32', u'C33', u'C34', u'C35', u'C36', u'C37', u'C38', u'*1', u'*2',u'D44', u'D45', u'D46', u'E47', u'E48', u'E50',u'F51', u'F52', u'G53', u'G54', u'G55', u'G56', u'G58', u'G59',u'H61', u'H62', u'I63', u'I64', u'I65', u'J66', u'J67', u'J68', u'J69', u'K70', u'L71', u'L72', u'M73', u'M74', u'N77', u'Q83', u'R85', u'R86', u'R87', u'S90'] df_BP = get_fundamentals(query(valuation.code, 1/valuation.pb_ratio, valuation.market_cap).filter(valuation.code.in_(get_index_stocks('000001.XSHG'))), date)df_BP.index = df_BP.codedel df_BP['code']df_BP.columns = ['BP', 'MC']industries_series = pd.Series()for aa in industries:alist = []stock_list = list(get_industry_stocks(aa, date=date))for bb in stock_list:if bb in list(df_BP.index):alist.append(bb)aseries = pd.Series(aa, index = alist)# print aseriesindustries_series = pd.concat([industries_series, aseries])# if industries_series.index.is_unique: # 2010年这个有重复# print 111# if df_BP.index.is_unique:# print 222# 删除重复现象:一个股票有两个行业代码,第二次出现删除,duplicated = industries_series.index.duplicated()index_list = []for x in range(len(industries_series.index)):if duplicated[x] ==True:index_list.append(list(industries_series.index)[x])industries_series.drop(index_list, inplace = True)df_BP['industries_detail'] = industries_seriesdf_BP['industries_brief'] = NaNfor aa in industries:df_BP['industries_brief'][df_BP['industries_detail'] == aa] = aa[0]df_BP.dropna(how = 'any', inplace = True) # 1173/1018 2016/12/31df_BP['BP_mean'] = NaNdf_BP['BP_std'] = NaN# 是否使用 winsorif winsor == True:# 获得 winsor 数据df_BP['BP_win'] = pd.DataFrame(winsorize(df_BP, 'BP', 2, h*e_negative))for x in range(len(industries)):# 生成均值数据df_BP['BP_mean'][df_BP['industries_brief'] == industries[x][0]] \= df_BP['BP_win'][df_BP['industries_brief'] == industries[x][0]].mean()# 生成方差数据df_BP['BP_std'][df_BP['industries_brief'] == industries[x][0]] \= df_BP['BP_win'][df_BP['industries_brief'] == industries[x][0]].std()df_BP['BP_ne'] = (df_BP['BP_win'] - df_BP['BP_mean'])/df_BP['BP_std']else:for x in range(len(industries)):# 生成均值数据df_BP['BP_mean'][df_BP['industries_brief'] == industries[x][0]] \= df_BP['BP'][df_BP['industries_brief'] == industries[x][0]].mean()# 生成方差数据df_BP['BP_std'][df_BP['industries_brief'] == industries[x][0]] \= df_BP['BP'][df_BP['industries_brief'] == industries[x][0]].std()df_BP['BP_ne'] = (df_BP['BP'] - df_BP['BP_mean'])/df_BP['BP_std']df_BP.dropna(how = 'any', inplace = True)# # 市值越大排名越高# df_BP['MC_sorted_rank'] = df_BP['MC'].rank(ascending = True, method = 'dense')# a,b = (int(len(df_BP['MC'])/3), int(len(df_BP['MC'])/3 *2))# df_BP['MC_group'] = 'group2'# df_BP['MC_group'][df_BP['MC_sorted_rank'] < a] = 'group1'# df_BP['MC_group'][df_BP['MC_sorted_rank'] > b] = 'group3'return df_BP # 先不用,后边优化使用# gourp_list = ['group1', 'group2', 'group3']df_BP_re = get_df_BP_re('2016-12-31', winsor = False)df_BP_re = df_BP_re.sort('BP', ascending = False)[df_BP_re['industries_detail'] == 'I64']df_BP_re.tail(10)# df_BP_re
BP | MC | industries_detail | industries_brief | BP_mean | BP_std | BP_ne | |
---|---|---|---|---|---|---|---|
code | |||||||
600986.XSHG | 0.308642 | 132.94 | I64 | I | 0.222964 | 0.143633 | 0.596511 |
600804.XSHG | 0.197628 | 307.28 | I64 | I | 0.222964 | 0.143633 | -0.176388 |
600652.XSHG | 0.194553 | 106.84 | I64 | I | 0.222964 | 0.143633 | -0.197803 |
603000.XSHG | 0.138889 | 195.27 | I64 | I | 0.222964 | 0.143633 | -0.585345 |
2005年到2016年行业BP均值,对应时间可以通过前边的days_list设定进行调整
# 多年的 BP 行业均值def get_df_BP_mean_by_industries(days_list):df_BP_mean_by_industries = pd.DataFrame()for day in days_list:df_BP_re = get_df_BP_re(day, winsor = False)df_BP_mean_by_industries[str(day.year)] = df_BP_re['BP'].groupby(df_BP_re['industries_brief']).mean()df_BP_mean_by_industries = df_BP_mean_by_industries.Treturn df_BP_mean_by_industriesdf_BP_mean_by_industries = get_df_BP_mean_by_industries(days_list)df_BP_mean_by_industries
industries_brief | A | B | C | D | E | F | G | H | I | J | K | L | R | S |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2005 | 0.777105 | 0.477912 | 0.658491 | 0.688694 | 0.800769 | 0.543639 | 0.579454 | 0.460161 | 0.378845 | 0.437369 | 0.628730 | 0.531923 | 0.259458 | 0.657543 |
2006 | 0.503947 | 0.339725 | 0.455897 | 0.570976 | 0.579828 | 0.322961 | 0.403457 | 0.240246 | 0.234073 | 0.223032 | 0.364954 | 0.326429 | 0.149455 | 0.430091 |
2007 | 0.149589 | 0.114161 | 0.184538 | 0.233328 | 0.183751 | 0.135261 | 0.173024 | 0.166025 | 0.118270 | 0.150929 | 0.157492 | 0.162200 | 0.122187 | 0.168153 |
2008 | 0.374612 | 0.448264 | 0.498060 | 0.554575 | 0.488183 | 0.438322 | 0.541578 | 0.563492 | 0.311685 | 0.496828 | 0.574449 | 0.506772 | 0.406377 | 0.157634 |
2009 | 0.205933 | 0.186459 | 0.227965 | 0.316908 | 0.307587 | 0.235446 | 0.353889 | 0.283667 | 0.150036 | 0.287638 | 0.253103 | 0.231736 | 0.267289 | 0.120904 |
2010 | 0.214515 | 0.214723 | 0.246373 | 0.356491 | 0.381328 | 0.256962 | 0.471909 | 0.296464 | 0.177116 | 0.488202 | 0.363025 | 0.245049 | 0.273193 | 0.237757 |
2011 | 0.297256 | 0.343108 | 0.398452 | 0.473769 | 0.614818 | 0.419816 | 0.723763 | 0.431123 | 0.311218 | 0.626153 | 0.534808 | 0.325069 | 0.338416 | 0.360488 |
2012 | 0.305660 | 0.409318 | 0.456482 | 0.531891 | 0.562795 | 0.455086 | 0.790998 | 0.553634 | 0.398358 | 0.642672 | 0.479245 | 0.422755 | 0.452111 | 0.278985 |
2013 | 0.297276 | 0.618793 | 0.474041 | 0.558515 | 0.698906 | 0.436854 | 0.807338 | 0.491183 | 0.292769 | 0.757181 | 0.595628 | 0.372790 | 0.373332 | 0.278414 |
2014 | 0.255702 | 0.479746 | 0.348729 | 0.391868 | 0.408063 | 0.356274 | 0.495994 | 0.431967 | 0.218943 | 0.504105 | 0.417777 | 0.303957 | 0.325778 | 0.229303 |
2015 | 0.186783 | 0.496920 | 0.255251 | 0.337159 | 0.359019 | 0.259505 | 0.409413 | 0.218156 | 0.143094 | 0.585005 | 0.308243 | 0.192836 | 0.242083 | 0.179056 |
2016 | 0.210164 | 0.502794 | 0.298634 | 0.442067 | 0.420113 | 0.342628 | 0.531768 | 0.298358 | 0.222964 | 0.714341 | 0.458004 | 0.273284 | 0.293430 | 0.240760 |
生成2014年到2016年行业BP的均值柱状图,行业按照每个行业的首字母进行划分
def show_graph_re():indices = df_BP_mean_by_industries.columnsfig = plt.figure(figsize = (20, 8))# 图1位置ax = fig.add_subplot(110)# 生成图超额收益率的最大回撤ax.bar([x-0.2 for x in range(len(indices))], df_BP_mean_by_industries.ix[-3], color = '#880000', width = 0.2, label = '2014')ax.bar([x for x in range(len(indices))], df_BP_mean_by_industries.ix[-2], color = '#B22222', width = 0.2, label = '2015')ax.bar([x+0.2 for x in range(len(indices))], df_BP_mean_by_industries.ix[-1], color = '#F08080', width = 0.2, label = '2016')# 设置图例样式ax.legend(loc='best',fontsize=15)# 设置y标签名称ax.set_ylabel('mean of BP', fontsize=15)# 设置y标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置x标签样式ticks = [int(x) for x in np.linspace(0, len(df_BP_mean_by_industries.columns)-1)]plt.xticks(ticks, [df_BP_mean_by_industries.columns[i] for i in ticks])# 设置图片标题样式ax.set_title("mean of BP for different industries level", fontsize=15)# 设定x轴长度plt.xlim(-0.3, len(indices)) show_graph_re()
win 不 win 的差别
df_BP = get_df_BP('2016-12-31',winsor = True, h*e_negative = True)fig = plt.figure(figsize = (20, 8))ax1 = fig.add_subplot(211)ax1 = sns.kdeplot(df_BP['BP'][df_BP['MC_group'] == 'group1'], label = 'group1', color="#FF0000", alpha=.6, shade=True)ax1 = sns.kdeplot(df_BP['BP'][df_BP['MC_group'] == 'group2'], label = 'group2', color="#C1F320", alpha=.6, shade=True)ax1 = sns.kdeplot(df_BP['BP'][df_BP['MC_group'] == 'group3'], label = 'group3', color="#888888", alpha=.6, shade=True)ax2 = fig.add_subplot(212)ax2 = sns.kdeplot(df_BP['BP_win'][df_BP['MC_group'] == 'group1'], label = 'group1', color="#FF0000", alpha=.6, shade=True)ax2 = sns.kdeplot(df_BP['BP_win'][df_BP['MC_group'] == 'group2'], label = 'group2', color="#C1F320", alpha=.6, shade=True)ax2 = sns.kdeplot(df_BP['BP_win'][df_BP['MC_group'] == 'group3'], label = 'group3', color="#888888", alpha=.6, shade=True)plt.xlim(-1, 2)
(-1, 2)
win 后 对市值中性与不中性的差别,包括负值
df_BP = get_df_BP('2016-12-31',winsor = True, h*e_negative = True)fig = plt.figure(figsize = (20, 8))ax1 = fig.add_subplot(211)ax1 = sns.kdeplot(df_BP['BP_win'][df_BP['MC_group'] == 'group1'], label = 'group1', color="#FF0000", alpha=.6, shade=True)ax1 = sns.kdeplot(df_BP['BP_win'][df_BP['MC_group'] == 'group2'], label = 'group2', color="#C1F320", alpha=.6, shade=True)ax1 = sns.kdeplot(df_BP['BP_win'][df_BP['MC_group'] == 'group3'], label = 'group3', color="#888888", alpha=.6, shade=True)ax2 = fig.add_subplot(212)ax2 = sns.kdeplot(df_BP['BP_ne'][df_BP['MC_group'] == 'group1'], label = 'group1', color="#FF0000", alpha=.6, shade=True)ax2 = sns.kdeplot(df_BP['BP_ne'][df_BP['MC_group'] == 'group2'], label = 'group2', color="#C1F320", alpha=.6, shade=True)ax2 = sns.kdeplot(df_BP['BP_ne'][df_BP['MC_group'] == 'group3'], label = 'group3', color="#888888", alpha=.6, shade=True)
借助之前的调参框架进行多个调参结果的对比,首先分别生成针对行业和市值的中性化分位回测,两组二十个。这里简化仅提取两个回测的结果,不在重复运行一边。
# 定义类'参数分析'class parameter_analysis(object):# 定义函数中不同的变量def __init__(self, algorithm_id=None):self.algorithm_id = algorithm_id # 回测idself.params_df = pd.DataFrame() # 回测中所有调参备选值的内容,列名字为对应修改面两名称,对应回测中的 g.XXXXself.results = {} # 回测结果的回报率,key 为 params_df 的行序号,value 为self.evaluations = {} # 回测结果的各项指标,key 为 params_df 的行序号,value 为一个 dataframeself.backtest_ids = {} # 回测结果的 id# 新加入的基准的回测结果 id,可以默认为空 '',则使用回测中设定的基准self.benchmark_id = 'ae0684d86e9e7128b1ab9c7d77893029' self.benchmark_returns = [] # 新加入的基准的回测回报率self.returns = {} # 记录所有回报率self.excess_returns = {} # 记录超额收益率self.log_returns = {} # 记录收益率的 log 值self.log_excess_returns = {} # 记录超额收益的 log 值self.dates = [] # 回测对应的所有日期self.excess_max_drawdown = {} # 计算超额收益的最大回撤self.excess_annual_return = {} # 计算超额收益率的年化指标self.evaluations_df = pd.DataFrame() # 记录各项回测指标,除日回报率外self.color = ['#E2BE1B', '#EF6C00', '#8A4AD7', '#EB27AD', '#46D30F', '#2A9A87', '#449DE3', '#2737D5', '#671C1C', '#D0A298'] # 回报率图形配色# 定义排队运行多参数回测函数def run_backtest(self, # algorithm_id=None, # 回测策略id running_max=10, # 回测中同时巡行最大回测数量 start_date='2006-01-01', # 回测的起始日期 end_date='2016-11-30', # 回测的结束日期 frequency='day', # 回测的运行频率 initial_cash='1000000', # 回测的初始持仓金额 param_names=[], # 回测中调整参数涉及的变量 param_values=[] # 回测中每个变量的备选参数值 ):# 当此处回测策略的 id 没有给出时,调用类输入的策略 idif algorithm_id == None: algorithm_id=self.algorithm_id# 生成所有参数组合并加载到 df 中# 包含了不同参数具体备选值的排列组合中一组参数的 tuple 的 listparam_combinations = list(itertools.product(*param_values))# 生成一个 dataframe, 对应的列为每个调参的变量,每个值为调参对应的备选值to_run_df = pd.DataFrame(param_combinations)# 修改列名称为调参变量的名字to_run_df.columns = param_names# 设定运行起始时间和保存格式start = time.time()# 记录结束的运行回测finished_backtests = {}# 记录运行中的回测running_backtests = {}# 计数器pointer = 0# 总运行回测数目,等于排列组合中的元素个数total_backtest_num = len(param_combinations)# 记录回测结果的回报率all_results = {}# 记录回测结果的各项指标all_evaluations = {}# 在运行开始时显示print '【运行中|已完成|待运行】:', # 当运行回测开始后,如果没有全部运行完全的话:while len(finished_backtests)<total_backtest_num:# 显示运行、完成和待运行的回测个数print('[%s|%s|%s].' % (len(finished_backtests), len(running_backtests), (total_backtest_num-len(finished_backtests)-len(running_backtests)) )),# 记录当前运行中的空位数量to_run = min(running_max-len(running_backtests), total_backtest_num-len(running_backtests)-len(finished_backtests))# 把可用的空位进行跑回测for i in range(pointer, pointer+to_run):# 备选的参数排列组合的 df 中第 i 行变成 dict,每个 key 为列名字,value 为 df 中对应的值params = to_run_df.ix[i].to_dict()# 记录策略回测结果的 id,调整参数 extras 使用 params 的内容backtest = create_backtest(algorithm_id = algorithm_id, start_date = start_date, end_date = end_date, frequency = frequency, initial_cash = initial_cash, extras = params, # 再回测中把改参数的结果起一个名字,包含了所有涉及的变量参数值 name = str(params) )# 记录运行中 i 回测的回测 idrunning_backtests[i] = backtest# 计数器计数运行完的数量 pointer = pointer+to_run# 获取回测结果failed = []finished = []# 对于运行中的回测,key 为 to_run_df 中所有排列组合中的序数for key in running_backtests.keys():# 研究调用回测的结果,running_backtests[key] 为运行中保存的结果 idbt = get_backtest(running_backtests[key])# 获得运行回测结果的状态,成功和失败都需要运行结束后返回,如果没有返回则运行没有结束status = bt.get_status()# 当运行回测失败if status == 'failed':# 失败 list 中记录对应的回测结果 idfailed.append(key)# 当运行回测成功时elif status == 'done':# 成功 list 记录对应的回测结果 id,finish 仅记录运行成功的finished.append(key)# 回测回报率记录对应回测的回报率 dict, key to_run_df 中所有排列组合中的序数, value 为回报率的 dict# 每个 value 一个 list 每个对象为一个包含时间、日回报率和基准回报率的 dictall_results[key] = bt.get_results()# 回测回报率记录对应回测结果指标 dict, key to_run_df 中所有排列组合中的序数, value 为回测结果指标的 dataframeall_evaluations[key] = bt.get_risk()# 记录运行中回测结果 id 的 list 中删除失败的运行for key in failed:running_backtests.pop(key)# 在结束回测结果 dict 中记录运行成功的回测结果 id,同时在运行中的记录中删除该回测for key in finished:finished_backtests[key] = running_backtests.pop(key)# 当一组同时运行的回测结束时报告时间if len(finished_backtests) != 0 and len(finished_backtests) % running_max == 0 and to_run !=0:# 记录当时时间middle = time.time()# 计算剩余时间,假设没工作量时间相等的话remain_time = (middle - start) * (total_backtest_num - len(finished_backtests)) / len(finished_backtests)# print 当前运行时间print('[已用%s时,尚余%s时,请不要关闭浏览器].' % (str(round((middle - start) / 60.0 / 60.0,3)), str(round(remain_time / 60.0 / 60.0,3)))),# 5秒钟后再跑一下time.sleep(5) # 记录结束时间end = time.time() print ''print('【回测完成】总用时:%s秒(即%s小时)。' % (str(int(end-start)), str(round((end-start)/60.0/60.0,2)))),# 对应修改类内部对应self.params_df = to_run_dfself.results = all_resultsself.evaluations = all_evaluationsself.backtest_ids = finished_backtests#7 最大回撤计算方法def find_max_drawdown(self, returns):# 定义最大回撤的变量result = 0# 记录最高的回报率点historical_return = 0# 遍历所有日期for i in range(len(returns)):# 最高回报率记录historical_return = max(historical_return, returns[i])# 最大回撤记录drawdown = 1-(returns[i] + 1) / (historical_return + 1)# 记录最大回撤result = max(drawdown, result)# 返回最大回撤值return result# log 收益、新基准下超额收益和相对与新基准的最大回撤def organize_backtest_results(self, benchmark_id=None):# 若新基准的回测结果 id 没给出if benchmark_id==None:# 使用默认的基准回报率,默认的基准在回测策略中设定self.benchmark_returns = [x['benchmark_returns'] for x in self.results[0]]# 当新基准指标给出后 else:# 基准使用新加入的基准回测结果self.benchmark_returns = [x['returns'] for x in get_backtest(benchmark_id).get_results()]# 回测日期为结果中记录的第一项对应的日期self.dates = [x['time'] for x in self.results[0]]# 对应每个回测在所有备选回测中的顺序 (key),生成新数据# 由 {key:{u'benchmark_returns': 0.022480100091729405,# u'returns': 0.03184566700000002,# u'time': u'2006-02-14'}} 格式转化为:# {key: []} 格式,其中 list 为对应 date 的一个回报率 listfor key in self.results.keys():self.returns[key] = [x['returns'] for x in self.results[key]]# 生成对于基准(或新基准)的超额收益率for key in self.results.keys():self.excess_returns[key] = [(x+1)/(y+1)-1 for (x,y) in zip(self.returns[key], self.benchmark_returns)]# 生成 log 形式的收益率for key in self.results.keys():self.log_returns[key] = [log(x+1) for x in self.returns[key]]# 生成超额收益率的 log 形式for key in self.results.keys():self.log_excess_returns[key] = [log(x+1) for x in self.excess_returns[key]]# 生成超额收益率的最大回撤for key in self.results.keys():self.excess_max_drawdown[key] = self.find_max_drawdown(self.excess_returns[key])# 生成年化超额收益率for key in self.results.keys():self.excess_annual_return[key] = (self.excess_returns[key][-1]+1)**(252./float(len(self.dates)))-1# 把调参数据中的参数组合 df 与对应结果的 df 进行合并self.evaluations_df = pd.concat([self.params_df, pd.DataFrame(self.evaluations).T], axis=1)# self.evaluations_df = # 获取最总分析数据,调用排队回测函数和数据整理的函数 def get_backtest_data(self, algorithm_id=None, # 回测策略id benchmark_id=None, # 新基准回测结果id file_name='results.pkl', # 保存结果的 pickle 文件名字 running_max=10, # 最大同时运行回测数量 start_date='2006-01-01', # 回测开始时间 end_date='2016-11-30', # 回测结束日期 frequency='day', # 回测的运行频率 initial_cash='1000000', # 回测初始持仓资金 param_names=[], # 回测需要测试的变量 param_values=[] # 对应每个变量的备选参数 ):# 调运排队回测函数,传递对应参数self.run_backtest(algorithm_id=algorithm_id, running_max=running_max, start_date=start_date, end_date=end_date, frequency=frequency, initial_cash=initial_cash, param_names=param_names, param_values=param_values )# 回测结果指标中加入 log 收益率和超额收益率等指标self.organize_backtest_results(benchmark_id)# 生成 dict 保存所有结果。results = {'returns':self.returns, 'excess_returns':self.excess_returns, 'log_returns':self.log_returns, 'log_excess_returns':self.log_excess_returns, 'dates':self.dates, 'benchmark_returns':self.benchmark_returns, 'evaluations':self.evaluations, 'params_df':self.params_df, 'backtest_ids':self.backtest_ids, 'excess_max_drawdown':self.excess_max_drawdown, 'excess_annual_return':self.excess_annual_return, 'evaluations_df':self.evaluations_df}# 保存 pickle 文件pickle_file = open(file_name, 'wb')pickle.dump(results, pickle_file)pickle_file.close()# 读取保存的 pickle 文件,赋予类中的对象名对应的保存内容 def read_backtest_data(self, file_name='results.pkl'):pickle_file = open(file_name, 'rb')results = pickle.load(pickle_file)self.returns = results['returns']self.excess_returns = results['excess_returns']self.log_returns = results['log_returns']self.log_excess_returns = results['log_excess_returns']self.dates = results['dates']self.benchmark_returns = results['benchmark_returns']self.evaluations = results['evaluations']self.params_df = results['params_df']self.backtest_ids = results['backtest_ids']self.excess_max_drawdown = results['excess_max_drawdown']self.excess_annual_return = results['excess_annual_return']self.evaluations_df = results['evaluations_df']# 回报率折线图 def plot_returns(self):# 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(110)# 作图for key in self.returns.keys():ax.plot(range(len(self.returns[key])), self.returns[key], c = self.color[key], label=key)# 设定benchmark曲线并标记ax.plot(range(len(self.benchmark_returns)), self.benchmark_returns, label='benchmark', c='k', linestyle='') ticks = [int(x) for x in np.linspace(0, len(self.dates)-1, 11)]plt.xticks(ticks, [self.dates[i] for i in ticks])# 设置图例样式ax.legend(loc = 2, fontsize = 10)# 设置y标签样式ax.set_ylabel('returns',fontsize=20)# 设置x标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置图片标题样式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(self.returns[0]))# 超额收益率图 def plot_excess_returns(self):# 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(110)# 作图for key in self.returns.keys():ax.plot(range(len(self.excess_returns[key])), self.excess_returns[key], c = self.color[key], label=key)# 设定benchmark曲线并标记ax.plot(range(len(self.benchmark_returns)), [0]*len(self.benchmark_returns), label='benchmark', c='k', linestyle='')ticks = [int(x) for x in np.linspace(0, len(self.dates)-1, 11)]plt.xticks(ticks, [self.dates[i] for i in ticks])# 设置图例样式ax.legend(loc = 2, fontsize = 10)# 设置y标签样式ax.set_ylabel('excess returns',fontsize=20)# 设置x标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置图片标题样式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(self.excess_returns[0]))# log回报率图 def plot_log_returns(self):# 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(110)# 作图for key in self.returns.keys():ax.plot(range(len(self.log_returns[key])), self.log_returns[key], c = self.color[key], label=key)# 设定benchmark曲线并标记ax.plot(range(len(self.benchmark_returns)), [log(x+1) for x in self.benchmark_returns], label='benchmark', c='k', linestyle='')ticks = [int(x) for x in np.linspace(0, len(self.dates)-1, 11)]plt.xticks(ticks, [self.dates[i] for i in ticks])# 设置图例样式ax.legend(loc = 2, fontsize = 10)# 设置y标签样式ax.set_ylabel('log returns',fontsize=20)# 设置图片标题样式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(self.log_returns[0]))# 超额收益率的 log 图def plot_log_excess_returns(self):# 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(110)# 作图for key in self.returns.keys():ax.plot(range(len(self.log_excess_returns[key])), self.log_excess_returns[key], c = self.color[key], label=key)# 设定benchmark曲线并标记ax.plot(range(len(self.benchmark_returns)), [0]*len(self.benchmark_returns), label='benchmark', c='k', linestyle='')ticks = [int(x) for x in np.linspace(0, len(self.dates)-1, 11)]plt.xticks(ticks, [self.dates[i] for i in ticks])# 设置图例样式ax.legend(loc = 2, fontsize = 10)# 设置y标签样式ax.set_ylabel('log excess returns',fontsize=20)# 设置图片标题样式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(self.log_excess_returns[0]))# 回测的4个主要指标,包括总回报率、最大回撤夏普率和波动def get_eval4_bar(self, sort_by=[]): sorted_params = self.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.indexfig = plt.figure(figsize=(20,7))# 定义位置ax1 = fig.add_subplot(221)# 设定横轴为对应分位,纵轴为对应指标ax1.bar(range(len(indices)), [self.evaluations[x]['algorithm_return'] for x in indices], 0.6, label = 'Algorithm_return')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 设置图例样式ax1.legend(loc='best',fontsize=15)# 设置y标签样式ax1.set_ylabel('Algorithm_return', fontsize=15)# 设置y标签样式ax1.set_yticklabels([str(x*100)+'% 'for x in ax1.get_yticks()])# 设置图片标题样式ax1.set_title("Strategy's of Algorithm_return performances of different quantile", fontsize=15)# x轴范围plt.xlim(0, len(indices))# 定义位置ax2 = fig.add_subplot(224)# 设定横轴为对应分位,纵轴为对应指标ax2.bar(range(len(indices)), [self.evaluations[x]['max_drawdown'] for x in indices], 0.6, label = 'Max_drawdown')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 设置图例样式ax2.legend(loc='best',fontsize=15)# 设置y标签样式ax2.set_ylabel('Max_drawdown', fontsize=15)# 设置x标签样式ax2.set_yticklabels([str(x*100)+'% 'for x in ax2.get_yticks()])# 设置图片标题样式ax2.set_title("Strategy's of Max_drawdown performances of different quantile", fontsize=15)# x轴范围plt.xlim(0, len(indices))# 定义位置ax3 = fig.add_subplot(223)# 设定横轴为对应分位,纵轴为对应指标ax3.bar(range(len(indices)),[self.evaluations[x]['sharpe'] for x in indices], 0.6, label = 'Sharpe')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 设置图例样式ax3.legend(loc='best',fontsize=15)# 设置y标签样式ax3.set_ylabel('Sharpe', fontsize=15)# 设置x标签样式ax3.set_yticklabels([str(x*100)+'% 'for x in ax3.get_yticks()])# 设置图片标题样式ax3.set_title("Strategy's of Sharpe performances of different quantile", fontsize=15)# x轴范围plt.xlim(0, len(indices))# 定义位置ax4 = fig.add_subplot(222)# 设定横轴为对应分位,纵轴为对应指标ax4.bar(range(len(indices)), [self.evaluations[x]['algorithm_volatility'] for x in indices], 0.6, label = 'Algorithm_volatility')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 设置图例样式ax4.legend(loc='best',fontsize=15)# 设置y标签样式ax4.set_ylabel('Algorithm_volatility', fontsize=15)# 设置x标签样式ax4.set_yticklabels([str(x*100)+'% 'for x in ax4.get_yticks()])# 设置图片标题样式ax4.set_title("Strategy's of Algorithm_volatility performances of different quantile", fontsize=15)# x轴范围plt.xlim(0, len(indices))#14 年化回报和最大回撤,正负双色表示def get_eval(self, sort_by=[]):sorted_params = self.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.index# 大小fig = plt.figure(figsize = (20, 8))# 图1位置ax = fig.add_subplot(110)# 生成图超额收益率的最大回撤ax.bar([x+0.3 for x in range(len(indices))], [-self.evaluations[x]['max_drawdown'] for x in indices], color = '#32CD32', width = 0.6, label = 'Max_drawdown', zorder=10)# 图年化超额收益ax.bar([x for x in range(len(indices))], [self.evaluations[x]['annual_algo_return'] for x in indices], color = 'r', width = 0.6, label = 'Annual_return')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 设置图例样式ax.legend(loc='best',fontsize=15)# 基准线plt.plot([0, len(indices)], [0, 0], c='k', linestyle='', label='zero')# 设置图例样式ax.legend(loc='best',fontsize=15)# 设置y标签样式ax.set_ylabel('Max_drawdown', fontsize=15)# 设置x标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置图片标题样式ax.set_title("Strategy's performances of different quantile", fontsize=15)# 设定x轴长度plt.xlim(0, len(indices))#14 超额收益的年化回报和最大回撤# 加入新的benchmark后超额收益和def get_excess_eval(self, sort_by=[]):sorted_params = self.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.index# 大小fig = plt.figure(figsize = (20, 8))# 图1位置ax = fig.add_subplot(110)# 生成图超额收益率的最大回撤ax.bar([x+0.3 for x in range(len(indices))], [-self.excess_max_drawdown[x] for x in indices], color = '#32CD32', width = 0.6, label = 'Excess_max_drawdown')# 图年化超额收益ax.bar([x for x in range(len(indices))], [self.excess_annual_return[x] for x in indices], color = 'r', width = 0.6, label = 'Excess_annual_return')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 设置图例样式ax.legend(loc='best',fontsize=15)# 基准线plt.plot([0, len(indices)], [0, 0], c='k', linestyle='', label='zero')# 设置图例样式ax.legend(loc='best',fontsize=15)# 设置y标签样式ax.set_ylabel('Max_drawdown', fontsize=15)# 设置x标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置图片标题样式ax.set_title("Strategy's performances of different quantile", fontsize=15)# 设定x轴长度plt.xlim(0, len(indices))
对class初始化,需要一个algorithm_id,但是没有实际调用,调用主要是在省略的分别运行市值中性化和行业中性化BP分位回测时
pa = parameter_analysis('3491a3dc9f546e44d2336*72b208f3d')pa.read_backtest_data('results4.pkl')evaluations4 = pa.evaluationsexcess_max_drawdown4 = pa.excess_max_drawdownexcess_annual_return4 = pa.excess_annual_returnpa.read_backtest_data('results5.pkl')
生成两组回测年化收入和最大回撤对比图
def get_eval(sort_by=[]):sorted_params = pa.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.index# 大小fig = plt.figure(figsize = (20, 8))# 图1位置ax = fig.add_subplot(110)# 生成图超额收益率的最大回撤ax.bar([x+0.1 for x in range(len(indices))], [-evaluations4[x]['max_drawdown'] for x in indices], color = '#228B22', width = 0.3, label = 'Max_drawdown_MC')ax.bar([x+0.4 for x in range(len(indices))], [-pa.evaluations[x]['max_drawdown'] for x in indices], color = '#7FFF00', width = 0.3, label = 'Max_drawdown_industries')# 图年化超额收益ax.bar([x for x in range(len(indices))], [evaluations4[x]['annual_algo_return'] for x in indices], color = '#880000', width = 0.3, label = 'Annual_return_MC')ax.bar([x+0.3 for x in range(len(indices))], [pa.evaluations[x]['annual_algo_return'] for x in indices], color = '#F08080', width = 0.3, label = 'Annual_return_industries')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 设置图例样式ax.legend(loc='best',fontsize=15)# 基准线plt.plot([0, len(indices)], [0, 0], c='k', linestyle='', label='zero')# 设置图例样式ax.legend(loc='best',fontsize=15)# 设置y标签样式ax.set_ylabel('Max_drawdown', fontsize=15)# 设置x标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置图片标题样式ax.set_title("Strategy's performances of different quantile", fontsize=15)# 设定x轴长度plt.xlim(0, len(indices))get_eval()
生成两组回测超额年化收入和最大回撤对比图
def get_excess_eval(sort_by=[]):sorted_params = pa.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.index# 大小fig = plt.figure(figsize = (20, 8))# 图1位置ax = fig.add_subplot(110)# 生成图超额收益率的最大回撤ax.bar([x+0.1 for x in range(len(indices))], [-excess_max_drawdown4[x] for x in indices], color = '#228B22', width = 0.3, label = 'Excess_max_drawdown_MC') ax.bar([x+0.4 for x in range(len(indices))], [-pa.excess_max_drawdown[x] for x in indices], color = '#7FFF00', width = 0.3, label = 'Excess_max_drawdown_industries')# 图年化超额收益ax.bar([x for x in range(len(indices))], [excess_annual_return4[x] for x in indices], color = '#880000', width = 0.3, label = 'Excess_annual_return_MC')ax.bar([x+0.3 for x in range(len(indices))], [pa.excess_annual_return[x] for x in indices], color = '#F08080', width = 0.3, label = 'Excess_annual_return_industries')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 设置图例样式ax.legend(loc='best',fontsize=15)# 基准线plt.plot([0, len(indices)], [0, 0], c='k', linestyle='', label='zero')# 设置图例样式ax.legend(loc='best',fontsize=15)# 设置y标签样式ax.set_ylabel('Max_drawdown', fontsize=15)# 设置x标签样式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 设置图片标题样式ax.set_title("Strategy's performances of different quantile", fontsize=15)# 设定x轴长度plt.xlim(0, len(indices))get_excess_eval()
industries=['A01',# 农业 1993-09-17'A02',# 林业 1996-12-06'A03', # 畜牧业 1997-06-11'A04', # 渔业 1993-05-07'A05', # 农、林、牧、渔服务业 1997-05-30'B06', # 煤炭开采和洗选业 1994-01-06'B07', # 石油和天然气开采业 1996-06-28'B08', # 黑色金属矿采选业 1997-07-08'B09', # 有色金属矿采选业 1996-03-20'B11', # 开采辅助活动 2002-02-05'C13', # 农副食品加工业 1993-12-15'C14', # 食品制造业 1994-08-18'C15', # 酒、饮料和精制茶制造业 1992-10-12'C17', # 纺织业 1992-06-16'C18', # 纺织服装、服饰业 1993-12-31'C19', # 皮革、毛皮、羽毛及其制品和制鞋业 1994-04-04'C20', # 木材加工及木、竹、藤、棕、草制品业 2005-05-10'C21', # 家具制造业 1996-04-25'C22', # 造纸及纸制品业 1993-03-12'C23', # 印刷和记录媒介复制业 1994-02-24'C24', # 文教、工美、体育和娱乐用品制造业 2007-01-10'C25', # 石油加工、炼焦及核燃料加工业 1993-10-25'C26', # 化学原料及化学制品制造业 1990-12-19'C27', # 医药制造业 1993-06-29'C28', # 化学纤维制造业 1993-07-28'C29', # 橡胶和塑料制品业 1992-08-28'C30', # 非金属矿物制品业 1992-02-28'C31', # 黑色金属冶炼及压延加工业 1994-01-06'C32', # 有色金属冶炼和压延加工业 1996-02-15'C33', # 金属制品业 1993-11-30'C34', # 通用设备制造业 1992-03-27'C35', # 专用设备制造业 1992-07-01'C36', # 汽车制造业 1992-07-24'C37', # 铁路、船舶、航空航天和其它运输设备制造业 1992-03-31'C38', # 电气机械及器材制造业 1990-12-19'C39', # 计算机、通信和其他电子设备制造业 1990-12-19'*0', # 仪器仪表制造业 1993-09-17'*1', # 其他制造业 1992-08-14'*2', # 废弃资源综合利用业 2012-10-26'D44', # 电力、热力生产和供应业 1993-04-16'D45', # 燃气生产和供应业 2000-12-11'D46', # 水的生产和供应业 1994-02-24'E47', # 房屋建筑业 1993-04-29'E48', # 土木工程建筑业 1994-01-28'E50', # 建筑装饰和其他建筑业 1997-05-22'F51', # 批发业 1992-05-06'F52', # 零售业 1992-09-02'G53', # 铁路运输业 1998-05-11'G54', # 道路运输业 1991-01-14'G55', # 水上运输业 1993-11-19'G56', # 航空运输业 1997-11-05'G58', # 装卸搬运和运输代理业 1993-05-05'G59', # 仓储业 1996-06-14'H61', # 住宿业 1993-11-18'H62', # 餐饮业 1997-04-30'I63', # 电信、广播电视和卫星传输服务 1992-12-02'I64', # 互联网和相关服务 1992-05-07'I65', # 软件和信息技术服务业 1992-08-20'J66', # 货币金融服务 1991-04-03'J67', # 资本市场服务 1994-01-10'J68', # 保险业 2007-01-09'J69', # 其他金融业 2012-10-26'K70', # 房地产业 1992-01-13'L71', # 租赁业 1997-01-30'L72', # 商务服务业 1996-08-29'M73', # 研究和试验发展 2012-10-26'M74', # 专业技术服务业 2007-02-15'N77', # 生态保护和环境治理业 2012-10-26'N78', # 公共设施管理业 1992-08-07'P82', # 教育 2012-10-26'Q83', # 卫生 2007-02-05'R85', # 新闻和出版业 1992-12-08'R86', # 广播、电视、电影和影视录音制作业 1994-02-24'R87', # 文化艺术业 2012-10-26'S90'] # 综合 1990-12-10']industries=['A01', 'A02', 'A03', 'A04', 'A05', 'B06', 'B07', 'B08', 'B09', 'B11', 'C13', 'C14', 'C15', 'C17', 'C18', 'C19', 'C20', 'C21', 'C22', 'C23','C24', 'C25', 'C26', 'C27', 'C28', 'C29', 'C30', 'C31', 'C32', 'C33', 'C34', 'C35', 'C36', 'C37', 'C38', '*1','*2','D44', 'D45', 'D46', 'E47', 'E48', 'E50','F51', 'F52', 'G53', 'G54', 'G55', 'G56', 'G58', 'G59','H61', 'H62', 'I63', 'I64', 'I65', 'J66', 'J67', 'J68', 'J69', 'K70', 'L71', 'L72', 'M73', 'M74', 'N77', 'Q83', 'R85', 'R86', 'R87', 'S90']
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程