请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  数理科学 帖子:3352732 新帖:8

高频量价因子在股票和期货中的表现 - 动量类因子

牛市来了发表于:6 月 10 日 10:00回复(1)


引言¶

研究目的:

本文参考海通证券研报《高频量价因子在股票和期货中的表现》,对其动量类因子——趋势强度因子和改进反转因子,进行单因子分析测试,以从各个方面考察因子的有效性,为日后构建实盘交易的多因子选股策略提供思路。
本篇研报将目光聚焦在股票和期货的日内交易特征上,并对比各因子在股票中的表现。 根据研报分析,传统一个月反转因子自2017年以来有效性有所减弱,一个重要的原因是投资者更加注重基本面研究和价值投资,青睐基本面向好的公司,抛弃基本面恶化或者业绩“暴雷”的公司。由于大多数公司选择在收盘之后发布财务数据等重要信息,股票次日往往跳空开盘,并在随后一段时间大幅波动。近年来,A股机构投资者占比提升,市场交易行为在慢慢向美股等发达市场靠拢,这种在美股财报季常见的开盘跳空现象属于市场对重要数据或信息的合理定价,而并非投资者行为造成的错误定价。
基于以上现象,我们可以尝试将隔夜和开盘后半小时的涨幅剔除,再计算当日涨幅$r_t$,将过去一个月$r_t$的累计值作为新的反转因子。
除了使用日内信息对传统因子做增强,还可以直接使用分钟级数据构建因子。本片研报根据股票分钟级日内数据定义了趋势强度指标,并以此前R日平均值进一步构建趋势强度因子。该因子可以理解为日内价格位移与路程之比,能够刻画日内趋势的强弱,具备一定的选股效果。
根据上述研报内容,本文对其日内动量因子在股票中的表现进行复现分析,并对股票价格日内模式进行了研究,从其中提取出 趋势强度 因子和 改进反转 因子,从而实现对股票未来收益的预测,为在日内高频量价数据中挖掘有效因子提供一定思路。

研究内容:

(1)利用聚宽因子分析API,构建因子函数类;
(2)研究在日内高频分钟级数据中挖掘构建趋势强度因子,并对该因子进行有效性检验,包括IC信息系数法检验以及构建分层回测组合,并对该因子剔除Barra风格因子和行业因子,并在分层回测过程中设置真实交易费率来模拟因子真实的选股能力;
(3)研究使用日内信息对传统反转因子做增强,通过剔除传统反转因子隔夜和开盘半小时内的收益数据,来构建新的反转因子,并进行有效性检验,包括IC信息系数法检验以及构建分层回测组合,并构建与原始因子的多空组合收益表现对比,以反映改进反转因子的性能提升情况。同时进一步对因子进行正交化,进行分层回测,并在分层回测过程中设置真实交易费率来模拟因子真实的选股能力。

研究结论:

最终,我们通过对日内动量因子的有效性分析以及分层回测检验,初步得到以下几个结论:

(1)趋势强度因子。
在 2018-01-2019-05 这 17 个月中,1、5、10、30日IC均值分别为为-0.016、-0.023、-0.029、-0.019,说明因子值与下月收益率呈现一定的负相关性,即因子值越低,下月预期收益率越高。
我们考察1、5、10、30日因子的IR值,分别为0.266、0.261、0.255、0.253。可见,10日换仓和30日换仓因子稳定性较好。
进一步的计算IC_IR值为:-0.060、-0.088、-0.114、-0.075。因此,在单位风险下,10日换仓因子具有最佳的有效性。
基于此,我们以10日为调仓周期,进行分层回测分析,发现正交后的趋势强度因子在2017年以后跑输大盘,一方面是因为因子的有效性有所减弱,另一方面是趋势强度因子往往在牛市中表现出色,投资者在牛市中的交易情绪高涨,容易出现不理性行为。
另外,趋势强度因子的空头方一直表现稳定,虽然目前A股市场无有效的做空手段,但可以用趋势强度因子的空头方对策略股票池做负向排除,实现避免踩雷的效果。
(2)改进反转因子。
除了直接使用分钟级别数据构建因子之外,我们还可以使用日内信息对传统因子做增强。
在 2018-01-2019-05 这 17 个月中,1、5、10、30日IC均值分别为为-0.014、-0.020、-0.022、-0.021,说明因子值与下月收益率呈现一定的负相关性,即因子值越低,下月预期收益率越高。
我们考察1、5、10、30日因子的IR值,分别为0.246、0.241、0.224、0.228。可见,10日换仓和30日换仓因子稳定性较好。
进一步的计算IC_IR值为:-0.057、-0.083、-0.098、-0.092。因此,在单位风险下,10日换仓因子具有最佳的有效性。
10日调仓和30日换仓IC_IR表现相差不大,但考虑换手率的情况,30日调仓会节省更多的交易成本。
基于此,我们以30日为调仓周期,进行分层回测分析,发现剔除了隔夜和开盘后半小时涨幅的一个月反转因子的多空组合月均收益差从 5%提升至 7.2%,2017 年以来提升效果尤为明显。
进一步对因子进行正交化和行业中性化后,分层回测组合中多头组合整体上明显跑赢空头组合,各组收益呈现出一定的单调性趋势,符合单因子有效性的检验。
正交后的改进反转因子即便在2019年以后因子的多空和空头均依然具备一定的有效性,如第三组仅仅在单个改进反转因子且没有进行组合优化的情况下,依然可以跑赢大盘指数,多头方依然具备稳定的收益能力。同时改进反转因子同样在牛市中表现出色,投资者在牛市中的交易情绪高涨,容易出现不理性行为。
基于此,可以利用正交改进反转因子对策略进行Alpha正向增强,提升策略的整体表现。

