本文在上一篇文章(DMA之六滑窗网格参数优选)基础上。
增加退出模块,虽然双均线本身可以生成退出信号,但是其信号大多滞后的,无法实现止损,跟踪止损,止盈等功能。
本文增加止损,跟踪止损,止盈等的回测。
本篇文章部分图示有误,以下一篇为准<31dma之九滑窗网格参数优选>31dma之九滑窗网格参数优选>
01,基础配置信息
#conda envs:vectorbt_envimport warningsimport vectorbt as vbtimport numpy as npimport pandas as pdfrom datetime import datetime, timedeltaimport pytzfrom dateutil.parser import parseimport ipywidgets as widgetsfrom copy import deepcopyfrom tqdm import tqdmimport imageiofrom IPython import displayimport plotly.graph_objects as goimport itertoolsimport dateparserimport gcimport mathfrom tools import dbtools
warnings.filterwarnings("ignore")
pd.set_option('display.max_rows',500)pd.set_option('display.max_columns',500)pd.set_option('display.width',1000)02,行情获取和可视化
a,时间交易参数配置
# Enter your parameters hereseed = 42symbol = '002594.XSHE'metric = 'total_return'
start_date = datetime(2020, 1, 1, tzinfo=pytz.utc) # time period for analysis, must be timezone-awareend_date = datetime(2023,1,1, tzinfo=pytz.utc)time_buffer = timedelta(days=100) # buffer before to pre-calculate SMA/EMA, best to set to max windowfreq = '1D'
vbt.settings.portfolio['init_cash'] = 10000. # 100$vbt.settings.portfolio['fees'] = 0.0025 # 0.25%vbt.settings.portfolio['slippage'] = 0.0025 # 0.25%b,获取行情和行情mask
# Download data with time buffercols = ['Open', 'High', 'Low', 'Close', 'Volume']# ohlcv_wbuf = vbt.YFData.download(symbol, start=start_date-time_buffer, end=end_date).get(cols)
ohlcv_wbuf=dbtools.MySQLData.download(symbol).get() # 自带工具类查询assert(~ohlcv_wbuf.empty)ohlcv_wbuf = ohlcv_wbuf.astype(np.float64)
print("ohlcv_wbuf.shape:",ohlcv_wbuf.shape)print("ohlcv_wbuf.columns:",ohlcv_wbuf.columns)
# Create a copy of data without time bufferwobuf_mask = (ohlcv_wbuf.index >= start_date) & (ohlcv_wbuf.index <= end_date) # mask without buffer
ohlcv = ohlcv_wbuf.loc[wobuf_mask, :]
print("ohlcv.shape:",ohlcv.shape)
# Plot the OHLC dataohlcv.vbt.ohlcv.plot().show_svg() # 绘制蜡烛图# remove show_svg() to display interactive chart!ohlcv_wbuf.shape: (978, 5)ohlcv_wbuf.columns: Index(['Open', 'High', 'Low', 'Close', 'Volume'], dtype='object')ohlcv.shape: (728, 5)
20,网格参数-指标计算和可视化
仅可视化第一列
fast_windows = np.arange(10, 50,5)slow_multis = np.arange(1.5, 5.5, 0.5)print("fast_windows:",fast_windows)print("slow_multis:",slow_multis)
price=ohlcv_wbuf['Close']dualma = vbt.DualMA.run(price, fast_window=fast_windows,slow_multi=slow_multis,param_product=True)dualma = dualma[wobuf_mask]# there should be no nans after removing time bufferassert(~dualma.fast_ma.isnull().any().any())assert(~dualma.slow_ma.isnull().any().any())
print()print('dualma.fast_ma.head(3)')print(dualma.fast_ma.head(3))print('dualma.slow_ma.head(3)')print(dualma.slow_ma.head(3))
print()fig = ohlcv['Close'].vbt.plot(trace_kwargs=dict(name='Price'))fig = dualma.fast_ma.iloc[:,0].vbt.plot(trace_kwargs=dict(name="Fast MA col %s"%str(dualma.fast_ma.iloc[:,0].name)), fig=fig)fig = dualma.slow_ma.iloc[:,0].vbt.plot(trace_kwargs=dict(name="Slow MA col %s"%str(dualma.slow_ma.iloc[:,0].name)), fig=fig)fig.show_svg()fast_windows: [10 15 20 25 30 35 40 45]slow_multis: [1.5 2. 2.5 3. 3.5 4. 4.5 5. ]
dualma.fast_ma.head(3)dualma_fast_window 10 15 20 25 30 35 40 45dualma_slow_multi 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0date2020-01-02 00:00:00+00:00 46.665 46.665 46.665 46.665 46.665 46.665 46.665 46.665 45.824667 45.824667 45.824667 45.824667 45.824667 45.824667 45.824667 45.824667 45.3025 45.3025 45.3025 45.3025 45.3025 45.3025 45.3025 45.3025 44.9476 44.9476 44.9476 44.9476 44.9476 44.9476 44.9476 44.9476 44.816667 44.816667 44.816667 44.816667 44.816667 44.816667 44.816667 44.816667 44.594571 44.594571 44.594571 44.594571 44.594571 44.594571 44.594571 44.594571 44.5425 44.5425 44.5425 44.5425 44.5425 44.5425 44.5425 44.5425 44.440222 44.440222 44.440222 44.440222 44.440222 44.440222 44.440222 44.4402222020-01-03 00:00:00+00:00 46.972 46.972 46.972 46.972 46.972 46.972 46.972 46.972 46.128667 46.128667 46.128667 46.128667 46.128667 46.128667 46.128667 46.128667 45.5025 45.5025 45.5025 45.5025 45.5025 45.5025 45.5025 45.5025 45.1420 45.1420 45.1420 45.1420 45.1420 45.1420 45.1420 45.1420 44.964000 44.964000 44.964000 44.964000 44.964000 44.964000 44.964000 44.964000 44.723714 44.723714 44.723714 44.723714 44.723714 44.723714 44.723714 44.723714 44.6265 44.6265 44.6265 44.6265 44.6265 44.6265 44.6265 44.6265 44.555556 44.555556 44.555556 44.555556 44.555556 44.555556 44.555556 44.5555562020-01-06 00:00:00+00:00 47.138 47.138 47.138 47.138 47.138 47.138 47.138 47.138 46.456000 46.456000 46.456000 46.456000 46.456000 46.456000 46.456000 46.456000 45.7310 45.7310 45.7310 45.7310 45.7310 45.7310 45.7310 45.7310 45.3376 45.3376 45.3376 45.3376 45.3376 45.3376 45.3376 45.3376 45.112667 45.112667 45.112667 45.112667 45.112667 45.112667 45.112667 45.112667 44.871143 44.871143 44.871143 44.871143 44.871143 44.871143 44.871143 44.871143 44.7115 44.7115 44.7115 44.7115 44.7115 44.7115 44.7115 44.7115 44.660222 44.660222 44.660222 44.660222 44.660222 44.660222 44.660222 44.660222dualma.slow_ma.head(3)dualma_fast_window 10 15 20 25 30 35 40 45dualma_slow_multi 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0date2020-01-02 00:00:00+00:00 45.824667 45.3025 44.9476 44.816667 44.594571 44.5425 44.440222 44.6384 45.180455 44.816667 44.545676 44.440222 44.717692 45.135167 45.513134 46.025200 44.816667 44.5425 44.6384 45.135167 45.697429 46.307750 46.683111 47.0983 44.545676 44.6384 45.235806 46.025200 46.560460 47.0983 47.997679 48.61136 44.440222 45.135167 46.025200 46.683111 47.425238 48.410917 48.769630 48.8484 44.717692 45.697429 46.560460 47.425238 48.496066 48.803714 48.852357 49.430914 45.135167 46.307750 47.0983 48.410917 48.803714 48.892313 49.622778 50.14240 45.513134 46.683111 47.997679 48.769630 48.852357 49.622778 50.162574 50.3758222020-01-03 00:00:00+00:00 46.128667 45.5025 45.1420 44.964000 44.723714 44.6265 44.555556 44.6660 45.373636 44.964000 44.652162 44.555556 44.741538 45.119167 45.485821 45.984267 44.964000 44.6265 44.6660 45.119167 45.666714 46.291125 46.643333 47.0707 44.652162 44.6660 45.229677 45.984267 46.549080 47.0707 47.936429 48.56848 44.555556 45.119167 45.984267 46.643333 47.349905 48.362083 48.758074 48.8320 44.741538 45.666714 46.549080 47.349905 48.460984 48.784357 48.838471 49.366457 45.119167 46.291125 47.0707 48.362083 48.784357 48.878875 49.584500 50.12260 45.485821 46.643333 47.936429 48.758074 48.838471 49.584500 50.141139 50.3797782020-01-06 00:00:00+00:00 46.456000 45.7310 45.3376 45.112667 44.871143 44.7115 44.660222 44.6908 45.562273 45.112667 44.787297 44.660222 44.773846 45.116667 45.474478 45.950800 45.112667 44.7115 44.6908 45.116667 45.641143 46.267875 46.621889 47.0449 44.787297 44.6908 45.232742 45.950800 46.534598 47.0449 47.864554 48.52880 44.660222 45.116667 45.950800 46.621889 47.278952 48.320667 48.743185 48.8232 44.773846 45.641143 46.534598 47.278952 48.406803 48.770500 48.833885 49.298743 45.116667 46.267875 47.0449 48.320667 48.770500 48.860063 49.552222 50.09115 45.474478 46.621889 47.864554 48.743185 48.833885 49.552222 50.122772 50.388044
21,网格参数-信号计算和可视化
仅可视化第一列
# 信号计算dmac_size=dualma.fast_ma_above(dualma.slow_ma)print('dmac_size.shape:',dmac_size.shape)print()print('dmac_size.iloc[:3,:3]:')print(dmac_size.iloc[:3,:3])
# 行情-指标-信号可视化fig = ohlcv['Close'].vbt.plot(trace_kwargs=dict(name='Price'))fig = dualma.fast_ma.iloc[:,0].vbt.plot(trace_kwargs=dict(name='Fast MA'), fig=fig)fig = dualma.slow_ma.iloc[:,0].vbt.plot(trace_kwargs=dict(name='Slow MA'), fig=fig)fig = dmac_size.iloc[:,0].vbt.signals.plot_as_markers(ohlcv['Close'], fig=fig)fig.show_svg()
# (单独)信号可视化fig = dmac_size.iloc[:,0].vbt.signals.plot(trace_kwargs=dict(name='Entries'))fig.show_svg()
# 信号的统计信息dmac_size.vbt.signals.stats()dmac_size.shape: (728, 64)
dmac_size.iloc[:3,:3]:dualma_fast_window 10dualma_slow_multi 1.5 2.0 2.5date2020-01-02 00:00:00+00:00 True True True2020-01-03 00:00:00+00:00 True True True2020-01-06 00:00:00+00:00 True True True

