LOADING
887 字
4 分钟
vectorbt学习_09WalkForwardOptimization

滚动窗口法回测

获取行情,可视化#

print(price)
date
2017-01-03 00:00:00+00:00 2.028
2017-01-04 00:00:00+00:00 2.046
2017-01-05 00:00:00+00:00 2.043
2017-01-06 00:00:00+00:00 2.035
2017-01-09 00:00:00+00:00 2.040
...
2022-12-26 00:00:00+00:00 2.612
2022-12-27 00:00:00+00:00 2.642
2022-12-28 00:00:00+00:00 2.649
2022-12-29 00:00:00+00:00 2.635
2022-12-30 00:00:00+00:00 2.649
Name: Close, Length: 1459, dtype: float64
price.vbt.plot().show_svg()

del01

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()

del01

(in_price, in_indexes), (out_price, out_indexes) = roll_in_and_out_samples(price, **split_kwargs)
print(in_price.shape, len(in_indexes)) # in-sample
print(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
)

del01

可见,in-sample,和out-sample加起来恰好1年。

print(in_indexes)

del01

每个周期的初始日期怎么来的呢?
根据可视化图形,可以看出,

初始日期差异=(总天数-单个周期天数)/(总周期数-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:00
2017-02-14 00:00:00+00:00
DatetimeIndex(['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_idx
0 0.954906
1 0.685842
2 0.868976
,,
27 0.383577
28 0.334316
29 0.088459
Name: 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 ranges
in_sharpe = simulate_all_params(in_price, windows, **pf_kwargs) #评估window里各参数组合的sharpe值
print(in_sharpe.shape)
(23400,)
print(in_sharpe)
fast_window slow_window split_idx
10 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.778645
Name: 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()].index
in_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()

del01

测试集实施hold策略#

out_hold_sharpe = simulate_holding(out_price, **pf_kwargs)
print(out_hold_sharpe)
split_idx
0 0.601067
1 0.850085
2 -0.844581
,,,
28 -1.233394
29 -0.335148
Name: sharpe_ratio, dtype: float64

测试集的参数探测

# Simulate all params for out-sample ranges
out_sharpe = simulate_all_params(out_price, windows, **pf_kwargs)
print(out_sharpe)
fast_window slow_window split_idx
10 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.405472
Name: sharpe_ratio, Length: 23400, dtype: float64
def 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)

del01

训练集,测试集上各策略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()

del01

vectorbt学习_09WalkForwardOptimization
/posts/quant/d1efe833/
作者
思想的巨人
发布于
2023-10-28
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时