回测参数设置:

(1)回测区间:2013-01 - 2019-05
(2)调仓频率:月末调仓
(3)股票池:中证800+中证1000,剔除ST、上市不满6个月的股票
(4)费率:由于部分因子难以确定影响方向,因此回测暂不扣费
(5)分10分位数进行回测,每组资金平均分配

测试平台:Python3

1 初始化¶

首先载入所需要的package

from jqdata import *import datetimeimport pandas as pdimport numpy as npimport pickleimport warningswarnings.filterwarnings('ignore')import matplotlib.dates as mdates

2 动量类因子分析¶

2.1 趋势强度¶

首先我们直接使用分钟级别的数据构建趋势强度因子。
记t日1分钟频率的价格序列为$P_t(t=1,2,..,n)$,则趋势强度指标定义如下:

$trendStrength=\cfrac{P_n-P_1}{\sum_{i=2}^nabs(P_i-P_{i-1})_t}$


该指标可以理解为日内价格位移与路程之比,能够刻画日内趋势的强弱。我们同样将过去R日指标均值定义为趋势强度因子。
在本篇中,我们取R为20日平均。

2.1.1 因子构建¶

本文使用JoinQuant因子分析API,在研究环境中调用以进行因子分析。API使用详情可参阅聚宽技术文档。
本节首先构建趋势强度因子类,并对因子进行正交化处理,剔除Barra风格因子和行业因子。

# 构建因子分析框架from jqfactor import Factorfrom jqdata.apis import *  import datetimeimport pandas as pdfrom jqfactor import neutralizefrom jqfactor import winsorize_medfrom jqfactor import standardlize# 构建趋势强度因子计算函数class TrendStrength(Factor):# 设置因子名称name = 'TrendStrength'# 设置获取数据的时间窗口长度max_window = 1# 设置依赖的数据dependencies = ['close']  #参考标的# 计算因子的函数, 需要返回一个 pandas.Series, index 是股票代码,value 是因子值def calc(self, data):# 获取日期,股票池date= data['close'].index[0]  #获取(上一)交易日  实际上的逻辑是用上一交易日的数据在今天进行交易,时间戳是昨天的时间security_list = data['close'].columns.tolist()# 以下为获取更多数据#获取分钟级数据,我们取前20日趋势强度平均值作为当期的因子值hist_datas = get_price(security_list,count =240 * 20, end_date=date,frequency='1m', fields='close', skip_paused=False, fq='none')['close'].fillna(0)  
        
        stock_close = hist_datas#以日为单位迭代计算当日因子值stock_close.index = [i.date() for i in stock_close.index]stock_close['day'] = stock_close.indexGroupBy = stock_close.groupby(by='day')anti_moment = pd.DataFrame()anti_moment_temp = pd.DataFrame()for day, group in GroupBy:group = group.drop(['day'], axis=1)temp = self.cal_factor(group)anti_moment[day] = temp['factor']anti_moment_temp['factor'] = anti_moment.apply(sum, axis=1) / 20# 中性化等数据处理模块if 1:# 中位数去极值anti_moment_adj = winsorize_med(anti_moment_temp, scale=3, inclusive=True, inf2nan=True)# # 行业、风格因子中性化anti_moment_adj = neutralize(anti_moment_temp, how=['sw_l2','size', 'beta', 'momentum', 'residual_volatility', 'non_linear_size', 'book_to_price_ratio', 'liquidity', 'earnings_yield', 'growth', 'leverage'], date = date, fillna='sw_l2', axis=0)# # zscore标准化#anti_moment_adj = standardlize(anti_moment_adj, inf2nan=True)# 去除 nan 值anti_moment_adj = anti_moment_adj.dropna()else:anti_moment_adj = anti_moment_temp  return  anti_moment_adj['factor']## 计算因子值def cal_factor(self, stock_df):   #计算路程之和distance = stock_df - stock_df.shift(1)distance = distance.dropna(axis=0)distance_total = distance.apply(abs)distance_total = distance_total.apply(sum, axis=0)#计算位移displacement = stock_df.iloc[-1] - stock_df.iloc[0]#计算因子值trend_strength = pd.DataFrame()trend_strength['factor'] = displacement / distance_totalreturn trend_strength