Start 2020-01-02 00:00:00+00:00End 2022-12-30 00:00:00+00:00Period 728Total 474.03125Rate [%] 65.114183First Index 2020-01-15 16:52:30+00:00Last Index 2022-11-07 20:15:00+00:00Norm Avg Index [-1, 1] -0.159967Distance: Min 1.0Distance: Max 82.734375Distance: Mean 1.464916Distance: Std 5.175417Total Partitions 6.671875Partition Rate [%] 1.510978Partition Length: Min 41.671875Partition Length: Max 211.171875Partition Length: Mean 110.468174Partition Length: Std 78.523847Partition Distance: Min 26.78125Partition Distance: Max 82.734375Partition Distance: Mean 51.365493Partition Distance: Std 28.015768Name: agg_func_mean, dtype: object22,行情,信号的滑窗处理
注意点:
01,训练集和验证集比例3:1,或者2:1,对应:window_len和set_lens为4<1>1>(或3<1>1>),过大了历史包袱沉重,无法及时响应最新行情,过小了则容易参数跳变,形成类似过拟合效果
a,参数设置和效果预览
代码中
#todo 这里是自然日计算的,但后面训练,验证集个数计算都完全正确,哪里应该和预想的不一致合理的。实测bar_days= 60时
print(in_indexes[0][0])print(in_indexes[1][0])print(in_indexes[0][53:55])
2019-01-02 00:00:00+00:002019-03-25 00:00:00+00:00DatetimeIndex(['2019-03-25 00:00:00+00:00', '2019-03-26 00:00:00+00:00'], dtype='datetime64[ns, UTC]', name='split_0', freq=None)可见第二行第一个位于第一行第53个,不足设置的60,就是由于切分优先保证了数据的足量,但是数据间隔方面则可能有所重叠。# 滚动周期参数设置和大致效果可视化start_end_days=int((end_date-start_date).days) #todo 这里是自然日计算的,但后面训练,验证集个数计算都完全正确,哪里应该和预想的不一致bar_days= 80 # 训练,验证集时间长度,以此为单位test_bar_num=2 # 训练集时间长度verify_bar_num=1 # 验证集时间长度verify_overlap=0 # 验证集重叠时间长度pre_test_days=0 # 由于测试集一部分时间用于计算指标,导致实际训练时间不足,这个是一定程度补充的days周期# n取值需要满足:确保验证集合收尾相接# => (n-1)*(verify_bar_num-verify_overlap)+(verify_bar_num+test_bar_num)=start_end_days/bar_days# => n=(start_end_days/bar_days-test_bar_num-verify_overlap)/(verify_bar_num-verify_overlap)calc_n=(start_end_days/bar_days-test_bar_num-verify_overlap)/(verify_bar_num-verify_overlap)
split_kwargs = dict( n=int(calc_n), window_len=int(bar_days*(test_bar_num+verify_bar_num)+pre_test_days), set_lens=(int(bar_days*verify_bar_num),), left_to_right=False) # 10 windows, each 2 years long, reserve 180 days for test# 合理设置n,最好确保验证集,连续且无重复pf_kwargs = dict( direction='longonly', # long and short freq='d')print('split_kwargs:',split_kwargs)
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()split_kwargs: {'n': 11, 'window_len': 240, 'set_lens': (80,), 'left_to_right': False}
b,根据滑窗参数切分行情数据和信号
in_price.shape: (160, 11)out_price.shape: (80, 11)
in_price.index: RangeIndex(start=0, stop=160, step=1)in_price.columns: Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype='int64', name='split_idx')
in_price[0:3]:split_idx 0 1 2 3 4 5 6 7 8 9 100 49.17 58.15 51.20 43.39 48.15 97.90 167.98 239.52 202.00 251.77 253.141 48.06 56.16 49.50 43.15 49.73 96.55 164.08 225.00 214.11 252.50 266.492 50.65 55.36 50.29 43.79 52.25 94.50 168.03 208.99 227.02 246.86 266.08
###############################in_dmac_size.shape: (160, 704)out_dmac_size.shape: (80, 704)
in_dmac_size.iloc[:5,:5]:split_idx 0dualma_fast_window 10dualma_slow_multi 1.5 2.0 2.5 3.0 3.50 True True True True True1 True True True True True2 True True True True True3 True True True True True4 True True True True True23,滑窗的收益数据计算
a,持有参数收益
在此区间,基础标的物表现
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.head(5))
out_hold_sharpe = simulate_holding(out_price, **pf_kwargs)print(out_hold_sharpe.head(5))split_idx0 0.2354461 -1.6306162 0.5988893 2.6473974 4.501923Name: sharpe_ratio, dtype: float64split_idx0 -0.9299561 2.0659912 4.1003003 4.8012914 0.688785Name: sharpe_ratio, dtype: float64b,网格参数收益(训练集和验证集)
in_sharpe.shape: (11264,)split_idx dualma_fast_window dualma_slow_multi sl_stop sl_trail tp_stop0 10 1.5 0.05 False 0.1 -0.875730 0.2 -0.875730 True 0.1 0.213753 0.2 0.213753 0.10 False 0.1 -0.279062 ...10 45 5.0 0.15 True 0.2 -1.593755 0.20 False 0.1 -2.154236 0.2 -2.154236 True 0.1 -1.910520 0.2 -1.910520Name: sharpe_ratio, Length: 11264, dtype: float64
split_idx 0 1 2 \dualma_fast_window 10 15 20 25 30 35 40 45 10 15 20 25 30 35 40 45 10 15dualma_slow_multi 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.50 True False False False False False False False False False False False True True True True False False True True True True True True False True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True1 False False False False False False False True False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False2 False True True True True False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False
split_idx 3 ... 7 \dualma_fast_window 20 25 30 35 40 45 10 15 20 25 30 35 40 45 ... 10 15 20 25 30dualma_slow_multi 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 ... 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.00 True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False True False False False False False True True True False False False True True True True True False True ... False False False False False False False False False False False False False False False False False False False False False False False False False False False False1 False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False ... False False False False False False False False False False False False False False False False False False False False False False False False False False False False2 False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False ... False False False False False False False False False False False False False False False False False False False False False False False False False False False False
split_idx 8 9 \dualma_fast_window 35 40 45 10 15 20 25 30 35 40 45 10 15 20 25 30 35dualma_slow_multi 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.50 False False False False False False False False False False False False False True False False False False False True True True False False False False True True True True False True True True True False False False True True True True True False False False True False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True1 False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False True True True False False False False False False False False False True True False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False2 False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False True False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False
split_idx 10dualma_fast_window 40 45 10 15 20 25 30 35 40 45dualma_slow_multi 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.00 True True True True True True True True True True True True True True True True True True True False False False False False False False False False False False False False False False False False False False False False False True True False False False False True True True True False False False True True True True True False False True True True True True True False False True True True True True True False True True True True True True True1 False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False2 False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False False
[3 rows x 704 columns]out_sharpe.shape: (11264,)split_idx dualma_fast_window dualma_slow_multi sl_stop sl_trail tp_stop0 10 1.5 0.05 False 0.1 -1.069452 0.2 -1.069452 True 0.1 -0.297334 0.2 -0.297334 0.10 False 0.1 -2.012693 ...10 45 5.0 0.15 True 0.2 -2.766034 0.20 False 0.1 -0.197531 0.2 -0.197531 True 0.1 -0.197531 0.2 -0.197531Name: sharpe_ratio, Length: 11264, dtype: float64import pandas as pdimport matplotlib.pyplot as plt
# 根据索引层筛选数据groups = in_return.groupby(level=['sl_trail','sl_stop'])
# 计算每个分组的统计数据# statistics = groups.agg(['mean', 'var', 'max', 'min', 'median'])statistics = groups.agg([ ('mean', 'mean'), ('var', 'var'), ('max', 'max'), ('min', 'min'), ('median', 'median'), ('25%', lambda x: np.percentile(x, 25)), ('75%', lambda x: np.percentile(x, 75))])print(statistics)
def compare_true_false_statistics(statistics): """ 比较 sl_trail 索引层为 True 和 False 时各统计指标的大小。
:param statistics: 包含 True 和 False 分组的统计数据。 :return: 一个新的 DataFrame,展示 True 是否大于 False。 """ # 确保索引是多重索引,并且第一个索引是 sl_trail if not isinstance(statistics.index, pd.MultiIndex) or statistics.index.names[0] != 'sl_trail': raise ValueError("数据的第一个索引必须是 'sl_trail' 并且为多重索引。")
# 提取 True 和 False 的统计数据 true_stats = statistics.xs(True, level='sl_trail') false_stats = statistics.xs(False, level='sl_trail')
# 比较 True 和 False 的每个统计指标 comparison = true_stats > false_stats
# 将比较结果转换为整数类型(True为1,False为0) comparison = comparison.astype(int)
return comparison
# 示例使用# 假设 statistics 是上述提供的 DataFramecomparison_results = compare_true_false_statistics(statistics)print(comparison_results) mean var max min median 25% 75%sl_trail sl_stopFalse 0.05 0.156963 0.158100 2.451051 -0.274652 -0.009410 -0.111586 0.300105 0.10 0.169930 0.169304 2.451051 -0.370515 0.026649 -0.134527 0.414560 0.15 0.162313 0.178970 2.451051 -0.355247 0.022469 -0.169337 0.438223 0.20 0.212244 0.170921 2.451051 -0.370934 0.081500 -0.086851 0.455634True 0.05 0.024409 0.026795 1.337420 -0.350482 -0.014005 -0.087060 0.130420 0.10 0.157421 0.081934 1.347645 -0.332450 0.133399 -0.097223 0.351686 0.15 0.111382 0.084515 1.294188 -0.410083 0.065638 -0.147819 0.338981 0.20 0.178996 0.128846 2.451051 -0.357140 0.113125 -0.067093 0.316135 mean var max min median 25% 75%sl_stop0.05 0 0 0 0 0 1 00.10 0 0 0 1 1 1 00.15 0 0 0 0 1 1 00.20 0 0 0 1 1 1 0c,训练集上的最佳参数用于验证集
大致思路:
01,获取各split_idx的最佳收益(sharp_radio)的参数组合idxmax,也就是fast_window,slow_window,split_idx,三维索引元组
02,按照split_idx进行聚类,取得各split_idx对应的最佳参数。实际含义就是各滑动窗口的最佳参数
v1<简单最大值优选法>简单最大值优选法> 选取,测试集合的最优参数作为验证集参数,如果sharp_ratio就最大,回撤就最小类似这样的简单优选策略。
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_basic = get_best_index(in_sharpe)
print('in_best_index')print(in_best_index_basic)
# 绘图:参数走势图df_plot_tmp = in_best_index_basic.to_frame(index=False)# 将split_idx设置为行索引,并按照split_idx从小到大排序df_plot_tmp.set_index('split_idx', inplace=True)df_plot_tmp.sort_index(inplace=True)df_plot_tmp['dualma_slow_window'] = df_plot_tmp['dualma_fast_window']*df_plot_tmp['dualma_slow_multi']df_plot_tmp[['dualma_fast_window','dualma_slow_window']].vbt.plot().show_svg()in_best_index[:5]MultiIndex([( 0, 35, 2.0, 0.1, True, 0.1), ( 1, 15, 4.0, 0.05, True, 0.1), ( 2, 10, 3.0, 0.05, False, 0.1), ( 3, 25, 3.5, 0.05, False, 0.1), ( 4, 40, 5.0, 0.05, False, 0.1), ( 5, 35, 5.0, 0.2, True, 0.1), ( 6, 10, 4.0, 0.1, True, 0.1), ( 7, 45, 3.0, 0.15, False, 0.1), ( 8, 40, 1.5, 0.1, False, 0.1), ( 9, 20, 1.5, 0.1, False, 0.1), (10, 30, 1.5, 0.05, True, 0.1)], names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop', 'sl_trail', 'tp_stop'])
将滚动获取的最佳参数用于验证集,统计收益信息
24,sharp ratio的汇总可视化
basic为例的基础分析视图
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_test_best_sharpe_basic.values, 'out_sample_hold': out_hold_sharpe.values, 'out_sample_median': out_sharpe.groupby('split_idx').median().values, 'out_sample_test': out_test_sharpe_basic.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()
关注点:
蓝色部分 正常排序是(从上到下):点线,实现,线段,
橘色部分
实线对实线
说明测试集和验证集的周期收益情况,二者同时出现0轴同侧较好(同时上涨,同时下跌,保持行情的稳定性or延续性)
线段对线段
二者一方面随着各自颜色的实线趋势变化(受各自实线影响较大),其他应该无必然联系
点线对点线
蓝色点高于橘色点线,蓝色是训练集内最佳,橘色则是训练集得到最优参数用于验证集结果收益,大概率低于验证集。
测试,验证集时间长度差异,引入偏差
由于测试集一般是验证集的2-3倍(或更多),对于单边行情(假如上涨),则(测试集的)实线收益。蓝色线大概率位于橘色线上方。
如果下跌,则相反。蓝色由于时间长,大概率位于橘色下方。
注意: 01,202406,对于当前case,y周取值为sharp ratio夏普比,而非收益率。所以数据点高低并不反映收益率。 所以,以上结论需要稍斟酌,并不完全准确。
25,滚动回测收益可视化
# 验证集:原始价格变动in_price_org=in_price.iloc[-1, :]/in_price.iloc[0, :]print('in_price_org shape:',in_price_org.shape)print('in_price_org.head(5)')print(in_price_org.head(5))
cv_results_df = pd.DataFrame({ 'out_price_org': in_price_org.cumprod(), 'out_test_return_mean': (in_test_best_return_basic.values+1).cumprod(),})
color_dmac_pfschema = vbt.settings['plotting']['color_schema']
cv_results_df.vbt.plot( trace_kwargs=[ dict(line_color=color_schema['blue']), dict(line_color=color_schema['green']) ]).show_svg()
# 验证集:原始价格变动out_price_org=out_price.iloc[-1, :]/out_price.iloc[0, :]print('out_price_org shape:',out_price_org.shape)print('out_price_org.head(5)')print(out_price_org.head(5))
print()print('out_test_return_basic shape:',out_test_return_basic.shape)print('out_test_return_basic.head(5) + 1')print(out_test_return_basic.head(5)+1)
cv_results_df = pd.DataFrame({ 'out_price_org': out_price_org.cumprod(), 'out_test_return_mean': (out_test_return_basic.values+1).cumprod(),})
color_dmac_pfschema = vbt.settings['plotting']['color_schema']
cv_results_df.vbt.plot( trace_kwargs=[ dict(line_color=color_schema['blue']), dict(line_color=color_schema['green']) ]).show_svg()in_price_org shape: (11,)in_price_org.head(5)split_idx0 1.0075251 0.7745492 1.0869143 1.9338564 3.468328dtype: float64
out_price_org shape: (11,)out_price_org.head(5)split_idx0 0.9405741 1.3154362 1.6259853 2.1112394 1.059162dtype: float64
out_test_return_basic shape: (11,)out_test_return_basic.head(5) + 1split_idx dualma_fast_window dualma_slow_multi sl_stop sl_trail tp_stop0 35 2.0 0.10 True 0.1 0.8759621 15 4.0 0.05 True 0.1 1.1957002 10 3.0 0.05 False 0.1 1.0641993 25 3.5 0.05 False 0.1 1.5710244 40 5.0 0.05 False 0.1 1.053886Name: total_return, dtype: float64
上图可见,以上参数优选方法表现基本接近(也符合之前的sharp ratio接近的特征),不论何种参数优选策略,均优于单纯的持有(不过,结论未必通用)。
26,计算正确性验证(略)
a,准备校验数据,数据展示b,行情->指标 计算正确c,指标->信号 计算正确entry record: 48.9220 price: 51.580027,回测结果汇总
原始双均线
参数
fast_windows = np.arange(10, 50,5)slow_multis = np.arange(1.5, 5.5, 0.5)最佳参数
in_best_index[:5]MultiIndex([( 0, 35, 2.0), ( 1, 10, 4.5), ( 2, 10, 2.0), ( 3, 25, 3.5), ( 4, 40, 5.0), ( 5, 35, 5.0), ( 6, 10, 1.5), ( 7, 45, 3.0), ( 8, 40, 1.5), ( 9, 20, 1.5), (10, 25, 1.5)], names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi'])最佳参数样本内网sharp

最佳参数测试集回测表现

最佳参数验证集回测表现

简单说明:对于训练集合,其整体时间为2T,2倍于预测时间。 所以
01,训练集和验证集数据图上,即使同一种颜色也无太大比较意义。
02,训练集绿色线高于蓝色线,说明参数优选效果(拟合效果,训练效果)较好。这也是合理,毕竟用训练的最优最结果又用来回测自身,效果当然也应当好嘛。
03,验证集绿色高于蓝色越多越好,说明相对简单持有具有超额收益。当然实际上有难度,尤其是行情好时。由于策略需要控制风险(追求高sharpe),所以时间维度难以持续满仓,所以牛市可能不如稳定持有,但是遇到大跌时,规避风险的优势就很明显了。
跟踪止损
参数
sl_stops = [0.05,0.1,0.15,0.20]sl_trail=True最佳参数
MultiIndex([( 0, 35, 2.0, 0.1), ( 1, 15, 4.0, 0.05), ( 2, 10, 2.0, 0.05), ( 3, 10, 1.5, 0.1), ( 4, 40, 5.0, 0.2), ( 5, 35, 5.0, 0.2), ( 6, 10, 4.0, 0.1), ( 7, 45, 2.5, 0.2), ( 8, 40, 1.5, 0.2), ( 9, 20, 1.5, 0.1), (10, 30, 1.5, 0.05)], names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop'])最佳参数样本内网sharp

最佳参数测试集回测表现

最佳参数验证集回测表现

跟踪止损+np.inf
参数
sl_stops = [np.inf,0.05,0.1,0.15,0.20]sl_trail=True最佳参数
merged_df[in_test_best_index_basic] in_sharpe in_return out_sharpe out_returnsplit_idx dualma_fast_window dualma_slow_multi sl_stop0 35 2.0 0.10 1.979770 0.319978 -2.876613 -0.1240381 15 4.0 0.05 0.324361 0.016997 2.231171 0.1957002 10 2.0 0.05 1.844657 0.303627 2.693463 0.1618173 25 3.5 inf 3.374967 1.233830 3.996941 0.5710244 40 5.0 inf 4.501923 2.451051 0.688785 0.0538865 35 5.0 0.20 2.959891 1.144421 -2.467499 -0.1936636 10 4.0 0.10 3.029320 0.727907 -1.060044 -0.0736907 45 3.0 inf 2.296182 0.749108 inf 0.0000008 40 1.5 inf 2.489317 0.692895 3.678357 0.4492879 20 1.5 inf 2.926436 0.575073 2.364898 0.12873410 30 1.5 0.05 2.402594 0.302466 -4.323912 -0.074724最佳参数样本内网sharp

最佳参数测试集回测表现

最佳参数验证集回测表现

止损止盈
sl_stops = [0.05,0.1,0.15,0.20]sl_trails = [False, True]tp_stops = [0.1, 0.2]最佳参数
MultiIndex([( 0, 45, 2.0, 0.05, False, 0.1), ( 1, 15, 4.0, 0.05, False, 0.1), ( 2, 10, 2.0, 0.05, True, 0.2), ( 3, 10, 2.0, 0.05, True, 0.2), ( 4, 15, 1.5, 0.05, False, 0.2), ( 5, 40, 3.5, 0.2, False, 0.2), ( 6, 20, 4.0, 0.05, False, 0.2), ( 7, 25, 2.0, 0.05, False, 0.2), ( 8, 40, 1.5, 0.1, False, 0.2), ( 9, 30, 5.0, 0.1, False, 0.2), (10, 30, 3.0, 0.05, False, 0.2)], names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop', 'sl_trail', 'tp_stop'])最佳参数样本内网sharp

最佳参数训练集回测表现

最佳参数验证集回测表现

止损止盈+np.inf
参数
sl_stops = [np.inf,0.05,0.1,0.15,0.20]sl_trails = [False, True]tp_stops = [np.inf,0.1, 0.2]最佳参数
merged_df[in_test_best_index_basic] in_sharpe in_return out_sharpe out_returnsplit_idx dualma_fast_window dualma_slow_multi sl_stop sl_trail tp_stop0 45 2.0 inf False 0.1 3.158667 0.237753 -0.929956 -0.0641111 15 4.0 inf False 0.1 0.664945 0.034948 2.563318 0.1459632 10 3.0 0.05 False inf 1.978273 0.408342 1.451395 0.0641993 25 3.5 inf False inf 3.374967 1.233830 3.996941 0.5710244 15 1.5 inf False 0.2 5.245280 1.127771 2.858175 0.2392335 35 5.0 0.20 True inf 2.959891 1.144421 -2.467499 -0.1936636 20 4.0 inf False 0.2 4.039079 0.462475 1.890751 0.2072207 45 3.0 inf False inf 2.296182 0.749108 inf 0.0000008 40 1.5 inf False 0.2 2.665200 0.492175 2.449875 0.2399419 30 5.0 inf False 0.2 3.738112 0.507262 -0.434888 -0.06348410 30 3.0 inf False 0.2 2.943695 0.258812 -3.877240 -0.075374最佳参数样本内网sharp

最佳参数测试集回测表现

最佳参数验证集回测表现

小汇总
| 策略 | 参数 | 训练区标的 | 训练区最优 | 验证区标的 | 验证区表现 |
|---|---|---|---|---|---|
| 原始双均线 | fast_windows = np.arange(10, 50,5) slow_multis = np.arange(1.5, 5.5, 0.5) | 25, | 220 | 8 | 5.5 |
| 跟踪止损 | sl_stops = [0.05,0.1,0.15,0.20] sl_trail=True | 25 | 220 | 8 | 3.5 |
| 跟踪止损+inf | sl_stops = [np.inf,0.05,0.1,0.15,0.20] sl_trail=True | 25 | 300 | 8 | 2 |
| 止损止盈 | sl_stops = [0.05,0.1,0.15,0.20] sl_trails = [False, True] tp_stops = [0.1, 0.2] | 25 | 40 | 8 | 1 |
| 止损止盈+inf | sl_stops = [np.inf,0.05,0.1,0.15,0.20] sl_trails = [False, True] tp_stops = [np.inf,0.1, 0.2] | 25 | 140 | 8 | 2 |
结论:
01,可见策略越复杂,则训练集上的最优效果也越好(拟合效果更佳)
比如:跟踪止损+inf 优于 跟踪止损,止损止盈+inf 优于 止损止盈
反例:止损止盈 小于 跟踪止损,这个主要是止盈的限制,盈利达到xx后卖出限制了受益最大值,所以可解释。
02,止盈可以考虑不加,效果整体不如不止盈的。
03,跟踪止损表现一般。
以上结果仅对此案例有效,无法通用泛化。还需更多数据支持。
比较反常的:“止损止盈+inf”的140小于“跟踪止损+inf”的220,原因是我们参数选择时并非基于最高收益,而是基于最高sharpe,所以“止损止盈+inf”选中标的和“跟踪止损+inf”肯定存在差异。
部分信息可能已经过时