历史文档

回测数据的深入分析

由clearyf创建,最终由clearyf 被浏览 1020 用户

导语

本文介绍如何对一个回测结果进行深入分析。

新建一个可视化AI策略

我们先构建一个可视化AI策略,如下所示。

回测结果

回测结果一般指策略运行完毕之后输出的能够综合反映策略效果的综合图表,如下所示:

可以看出,回测结果包括收益概括、交易详情、每日持仓、输出日志。

对回测结果进行深入分析需要一定的基础知识,如下:

回测结果的简单解读参考第三点即可,本文主要介绍怎样对回测数据进行深入分析以验证回测结果。

读取回测详细数据

示例代码:

m19.raw_perf.read_df()

输出结果: 回测详细数据全部保存在m6.raw_perf对象中,为DataSource类型,因此需要使用read_df方法读取数据。该数据为时间序列数据,按交易日记录了详细信息。

回测信息解读:

通过查看DataFrame的列名可知回测数据主要包含了如下各列:

其中主要包括三块数据:

策略相关:

  • 策略累计收益率(algorithm_period_return)、 策略收益波动率(algo_volatility)
  • 基准累计收益率(benchmark_period_return)、基准收益波动率(benchmark_volatility)
  • 策略每日收益率(returns)
  • 阿尔法(alpha)、贝塔(beta)、胜率(win_percent)
  • 夏普比率(sharpe)、信息比(information)、索提纳比率(sortino)。

账户权益:

  • 每日账户初始市值(starting_value),每日账户期末市值(ending_value)
  • 每日账户初始现金(starting_cash),每日期末账户现金(ending_cash)
  • 每日账户期末总资产(portfolio_value)
  • 每日账户盈亏(pnl),每日账户盈亏比例(pnl_ratio)
  • 每日持仓详情(positions)
  • 每日多头市值(long_value)、每日空头市值(short_value)、每日多头+空头净市值(ending_value)
  • 每日多头风险暴露(long_exposure) 、每日空头风险暴露(short_exposure)

交易相关:

  • 每日订单(orders)
  • 每日交易成本(transactions)
  • 每日交易时间段(trade_times)

在策略回测结果图上,所有数据都是来自回测详细数据。可以通过代码获取对应数据,举例如下:

# 获取最后一日的夏普比率
m19.raw_perf.read_df()['sharpe'][-1]

# 获取2015-01-06的日收益率数据
m19.raw_perf.read_df().ix['2015-01-06']['returns'][0]

回测数据深入分析技巧

a. 数据下载

b. 检查策略杠杆是否正常

因为回测机制允许融资交易,当持仓市值超过总资金时借用资金杠杆,此时杠杆比率大于1,一方面可以通过 m19.raw_perf.read_df()['gross_leverage'] 查询,一方面可以观察回测结果图。

在上图中,下方的绿色曲线表示策略的杠杆比率,可以看出杠杆比率几乎都是小于1,表明没有借用资金杠杆,同时看出,杠杆比率在前期缓慢提高,这正是策略开始在建仓期间不断买入股票没有卖出直接相关。

c. 抽样检查买入/卖出股票是否正确

以买入的股票为例,通过回测结果查询的实际成交情况:

通过测试集预测结果验证买入股票: 因为是当日收盘以后,才能预测出下个交易日的股票排序,因此测试集预测结果当日的股票排序应该和下一个交易日的买入股票相对应。通过上面两图的对比,抽样检查买入股票是正确的。

d. 抽样检查买入价格是否是正确

通过交易详情,我们可以知道每只股票的买入价格,因此我们抽样验证股票买入价格,检查是否存在夸大策略收益的情形。我们先看2015-01-06买入的几只股票的买入价。

通过数据API单独获取数据检验成交价格,之所以获取开盘价,是因为股票成交策略回测假设是开盘买入。

通过两图的对比,我们发现成交价格是正确的,回测过程中并没有偷价漏价的情形。

e. 检查每只股票的成交金额是否正确

之所以检查成交金额,实际上是检查每只股票的仓位资金配置是否和策略的资金配置思想相一致。我们先看2016-01-05买入的5只股票的成交情况。

可以看出,当日买入股票的资金配置按高到低依次是:300391.SZA、300038.SZA、300367.SZA、 300302.SZA 和 300109.SZA 。 我们再看看,在测试集上当天的股票买入顺序是怎样的。

股票的买入顺序是根据股票得分(score)排序所得,得分高的股票优先买入,买入的仓位也会较大。从测试集上的股票排序顺序来看,和回测交给交易详情是对应的。

f. 检查手续费计算是否正确

手续费对策略收益率具有直接影响,如果交易频繁,持仓时间短,手续费设置不合理将会高估策略收益。因此需要对交易手续费进行检查。我们先看2016-01-05实际买入手续费。

在策略设置(initialize函数)中,策略手续费设置如下:

 context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))

可以看出,买入股票交易费用为成交金额的万分之三,不足5元按5元收取。在2016-01-05买入的股票中,成交金额最大的股票为300391.SZA,成交金额为67700.69元,按照万三的手续费计算,手续费为20.31元。

如下图所示,我们再来看2016-01-13日的股票交易记录,以002127.SZA为例由于当日买入的总金额为493.43,按照万三的手续费计算,手续费不足5元按5元收取。

因此在手续费这块,回测试没有问题的。