2.1.2 因子分析引擎初始化¶

下面我们对上面构建的趋势强度因子进行回测分析,从各个方面考察因子的有效性。
参数设置如下:
(1)测试时间:2018-01-01 至 2019-05-17;
(2)分位数:十分位数;
(3)调仓周期:1日、5日、10日、30日;
(4)仓位配置:等权配置;
(5)股票池:中证800

#对因子进行分析start = datetime.datetime.today()far = analyze_factor(TrendStrength(), start_date='2018-01-01', end_date='2019-05-17', universe='000906.XSHG',  quantiles=10,periods=(1,5,10,30),industry='jq_l1',weight_method='*g',use_real_price=True, skip_paused=False,max_loss=0.2)end = datetime.datetime.today()print('程序耗时:', end - start)
程序耗时: 1:58:53.225250

2.1.3 因子IC值分析¶

目前常用的因子有效性分析法有回归法、分层回测法和IC信息系数法。在实际操作中,往往三种方法结合使用,从各个方面来考察因子的有效性。
在本节我们使用IC信息系数法对趋势强度因子进行检验。

IC值定义如下:

$IC = corr(f_{t-1},r_t)$

其中$f_{t-1}$表示上一期的因子值,$r_t$表示当期的股票收益率。即因子的IC值实际上就是因子值与下一期收益率的相关性,用来反映因子的预测能力。
进一步的,我们排除因子值大小的影响,使用RankIC来替代IC值进行分析。RankIC为当期因子值的排名与下一期因子值排名的相关性。
考虑预测能力的同时也要考虑因子预测能力的稳定性。

我们引入IR:

$IR = \sigma(IC)$

用IR来反映因子预测能力的波动性。

进一步引入IC_IR:

$IC\_IR = \frac{IC}{IR}$

IC_IR用来表示单位波动性因子的预测能力,是收益与风险的权衡指标。

下面我们绘制因子1日、5日、10日、30日的RankIC时间序列图和月度均值图。

far.plot_ic_ts(group_adjust=False, method='rank')
<Figure size 432x288 with 0 Axes>
far.plot_monthly_ic_heatmap(group_adjust=False)
<Figure size 432x288 with 0 Axes>

在 2018-01-2019-05 这 17 个月中,1、5、10、30日IC均值分别为为-0.016、-0.023、-0.029、-0.019,说明因子值与下月收益率呈现一定的负相关性,即因子值越低,下月预期收益率越高。

我们考察1、5、10、30日因子的IR值,分别为0.266、0.261、0.255、0.253。可见,10日换仓和30日换仓因子稳定性较好。
进一步的计算IC_IR值为:-0.060、-0.088、-0.114、-0.075。因此,在单位风险下,10日换仓因子具有最佳的有效性。

基于此,我们以10日为调仓周期,进行分层回测分析,进一步考察因子有效性。

2.1.4 趋势强度因子回测分析¶

股票池:中证800+中证1000,剔除 ST、停牌、上市时间小于 6 个月的股票
回测时间:2013-01-01 至 2019-06-01
调仓期:每十天
费率:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5元
选股步骤可分为以下三部分:
(1)在每个调仓日第一天计算因子值
(2)对因子值根据从小到大的顺序进行排序,并将其等分为 10 组
(3)每个调仓日对每组股票池进行调仓交易,从而获得 10 组股票组合的收益曲线

在本节使用的因子依然是正交因子。

#1 先导入所需要的程序包import datetimeimport numpy as np import pandas as pdimport timefrom jqdata import *from pandas import Series, DataFrameimport matplotlib.pyplot as pltimport seaborn as snsimport itertoolsimport copyimport pickle# 定义类'参数分析'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()        # 记录各项回测指标,除日回报率外# 定义排队运行多参数回测函数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(111)# 作图for key in self.returns.keys():ax.plot(range(len(self.returns[key])), self.returns[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_long_short(self):   # 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(111)# 作图a1 = [i+1 for i in self.returns[0]]a2 = [i+1 for i in self.returns[4]]a1.insert(0,1)   a2.insert(0,1)b = []for i in range(len(a1)-1):b.append((a1[i+1]/a1[i]-a2[i+1]/a2[i])/2)c = []c.append(1)for i in range(len(b)):c.append(c[i]*(1+b[i]))ax.plot(range(len(c)), c)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)ax.set_title("Strategy's long_short performances",fontsize=20)# 设置图片标题样式plt.xlim(0, len(c))     return c

        # 回测的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))
