887 字
4 分钟
vectorbt学习_09WalkForwardOptimization
滚动窗口法回测
获取行情,可视化
print(price)date2017-01-03 00:00:00+00:00 2.0282017-01-04 00:00:00+00:00 2.0462017-01-05 00:00:00+00:00 2.0432017-01-06 00:00:00+00:00 2.0352017-01-09 00:00:00+00:00 2.040 ...2022-12-26 00:00:00+00:00 2.6122022-12-27 00:00:00+00:00 2.6422022-12-28 00:00:00+00:00 2.6492022-12-29 00:00:00+00:00 2.6352022-12-30 00:00:00+00:00 2.649Name: Close, Length: 1459, dtype: float64
price.vbt.plot().show_svg()
rolling_split数据切分,可视化
split_kwargs = dict( n=30, window_len=365 * 2,#每个case总长365*2,包含set_lens的180=730除250交易日/年,大致对应了3年 set_lens=(180,), left_to_right=False) # 30 windows, each 2 years long, reserve 180 days for test
def roll_in_and_out_samples(price, **kwargs): return price.vbt.rolling_split(**kwargs)roll_in_and_out_samples(price, **split_kwargs, plot=True, trace_names=['in-sample', 'out-sample']).show_svg()
(in_price, in_indexes), (out_price, out_indexes) = roll_in_and_out_samples(price, **split_kwargs)
print(in_price.shape, len(in_indexes)) # in-sampleprint(out_price.shape, len(out_indexes)) # out-sample
(550, 30) 30 #30对应上面的n=30,365*2=730-180=550(180, 30) 30 #180对应上面的set_lens如果使用如下配置参数
split_kwargs = dict( n=30, window_len=250, # 1年的交易日 set_lens=(70,),# 70天用来做测试集 left_to_right=False)
可见,in-sample,和out-sample加起来恰好1年。
print(in_indexes)
每个周期的初始日期怎么来的呢?
根据可视化图形,可以看出,
初始日期差异=(总天数-单个周期天数)/(总周期数-1)=》diff=(1459-365*2)/(30-1)=25.13
验证一下print(in_indexes[0][0])print(in_indexes[1][0])print(in_indexes[0][25:27]) # 观察in_indexes[1][0]位于in_indexes[0][0]的第几个位置,此处应该为26
2017-01-03 00:00:00+00:002017-02-14 00:00:00+00:00DatetimeIndex(['2017-02-14 00:00:00+00:00', '2017-02-15 00:00:00+00:00'], dtype='datetime64[ns, UTC]', name='split_0', freq=None)可见,2个区间的起始日期in_indexes[0][0],in_indexes[1][0],相差的交易日个数,恰好为26持有收益
pf_kwargs = dict( direction='both', # long and short freq='d')
def simulate_holding(price, **kwargs): pf = vbt.Portfolio.from_holding(price, **kwargs) return pf.sharpe_ratio()
in_hold_sharpe = simulate_holding(in_price, **pf_kwargs)print(in_hold_sharpe)
split_idx0 0.9549061 0.6858422 0.868976,,27 0.38357728 0.33431629 0.088459Name: sharpe_ratio, dtype: float64含义为,30个时间区间的sharpe值,由于采用hold策略,只要买入没卖出,可以看做价格本身的sharpe值from_holding
close = pd.Series([1, 2, 3, 4, 5])pf = vbt.Portfolio.from_holding(close)pf.final_value()
500 #第一天买入100块的,100/1=100份,第五天卖出得到总资产500,双均线
def simulate_all_params(price, windows, **kwargs): fast_ma, slow_ma = vbt.MA.run_combs(price, windows, r=2, short_names=['fast', 'slow']) # 指标参数组合构造 entries = fast_ma.ma_crossed_above(slow_ma) #指标转买卖信号 exits = fast_ma.ma_crossed_below(slow_ma) pf = vbt.Portfolio.from_signals(price, entries, exits, **kwargs) #信号组合的收益评估 return pf.sharpe_ratio()#回测的sharpe值
# Simulate all params for in-sample rangesin_sharpe = simulate_all_params(in_price, windows, **pf_kwargs) #评估window里各参数组合的sharpe值
print(in_sharpe.shape)(23400,)
print(in_sharpe)fast_window slow_window split_idx10 11 0 1.202383 1 1.358301 2 1.629335 3 1.760235 4 1.258015 ...48 49 25 0.610420 26 0.738654 27 0.602987 28 0.689741 29 0.778645Name: sharpe_ratio, Length: 23400, dtype: float64各时间区间sharpe最高的参数组
def get_best_index(performance, higher_better=True): if higher_better: return performance[performance.groupby('split_idx').idxmax()].index return performance[performance.groupby('split_idx').idxmin()].indexin_best_index = get_best_index(in_sharpe)
print(in_best_index) #最优参数组MultiIndex([(21, 35, 0), (21, 35, 1), (11, 14, 2), ,,, (45, 48, 28), (45, 48, 29)], names=['fast_window', 'slow_window', 'split_idx'])
def get_best_params(best_index, level_name): return best_index.get_level_values(level_name).to_numpy()in_best_fast_windows = get_best_params(in_best_index, 'fast_window')in_best_slow_windows = get_best_params(in_best_index, 'slow_window')in_best_window_pairs = np.array(list(zip(in_best_fast_windows, in_best_slow_windows)))
print(in_best_window_pairs) #最优参数组,去掉了时间区间信息[[21 35] [21 35] [11 14],,, [45 48] [45 48]]参数变化可视化
pd.DataFrame(in_best_window_pairs, columns=['fast_window', 'slow_window']).vbt.plot().show_svg()
测试集实施hold策略
out_hold_sharpe = simulate_holding(out_price, **pf_kwargs)
print(out_hold_sharpe)split_idx0 0.6010671 0.8500852 -0.844581,,,28 -1.23339429 -0.335148Name: sharpe_ratio, dtype: float64测试集的参数探测
# Simulate all params for out-sample rangesout_sharpe = simulate_all_params(out_price, windows, **pf_kwargs)
print(out_sharpe)fast_window slow_window split_idx10 11 0 0.495589 1 -1.111064 2 -2.526381 3 -1.415069 4 -1.979918 ...48 49 25 -1.560619 26 -3.410102 27 -1.743216 28 -1.648796 29 -0.405472Name: sharpe_ratio, Length: 23400, dtype: float64def simulate_best_params(price, best_fast_windows, best_slow_windows, **kwargs): fast_ma = vbt.MA.run(price, window=best_fast_windows, per_column=True) slow_ma = vbt.MA.run(price, window=best_slow_windows, per_column=True) entries = fast_ma.ma_crossed_above(slow_ma) exits = fast_ma.ma_crossed_below(slow_ma) pf = vbt.Portfolio.from_signals(price, entries, exits, **kwargs) return pf.sharpe_ratio()# Use best params from in-sample ranges and simulate them for out-sample ranges# 入参:in_best_fast_windows,in_best_slow_windows是蓝色数据集计算出的 最佳参数组合# 整体效果是,用历史数据得到最佳fast,slow参数组合,然后用户未来一段时间上out_test_sharpe = simulate_best_params(out_price, in_best_fast_windows, in_best_slow_windows, **pf_kwargs)
print(out_test_sharpe)
训练集,测试集上各策略sharpe中位数,最大值变化,可视化
cv_results_df = pd.DataFrame({ 'in_sample_hold': in_hold_sharpe.values, 'in_sample_median': in_sharpe.groupby('split_idx').median().values, 'in_sample_best': in_sharpe[in_best_index].values, 'out_sample_hold': out_hold_sharpe.values, 'out_sample_median': out_sharpe.groupby('split_idx').median().values, 'out_sample_test': out_test_sharpe.values})
color_schema = vbt.settings['plotting']['color_schema']
cv_results_df.vbt.plot( trace_kwargs=[ dict(line_color=color_schema['blue']), dict(line_color=color_schema['blue'], line_dash='dash'), dict(line_color=color_schema['blue'], line_dash='dot'), dict(line_color=color_schema['orange']), dict(line_color=color_schema['orange'], line_dash='dash'), dict(line_color=color_schema['orange'], line_dash='dot') ]).show_svg()
vectorbt学习_09WalkForwardOptimization
/posts/quant/d1efe833/ 部分信息可能已经过时