g. 通过代码查看回测细节

查看回测细节不仅可以通过回测结果点击可视化或是下载方式查询,还可以通过代码查询,查询的数据将会更加详细、深入,因为所有结果都是在m19.raw_perf.read_df()中,所以通过该函数就可以知道全部回测细节。

perf = m12.raw_perf.read_df()
period_data = perf['2016-02-01': '2016-02-15']
print(period_data.columns)
days = period_data.shape[0]
for i in range(days):
    cd = period_data.ix[i]
    dt = cd['period_open'] # 日期
    starting_cash = cd['starting_cash'] # 开始现金
    ending_cash = cd['ending_cash'] # 结束现金
    portfolio_value = cd['portfolio_value'] # 权益值
    gross_leverage = cd['gross_leverage'] # 杠杆
    print("{}".format(dt))
    # 持仓
    positions = cd['positions']
    positions_str = ''
    for p in positions:
        positions_str += '    '+"{}/{}/{}/{},".format(p['sid'].symbol, p['amount'], p['last_sale_price'],p['cost_basis'])
    print("positions: {}".format(positions_str))
    # 订单
    orders = cd['orders']
    orders_str = ''
    for o in orders:
        if o['status'] != 0:
            continue 
        orders_str += "{}/{}/{}/{},".format(o['sid'].symbol, o['amount'], o['filled'], o['commission'])
    print("orders: {}".format(orders_str))
    # 成交
    transactions = cd['transactions']
    transactions_str = ''
    for t in transactions:
        transactions_str += "{}/{}/{},".format(t['sid'].symbol, t['amount'], round(t['price'], 2))
    print("transactions: {}".format(transactions_str))
    print("\n") # 换行

运行上方代码后输出结果为一个时间段上的每日持仓、每日订单、每日交易三个详细的信息,通过每日订单还能查询每个订单的具体成交结果,包括订单是否完全成交、是否有未成交订单、是否有遇到涨跌停、停牌情形无法成交订单。如下图所示:

计算绩效指标

根据回测结果数据计算绩效指标:

当我们获取到回测模块的运行结果的DataFrame后,可以根据结果数据和empyrical库自行计算alpha、beta、annual_return_ratio、sharp_ratio、return_volatility和max_drawdown等指标,注意我们传入计算的是策略日收益率序列returns而不是策略累计收益率序列algorithm_period_return。 日收益率序列=(1+累计收益率序列).pct_change() [details=点击查看绩效指标计算代码]

import empyrical
# 统计策略指标
def get_stats(results):
    return_ratio  = empyrical.cum_returns_final(results.returns)
    annual_return_ratio  = empyrical.annual_return(results.returns)
    sharp_ratio = empyrical.sharpe_ratio(results.returns,0.035/252)
    return_volatility = empyrical.annual_volatility(results.returns)
    max_drawdown  = empyrical.max_drawdown(results.returns)
    benchmark_returns = (results.benchmark_period_return+1)/(results.benchmark_period_return+1).shift(1)-1
    alpha, beta =empyrical.alpha_beta_aligned(results.returns, benchmark_returns)
    
    return {
      'return_ratio': return_ratio,
      'annual_return_ratio': annual_return_ratio,
      'beta': beta,
      'alpha': alpha,
      'sharp_ratio': sharp_ratio,
      'return_volatility': return_volatility,
      'max_drawdown': max_drawdown,
    }
get_stats(m.raw_perf.read_df()) # 这里m是Trade模块的模块号,自行替换即可

如果需要计算策略相对基准的超额日收益率序列的绩效指标,可以先计算策略日收益率、基准日收益率,然后计算超额日收益率序列,再用empyrical库计算序列绩效。

点击查看相对收益率绩效计算代码

import empyrical
# 统计策略指标
def get_stats(results):
    return_ratio  = empyrical.cum_returns_final(results.relative_returns)
    annual_return_ratio  = empyrical.annual_return(results.relative_returns)
    sharp_ratio = empyrical.sharpe_ratio(results.relative_returns,0.035/252)
    return_volatility = empyrical.annual_volatility(results.relative_returns)
    max_drawdown  = empyrical.max_drawdown(results.relative_returns)
    benchmark_returns = results.benchmark_dailyret
    alpha, beta =empyrical.alpha_beta_aligned(results.relative_returns, benchmark_returns)
    
    return {
      'return_ratio': return_ratio,
      'annual_return_ratio': annual_return_ratio,
      'beta': beta,
      'alpha': alpha,
      'sharp_ratio': sharp_ratio,
      'return_volatility': return_volatility,
      'max_drawdown': max_drawdown,
    }

df['algo_dailyret'] = (m.raw_perf.read_df()['algorithm_period_return']+1).pct_change().fillna(0).to_frame()
df['benchmark_dailyret'] = (m.raw_perf.read_df()['benchmark_period_return']+1).pct_change().fillna(0).to_frame()
df['relative_returns'] = df['algo_dailyret'] - df['benchmark_dailyret']
get_stats(df)

小结: 本文为大家提供分析回测数据时的几个技巧,帮助大家对回测数据有更加深刻的认识,让大家在对回测数据进行深入分析时有迹可循。

标签

投资策略金融市场风险评估收益率夏普比率
评论
  • 计算每个月的单利收益如何计算呢?
  • mark
{link}