def group_backtest(start_date,end_date,num):warnings.filterwarnings("ignore")pa = parameter_analysis()pa.get_backtest_data(file_name = 'khd-results.pkl',  running_max = 10,  algorithm_id = 'b07db15bd32dd68f2c9e6863bfb5872f',  start_date=start_date,  end_date=end_date,  frequency = 'day',  initial_cash = '10000000',  param_names = ['num'],  param_values = [num]                     
                          )start_date = '2013-01-01' end_date = '2019-06-01' num = range(1,11)group_backtest(start_date,end_date,num)
【已完成|运行中|待运行】:
[0|0|10].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[4|6|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].

【回测完成】总用时:9985秒(即2.77小时)。

整个回测需要运行2.77小时,回测结束后,将回测结果保存在研究环境中,方便以后的调用。

2.1.5 分层回测结果¶

pa = parameter_analysis()pa.read_backtest_data('khd-results.pkl')

绘制 10 个组合及 HS300 基准的净值收益曲线,具体下图所示。

pa.plot_returns()

由图可以看出,组合 1 能够明显跑赢组合 10,且各组收益呈现出单调性,符合单因子有效性的检验。
进一步分析,正交后的趋势强度因子在2017年以后跑输大盘,一方面是因为因子的有效性有所减弱,另一方面是趋势强度因子往往在牛市中表现出色,投资者在牛市中的交易情绪高涨,容易出现不理性行为。
另外,趋势强度因子的空头方一直表现稳定,虽然目前A股市场无有效的做空手段,但可以用趋势强度因子的空头方对策略股票池做负向排除,实现避免踩雷的效果。

pa.get_eval4_bar()

上面几张表分析了每个投资组合的评价指标,根据年化收益、年化波动率、夏普比率及最大回撤分析,随并非严格单调,但也呈现出明显的下降趋势,基本上满足随着组合数的递增,收益能力上升且风险控制能力下降的趋势,由此符合单因子有效性的检验。空头方效果明显,即便如今也可以利用趋势强度因子的空头进行负向排除。

long_short = pa.plot_long_short()
No handles with labels found to put in legend.

进一步构建多空对从组合,做多组合1做空组合10,年化收益率为1.6%,最大回撤6.4%,趋势强度因子的选股能力主要来源于空头。

def MaxDrawdown(return_list):'''最大回撤率'''i = np.argmax((np.maximum.accumulate(return_list) - return_list) / np.maximum.accumulate(return_list))  # 结束位置if i == 0:return 0j = np.argmax(return_list[:i])  # 开始位置return (return_list[j] - return_list[i]) / (return_list[j])def cal_indictor(long_short):total_return = long_short[-1] / long_short[0] - 1ann_return = pow((1+total_return), 250/float(len(long_short)))-1pchg = []#计算收益率for i in range(1, len(long_short)):pchg.append(long_short[i]/long_short[i-1] - 1)temp = 0for i in pchg:temp += pow(i-mean(pchg), 2)annualVolatility = sqrt(250/float((len(pchg)-1))*temp)sharpe_ratio = (ann_return - 0.04)/annualVolatilityprint( "总收益: ", total_return)print( "年化收益: ", ann_return)print( "年化收益波动率: ", annualVolatility)print( "夏普比率: ",sharpe_ratio)print( "最大回测: ",MaxDrawdown(long_short))cal_indictor(long_short)
总收益:  0.10925208098088945
年化收益:  0.016776851365017498
年化收益波动率:  0.03270714858830064
夏普比率:  -0.7100328104813585
最大回测:  0.06411199887967968

2.2 改进反转因子¶

2.2.1 因子构建¶

除了直接使用分钟级别数据构建因子之外,我们还可以使用日内信息对传统因子做增强。
例如一个月反转因子自 2017 年以来有效性有所减弱,一个重要的原因是投资者更加注重基本面研究和价值投资,青睐基本面向好的公司,抛弃基本面恶化或者业绩“爆雷”的公司。由于大多数公司选择在收盘之后发布财务数据等重要信息,股票次日往往跳空开盘,并在随后一段时间大幅波动。近年来,A 股机构投资者占比提升,市场交易行为在慢慢向美股等发达市场靠拢,这种在美股财报季常见的开盘跳空现象属于市场对重要数据或信息的合理定价,而并非投资者行为造成的错误定价。
基于以上现象,我们可以尝试将将隔夜和开盘后半小时的涨幅剔除,再计算当日涨幅 $𝒓_𝒕$,将过去一个月 $𝒓_𝒕$ 的累计值作为新的反转因子。

本节依然使用因子分析框架,并对改进反转因子进行风格因子和行业因子正交化。

