更新内容:
增加python版本选择
增加回测失败提示
导语:本文提供了在研究中自动根据指定参数进行回测,并将一组回测结果可视化的框架。
引言
在因子分析系列中我们遇到了一个比较明显的需求,就是把全部股票按照各个分位进行回测,并且分析每个分位的收益情况。但其实有这个需求的不仅限于因子的分析,有时我们在策略中还会有一些其他的参数需要进行调整并分析调整后的结果。本篇文章对于这个需求设立了一个参数分析框架,集合运行回测和各种分析结果的功能于一体。也希望读者多提意见,我们会对其进行改进。
整个框架写在了一个parameter_analysis 类里面,附在文章结尾的研究模块。下面讲功能和使用方法。
初始化回测
如果想进行分析的回测还没有被运行过,我们需要运行回测并保存回测结果。首先,
parameter_analysis(algorithm_id=None)
将这个类初始化。输入中的 algorithm_id 是想要回测使用的策略编码,获取方式是策略编辑页 url 的尾部,如下
parameter_analysis.run_backtest() 函数可以运行回测,并且 parameter_analysis.organize_backtest_results() 函数可以以回测结果分析一些指标和数据。但这两个函数都已经打包在 parameter_analysis.get_backtest_data() 中,所以我们只讲最后的这个函数。调用这个函数的输入较多,有
parameter_analysis.get_backtest_data(algorithm_id=None, benchmark_id_None, file_name='results.pkl', running_max=10, start_date='2006-01-01', end_date='2016-11-30', frequency='day', initial_cash='1000000', param_names=[], param_values=[])
我们一个一个来讲。
algorithm_id 正如其名是策略的 id 编码,和之前提到的一样。如果在创建 parameter_analysis 时已经提供了的话这里就不用再提供了。
benchmark_id 是自定义基准的回测编码,注意不是策略而是回测。函数会以这个回测的收益率曲线作为基准曲线,要求这个回测的起始和结束时间和参数中的 start_date 与 end_date 分别吻合。如果不提供 benchmark_id,则策略会自动用 algorithm_id 对应的策略中指定的基准作为基准。
file_name 是储存数据的文件名,因为是以 pickle 进行存储,文件名结尾必须是 .pkl。回测运行完毕后的 pickle 文件将被存储于和研究的 .ipyny 相同的文件夹,默认文件名为‘results.pkl’。
running_max 是同时可运行的回测的上限。由于在平台上同时运行的回测数量上限是 10 个,固 running_max 默认设为 10。但是,如果需要在这个框架运行的时候留出一些空位来运行其他回测,可以将 running_max 设为 9 或者更小的数字。当这个函数被要求运行超过 running_max 数量的回测时,会自动将它们进行排队,在之前的完成后再运行后面的。
start_date 和 end_date 顾名思义就是回测起始和结束的日期,默认数值如上所述。
frequency 是回测频率,按需求使用 ‘day’ 或者 ‘minute’,默认为‘day’。
initial_cash 是回测的起始资金,默认一百万。
值得细讲的是 param_names 和 param_values 两个输入。这里 param_names 是一个 list 的 strings,它对应着策略中所有需要调整的全局变量的名字。比如说,我们需要将策略中的 g.abc 和 g.x_y_z 两个参数更改数值进行多次回测,那么 param_names 应该设为 [‘abc’, ‘x_y_z’]。而param_values 是一个 list 的 list,要求 len(param_values)==len(param_names)。其中 param_values[i] 是 param_names[i] 的名字所对应的调参值。举例来说,我们需要把 g.abc 和 g.x_y_z 分别赋予 [‘a’,’b’, ‘c’] 和 [1,2] 的值进行回测,那么就应该输入 param_values=[[‘a’,’b’,’c’], [1,2]]。函数会自动列举所有这些参数选项的组合进行回测,在上面的例子中就会产生六个回测,分别对应参数值 (g.abc, g.x_y_z) 等于 (‘a’,1)、(‘a’,2)、(‘b’,1)、(‘b’,2)、(‘c’,1)、(‘c’,2)。
在所有参数对应的回测运行完成之后,函数会整理数据并存储 pickle 文件。
数据内容和读取文件
当我们运行完 parameter_analysis.get_backtest_data() 之后,除了保存在 pickle 文件,还会直接保存在这个类下面。我们逐一介绍这些项目:
parameter_analysis.algorithm_id 顾名思义就是我们输入的策略编码。
parameter_analysis.params_df 是包含了所有回测使用的参数的 DataFrame。其中,横行 index 是回测的编号,代表它是第几个被运行的回测,从 0 开始计数。竖列 columns 是之前输入的 param_names 的名字。df 中的内容是回测相对应的参数数值。举例来说,param_names = [‘abc’, ‘x_y_z’],param_values=[[‘a’,’b’,’c’], [1,2]] 时,生成的 Data Frame 如下:.
parameter_analysis.evaluations 是一个 dict 的 dict。如果 i 是一个回测的编号(对应params_df 里的 index),那么 parameter_analysis.evaluations[i] 就是这个回测的各项指标,比如收益率、夏普比率、最大回撤。
当然,为了可以更直观地查看数据,还有 DataFrame 版的 parameter_analysis.evaluations_df。它是 evaluations 的 df 版,并且还带有 params_df 中的参数数据。
parameter_analysis.backtest_ids 是一个 dict 的 strings,keys 依旧是回测的编号,对应的 内容是该回测的 url 编码。
parameter_analysis.benchmark_id 是自定义基准回测的编码,如果没提供则是空。
parameter_analysis.dates 是一个 list 的 strings,是按序排列的回测中的每一个交易日。
parameter_analysis.benchmark_returns 是一 list 的 float,是基准的收益率数据,对应着 dates 中相同位置的日期。
parameter_analysis.returns 和 excess_returns 和 log_returns 和 log_excess_returns 都是一 dict 的 list,keys 是各个回测的编号,而相对应的 list 分别是该策略的收益率和超额收益率和对数收益率和对数超额收益率。其中,超额收益率是用回测净值除以基准净值计算而出,对数超额收益率是超额收益率的对数。和 benchmark_returns 一样,list 中的数据对应着 dates 中相同位置的日期。
最后两个特别计算的指标是 self.excess_max_drawdown 和 self.excess_annual_return。这两个都是 dict,keys 是回测的编号,内容分别是策略超额收益曲线的最大回撤和策略超额收益曲线的年化收益率。这两个指标的意义在于,有时我们想看策略相对于基准的强弱,所以在排除掉基准的影响之后可以进行一些有意义的分析。
如果我们通过 parameter_analysis.get_backtest_data() 运行了回测并且获取了各种数据,但是关闭了研究模块,那么在再次打开研究继续分析时不需要再重新把回测都运行一遍,可以使用读取 pickle 文件的函数把已保存的数据读取。
使用 parameter_analysis.read_backtest_data(file_name=’results.pkl’) 即可读取数据。这里 file_name 是被读取的文件名,如果不是默认的 results.pkl 的话则需要另行输入。在读取之后,parameter_analysis 中的每个项目会变成上面所讲的内容,可以进行调取或者使用接下来的介绍的功能。
一些可视化功能
在获取数据之后,我们可以把一些指标或者曲线画出来,便于进行观察和分析。
首先是 parameter_analysis.get_eval4_bar(sort_by=[]) 函数,这个函数会以 bar 图的形式画出回测的收益率、最大回撤、夏普比率和波动率四个图表。每个图表上从左到右是每一个回测,默认是按照回测编号进行排列,但是也可以输入 sort_by 进行自定义排列。sort_by 是一个 list 的 strings,它是参数名称 param_names 的一个子集,意义为按照这些变量进行排序。举例来说,如果 sort_by=[‘abc’],那就是按照 ‘abc’ 参数从小到大排列回测,然后划出四张图表;如果 sort_by=[‘abc’, ‘x_y_z’],那就是先按照 ‘abc’ 进行排列,然后再按照 ‘x_y_z’ 进行排列(以 ‘x_y_z’ 排序后,组内按照 ‘abc’ 排序),排好之后画图。下图是按照 BP 指标大小排列出来的, get_eval4_bar() 函数使用默认值既可。
接下来是 parameter_analysis.get_eval(sort_by=[]) 和 parameter_analysis.get_excess_eval(sort_by=[])。第一个函数会画出各个回测年化收益率和最大回撤,而第二个函数会画出超额年化收益率和超额最大回撤,sort_by 的功能和之前解释的一样。这两个函数的效果还是直接举例画出来最直接。下图是以 BP 单因子策略十分位结果的 paramter_analysis.get_eval() 得出的图。
最后是画出回测的收益曲线、超额收益曲线、对数收益曲线以及超额对数收益曲线的函数,分别是 parameter_analysis.plot_returns() 、plot_excess_returns()、plot_log_returns() 和 plot_log_excess_returns()。这些函数没有输入,直接用就行。图是以 BP 单因子策略十分位结果的 paramter_analysis.plot_returns() 得出的图。
深入研究举例
作为示例,这里我们以 BP 因子为例将策略分为 (0,10),(10,20),… ,(90,100) 十个分位区间进行回测并分析。
在回测之前我们需要制定一个自定义基准。在三篇因子研究分析中,我们发现很多因子不论是最大 5% 还是最小 5% 的分位都很轻松地跑赢了上证指数。经过一些分析,可以发现原因在于上证指数的指数构成偏大盘股,并且按照市值进行加权,所以该指数的小市值暴露度很低;然而我们回测的策略是把所有分位内的股票进行等权分配,小市值暴露度比上证指数要大,所以从这点来看可比性不高。为了剔除市值影响造成的策略和基准之间的差异,我们构建一个“等权全指”作为自定义基准,就是每月初将资金等权分配于所有二十一个交易日没停牌的股票之间,的到回测如下,下图基准是上证指数。
对应我们调用的指令如下,初始化 parameter_analysis 类并且启动【量化课堂】因子研究系列之一 估值和资本结构因子中的 BP 因子回测,按 10 个百分点进行分位(共十个回测),并使用上述的等权全指作为基准:
# 初始化 parameter_analysis 类,设定回测策略 id pa = parameter_analysis('bce2e5c55b3b631f91985c9bf113414f')[这个怎么表述?可以定义简化名称调用class?]# 运行回测pa.get_backtest_data(file_name = 'results.pkl', running_max = 10, benchmark_id = 'ae0684d86e9e7128b1ab9c7d77893029', start_date = '2006-02-01', end_date = '2016-11-01', frequency = 'day', initial_cash = '2000000', param_names = ['factor', 'quantile'], param_values = [['BP'], tuple(zip(range(0,100,10), range(10,101,10)))] )
我们先进行回测,然后画出收益图。首先是收益曲线 parameter_analysis.plot_returns(),这里回测编号从 0 到 9 是 BP 值从小到大的回测。可以看出这个因子是具有单调性质的,也就是说 BP 值更大的股票表现一般比 BP 小的股票表现更好。但是,BP 指标的单调性并不是绝对的,比如 (10,20) 分位的股票的收益率是最高的 。
再用 parameter_analysis.get_eval4_bar() 画出策略的年化收益、最大回撤、夏普率和波动率,可以看出 BP 策略各分位表现出了收益不单调,对应的其他指标也并不单调。能够简单总结 BP 较小还是体现公司的股票回报率会好于 BP 较高的股票,伴随的是收益较高反而最大回撤较小。夏普率的变动与回报率较为一致,但是波动率变动在不同分位间差距不明显。
我们使用正的年化收益与负的最大回撤构建柱状图,parameter_analysis.get_eval():
加入新的基准后可以计算对于新基准回报率的超额收益率。进一步分析,我们使用 parameter_analysis.plot_excess_returns() 画出几个回测的超额收益曲线。这个图给我们一些有用的信息:BP 值最小的 10% 的股票的波动率极大,并且收益并没有超出基准太多,说明选这个区间的股票进行投资也许并不是很好。第二现象是,几个分位的回测在 06 年和 10 之间的超额收益有相互拉开,但在 10 年之后不同分位并没有明显的超额收益区别,说明这个因子在 10 年之后基本已经失效。
如【更新说明】新超额收益 和 对数轴的思路,我们也生成对数轴回报率,使用函数 parameter_analysis.plot_log_returns() 获取。如对数轴说明文章中介绍的,这样的图形在收益膨胀了现值后仍然可以明晰波动的相对大小。
结合超额收益与对数轴,可以通过 parameter_analysis.plot_log_excess_returns() 获得超额收益的对数轴图。如果是超额收益始终持续放大的过程,超额收益的对数轴图形将会是斜线上升的,然而下图中收益持平说明在相当长的一段时间中没有变动。
最后,可以通过 parameter_analysis.get_excess_eval() 获得超额回报和超额回报最大回撤的柱状图。可以看出,超额回报要明显小于策略原始回报,因为剔除了大盘整体的收益。同时,最大回撤也拉开了距离;按照策略净值算的最大回撤由于经历过同样的股灾,所以最大回撤都是百分之七十多,但在剔除大盘影响之后就能比较清晰地对比超额收益的回撤。整体来说,按 BP 选股的各个分为超额收益都不高,并且相对于基准的超额回撤都不小。
但这并不说明 BP 因子是一个无用的指标,只是说我们不能单单使用 BP 作为衡量标准并希望能得到超额收益,也许分行业分别计算 BP 或者结合其他的一些方法依然可以获得较好的收益。
小结
本文提供了一个使用研究模块调用回测结果进行调参并且研究分析的代码框架,其具有较好的兼容性和拓展性,细节之处还需要各位测试了解。在接下来的文章里,我们将使用次框架对因子系列文章 (【量化课堂】因子研究系列之一 估值和资本结构因子、【量化课堂】因子研究系列之二 成长因子和【量化课堂】因子研究系列之三 技术因子)中的因子按分位排列进行回测并深入地分析这些因子的收益效果,敬请期待。
本文参考了多篇社区优秀帖子,这里一并感谢:
研究调用回测:【重磅更新】研究模块调用回测功能
回测排队运行:多个回测同时优化,改改列表和算法ID就行 by zhao
结果数据保存:JQ平台如何把DataFrame对象pickle或者json出来?
#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# 定义类'参数分析'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 = 'f16629492d6b6f4040b2546262782c78' 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=[], # 回测中每个变量的备选参数值 python_version = 2, # 回测的python版本 ):# 当此处回测策略的 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), python_version = python_version )# 记录运行中 i 回测的回测 idrunning_backtests[i] = backtest# 计数器计数运行完的数量 pointer = pointer+to_run# 获取回测结果failed = []finished = []# 对于运行中的回测,key 为 to_run_df 中所有排列组合中的序数for key in running_backtests.keys():# 研究调用回测的结果,running_backtests[key] 为运行中保存的结果 idback_id = running_backtests[key]bt = get_backtest(back_id)# 获得运行回测结果的状态,成功和失败都需要运行结束后返回,如果没有返回则运行没有结束status = bt.get_status()# 当运行回测失败if status == 'failed':# 失败 list 中记录对应的回测结果 idprint ('回测失败 : https://www.joinquant.com/algorithm/backtest/detail?backtestId='+back_id)failed.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=[], # 对应每个变量的备选参数 python_version = 2 ):# 调运排队回测函数,传递对应参数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, python_version = python_version )# 回测结果指标中加入 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_excess_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.excess_returns[key])), self.excess_returns[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(111)# 作图for key in self.returns.keys():ax.plot(range(len(self.log_returns[key])), self.log_returns[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(111)# 作图for key in self.returns.keys():ax.plot(range(len(self.log_excess_returns[key])), self.log_excess_returns[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(111)# 生成图超额收益率的最大回撤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(111)# 生成图超额收益率的最大回撤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))
#2 设定回测的 策略id pa = parameter_analysis('2e90ddc1230314ee5e4a00675c55ecd3')
#3 运行回测pa.get_backtest_data(file_name = 'results.pkl', # 保存回测结果的Pickle文件名 running_max = 10, # 同时回测的最大个数,可以通过积分商城兑换 benchmark_id = None, # 基准的回测ID,注意是回测ID而不是策略ID,为None时为策略中使用的基准 start_date = '2016-02-01', #回测开始时间 end_date = '2016-11-01', #回测结束时间 frequency = 'day', #回测频率,支持 day, minute, tick initial_cash = '2000000', #初始资金 param_names = ['factor', 'quantile'], #变量名称 param_values = [['BP'], tuple(zip(range(0,100,10), range(10,101,10)))], #变量对应的参数 python_version = 3 # 回测python版本 )
【已完成|运行中|待运行】: [0|0|10]. [0|10|0]. [0|10|0]. [0|10|0]. [0|10|0]. [0|10|0]. [0|10|0]. [1|9|0]. [6|4|0]. [9|1|0]. [9|1|0]. [9|1|0]. 【回测完成】总用时:105秒(即0.03小时)。
#4 数据读取pa.read_backtest_data('results.pkl')
#5 回测参数的 Dataframepa.params_df
factor | quantile | |
---|---|---|
0 | BP | (0, 10) |
1 | BP | (10, 20) |
2 | BP | (20, 30) |
3 | BP | (30, 40) |
4 | BP | (40, 50) |
5 | BP | (50, 60) |
6 | BP | (60, 70) |
7 | BP | (70, 80) |
8 | BP | (80, 90) |
9 | BP | (90, 100) |
#6 查看回测结果指标pa.evaluations_df
factor | quantile | __version | algorithm_return | algorithm_volatility | alpha | annual_algo_return | annual_bm_return | *g_excess_return | *g_position_days | ... | max_leverage | period_label | profit_loss_ratio | sharpe | sortino | trading_days | treasury_return | turnover_rate | win_count | win_ratio | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | BP | (0, 10) | 101 | 0.2513076 | 0.2083657 | 0.150542 | 0.3629526 | 0.1992251 | 0.0005176242 | 119.0479 | ... | 0 | 2016-11 | 21.62704 | 1.549932 | 1.973999 | 181 | 0.0300274 | 0.008532295 | 246 | 0.9213483 |
1 | BP | (10, 20) | 101 | 0.2754474 | 0.2416627 | 0.1588703 | 0.3994029 | 0.1992251 | 0.0006287361 | 85.82759 | ... | 0 | 2016-11 | 5.825564 | 1.487208 | 1.870042 | 181 | 0.0300274 | 0.01402903 | 284 | 0.7867036 |
2 | BP | (20, 30) | 101 | 0.2756466 | 0.2576005 | 0.1502772 | 0.3997048 | 0.1992251 | 0.0006377947 | NaN | ... | 0 | 2016-11 | NaN | 1.396367 | 1.681388 | 181 | 0.0300274 | NaN | NaN | NaN |
3 | BP | (30, 40) | 101 | 0.2386496 | 0.2652116 | 0.09026268 | 0.3439461 | 0.1992251 | 0.000479524 | 66.10487 | ... | 0 | 2016-11 | 3.357298 | 1.146051 | 1.347272 | 181 | 0.0300274 | 0.01856834 | 253 | 0.6728723 |
4 | BP | (40, 50) | 101 | 0.2880337 | 0.2684 | 0.1632633 | 0.4185126 | 0.1992251 | 0.0006976703 | 66 | ... | 0 | 2016-11 | 3.498517 | 1.410255 | 1.646609 | 181 | 0.0300274 | 0.01877571 | 251 | 0.6820652 |
5 | BP | (50, 60) | 101 | 0.3063778 | 0.2716765 | 0.190655 | 0.4464919 | 0.1992251 | 0.000778694 | 65.12222 | ... | 0 | 2016-11 | 4.164675 | 1.496235 | 1.780469 | 181 | 0.0300274 | 0.0192487 | 289 | 0.7429306 |
6 | BP | (60, 70) | 101 | 0.2752783 | 0.2631897 | 0.1521409 | 0.3991466 | 0.1992251 | 0.0006441905 | 66.01852 | ... | 0 | 2016-11 | 4.050628 | 1.364592 | 1.660482 | 181 | 0.0300274 | 0.01759347 | 256 | 0.7211268 |
7 | BP | (70, 80) | 101 | 0.3225983 | 0.2764343 | 0.2164197 | 0.4713574 | 0.1992251 | 0.0008531318 | 72.7551 | ... | 0 | 2016-11 | 3.69159 | 1.560433 | 1.842131 | 181 | 0.0300274 | 0.01666349 | 242 | 0.7289157 |
8 | BP | (80, 90) | 101 | 0.2483731 | 0.2791272 | 0.1043786 | 0.3585398 | 0.1992251 | 0.0005376109 | 72.66529 | ... | 0 | 2016-11 | 2.346379 | 1.1412 | 1.353025 | 181 | 0.0300274 | 0.01487333 | 200 | 0.6644518 |
9 | BP | (90, 100) | 101 | 0.2792142 | 0.2861785 | 0.1481436 | 0.4051144 | 0.1992251 | 0.0006780246 | 84.50935 | ... | 0 | 2016-11 | 2.940914 | 1.275828 | 1.514055 | 181 | 0.0300274 | 0.01211476 | 216 | 0.7346939 |
10 rows × 33 columns
#7 回报率折线图 pa.plot_returns()
#8 超额收益率图 pa.plot_excess_returns()
#9 log回报率图 pa.plot_log_returns()
#10 超额收益率的 log 图pa.plot_log_excess_returns()
#11 回测的4个主要指标,包括总回报率、最大回撤夏普率和波动# get_eval4_bar(self, sort_by=[])pa.get_eval4_bar()
#12 年化回报和最大回撤,正负双色显示# get_eval(self, sort_by=[])pa.get_eval()
#13 超额收益的年化回报和最大回撤# 加入新的benchmark后超额收益和# get_excess_eval(self, sort_by=[])pa.get_excess_eval()
# test 测试最后bar图中的sort_by对应内容param_names=['abc','x_y_z']param_values=[['a','b','c'], [1,2]]param_combinations = list(itertools.product(*param_values))to_run_df = pd.DataFrame(param_combinations)to_run_df.columns = param_names# to_run_df.ix[1].to_dict()to_run_df# sort_by = ['abc']sort_by = ['abc', 'x_y_z']# sort_by = ['x_y_z']# sort_by = ['x_y_z','abc']sorted_params = to_run_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.indexsorted_params
abc | x_y_z | |
---|---|---|
0 | a | 1 |
2 | b | 1 |
4 | c | 1 |
1 | a | 2 |
3 | b | 2 |
5 | c | 2 |