#载入函数库from jqfactor import analyze_factorfrom jqfactor import Factorfrom jqdata.apis import *  import datetimeimport pandas as pdfrom jqfactor import neutralizefrom jqfactor import winsorize_medfrom jqfactor import standardlizeimport pickleclass REVERSAL(Factor):# 设置因子名称name = 'reversal'# 设置获取数据的时间窗口长度max_window = 1# 设置依赖的数据dependencies = ['close']  #参考标的# 计算因子的函数, 需要返回一个 pandas.Series, index 是股票代码,value 是因子值def calc(self, data):# 获取日期,股票池date= data['close'].index[0]  #获取(上一)交易日  实际上的逻辑是用上一交易日的数据在今天进行交易,时间戳是昨天的时间security_list = data['close'].columns.tolist()# 以下为获取更多数据hist_datas = get_price(security_list,count =240 * 30, end_date=date,frequency='1m', fields='close', skip_paused=False, fq='none')['close'].fillna(0)  #获取额外数据,注意是否开启动态复权将失效stock_close = hist_datasstock_close.index = [i.date() for i in stock_close.index]stock_close['day'] = stock_close.indexGroupBy = stock_close.groupby(by='day')anti_moment = pd.DataFrame()anti_moment_temp = pd.DataFrame()for day, group in GroupBy:group = group.drop(['day'], axis=1)temp = self.cal_factor(group)anti_moment[day] = temp['factor']anti_moment_temp['factor'] = anti_moment.apply(sum, axis=1)if 1:# 中性化等数据处理模块# 中位数去极值anti_moment_adj = winsorize_med(anti_moment_temp, scale=3, inclusive=True, inf2nan=True)# 行业、风格因子中性化anti_moment_adj = neutralize(anti_moment_temp, how=['sw_l2','size', 'beta', 'momentum', 'residual_volatility', 'non_linear_size', 'book_to_price_ratio', 'liquidity', 'earnings_yield', 'growth', 'leverage'], date = date, fillna='sw_l2', axis=0)# zscore标准化#anti_moment_adj = standardlize(anti_moment_adj, inf2nan=True)else:anti_moment_adj = anti_moment_tempreturn  anti_moment_adj['factor']## 计算因子值def cal_factor(self, stock_df):   
        anti_moment = pd.DataFrame()anti_moment['factor'] = stock_df.iloc[-1] / stock_df.iloc[30] - 1return anti_moment

2.1.2 因子分析引擎初始化¶

下面我们对上面构建的改进反转因子进行回测分析,从各个方面考察因子的有效性。
参数设置如下:
(1)测试时间:2018-01-01 至 2019-05-17;
(2)分位数:十分位数;
(3)调仓周期:1日、5日、10日、30日;
(4)仓位配置:等权配置;
(5)股票池:中证800

#对因子进行分析start = datetime.datetime.today()far = analyze_factor(REVERSAL(), start_date='2018-01-01', end_date='2019-05-17', universe='000906.XSHG',  # factor_data 为因子值的 pandas.DataFramequantiles=10,periods=(1,5,10,30),industry='jq_l1',weight_method='*g',use_real_price=True, skip_paused=False,max_loss=0.2)end = datetime.datetime.today()print('程序耗时:', end - start)
程序耗时: 3:29:27.467670

2.1.3 因子IC值分析¶

如趋势强度因子的分析流程,下面我们绘制改进反转因子1日、5日、10日、30日的IC时间序列图和月度均值图。

far.plot_ic_ts(group_adjust=False, method='normal')
<Figure size 432x288 with 0 Axes>
far.plot_monthly_ic_heatmap(group_adjust=False)
<Figure size 432x288 with 0 Axes>

在 2018-01-2019-05 这 17 个月中,1、5、10、30日IC均值分别为为-0.014、-0.020、-0.022、-0.021,说明因子值与下月收益率呈现一定的负相关性,即因子值越低,下月预期收益率越高。

我们考察1、5、10、30日因子的IR值,分别为0.246、0.241、0.224、0.228。可见,10日换仓和30日换仓因子稳定性较好。
进一步的计算IC_IR值为:-0.057、-0.083、-0.098、-0.092。因此,在单位风险下,10日换仓因子具有最佳的有效性。

10日调仓和30日换仓IC_IR表现相差不大,但考虑换手率的情况,30日调仓会节省更多的交易成本。
基于此,我们以30日为调仓周期,进行分层回测分析,进一步考察因子有效性。

2.2.2 改进反转因子回测分析¶

首先我们构建改进反转因子和原始反转因子的多空组合回测情况,用来反映改进后因子的表现情况。在这一环节我们不设置回测费率。
下一步我们构建正交改进反转因子的分层回测组合,用于考察因子实际的选股能力。这一环节我们使用真实费率。

股票池:中证800+中证1000,剔除 ST、停牌、上市时间小于 6 个月的股票
回测时间:2013-01-01 至 2019-06-01
调仓期:每30天
费率:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5元
选股步骤可分为以下三部分:
(1)在每个调仓日第一天计算因子值
(2)对因子值根据从小到大的顺序进行排序,并将其等分为 10 组
(3)每个调仓日对每组股票池进行调仓交易,从而获得 10 组股票组合的收益曲线

2.2.2.1 初始化分层回测函数¶

#1 先导入所需要的程序包import datetimeimport numpy as np import pandas as pdimport timefrom jqdata import *from pandas import Series, DataFrameimport matplotlib.pyplot as pltimport seaborn as snsimport itertoolsimport copyimport pickle# 定义类'参数分析'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()        # 记录各项回测指标,除日回报率外# 定义排队运行多参数回测函数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(111)# 作图for key in self.returns.keys():ax.plot(range(len(self.returns[key])), self.returns[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_long_short(self):   # 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(111)# 作图a1 = [i+1 for i in self.returns[0]]a2 = [i+1 for i in self.returns[1]]a1.insert(0,1)   a2.insert(0,1)b = []for i in range(len(a1)-1):b.append((a1[i+1]/a1[i]-a2[i+1]/a2[i])/2)c = []c.append(1)for i in range(len(b)):c.append(c[i]*(1+b[i]))ax.plot(range(len(c)), c)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)ax.set_title("Strategy's long_short performances",fontsize=20)# 设置图片标题样式plt.xlim(0, len(c))     return c# 回测的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))def get_excess_eval_new(self, sort_by=[]):sorted_params = self.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.index'''        for key in self.returns.keys():            ax.plot(range(len(self.excess_returns[key])), self.excess_returns[key], label=key)'''# 大小fig = plt.figure(figsize = (22, 8))# 图1位置ax = fig.add_subplot(111)# 生成图绝对收益ax.bar([x for x in range(len(indices))], 
              [self.evaluations[x]['algorithm_return'] for x in indices], 0.3, label = 'Algorithm_return')# 图年化超额收益ax.bar([x + 0.3 for x in range(len(indices))],   [self.excess_returns[x][-1] for x in sorted(self.returns.keys())], 
                     width = 0.3, label = 'Excess_returns')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))

2.2.2.2 构建改进反转因子多空组合¶

在聚宽策略环境中构建改进反转因子的单因子选股策略,并利用分层回测引擎进行回测,并将回测结果保存到研究环境中,方便后续的对比分析。
整个策略回测运行大约耗时4小时。

def group_backtest(start_date,end_date,num):warnings.filterwarnings("ignore")pa = parameter_analysis()pa.get_backtest_data(file_name = 'results_gj-dk.pkl',  running_max = 10,  algorithm_id = '7c8de03576799d8e735a28c2d90ca3dc',  start_date=start_date,  end_date=end_date,  frequency = 'day',  initial_cash = '10000000',  param_names = ['start','num'],  param_values = [[30],num]                     
                          )start_date = '2013-01-01' end_date = '2018-09-01' num = [1,10]group_backtest(start_date,end_date,num)
【已完成|运行中|待运行】:
[0|0|2].
-KeyboardInterrupt                         Traceback (most recent call last)<ipython-input-66-bdb434eced25> in <module>     16 end_date = '2018-09-01'     17 num = [1,10]-> 18 group_backtest(start_date,end_date,num)<ipython-input-66-bdb434eced25> in group_backtest(start_date, end_date, num)     10                           initial_cash = '10000000',     11                           param_names = ['start','num'],-> 12                           param_values = [[30],num]     13                           )     14 <ipython-input-63-ef5446136*1> in get_backtest_data(self, algorithm_id, benchmark_id, file_name, running_max, start_date, end_date, frequency, initial_cash, param_names, param_values)    231                           initial_cash=initial_cash,    232                           param_names=param_names,> 233                           param_values=param_values    234                           )    235         # 回测结果指标中加入 log 收益率和超额收益率等指标<ipython-input-63-ef5446136*1> in run_backtest(self, algorithm_id, running_max, start_date, end_date, frequency, initial_cash, param_names, param_values)    141                                           str(round(remain_time / 60.0 / 60.0,3)))),    142             # 5秒钟后再跑一下> 143             time.sleep(5)    144         # 记录结束时间    145         end = time.time()KeyboardInterrupt:
pa1 = parameter_analysis()pa1.read_backtest_data('results_gj-dk.pkl')

2.2.2.3 构建原始反转因子多空组合¶

在聚宽策略环境中构建原始反转因子的单因子选股策略,并利用分层回测引擎进行回测,并将回测结果保存到研究环境中,方便后续的对比分析。
整个策略回测运行大约耗时2小时。

def group_backtest(start_date,end_date,num):warnings.filterwarnings("ignore")pa = parameter_analysis()pa.get_backtest_data(file_name = 'results_ys-dk.pkl',  running_max = 10,  algorithm_id = '7c8de03576799d8e735a28c2d90ca3dc',  start_date=start_date,  end_date=end_date,  frequency = 'day',  initial_cash = '10000000',  param_names = ['start','num'],  param_values = [[0],num]                     
                          )start_date = '2013-01-01' end_date = '2018-09-01' num = [1,10]group_backtest(start_date,end_date,num)
【已完成|运行中|待运行】:
[0|0|2].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[0|2|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].
[1|1|0].

【回测完成】总用时:6679秒(即1.86小时)。
pa2 = parameter_analysis()pa2.read_backtest_data('results_ys-dk.pkl')

2.2.2.4 多空组合对比分析¶

下面我们将改进反转因子和原始反转因子进行对比分析。
首先我们绘制Top-Bottom多空对冲组合净值的时间序列对比图,对比如下图所示。

# 绘制多空收益对比图def plot_new_old_returns(pa1, pa2): fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(111)# 作图1a1 = [i+1 for i in pa1.returns[0]]a2 = [i+1 for i in pa1.returns[1]]a1.insert(0,1)   a2.insert(0,1)b1 = []for i in range(len(a1)-1):b1.append((a1[i+1]/a1[i]-a2[i+1]/a2[i])/2)c1 = []c1.append(1)for i in range(len(b1)):c1.append(c1[i]*(1+b1[i]))ax.plot(range(len(c1)), c1)# 作图2a3 = [i+1 for i in pa2.returns[0]]a4 = [i+1 for i in pa2.returns[1]]a3.insert(0,1)   a4.insert(0,1)b2 = []for i in range(len(a3)-1):b2.append((a3[i+1]/a3[i]-a4[i+1]/a4[i])/2)c2 = []c2.append(1)for i in range(len(b2)):c2.append(c2[i]*(1+b2[i]))ax.plot(range(len(c2)), c2)ticks = [int(x) for x in np.linspace(0, len(pa1.dates)-1, 11)]plt.xticks(ticks, [pa1.dates[i] for i in ticks])# 设置图例样式ax.legend(loc = 2, fontsize = 10)ax.set_title("Strategy's long_short performances",fontsize=20)# 设置图片标题样式plt.xlim(0, len(c1)) 
    return c1, c2c1, c2 = plot_new_old_returns(pa1,pa2)
No handles with labels found to put in legend.
print("改进反转因子的多空组合表现:\n")cal_indictor(c1)print("\n原始反转因子的多空组合表现:\n")cal_indictor(c2)
改进反转因子的多空组合表现:

总收益:  0.46985006164107124
年化收益:  0.07226735962225961
年化收益波动率:  0.05870507715061678
夏普比率:  0.5496519413384434
最大回测:  0.0845738804390966

原始反转因子的多空组合表现:

总收益:  0.3109865492607302
年化收益:  0.05027741898526705
年化收益波动率:  0.07247430595899636
夏普比率:  0.141807759995407
最大回测:  0.1358157255456328

从图中我们可以得出,经过改进的反转因子的多空组合年化收益由 5% 提升至 7.2%,最大回测由 13% 下降到 8 % ,2017 年以来提升效果尤为明显,这充分说明改进反转因子的有效性提升。

2.2.2.5 正交因子分层收益¶

下面我们进一步对改进反转因子进行正交化,正交化即为剔除Barra风格因子的相关性,得到正交反转因子。为了验证正交因子的有效性,我们下一步构建10组分层组合,并进行分层回测分析。为了考察真是选股能力,本环节设置真实交易费率。
整个回测过程大约耗时1小时。

def group_backtest(start_date,end_date,num):warnings.filterwarnings("ignore")pa = parameter_analysis()pa.get_backtest_data(file_name = 'khd-results_zj-fc.pkl',  running_max = 10,  algorithm_id = '6690d0b6de477b2d70a4c8dc5e174d58',  start_date=start_date,  end_date=end_date,  frequency = 'day',  initial_cash = '10000000',  param_names = ['start','num'],  param_values = [[30],num]                     
                          )start_date = '2013-01-01' end_date = '2019-06-01' num = range(1,11)group_backtest(start_date,end_date,num)
【已完成|运行中|待运行】:
[0|0|10].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[0|10|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[1|9|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[2|8|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[3|7|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[4|6|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[5|5|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[6|4|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[7|3|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[8|2|0].
[9|1|0].
[9|1|0].
[9|1|0].
[9|1|0].

【回测完成】总用时:3908秒(即1.09小时)。
pa = parameter_analysis()pa.read_backtest_data('khd-results_zj-fc.pkl')
pa.plot_returns()

由图可以看出,多头组合整体上明显跑赢空头组合,各组收益呈现出一定的单调性趋势,符合单因子有效性的检验。
进一步分析,正交后的改进反转因子即便在2019年以后因子的多空和空头均依然具备一定的有效性,如第三组仅仅在单个改进反转因子且没有进行组合优化的情况下,依然可以跑赢大盘指数,多头方依然具备稳定的收益能力。同时改进反转因子同样在牛市中表现出色,投资者在牛市中的交易情绪高涨,容易出现不理性行为。
基于此,可以利用正交改进反转因子对策略进行Alpha正向增强,提升策略的整体表现。

pa.get_excess_eval_new()

由上表可以看出,正交因子10分位数组合净值表现呈现单调下降趋势,正交后的改进反转因子多空和空头均依然具备一定的有效性,可用作Alpha增强。

总结¶

根据上述报告内容,我们在本文中对其日内动量因子在股票中的表现进行了复现分析,并对股票价格日内模式进行了研究,并从其中提取出 趋势强度 因子和 改进反转 因子,并进行单因子分析测试,从各个方面考察因子的有效性,为构建可实盘交易的多因子选股策略提供一定思路。

本篇的研究步骤大体可以分为以下三部分:
(1)利用聚宽因子分析API,构建因子函数类;
(2)研究在日内高频分钟级数据中挖掘构建趋势强度因子,并对该因子进行有效性检验,包括IC信息系数法检验以及构建分层回测组合,并对该因子剔除Barra风格因子和行业因子,并在分层回测过程中设置真实交易费率来模拟因子真实的选股能力;
(3)研究使用日内信息对传统反转因子做增强,通过剔除传统反转因子隔夜和开盘半小时内的收益数据,来构建新的反转因子,并进行有效性检验,包括IC信息系数法检验以及构建分层回测组合,并构建与原始因子的多空组合收益表现对比,以反映改进反转因子的性能提升情况。同时进一步对因子进行正交化,进行分层回测,并在分层回测过程中设置真实交易费率来模拟因子真实的选股能力。

最终,我们通过以上对日内动量因子的有效性分析以及分层回测检验,初步得到以下几个结论:
(1)趋势强度因子。
在 2018-01-2019-05 这 17 个月中,1、5、10、30日IC均值分别为为-0.016、-0.023、-0.029、-0.019,说明因子值与下月收益率呈现一定的负相关性,即因子值越低,下月预期收益率越高。
我们考察1、5、10、30日因子的IR值,分别为0.266、0.261、0.255、0.253。可见,10日换仓和30日换仓因子稳定性较好。
进一步的计算IC_IR值为:-0.060、-0.088、-0.114、-0.075。因此,在单位风险下,10日换仓因子具有最佳的有效性。
基于此,我们以10日为调仓周期,进行分层回测分析,发现正交后的趋势强度因子在2017年以后跑输大盘,一方面是因为因子的有效性有所减弱,另一方面是趋势强度因子往往在牛市中表现出色,投资者在牛市中的交易情绪高涨,容易出现不理性行为。
另外,趋势强度因子的空头方一直表现稳定,虽然目前A股市场无有效的做空手段,但可以用趋势强度因子的空头方对策略股票池做负向排除,实现避免踩雷的效果。
(2)改进反转因子。
除了直接使用分钟级别数据构建因子之外,我们还可以使用日内信息对传统因子做增强。
在 2018-01-2019-05 这 17 个月中,1、5、10、30日IC均值分别为为-0.014、-0.020、-0.022、-0.021,说明因子值与下月收益率呈现一定的负相关性,即因子值越低,下月预期收益率越高。
我们考察1、5、10、30日因子的IR值,分别为0.246、0.241、0.224、0.228。可见,10日换仓和30日换仓因子稳定性较好。
进一步的计算IC_IR值为:-0.057、-0.083、-0.098、-0.092。因此,在单位风险下,10日换仓因子具有最佳的有效性。
10日调仓和30日换仓IC_IR表现相差不大,但考虑换手率的情况,30日调仓会节省更多的交易成本。
基于此,我们以30日为调仓周期,进行分层回测分析,发现剔除了隔夜和开盘后半小时涨幅的一个月反转因子的多空组合月均收益差从 5%提升至 7.2%,2017 年以来提升效果尤为明显。
进一步对因子进行正交化和行业中性化后,分层回测组合中多头组合整体上明显跑赢空头组合,各组收益呈现出一定的单调性趋势,符合单因子有效性的检验。
正交后的改进反转因子即便在2019年以后因子的多空和空头均依然具备一定的有效性,如第三组仅仅在单个改进反转因子且没有进行组合优化的情况下,依然可以跑赢大盘指数,多头方依然具备稳定的收益能力。同时改进反转因子同样在牛市中表现出色,投资者在牛市中的交易情绪高涨,容易出现不理性行为。
基于此,可以利用正交改进反转因子对策略进行Alpha正向增强,提升策略的整体表现。

全部回复

0/140

量化课程

    移动端课程