0%

01,定投收益率矩阵

表示张三,某年某月随机决定定投此标的,并且于某年某月随机退出的收益分布情况。
表格各个取值含义为:从xx日到yy日的定投收益率
举例:
比如第3行(2022-07),第6列(2022-09),对应的-15.14%
意思是:从2022年07月开始定投到2022年10月结束定投,收益率负15.14%

换一个更大的视角,可见,绿色部分(深度亏损),红色部分(深度盈利),都是列聚合的(同列取值更接近)。而列是退出时间,说明对定投而言,退出时间非常重要,这个也符合直观理解,毕竟,由于定投是持续加仓,退出时的仓位是最大的(即将退出时的行情表现,对总资产盈亏影响力很大)。

02,收益率分布情况

这个表示了上面那个张三,在当前标的上,一次“随意开启-终止”定投,动作。 亏损-收益的概率分布,
比如当前就是亏损概率45%,盈利概率55%,具体分位表现如下图。

03,月度定投记录-先读懂这个table

上面计算均源于此表,各个数据列关系,以在图中标识,基本比较明晰了。 不做过度解释。

上面的计算可能看起来对不齐,实际由于四舍五入引起的,原始table表格如下

04,detail页面,查看定投详情

标的价格走势,只取得了每月第一工作日价格,所以价格点稀疏。
下面的定投资金,起点并非1000,主要原因是,避免计算复杂,数值是从上面的03部分的月度定投记录,截取的一部分。所以并不十分精准。数值上不准确,但是整体并不影响最终收益率结论。
20250510:最新系统已做升级,从0开始计算。


详细数据计算链

基于vectorbt的基础唐奇安通道

09dc_01basic:基础方法
09dc_01basicV2:和基础方法等价,不过是基于vectorbt的指标机制的集成的dch指标。

01,基础配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#conda envs:vectorbt_env
import warnings
import vectorbt as vbt
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import pytz
from dateutil.parser import parse
import ipywidgets as widgets
from copy import deepcopy
from tqdm import tqdm
import imageio
from IPython import display
import plotly.graph_objects as go
import itertools
import dateparser
import gc
import math
from 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,时间交易参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# Enter your parameters here
seed = 42
symbol = '600089.XSHG' #601318
metric = 'total_return'

start_date = datetime(2021, 1, 1, tzinfo=pytz.utc) # time period for analysis, must be timezone-aware
end_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 window
freq = '1D'

vbt.settings.portfolio['init_cash'] = 100000. # 100$
vbt.settings.portfolio['fees'] = 0.0025 # 0.25%
vbt.settings.portfolio['slippage'] = 0.0025 # 0.25%

b,获取行情和行情mask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Download data with time buffer
cols = ['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("origin ohlcv_wbuf size:",ohlcv_wbuf.shape)
print(ohlcv_wbuf.columns)


# Create a copy of data without time buffer
wobuf_mask = (ohlcv_wbuf.index >= start_date) & (ohlcv_wbuf.index <= end_date) # mask without buffer

ohlcv = ohlcv_wbuf.loc[wobuf_mask, :]

print("wobuf_mask ohlcv size:",ohlcv.shape)

# Plot the OHLC data
ohlcv.vbt.ohlcv.plot().show_svg() # 绘制蜡烛图
# remove show_svg() to display interactive chart!
origin ohlcv_wbuf size: (1409, 5)
Index(['Open', 'High', 'Low', 'Close', 'Volume'], dtype='object')
wobuf_mask ohlcv size: (485, 5)

svg

03,指标计算和可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# fig.show_svg()
upper_window = 20 # 上边界滚动计算周期
lower_window = 10 # 下边界滚动计算周期
upper_window_comp_skip = 5 # 突破比较时需要和前n日的比较
lower_window_comp_skip = 3 # 突破比较时需要和前n日的比较
# Pre-calculate running windows on data with time buffer
upper_band = vbt.talib('MAX').run(ohlcv_wbuf['High'], timeperiod=upper_window)
# print(upper_band.real.head(30))
lower_band = vbt.talib('MIN').run(ohlcv_wbuf['Low'], timeperiod=lower_window)

print(upper_band.real.shape)
print(lower_band.real.shape)


# Remove time buffer
upper_band = upper_band[wobuf_mask]
lower_band = lower_band[wobuf_mask]

# there should be no nans after removing time buffer
assert(~upper_band.real.isnull().any())
assert(~lower_band.real.isnull().any())

print(upper_band.real.shape)
print(lower_band.real.shape)


fig = ohlcv['Close'].vbt.plot(trace_kwargs=dict(name='Price'))
fig = upper_band.real.vbt.plot(trace_kwargs=dict(name='Upper Band'), fig=fig)
fig = lower_band.real.vbt.plot(trace_kwargs=dict(name='Lower Band'), fig=fig)
fig.show_svg()

(1409,)
(1409,)
(485,)
(485,)

svg

04,信号计算和可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# def crossed_above(series1, series2):
# """Returns a boolean Series indicating where series1 crosses above series2."""
# return (series1 > series2) & (series1.shift(1) <= series2.shift(1))

# def crossed_below(series1, series2):
# """Returns a boolean Series indicating where series1 crosses below series2."""
# return (series1 < series2) & (series1.shift(1) >= series2.shift(1))

# dc_entries = crossed_above(ohlcv['Close'],upper_band.real.shift(5).fillna(method='ffill'))# 后移5/3天,当天视角看就是close比对3天前的upper/lower
# dc_exits = crossed_below(ohlcv['Close'],lower_band.real.shift(3).fillna(method='ffill'))


# 信号计算
upper_band_nd_ago = upper_band.real.shift(upper_window_comp_skip) #n日前max价格
lower_band_nd_ago = lower_band.real.shift(lower_window_comp_skip) #
# Long when MACD is above zero AND signal
# 优化:ohlcv_wbuf["Close"]->ohlcv_wbuf["High"]
# 原理:最高价不断走高的是好行情,最高价包含情绪因素,比收盘价更具代表性
dc_entries = ohlcv["Close"][upper_band_nd_ago.index].vbt.crossed_above(upper_band_nd_ago)
dc_exits= ohlcv["Close"][lower_band_nd_ago.index].vbt.crossed_below(lower_band_nd_ago)

# 行情-指标-信号可视化
fig = ohlcv['Close'].vbt.plot(trace_kwargs=dict(name='Price'))
fig = upper_band.real.vbt.plot(trace_kwargs=dict(name='Fast MA'), fig=fig)
fig = lower_band.real.vbt.plot(trace_kwargs=dict(name='Slow MA'), fig=fig)
fig = dc_entries.vbt.signals.plot_as_entry_markers(ohlcv['Close'], fig=fig)
fig = dc_exits.vbt.signals.plot_as_exit_markers(ohlcv['Close'], fig=fig)
fig.show_svg()

# (单独)信号可视化
fig = dc_entries.vbt.signals.plot(trace_kwargs=dict(name='Entries'))
dc_exits.vbt.signals.plot(trace_kwargs=dict(name='Exits'), fig=fig).show_svg()

# 信号的统计信息
dc_entries.vbt.signals.stats(settings=dict(other=dc_exits))

svg

svg

Start                       2021-01-04 00:00:00+00:00
End                         2022-12-30 00:00:00+00:00
Period                                            485
Total                                              19
Rate [%]                                     3.917526
Total Overlapping                                   0
Overlapping Rate [%]                              0.0
First Index                 2021-01-18 00:00:00+00:00
Last Index                  2022-08-29 00:00:00+00:00
Norm Avg Index [-1, 1]                       -0.19552
Distance -> Other: Min                            3.0
Distance -> Other: Max                           67.0
Distance -> Other: Mean                     25.315789
Distance -> Other: Std                       17.34952
Total Partitions                                   19
Partition Rate [%]                              100.0
Partition Length: Min                             1.0
Partition Length: Max                             1.0
Partition Length: Mean                            1.0
Partition Length: Std                             0.0
Partition Distance: Min                           2.0
Partition Distance: Max                          61.0
Partition Distance: Mean                    21.722222
Partition Distance: Std                     18.549792
dtype: object

05,交易统计

a,基准比对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dmac_pf = vbt.Portfolio.from_signals(ohlcv['Close'], dc_entries, dc_exits)
# Print stats
print(dmac_pf.stats())

# Now build portfolio for a "Hold" strategy
# Here we buy once at the beginning and sell at the end
hold_entries = pd.Series.vbt.signals.empty_like(dc_entries)
hold_entries.iloc[0] = True
hold_exits = pd.Series.vbt.signals.empty_like(hold_entries)
hold_exits.iloc[-1] = True
hold_pf = vbt.Portfolio.from_signals(ohlcv['Close'], hold_entries, hold_exits)

# Equity
fig = dmac_pf.value().vbt.plot(trace_kwargs=dict(name='Value (DMAC)'))
hold_pf.value().vbt.plot(trace_kwargs=dict(name='Value (Hold)'), fig=fig).show_svg()
Start                         2021-01-04 00:00:00+00:00
End                           2022-12-30 00:00:00+00:00
Period                                              485
Start Value                                    100000.0
End Value                                 135478.738311
Total Return [%]                              35.478738
Benchmark Return [%]                         104.325952
Max Gross Exposure [%]                            100.0
Total Fees Paid                             6084.898884
Max Drawdown [%]                               52.33349
Max Drawdown Duration                             318.0
Total Trades                                          9
Total Closed Trades                                   9
Total Open Trades                                     0
Open Trade PnL                                      0.0
Win Rate [%]                                  33.333333
Best Trade [%]                                97.749209
Worst Trade [%]                              -19.171936
Avg Winning Trade [%]                         42.098525
Avg Losing Trade [%]                          -9.811332
Avg Winning Trade Duration                         47.0
Avg Losing Trade Duration                          11.0
Profit Factor                                  1.374907
Expectancy                                  3942.082035
dtype: object

svg

b,交易详情和可视化

1
2
3
# Plot trades
print(dmac_pf.trades.records.head(5))
dmac_pf.trades.plot().show_svg()
   id  col        size  entry_idx  entry_price  entry_fees  exit_idx   exit_price   exit_fees           pnl    return  direction  status  parent_id
0   0    0  183.350720         10   544.042715  249.376559        38   550.239953  252.217228    634.674170  0.006363          0       1          0
1   1    0  175.831773         73   570.907710  250.959287        91   565.963545  248.785934  -1369.086519 -0.013639          0       1          1
2   2    0  164.485565        113   601.986212  247.545106       180  1194.915225  491.365766  96789.353009  0.977492          0       1          2
3   3    0  141.336528        197  1383.690600  488.915064       208  1124.681250  397.396358 -37493.793739 -0.191719          0       1          3
4   4    0  129.545854        230  1220.924700  395.414331       235  1108.431975  358.981916 -15327.362345 -0.096907          0       1          4

svg

之前有部分想当然的结论,重新做下验证

结论:
01,4个参数优选方法中,
猜测的是in_test_best_index_basic最优,
实际是in_test_best_index_nb_coord稍稍胜出

02,自适应周期计算中,
猜测的是自适应周期最优(此处是通过傅里叶变换计算的),
实际是fixed60最优,本质是自适应周期只要不是最优,就基本说明这个思路是无效的了。

01,基础数据格式

csv中的数据格式

1
2
3
4
5
6
7
8
9
Start,End,Period,Start Value,End Value,Total Return [%],Benchmark Return [%],Max Gross Exposure [%],Total Fees Paid,Max Drawdown [%],Max Drawdown Duration,Total Trades,Total Closed Trades,Total Open Trades,Open Trade PnL,Win Rate [%],Best Trade [%],Worst Trade [%],Avg Winning Trade [%],Avg Losing Trade [%],Avg Winning Trade Duration,Avg Losing Trade Duration,Profit Factor,Expectancy,Sharpe Ratio,Calmar Ratio,Omega Ratio,Sortino Ratio,choose_method,file_name,symbol
0,79,80 days,10000.00,10134.98,1.35,6.77,100.00,91.46,12.38,54 days 14:24:00,1.80,1.80,0.00,0.00,30.00,1.87,-2.59,7.81,-4.94,11 days 06:00:00,16 days 15:00:00,inf,-12.60,-0.67,0.65,0.85,-0.55,in_test_best_index_basic,92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2/002230XSHE_20200101_20240801_bar80_filter1.md,002230.XSHE
0,79,80 days,10000.00,10482.11,4.82,-0.01,100.00,51.21,10.04,52 days 04:48:00,1.00,1.00,0.00,0.00,40.00,4.83,4.83,18.41,-4.22,20 days 00:00:00,32 days 08:00:00,inf,482.11,0.22,5.98,1.20,1.28,in_test_best_index_nb_coord,92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2/002230XSHE_20200101_20240801_bar80_filter1.md,002230.XSHE
0,79,80 days,10000.00,10358.56,3.59,4.13,100.00,69.45,10.67,54 days 08:00:00,1.33,1.33,0.00,0.00,41.67,2.68,-0.44,7.57,-4.30,20 days 20:00:00,17 days 18:00:00,inf,125.02,-0.12,1.29,0.96,0.14,in_test_best_index_nb_mean,92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2/002230XSHE_20200101_20240801_bar80_filter1.md,002230.XSHE
0,79,80 days,10000.00,10160.66,1.61,4.13,100.00,76.51,12.49,54 days 08:00:00,1.50,1.50,0.00,0.00,38.89,2.48,-1.77,7.13,-5.17,19 days 20:00:00,16 days 12:00:00,inf,52.32,-0.40,0.97,0.89,-0.16,in_test_best_index_nb_median,92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2/002230XSHE_20200101_20240801_bar80_filter1.md,002230.XSHE
0,99,100 days,10000.00,10258.43,2.58,2.67,100.00,54.29,13.10,69 days 06:00:00,1.50,0.75,0.75,699.01,0.00,-5.89,-5.89,,-5.89,,19 days 08:00:00,0.00,-587.44,0.07,1.86,0.99,0.42,in_test_best_index_basic,92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2/002180XSHE_20200101_20240801_bar100_filter1.md,002180.XSHE
0,99,100 days,10000.00,10335.80,3.36,4.75,100.00,95.13,12.27,65 days 19:12:00,2.20,1.60,0.60,574.26,13.33,-2.35,-4.66,6.93,-4.63,11 days 12:00:00,15 days 19:12:00,0.95,-333.04,0.27,2.01,1.07,0.69,in_test_best_index_nb_coord,92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2/002180XSHE_20200101_20240801_bar100_filter1.md,002180.XSHE
0,99,100 days,10000.00,10329.60,3.30,2.67,100.00,54.63,12.47,69 days 06:00:00,1.50,0.75,0.75,702.18,0.00,-4.98,-4.98,,-4.98,,20 days 00:00:00,0.00,-496.76,0.16,1.93,1.01,0.53,in_test_best_index_nb_mean,92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2/002180XSHE_20200101_20240801_bar100_filter1.md,002180.XSHE
0,99,100 days,10000.00,10302.37,3.02,2.67,100.00,54.50,12.71,69 days 06:00:00,1.50,0.75,0.75,700.97,0.00,-5.33,-5.33,,-5.33,,20 days 08:00:00,0.00,-531.47,0.13,1.90,1.00,0.49,in_test_best_index_nb_median,92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2/002180XSHE_20200101_20240801_bar100_filter1.md,002180.XSHE

02,4种参数优选方法的比较

交叉表统计,各优选方法的rank计数
主要用来验证常见的4种参数优选方法的优劣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import pandas as pd
pd.set_option('display.max_rows',500)
pd.set_option('display.max_columns',500)
pd.set_option('display.width',1000)

# 步骤1: 读取数据
data_path = '/home/john/git/repo_quant/myvectorbt/92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2_rst.csv'
df = pd.read_csv(data_path)

# 步骤2: 数据清洗
df = df.dropna(subset=['Start Value'])

# 步骤3: 排序和分组
df['End Value'] = pd.to_numeric(df['End Value'], errors='coerce')
df_sorted = df.sort_values(by=['symbol', 'End Value'], ascending=[True, False])

# 计算排名
df_sorted['rank'] = df_sorted.groupby('symbol')['End Value'].rank("dense", ascending=False)

# 步骤4: 创建交叉表
result_table = pd.crosstab(index=df_sorted['rank'], columns=df_sorted['choose_method'])

# 打印结果表格
print(result_table)


choose_method  in_test_best_index_basic  in_test_best_index_nb_coord  in_test_best_index_nb_mean  in_test_best_index_nb_median
rank                                                                                                                          
1.0                                  19                           26                          20                            21
2.0                                  21                           12                          30                            22
3.0                                  20                           21                          18                            22
4.0                                  20                           21                          12                            15

数据含义:列名表示优选方法,
第一列第一行表示in_test_best_index_basic优选方法,在同一个标的的4中优选方法,是最优的情况出现19次。
第一列第二行表示in_test_best_index_basic优选方法,在同一个标的的4中优选方法,是次优的情况出现21次。
注意下:由于采用的rank(“dense”)方式,所以可能存在并列第一(并列第二,第三都有可能)的情况。 所有理论上的每一行的sum应该相等,且等于总标的*4并不成立,而且部分标的部分择优方法可能存在缺失值,都会导致rank1的总数量大于rank4(的总数)

从结果上看,in_test_best_index_nb_coord均值方法最佳,但也说不上显著。

03,自适应周期和固定周期的比较

在做滚动性的周期回测时,需要设定训练-预测周期,比例和基础周期,
比如 80天,2:1
就是训练集80*2=160天,
预测集80*1=80天.(可以认为多久重置一次参数的周期)。
太长了,可能学习到真实的参数,但是对市场反应过于迟钝了,学习到的正确参数可能已经不再适用当下,
太短了,可能并未学习到真实参数,只是过拟合出一个不错的结果,而且切分出的验证集次数过多,累乘时,容易偏向于过拟合。

认为自适应计算出的标的的 训练-预测 周期,优于固定的周期。所以,理想的自适应周期的回测结果应该优于任何一组固定周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import pandas as pd

# 读取和合并数据
files = {
'adaptive': '../92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2_rst.csv',
'fixed_40': '../92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2_cycle40_rst.csv',
'fixed_60': '../92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2_cycle60_rst.csv',
'fixed_80': '../92reportConfig/01_05dma_06rollingFilterGridParamExitFuncV2_cycle80_rst.csv'
}

dfs = []
for key, file in files.items():
df = pd.read_csv(file)
df = df[df['choose_method'] == 'in_test_best_index_basic']
df['source'] = key
dfs.append(df)

combined_df = pd.concat(dfs)
# print(combined_df.head(3))
# 计算排名
combined_df['rank'] = combined_df.groupby(['symbol'])['End Value'].rank(method='dense', ascending=False)
# print(combined_df.head(3))
# 创建交叉表
crosstab_result = pd.crosstab(index=combined_df['rank'] , columns=combined_df['source'])

# 打印结果
print(crosstab_result)


source  adaptive  fixed_40  fixed_60  fixed_80
rank                                          
1.0           21        23        28        21
2.0           25        23        22        21
3.0           21        28        22        17
4.0           13         6         9        20

本以为自适应周期的应该是最优的,实际好像也未必
上面看fixed_60的表现是最佳的,并不符合最初猜测。

当然此时,并不能说明猜测一定是错误的。
原因如下:
01,自己的最佳周期计算函数有问题(傅里叶变换),计算出的最佳周期未必真正的最佳周期。
02,最佳周期计算采用202001-202301数据得来的,实际测试周期2020-202408,所以并非完全卡着周期的,这也会引入偏差。只是这部分误差可能难以消除,毕竟,不可能提前预知未来一段时间的最佳周期参数。

接上一篇文章《29DMA之七回顾小节》

DMA之八滑窗网格参数优选(止损,止盈)

回测结果汇总

原始双均线

参数

1
2
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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),所以时间维度难以持续满仓,所以牛市可能不如稳定持有,但是遇到大跌时,规避风险的优势就很明显了。

跟踪止损

参数

1
2
sl_stops = [0.05,0.1,0.15,0.20]
sl_trail=True

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
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

参数

1
2
sl_stops = [np.inf,0.05,0.1,0.15,0.20]
sl_trail=True

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
merged_df[in_test_best_index_basic]
in_sharpe in_return out_sharpe out_return
split_idx dualma_fast_window dualma_slow_multi sl_stop
0 35 2.0 0.10 1.979770 0.319978 -2.876613 -0.124038
1 15 4.0 0.05 0.324361 0.016997 2.231171 0.195700
2 10 2.0 0.05 1.844657 0.303627 2.693463 0.161817
3 25 3.5 inf 3.374967 1.233830 3.996941 0.571024
4 40 5.0 inf 4.501923 2.451051 0.688785 0.053886
5 35 5.0 0.20 2.959891 1.144421 -2.467499 -0.193663
6 10 4.0 0.10 3.029320 0.727907 -1.060044 -0.073690
7 45 3.0 inf 2.296182 0.749108 inf 0.000000
8 40 1.5 inf 2.489317 0.692895 3.678357 0.449287
9 20 1.5 inf 2.926436 0.575073 2.364898 0.128734
10 30 1.5 0.05 2.402594 0.302466 -4.323912 -0.074724

最佳参数样本内网sharp

最佳参数测试集回测表现

最佳参数验证集回测表现

止损止盈

1
2
3
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = [False, True]
tp_stops = [0.1, 0.2]

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
13
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

参数

1
2
3
sl_stops = [np.inf,0.05,0.1,0.15,0.20]
sl_trails = [False, True]
tp_stops = [np.inf,0.1, 0.2]

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
merged_df[in_test_best_index_basic]
in_sharpe in_return out_sharpe out_return
split_idx dualma_fast_window dualma_slow_multi sl_stop sl_trail tp_stop
0 45 2.0 inf False 0.1 3.158667 0.237753 -0.929956 -0.064111
1 15 4.0 inf False 0.1 0.664945 0.034948 2.563318 0.145963
2 10 3.0 0.05 False inf 1.978273 0.408342 1.451395 0.064199
3 25 3.5 inf False inf 3.374967 1.233830 3.996941 0.571024
4 15 1.5 inf False 0.2 5.245280 1.127771 2.858175 0.239233
5 35 5.0 0.20 True inf 2.959891 1.144421 -2.467499 -0.193663
6 20 4.0 inf False 0.2 4.039079 0.462475 1.890751 0.207220
7 45 3.0 inf False inf 2.296182 0.749108 inf 0.000000
8 40 1.5 inf False 0.2 2.665200 0.492175 2.449875 0.239941
9 30 5.0 inf False 0.2 3.738112 0.507262 -0.434888 -0.063484
10 30 3.0 inf False 0.2 2.943695 0.258812 -3.877240 -0.075374

最佳参数样本内网sharp

最佳参数测试集回测表现

最佳参数验证集回测表现

DMA之九滑窗网格参数优选(4种参数优选)

回测结果汇总

参数
4种优选方法 训练集夏普sharp ratio
4种优选方法 验证集夏普sharp ratio
4种优选方法 最佳参数测试集回测表现
4种优选方法 最佳参数验证集回测表现

跟踪止损

参数

1
2
3
4
5
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = True
无止盈

4种优选方法 训练集夏普sharp ratio
svg

4种优选方法 验证集夏普sharp ratio

svg

4种优选方法 最佳参数测试集回测表现
svg

4种优选方法 最佳参数验证集回测表现
svg

非跟踪止损

参数

1
2
3
4
5
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = False
无止盈

4种优选方法 训练集夏普sharp ratio

4种优选方法 验证集夏普sharp ratio

4种优选方法 最佳参数测试集回测表现

4种优选方法 最佳参数验证集回测表现

跟踪止损+止盈

参数

1
2
3
4
5
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = True
tp_stops = [0.1, 0.2]

参数
4种优选方法 训练集夏普sharp ratio

4种优选方法 验证集夏普sharp ratio

4种优选方法 最佳参数测试集回测表现

4种优选方法 最佳参数验证集回测表现

非跟踪止损+止盈

参数

1
2
3
4
5
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = False
tp_stops = [0.1, 0.2]

4种优选方法 训练集夏普sharp ratio

4种优选方法 验证集夏普sharp ratio

4种优选方法 最佳参数测试集回测表现

4种优选方法 最佳参数验证集回测表现

DMA之十滑窗网格参数优选(过滤器)

std_indicator

过滤器规则:

1
2
3
4
5
std_close_wbuf = ohlcv_wbuf['Close'].rolling(window=20).std()
std_close_ma_wbuf = std_close_wbuf.rolling(window=20).mean()
std_close=std_close_wbuf[wobuf_mask]
std_close_ma=std_close_ma_wbuf[wobuf_mask]
std_indicator = (std_close > std_close_ma )

4种优选方法的训练集夏普sharp ratio
svg

4种优选方法的验证集夏普sharp ratio
svg
样本内滚动收益
svg

样本外滚动收益
svg

diff_indicator

过滤器规则:

1
2
3
4
5
diff_close_wbuf = ohlcv_wbuf['Close'] - ohlcv_wbuf['Close'].rolling(window=int(20/5)).mean().shift(20)
diff_close_ma_wbuf = diff_close_wbuf.rolling(window=20).mean()
diff_close=diff_close_wbuf[wobuf_mask]
diff_close_ma=diff_close_ma_wbuf[wobuf_mask]
diff_indicator = ((diff_close > diff_close_ma )&(diff_close_ma>200*0.0025*20))

4种优选方法的训练集夏普sharp ratio

4种优选方法的验证集夏普sharp ratio

样本内滚动收益

样本外滚动收益

小节

过滤器优化后,相比原始的:DMA之九滑窗网格参数优选(4种参数优选)
其sharpe和收益曲线均未见明显改善,但过滤后可视化角度看,的确过滤掉部分低波动行情。

本文在上一篇文章(31DMA之九滑窗网格参数优选)基础上。
之前文章:
01,增加了止盈,止损,跟踪止损等参数,但实际效果看训练集上效果尚可,验证集上效果更差,怀疑过拟合导致。
02.增加几种避免过拟合的参数优选方法。
新增3种参数优选方法,一定程度上降低参数过拟合的可能。

1
2
3
4
v1:直接(简单最大值)优选法  
v2:邻近域优选法
v3:邻居权重优选法-均值
v4:邻居权重优选法-中位数

03,止损参数,止盈参数也是类似的,不止2个维度了,邻居采用立方体思路.对角点相接的也算作邻居,
之前2维时是同边才算邻居,比如(1,3),邻居是(2,3),(1,2),(1,4),新的规则会新增(2,2),(2,4)

本文增加:
增加行情过滤器,过滤掉低波动行情,开仓时如果行情波动性不足,不开仓。
原始买卖状态信号:
1111111100000000000001111100
=》+ 过滤器
0011100000000000000111100000
=》期望效果
0011111100000000000001111100

可见过滤器影响原始买卖状态信号的头,不影响原始买卖状态的尾巴。

01,基础配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#conda envs:vectorbt_env
import warnings
import vectorbt as vbt
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import pytz
from dateutil.parser import parse
import ipywidgets as widgets
from copy import deepcopy
from tqdm import tqdm
import imageio
from IPython import display
import plotly.graph_objects as go
import itertools
import dateparser
import gc
import math
from 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,时间交易参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# Enter your parameters here
seed = 42
symbol = '002594.XSHE'
metric = 'total_return'

start_date = datetime(2020, 1, 1, tzinfo=pytz.utc) # time period for analysis, must be timezone-aware
end_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 window
freq = '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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Download data with time buffer
cols = ['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 buffer
wobuf_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 data
ohlcv.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)

svg

20,网格参数-指标计算和可视化

仅可视化第一列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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_wbuf=ohlcv_wbuf['Close']
dualma_wbuf = vbt.DualMA.run(price_wbuf, fast_window=fast_windows,slow_multi=slow_multis,param_product=True)
dualma = dualma_wbuf[wobuf_mask]
# there should be no nans after removing time buffer
assert(~dualma.fast_ma.isnull().any().any())
assert(~dualma.slow_ma.isnull().any().any())

# 计算收盘价的标准差
std_close_wbuf = ohlcv_wbuf['Close'].rolling(window=20).std()
std_close_ma_wbuf = std_close_wbuf.rolling(window=20).mean()
std_close=std_close_wbuf[wobuf_mask]
std_close_ma=std_close_ma_wbuf[wobuf_mask]
std_indicator = (std_close > std_close_ma )

# 计算收盘价diff
diff_close_wbuf = ohlcv_wbuf['Close'] - ohlcv_wbuf['Close'].rolling(window=int(20/5)).mean().shift(20)
diff_close_ma_wbuf = diff_close_wbuf.rolling(window=20).mean()
diff_close=diff_close_wbuf[wobuf_mask]
diff_close_ma=diff_close_ma_wbuf[wobuf_mask]
diff_indicator = ((diff_close > diff_close_ma )&(diff_close_ma>200*0.0025*20))

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                                                                        45                                                                             
dualma_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
date                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
2020-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.440222
2020-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.555556
2020-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.660222
dualma.slow_ma.head(3)
dualma_fast_window                10                                                                              15                                                                                      20                                                                                25                                                                                 30                                                                                    35                                                                                      40                                                                                   45                                                                             
dualma_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
date                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
2020-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.375822
2020-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.379778
2020-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

svg

21,网格参数-信号计算和可视化

仅可视化第一列

dmac_size.shape: (728, 64)
dmac_size.iloc[:3,:3]:
dualma_fast_window           10            
dualma_slow_multi           1.5   2.0   2.5
date                                       
2020-01-02 00:00:00+00:00  True  True  True
2020-01-03 00:00:00+00:00  True  True  True
2020-01-06 00:00:00+00:00  True  True  True

svg

Start                       2020-01-02 00:00:00+00:00
End                         2022-12-30 00:00:00+00:00
Period                                            728
Total                                             295
Rate [%]                                    40.521978
First Index                 2020-01-02 00:00:00+00:00
Last Index                  2022-12-21 00:00:00+00:00
Norm Avg Index [-1, 1]                      -0.160021
Distance: Min                                     1.0
Distance: Max                                    89.0
Distance: Mean                                2.44898
Distance: Std                                8.855444
Total Partitions                                   13
Partition Rate [%]                            4.40678
Partition Length: Min                             1.0
Partition Length: Max                            52.0
Partition Length: Mean                      22.692308
Partition Length: Std                       16.428556
Partition Distance: Min                           7.0
Partition Distance: Max                          89.0
Partition Distance: Mean                         36.5
Partition Distance: Std                     27.750512
Name: (10, 1.5), dtype: object

22,行情,信号的滑窗处理

a,参数设置和效果预览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 滚动周期参数设置和大致效果可视化
start_end_days=ohlcv.shape[0]
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)

price=ohlcv['Close']
# 验证:单列数据验证,橘黄色验证集连续且无重复
roll_in_and_out_samples(price, **split_kwargs, plot=True, trace_names=['in-sample', 'out-sample']).show_svg()
split_kwargs: {'n': 7, 'window_len': 240, 'set_lens': (80,), 'left_to_right': False}

svg

b,根据滑窗参数切分行情数据和信号

in_price.shape: (160, 7)
out_price.shape: (80, 7)

in_price.index: RangeIndex(start=0, stop=160, step=1)
in_price.columns: Int64Index([0, 1, 2, 3, 4, 5, 6], dtype='int64', name='split_idx')

in_price[0:3]:
split_idx      0      1      2       3       4       5       6
0          48.17  59.78  92.59  219.90  146.56  254.11  250.02
1          48.04  58.88  90.00  216.30  153.73  277.60  246.50
2          48.28  59.13  94.74  225.04  148.99  275.95  246.30

###############################
in_dmac_size.shape: (160, 448)
out_dmac_size.shape: (80, 448)

in_dmac_size.iloc[:5,:5]:
split_idx              0                        
dualma_fast_window    10                        
dualma_slow_multi    1.5   2.0   2.5   3.0   3.5
0                   True  True  True  True  True
1                   True  True  True  True  True
2                   True  True  True  True  True
3                   True  True  True  True  True
4                   True  True  True  True  True

23,滑窗的收益数据计算

a,持有参数收益

在此区间,基础标的物表现

1
2
3
4
5
6
7
8
9
10
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_idx
0    2.315678
1    3.890261
2    1.812302
3    1.122310
4    2.388496
Name: sharpe_ratio, dtype: float64
split_idx
0    4.885519
1   -0.547754
2    4.538256
3   -0.039085
4   -0.527252
Name: sharpe_ratio, dtype: float64

b,网格参数收益(训练集和验证集)

in_sharpe.shape: (1792,)
split_idx  dualma_fast_window  dualma_slow_multi  sl_stop
0          10                  1.5                0.05       1.850726
                                                  0.10       1.473377
                                                  0.15       1.272865
                                                  0.20       1.397542
                               2.0                0.05       2.399222
                                                               ...   
6          45                  4.5                0.20      -1.054460
                               5.0                0.05      -0.331869
                                                  0.10      -1.664299
                                                  0.15      -1.487590
                                                  0.20      -1.513999
Name: sharpe_ratio, Length: 1792, 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                                                      15                 
dualma_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.5   
0                   False  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  False  False  False  False  False  False  False  False  False  False  False   
1                    True  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  False   
2                   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                                                                                                                                                                                                                                                                                                                                                                                                                                                               4                                                                                                                                                     \
dualma_fast_window                                        20                                                      25                                                      30                                                      35                                                      40                                                      45                                                      10                                                      15                                                      20                                                      25                                                      30                                                      35                                                      40                                                      45                                                      10                                                      15                                                      20                                      
dualma_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    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   
0                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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  False  False  False  False  False   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  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False   
1                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False   
2                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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                                                                                                                                                                                                                                                                                                                     5                                                                                                                                                                                                                                                                                                                                                                                                                                                               6                                                                                                                                                                                                                                  \
dualma_fast_window                   25                                                      30                                                      35                                                      40                                                      45                                                      10                                                      15                                                      20                                                      25                                                      30                                                      35                                                      40                                                      45                                                      10                                                      15                                                      20                                                      25                                                      30   
dualma_slow_multi     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.5    4.0    4.5    5.0    1.5   
0                   False  False  False  False  False   True   True   True   True   True  False  False   True   True   True   True   True   True  False   True   True   True   True   True   True   True  False   True   True   True   True   True   True   True   True   True   True   True   True   True   True   True  False  False  False   True  False   True   True   True  False   True   True   True   True   True   True  False   True   True   True   True  False  False  False  False   True   True   True  False  False  False  False  False   True   True  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   True   True  False  False  False  False   True   True   True   True  False   
1                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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   True  False  False  False   True  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   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   
2                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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  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   

split_idx                                                                                                                                                                                                                                    
dualma_fast_window                                                      35                                                      40                                                      45                                                   
dualma_slow_multi     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  
0                   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   True  
1                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  
2                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  
out_sharpe.shape: (1792,)

c,训练集上的最佳参数用于验证集

大致思路:
01,获取各split_idx的最佳收益(sharp_radio)的参数组合idxmax,也就是fast_window,slow_window,split_idx,三维索引元组
02,按照split_idx进行聚类,取得各split_idx对应的最佳参数。实际含义就是各滑动窗口的最佳参数

v1:简单最大值优选法
选取,测试集合的最优参数作为验证集参数,如果sharp_ratio就最大,回撤就最小类似这样的简单优选策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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_test_best_index_basic = get_best_index(in_sharpe)

merged_df = pd.concat([in_sharpe, in_return,out_sharpe,out_return], axis=1, keys=['in_sharpe', 'in_return','out_sharpe', 'out_return'])
print('merged_df[in_test_best_index_basic]')
print(merged_df.loc[in_test_best_index_basic])

# 绘图:参数走势图
df_plot_tmp = in_test_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()
merged_df[in_test_best_index_basic]
                                                        in_sharpe  in_return  out_sharpe  out_return
split_idx dualma_fast_window dualma_slow_multi sl_stop                                              
0         40                 1.5               0.05      2.713139   0.576378    0.204789    0.004091
1         20                 2.0               0.10      4.260810   1.625152    2.600636    0.278124
2         20                 1.5               0.10      3.841407   1.434804    2.621509    0.381941
3         10                 3.5               0.10      2.015480   0.526104    0.451947    0.023056
4         40                 5.0               0.05      2.835772   0.428931   -1.923102   -0.062478
5         10                 3.5               0.05      1.667313   0.159633    3.346740    0.295341
6         10                 2.0               0.10      2.966911   0.445291   -3.783652   -0.141299

svg

v2:邻近域优选法
有些情况下,测试集得到参数会突然发生较大变化,这可能偶发事件导致的,
比如:之前的双均线最佳参数一直是,(20,40),本期突然变成(80,160),显然不大合理,为了避免这种突变,让参数的变化也具有一定连贯性(当然,增加连贯性也一定程度降低过拟合风险)

in_test_best_index_nb_coord[:5]
MultiIndex([(0, 40, 1.5, 0.05),
            (1, 30, 1.5,  0.1),
            (2, 20, 1.5,  0.1),
            (3, 15, 2.5,  0.1),
            (4, 10, 3.5, 0.05)],
           names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop'])

svg

v3:邻居权重优选法-均值
在评估一组参数是否最佳时,并不单纯观察此参数本身是否最优,而是综合考虑参数本以及参数的邻居表现。
比如:
0.5 0.7 0.5 0.2 0.2
0.8 0.7 0.6 0.9 0.2
0.5 0.7 0.5 0.2 0.2
按照基础的最大值法,则选择0.9,但是0.9的邻居表现均不佳。
定义:新取值=原值 + (邻居的平均值)
则可以综合考虑参数本身和参数邻居点的表现。

in_test_best_index_nb_mean[:5]
MultiIndex([(0, 25, 1.5, 0.05),
            (1, 20, 2.0, 0.15),
            (2, 20, 1.5, 0.15),
            (3, 10, 3.5, 0.05),
            (4, 45, 5.0, 0.05)],
           names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop'])

svg

v4:邻居权重优选法-中位数
由于均值受极值影响较大,可以考虑用 median( 多个邻居),代替上面”邻居的平均值”。

in_test_best_index_nb_median[:5]
MultiIndex([(0, 30, 2.0, 0.05),
            (1, 20, 2.0,  0.1),
            (2, 20, 1.5,  0.1),
            (3, 10, 3.5, 0.05),
            (4, 45, 5.0, 0.05)],
           names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop'])

svg

将滚动获取的最佳参数用于验证集,统计收益信息

24,sharp ratio的汇总可视化

basic为例的基础分析视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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()

svg

关注点:

蓝色部分
正常排序是(从上到下):点线,实现,线段,

橘色部分

实线对实线
说明测试集和验证集的周期收益情况,二者同时出现0轴同侧较好(同时上涨,同时下跌,保持行情的稳定性or延续性)

线段对线段
二者一方面随着各自颜色的实线趋势变化(受各自实线影响较大),其他应该无必然联系

点线对点线
蓝色点高于橘色点线,蓝色是训练集内最佳,橘色则是训练集得到最优参数用于验证集结果收益,大概率低于验证集。

测试,验证集时间长度差异,引入偏差
由于测试集一般是验证集的2-3倍(或更多),对于单边行情(假如上涨),则(测试集的)实线收益。蓝色线大概率位于橘色线上方。
如果下跌,则相反。蓝色由于时间长,大概率位于橘色下方。

注意:
01,202406,对于当前case,y周取值为sharp ratio夏普比,而非收益率。所以数据点高低并不反映收益率。
所以,以上结论需要稍斟酌,并不完全准确。

4种优选方法的训练集夏普sharp ratio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cv_results_df = pd.DataFrame({
'in_sample_hold': in_hold_sharpe.values,
'in_sample_best_basic': in_sharpe[in_test_best_index_basic].values,
'in_sample_best_coord': in_sharpe[in_test_best_index_nb_coord].values,
'in_sample_best_mean': in_sharpe[in_test_best_index_nb_mean].values,
'in_sample_best_median': in_sharpe[in_test_best_index_nb_median].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['green']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()

svg

4种优选方法的验证集夏普sharp ratio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cv_results_df = pd.DataFrame({
'out_sample_hold': out_hold_sharpe.values,
'out_sample_test_basic': out_test_sharpe_basic.values,
'out_sample_test_coord': out_test_sharpe_coord.values,
'out_sample_test_mean': out_test_sharpe_mean.values,
'out_sample_test_median': out_test_sharpe_median.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['green']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()

svg

25,滚动回测收益可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 测试集:原始价格变动
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(),
'in_test_best_return_basic': (in_test_best_return_basic.values+1).cumprod(),
'in_test_best_return_coord': (in_test_best_return_nb_coord.values+1).cumprod(),
'in_test_best_return_mean': (in_test_best_return_nb_mean.values+1).cumprod(),
'in_test_best_return_median': (in_test_best_return_nb_median.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']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).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_basic': (out_test_return_basic.values+1).cumprod(),
'out_test_return_coord': (out_test_return_coord.values+1).cumprod(),
'out_test_return_mean': (out_test_return_mean.values+1).cumprod(),
'out_test_return_median': (out_test_return_median.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']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()
in_price_org shape: (7,)
in_price_org.head(5)
split_idx
0    1.772680
1    2.987621
2    1.620045
3    1.282265
4    1.822666
dtype: float64

svg

out_price_org shape: (7,)
out_price_org.head(5)
split_idx
0    2.210941
1    0.876075
2    2.001737
3    0.971119
4    0.902879
dtype: float64

out_test_return_basic shape: (7,)
out_test_return_basic.head(5) + 1
split_idx  dualma_fast_window  dualma_slow_multi  sl_stop
0          40                  1.5                0.05       1.004091
1          20                  2.0                0.10       1.278124
2          20                  1.5                0.10       1.381941
3          10                  3.5                0.10       1.023056
4          40                  5.0                0.05       0.937522
Name: total_return, dtype: float64

svg

上图可见,以上参数优选方法表现基本接近(也符合之前的sharp ratio接近的特征)

26,计算正确性验证(略)

27,回测结果汇总

std_indicator

过滤器规则:

1
2
3
4
5
std_close_wbuf = ohlcv_wbuf['Close'].rolling(window=20).std()
std_close_ma_wbuf = std_close_wbuf.rolling(window=20).mean()
std_close=std_close_wbuf[wobuf_mask]
std_close_ma=std_close_ma_wbuf[wobuf_mask]
std_indicator = (std_close > std_close_ma )

4种优选方法的训练集夏普sharp ratio
svg

4种优选方法的验证集夏普sharp ratio
svg
样本内滚动收益
svg

样本外滚动收益
svg

diff_indicator

过滤器规则:

1
2
3
4
5
diff_close_wbuf = ohlcv_wbuf['Close'] - ohlcv_wbuf['Close'].rolling(window=int(20/5)).mean().shift(20)
diff_close_ma_wbuf = diff_close_wbuf.rolling(window=20).mean()
diff_close=diff_close_wbuf[wobuf_mask]
diff_close_ma=diff_close_ma_wbuf[wobuf_mask]
diff_indicator = ((diff_close > diff_close_ma )&(diff_close_ma>200*0.0025*20))

4种优选方法的训练集夏普sharp ratio

4种优选方法的验证集夏普sharp ratio

样本内滚动收益

样本外滚动收益

策略研发过程中,除了纯粹的买卖技术指标之外,还有一类信号用于判断当前行情是否应该入场。
比如:大盘或个股持续处于波动性中,没有明显方向性,拉锯状态,此时大多数趋势类策略都会反复开仓,止损,开仓,止损,导致稳定的小额亏损。

01,基础配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#conda envs:vectorbt_env
import warnings
import vectorbt as vbt
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import pytz
from dateutil.parser import parse
import ipywidgets as widgets
from copy import deepcopy
from tqdm import tqdm
import imageio
from IPython import display
import plotly.graph_objects as go
import itertools
import dateparser
import gc
import math
from 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,时间交易参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# Enter your parameters here
seed = 42
symbol = '002594.XSHE'
metric = 'total_return'

start_date = datetime(2020, 1, 1, tzinfo=pytz.utc) # time period for analysis, must be timezone-aware
end_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 window
freq = '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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Download data with time buffer
cols = ['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 buffer
wobuf_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 data
ohlcv.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)

svg

03,朴素std,stdma

最简单的过滤方法是计算std,然后再计算标准差的ma,当std>ma(std),说明波动增大,反之波动减小。趋势策略当std>ma(std)时才开仓
大致效果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# 计算收盘价的标准差
std_close_wbuf = ohlcv_wbuf['Close'].rolling(window=20).std()
# 计算标准差的移动平均
std_close_ma_wbuf = std_close_wbuf.rolling(window=20).mean()

std_close=std_close_wbuf[wobuf_mask]
std_close_ma=std_close_ma_wbuf[wobuf_mask]


# 创建一个子图布局:2行1列
fig = make_subplots(rows=4, cols=1, shared_xaxes=True, vertical_spacing=0.02)

# 第一行添加蜡烛图
ohlcv_fig = ohlcv[['Open', 'High', 'Low', 'Close', 'Volume']].vbt.ohlcv.plot()
fig.add_trace(ohlcv_fig.data[0], row=1, col=1) # 添加蜡烛图数据
fig.add_trace(ohlcv_fig.data[1], row=2, col=1) # 添加交易量柱状图

# 第二行添加两个移动平均线
fig.add_trace(go.Scatter(x=ohlcv.index, y=std_close, mode='lines', name='SMA 20'), row=3, col=1)
fig.add_trace(go.Scatter(x=ohlcv.index, y=std_close_ma, mode='lines', name='SMA 50'), row=3, col=1)

# 创建条件指标
std_indicator = (std_close > std_close_ma ).astype(int)
# 添加条件指标图
fig.add_trace(go.Scatter(x=std_indicator.index, y=std_indicator, mode='lines', name='STD Condition', fill='tozeroy'), row=4, col=1)

# 更新图表的布局设置
fig.update_layout(height=600, width=800, title_text="蜡烛图与移动平均线")
fig.show()

最左侧的过滤效果并不佳,最左侧虽然满足过滤条件,但是图示上看,价格波动其实很小。

04,自适应的std,stdma(波动率的top30%过滤),

在基础std,stdma基础上,增加动态过滤功能,通过设置一个合适的gateway_ma

定义:std_close > std_close_ma 且 std_close_ma > gateway_ma 为有效(意思是通过过滤条件)
那么有效数据在 整个数据比例,称为”有效” 率。
如果不考虑,std_close_ma_wbuf > gateway_ma
显然 “有效”率 是一个固定的数字。比如40%。
而实际我们希望有效率降低到目标有效率,target_ratio,比如30%,意味着要降低10%的有效率。
此时就要通过提高gateway_ma,来让有效的不在有效。从而丢弃部分std_close_ma较小的时间区间,保留波动性相对较大的部分。

05,价格变动的一致性(动量)

除了考虑波动性,还需要考虑价格的变动是否有一致的方向。单纯的波动性增加,但价格突上突下无法形成显著方向,依然不行,所以期望过滤掉价格无方向的时间区间。
可以考虑如下的思路

20diff:
比如,t->T-20,t-1->T-21,等小柱子,计算小柱子,均值方差等状态。

结论:效果尚可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 计算收盘价的标准差
diff_close_wbuf = ohlcv_wbuf['Close'].diff(periods=20)
# 计算标准差的移动平均
diff_close_ma_wbuf = diff_close_wbuf.rolling(window=20).mean()

diff_close=diff_close_wbuf[wobuf_mask]
diff_close_ma=diff_close_ma_wbuf[wobuf_mask]



# 创建一个子图布局:2行1列
fig = make_subplots(rows=4, cols=1, shared_xaxes=True, vertical_spacing=0.02)

# 第一行添加蜡烛图
ohlcv_fig = ohlcv[['Open', 'High', 'Low', 'Close', 'Volume']].vbt.ohlcv.plot()
fig.add_trace(ohlcv_fig.data[0], row=1, col=1) # 添加蜡烛图数据
fig.add_trace(ohlcv_fig.data[1], row=2, col=1) # 添加交易量柱状图

# 第二行添加两个移动平均线
fig.add_trace(go.Scatter(x=ohlcv.index, y=diff_close, mode='lines', name='SMA 20'), row=3, col=1)
fig.add_trace(go.Scatter(x=ohlcv.index, y=diff_close_ma, mode='lines', name='SMA 50'), row=3, col=1)

# 创建条件指标
diff_indicator = ((diff_close > diff_close_ma )&(diff_close_ma>200*0.05)).astype(int) #20日达到5% 认为趋势性才足够大
# 添加条件指标图
fig.add_trace(go.Scatter(x=diff_indicator.index, y=diff_indicator, mode='lines', name='STD Condition', fill='tozeroy'), row=4, col=1)

# 更新图表的布局设置
fig.update_layout(height=600, width=800, title_text="蜡烛图与移动平均线")
fig.show()

06,价格变动的一致性,优化,过去值平滑处理,T-20用ma(T,20/4=5)代替

缺点t->T-20,取值也取决于T-20,所以t->T-20,的过去价格T-20,用ma后的价格替代,等于做了平滑。

结论:噪点变少,


另一种变体,重新定义均线为
T-ma(T)[T-20]

07,结合上面2指标,对比同列

std波动性,diff20趋势性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# 计算收盘价的标准差
std_close_wbuf = ohlcv_wbuf['Close'].rolling(window=20).std()
# 计算标准差的移动平均
std_close_ma_wbuf = std_close_wbuf.rolling(window=20).mean()

std_close=std_close_wbuf[wobuf_mask]
std_close_ma=std_close_ma_wbuf[wobuf_mask]


# 计算收盘价diff
diff_close_wbuf = ohlcv_wbuf['Close'] - ohlcv_wbuf['Close'].rolling(window=int(20/5)).mean().shift(20)
# 计算收盘价diff的移动平均
diff_close_ma_wbuf = diff_close_wbuf.rolling(window=20).mean()

diff_close=diff_close_wbuf[wobuf_mask]
diff_close_ma=diff_close_ma_wbuf[wobuf_mask]


# 创建一个子图布局:2行1列
fig = make_subplots(rows=4, cols=1, shared_xaxes=True, vertical_spacing=0.02)

# 第一行添加蜡烛图
ohlcv_fig = ohlcv[['Open', 'High', 'Low', 'Close', 'Volume']].vbt.ohlcv.plot()
fig.add_trace(ohlcv_fig.data[0], row=1, col=1) # 添加蜡烛图数据
fig.add_trace(ohlcv_fig.data[1], row=2, col=1) # 添加交易量柱状图

# 第二行添加两个移动平均线
fig.add_trace(go.Scatter(x=ohlcv.index, y=std_close, mode='lines', name='SMA 20'), row=3, col=1)
fig.add_trace(go.Scatter(x=ohlcv.index, y=std_close_ma, mode='lines', name='SMA 50'), row=3, col=1)

fig.add_trace(go.Scatter(x=ohlcv.index, y=diff_close, mode='lines', name='diff 20'), row=4, col=1)
fig.add_trace(go.Scatter(x=ohlcv.index, y=diff_close_ma, mode='lines', name='diff 50'), row=4, col=1)

# # 创建条件指标
# std_indicator = (std_close > std_close_ma ).astype(int)
# # 添加条件指标图
# fig.add_trace(go.Scatter(x=std_indicator.index, y=std_indicator, mode='lines', name='STD Condition', fill='tozeroy'), row=4, col=1)

# 更新图表的布局设置
fig.update_layout(height=600, width=800, title_text="蜡烛图与移动平均线")
fig.show()

本文在上一篇文章(30DMA之八滑窗网格参数优选)基础上。
上一篇文章增加了止盈,止损,跟踪止损等参数,但实际效果看训练集上效果尚可,验证集上效果更差,怀疑过拟合导致。

故本篇文章增加几种避免过拟合的参数优选方法。之前文章方法类似。
差异在于:对本文而言,止损参数,止盈参数也是类似的,不止2个维度了,邻居采用立方体思路(对角点相接的也算作邻居,之前2维时是同边才算邻居,比如(1,3),邻居是(2,3),(1,2),(1,4),新的规则会新增(2,2),(2,4))

新增3种参数优选方法,一定程度上降低参数过拟合的可能。

v1:直接(简单最大值)优选法
选取,测试集合的最优参数作为验证集参数,如果sharp_ratio就最大,回撤就最小类似这样的简单优选策略。

v2:邻近域优选法
在上一个策略中,实际上是选取,测试集合的最优参数作为验证集参数。而有些情况下,测试集得到参数会突然发生较大变化,这可能偶发事件导致的,
比如:之前的双均线最佳参数一直是,(20,40),本期突然变成(80,160),显然不大合理,为了避免这种突变,让参数的变化也具有一定连贯性(当然,增加连贯性也一定程度降低过拟合风险)

v3:邻居权重优选法-均值
在评估一组参数是否最佳时,并不单纯观察此参数本身是否最优,而是综合考虑参数本以及参数的邻居表现。
比如:
0.5 0.7 0.5 0.2 0.2
0.8 0.7 0.6 0.9 0.2
0.5 0.7 0.5 0.2 0.2
按照基础的最大值法,则选择0.9,但是0.9的邻居表现均不佳。
定义:新取值=原值 + (邻居的平均值)
则可以综合考虑参数本身和参数邻居点的表现。

v4:邻居权重优选法-中位数
由于均值受极值影响较大,可以考虑用 median( 多个邻居),代替上面”邻居的平均值”。

01,基础配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#conda envs:vectorbt_env
import warnings
import vectorbt as vbt
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import pytz
from dateutil.parser import parse
import ipywidgets as widgets
from copy import deepcopy
from tqdm import tqdm
import imageio
from IPython import display
import plotly.graph_objects as go
import itertools
import dateparser
import gc
import math
from 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,时间交易参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# Enter your parameters here
seed = 42
symbol = '002594.XSHE'
metric = 'total_return'

start_date = datetime(2020, 1, 1, tzinfo=pytz.utc) # time period for analysis, must be timezone-aware
end_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 window
freq = '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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Download data with time buffer
cols = ['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 buffer
wobuf_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 data
ohlcv.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)

svg

20,网格参数-指标计算和可视化

仅可视化第一列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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_wbuf=ohlcv_wbuf['Close']
dualma = vbt.DualMA.run(price_wbuf, fast_window=fast_windows,slow_multi=slow_multis,param_product=True)
dualma = dualma[wobuf_mask]
# there should be no nans after removing time buffer
assert(~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                                                                        45                                                                             
dualma_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
date                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
2020-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.440222
2020-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.555556
2020-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.660222
dualma.slow_ma.head(3)
dualma_fast_window                10                                                                              15                                                                                      20                                                                                25                                                                                 30                                                                                    35                                                                                      40                                                                                   45                                                                             
dualma_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
date                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
2020-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.375822
2020-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.379778
2020-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

svg

21,网格参数-信号计算和可视化

仅可视化第一列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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           10            
dualma_slow_multi           1.5   2.0   2.5
date                                       
2020-01-02 00:00:00+00:00  True  True  True
2020-01-03 00:00:00+00:00  True  True  True
2020-01-06 00:00:00+00:00  True  True  True

svg

svg

Start                       2020-01-02 00:00:00+00:00
End                         2022-12-30 00:00:00+00:00
Period                                            728
Total                                       474.03125
Rate [%]                                    65.114183
First Index                 2020-01-15 16:52:30+00:00
Last Index                  2022-11-07 20:15:00+00:00
Norm Avg Index [-1, 1]                      -0.159967
Distance: Min                                     1.0
Distance: Max                               82.734375
Distance: Mean                               1.464916
Distance: Std                                5.175417
Total Partitions                             6.671875
Partition Rate [%]                           1.510978
Partition Length: Min                       41.671875
Partition Length: Max                      211.171875
Partition Length: Mean                     110.468174
Partition Length: Std                       78.523847
Partition Distance: Min                      26.78125
Partition Distance: Max                     82.734375
Partition Distance: Mean                    51.365493
Partition Distance: Std                     28.015768
Name: agg_func_mean, dtype: object

22,行情,信号的滑窗处理

注意点:
01,训练集和验证集比例3:1,或者2:1,对应:window_len和set_lens为4:1(或3:1),过大了历史包袱沉重,无法及时响应最新行情,过小了则容易参数跳变,形成类似过拟合效果

a,参数设置和效果预览

代码中

1
2
3
4
5
6
7
8
9
10
11
#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:00
2019-03-25 00:00:00+00:00
DatetimeIndex(['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,就是由于切分优先保证了数据的足量,但是数据间隔方面则可能有所重叠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 滚动周期参数设置和大致效果可视化
start_end_days=ohlcv.shape[0]
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)

price=ohlcv['Close']
# 验证:单列数据验证,橘黄色验证集连续且无重复
roll_in_and_out_samples(price, **split_kwargs, plot=True, trace_names=['in-sample', 'out-sample']).show_svg()
split_kwargs: {'n': 7, 'window_len': 240, 'set_lens': (80,), 'left_to_right': False}

svg

b,根据滑窗参数切分行情数据和信号

in_price.shape: (160, 7)
out_price.shape: (80, 7)

in_price.index: RangeIndex(start=0, stop=160, step=1)
in_price.columns: Int64Index([0, 1, 2, 3, 4, 5, 6], dtype='int64', name='split_idx')

in_price[0:3]:
split_idx      0      1      2       3       4       5       6
0          48.17  59.78  92.59  219.90  146.56  254.11  250.02
1          48.04  58.88  90.00  216.30  153.73  277.60  246.50
2          48.28  59.13  94.74  225.04  148.99  275.95  246.30

###############################
in_dmac_size.shape: (160, 448)
out_dmac_size.shape: (80, 448)

in_dmac_size.iloc[:5,:5]:
split_idx              0                        
dualma_fast_window    10                        
dualma_slow_multi    1.5   2.0   2.5   3.0   3.5
0                   True  True  True  True  True
1                   True  True  True  True  True
2                   True  True  True  True  True
3                   True  True  True  True  True
4                   True  True  True  True  True

23,滑窗的收益数据计算

a,持有参数收益

在此区间,基础标的物表现

1
2
3
4
5
6
7
8
9
10
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_idx
0    2.315678
1    3.890261
2    1.812302
3    1.122310
4    2.388496
Name: sharpe_ratio, dtype: float64
split_idx
0    4.885519
1   -0.547754
2    4.538256
3   -0.039085
4   -0.527252
Name: sharpe_ratio, dtype: float64

b,网格参数收益(训练集和验证集)

in_sharpe.shape: (1792,)
split_idx  dualma_fast_window  dualma_slow_multi  sl_stop
0          10                  1.5                0.05       2.698831
                                                  0.10       2.487661
                                                  0.15       2.305821
                                                  0.20       2.389847
                               2.0                0.05       2.344002
                                                               ...   
6          45                  4.5                0.20      -1.054460
                               5.0                0.05      -0.331869
                                                  0.10      -1.664299
                                                  0.15      -1.487590
                                                  0.20      -1.513999
Name: sharpe_ratio, Length: 1792, 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                                                      15                 
dualma_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.5   
0                    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  False  False  False  False  False  False  False  False  False  False  False   
1                   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  False   
2                   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                                                                                                                                                                                                                                                                                                                                                                                                                                                               4                                                                                                                                                     \
dualma_fast_window                                        20                                                      25                                                      30                                                      35                                                      40                                                      45                                                      10                                                      15                                                      20                                                      25                                                      30                                                      35                                                      40                                                      45                                                      10                                                      15                                                      20                                      
dualma_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    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   
0                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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  False  False  False  False  False   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  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False   
1                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False   
2                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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                                                                                                                                                                                                                                                                                                                     5                                                                                                                                                                                                                                                                                                                                                                                                                                                               6                                                                                                                                                                                                                                  \
dualma_fast_window                   25                                                      30                                                      35                                                      40                                                      45                                                      10                                                      15                                                      20                                                      25                                                      30                                                      35                                                      40                                                      45                                                      10                                                      15                                                      20                                                      25                                                      30   
dualma_slow_multi     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.5    4.0    4.5    5.0    1.5   
0                   False  False  False  False  False   True   True   True   True   True  False  False   True   True   True   True   True   True  False   True   True   True   True   True   True   True  False   True   True   True   True   True   True   True   True   True   True   True   True   True   True   True  False  False  False   True  False   True   True   True  False   True   True   True   True   True   True  False   True   True   True   True  False  False  False  False   True   True   True  False  False  False  False  False   True   True  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   True   True  False  False  False  False   True   True   True   True  False   
1                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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   True  False  False  False   True  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   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   
2                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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  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   

split_idx                                                                                                                                                                                                                                    
dualma_fast_window                                                      35                                                      40                                                      45                                                   
dualma_slow_multi     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  
0                   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   True  
1                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  
2                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  
out_sharpe.shape: (1792,)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# import pandas as pd
# import 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 是上述提供的 DataFrame
# comparison_results = compare_true_false_statistics(statistics)
# print(comparison_results)

c,训练集上的最佳参数用于验证集

大致思路:
01,获取各split_idx的最佳收益(sharp_radio)的参数组合idxmax,也就是fast_window,slow_window,split_idx,三维索引元组
02,按照split_idx进行聚类,取得各split_idx对应的最佳参数。实际含义就是各滑动窗口的最佳参数

v1:简单最大值优选法
选取,测试集合的最优参数作为验证集参数,如果sharp_ratio就最大,回撤就最小类似这样的简单优选策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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_test_best_index_basic = get_best_index(in_sharpe)

merged_df = pd.concat([in_sharpe, in_return,out_sharpe,out_return], axis=1, keys=['in_sharpe', 'in_return','out_sharpe', 'out_return'])
print('merged_df[in_test_best_index_basic]')
print(merged_df.loc[in_test_best_index_basic])

# 绘图:参数走势图
df_plot_tmp = in_test_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()
merged_df[in_test_best_index_basic]
                                                        in_sharpe  in_return  out_sharpe  out_return
split_idx dualma_fast_window dualma_slow_multi sl_stop                                              
0         40                 1.5               0.05      3.032440   0.678169    0.204789    0.004091
1         20                 2.0               0.10      4.264742   1.678727    2.600636    0.278124
2         20                 1.5               0.10      3.698019   1.458562    2.820473    0.422292
3         10                 3.5               0.10      2.015480   0.526104    0.451947    0.023056
4         40                 5.0               0.05      2.835772   0.428931   -1.923102   -0.062478
5         15                 2.0               0.05      0.717109   0.062730    3.414356    0.302466
6         25                 2.5               0.05      2.848787   0.414228    0.900054    0.004202

svg

v2:邻近域优选法
有些情况下,测试集得到参数会突然发生较大变化,这可能偶发事件导致的,
比如:之前的双均线最佳参数一直是,(20,40),本期突然变成(80,160),显然不大合理,为了避免这种突变,让参数的变化也具有一定连贯性(当然,增加连贯性也一定程度降低过拟合风险)

in_test_best_index_nb_coord[:5]
MultiIndex([(0, 40, 1.5, 0.05),
            (1, 30, 1.5,  0.1),
            (2, 20, 1.5,  0.1),
            (3, 15, 2.5,  0.1),
            (4, 10, 3.5, 0.05)],
           names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop'])

svg

v3:邻居权重优选法-均值
在评估一组参数是否最佳时,并不单纯观察此参数本身是否最优,而是综合考虑参数本以及参数的邻居表现。
比如:
0.5 0.7 0.5 0.2 0.2
0.8 0.7 0.6 0.9 0.2
0.5 0.7 0.5 0.2 0.2
按照基础的最大值法,则选择0.9,但是0.9的邻居表现均不佳。
定义:新取值=原值 + (邻居的平均值)
则可以综合考虑参数本身和参数邻居点的表现。

in_test_best_index_nb_mean[:5]
MultiIndex([(0, 25, 2.5, 0.05),
            (1, 20, 2.0, 0.15),
            (2, 20, 1.5, 0.15),
            (3, 10, 3.5, 0.05),
            (4, 45, 5.0, 0.05)],
           names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop'])

svg

v4:邻居权重优选法-中位数
由于均值受极值影响较大,可以考虑用 median( 多个邻居),代替上面”邻居的平均值”。

in_test_best_index_nb_median[:5]
MultiIndex([(0, 25, 2.5, 0.05),
            (1, 25, 2.5,  0.2),
            (2, 20, 1.5,  0.1),
            (3, 10, 3.5, 0.05),
            (4, 45, 5.0, 0.05)],
           names=['split_idx', 'dualma_fast_window', 'dualma_slow_multi', 'sl_stop'])

svg

将滚动获取的最佳参数用于验证集,统计收益信息

24,sharp ratio的汇总可视化

basic为例的基础分析视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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()

svg

关注点:

蓝色部分
正常排序是(从上到下):点线,实现,线段,

橘色部分

实线对实线
说明测试集和验证集的周期收益情况,二者同时出现0轴同侧较好(同时上涨,同时下跌,保持行情的稳定性or延续性)

线段对线段
二者一方面随着各自颜色的实线趋势变化(受各自实线影响较大),其他应该无必然联系

点线对点线
蓝色点高于橘色点线,蓝色是训练集内最佳,橘色则是训练集得到最优参数用于验证集结果收益,大概率低于验证集。

测试,验证集时间长度差异,引入偏差
由于测试集一般是验证集的2-3倍(或更多),对于单边行情(假如上涨),则(测试集的)实线收益。蓝色线大概率位于橘色线上方。
如果下跌,则相反。蓝色由于时间长,大概率位于橘色下方。

注意:
01,202406,对于当前case,y周取值为sharp ratio夏普比,而非收益率。所以数据点高低并不反映收益率。
所以,以上结论需要稍斟酌,并不完全准确。

4种优选方法的训练集夏普sharp ratio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cv_results_df = pd.DataFrame({
'in_sample_hold': in_hold_sharpe.values,
'in_sample_best_basic': in_sharpe[in_test_best_index_basic].values,
'in_sample_best_coord': in_sharpe[in_test_best_index_nb_coord].values,
'in_sample_best_mean': in_sharpe[in_test_best_index_nb_mean].values,
'in_sample_best_median': in_sharpe[in_test_best_index_nb_median].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['green']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()

svg

4种优选方法的验证集夏普sharp ratio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cv_results_df = pd.DataFrame({
'out_sample_hold': out_hold_sharpe.values,
'out_sample_test_basic': out_test_sharpe_basic.values,
'out_sample_test_coord': out_test_sharpe_coord.values,
'out_sample_test_mean': out_test_sharpe_mean.values,
'out_sample_test_median': out_test_sharpe_median.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['green']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()

svg

25,滚动回测收益可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 测试集:原始价格变动
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(),
'in_test_best_return_basic': (in_test_best_return_basic.values+1).cumprod(),
'in_test_best_return_coord': (in_test_best_return_nb_coord.values+1).cumprod(),
'in_test_best_return_mean': (in_test_best_return_nb_mean.values+1).cumprod(),
'in_test_best_return_median': (in_test_best_return_nb_median.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']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).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_basic': (out_test_return_basic.values+1).cumprod(),
'out_test_return_coord': (out_test_return_coord.values+1).cumprod(),
'out_test_return_mean': (out_test_return_mean.values+1).cumprod(),
'out_test_return_median': (out_test_return_median.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']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()
in_price_org shape: (7,)
in_price_org.head(5)
split_idx
0    1.772680
1    2.987621
2    1.620045
3    1.282265
4    1.822666
dtype: float64

svg

out_price_org shape: (7,)
out_price_org.head(5)
split_idx
0    2.210941
1    0.876075
2    2.001737
3    0.971119
4    0.902879
dtype: float64

out_test_return_basic shape: (7,)
out_test_return_basic.head(5) + 1
split_idx  dualma_fast_window  dualma_slow_multi  sl_stop
0          40                  1.5                0.05       1.004091
1          20                  2.0                0.10       1.278124
2          20                  1.5                0.10       1.422292
3          10                  3.5                0.10       1.023056
4          40                  5.0                0.05       0.937522
Name: total_return, dtype: float64

svg

上图可见,以上参数优选方法表现基本接近(也符合之前的sharp ratio接近的特征),不论何种参数优选策略,均优于单纯的持有(不过,结论未必通用)。

26,计算正确性验证(略)

a,准备校验数据,数据展示
b,行情->指标 计算正确
c,指标->信号 计算正确

27,回测结果汇总

参数
4种优选方法 训练集夏普sharp ratio
4种优选方法 验证集夏普sharp ratio
4种优选方法 最佳参数测试集回测表现
4种优选方法 最佳参数验证集回测表现

跟踪止损

参数

1
2
3
4
5
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = True
无止盈

4种优选方法 训练集夏普sharp ratio
svg

4种优选方法 验证集夏普sharp ratio

svg

4种优选方法 最佳参数测试集回测表现
svg

4种优选方法 最佳参数验证集回测表现
svg

非跟踪止损

参数

1
2
3
4
5
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = False
无止盈

4种优选方法 训练集夏普sharp ratio

4种优选方法 验证集夏普sharp ratio

4种优选方法 最佳参数测试集回测表现

4种优选方法 最佳参数验证集回测表现

跟踪止损+止盈

参数

1
2
3
4
5
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = True
tp_stops = [0.1, 0.2]

参数
4种优选方法 训练集夏普sharp ratio

4种优选方法 验证集夏普sharp ratio

4种优选方法 最佳参数测试集回测表现

4种优选方法 最佳参数验证集回测表现

非跟踪止损+止盈

参数

1
2
3
4
5
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = False
tp_stops = [0.1, 0.2]

4种优选方法 训练集夏普sharp ratio

4种优选方法 验证集夏普sharp ratio

4种优选方法 最佳参数测试集回测表现

4种优选方法 最佳参数验证集回测表现

小汇总

跟踪止损 vs 非跟踪止损(买入价固定百分比止损):一般常规认为跟踪止损更佳,此处并不显著。
跟踪止损+止盈 vs 跟踪止损:趋势策略添加止盈等于给跳蚤加盖测跳高,极度不合适,训练集上效果都上不来。
非跟踪止损+止盈 vs 非跟踪止损:加了止盈不怎么样了。
趋势策略不考虑止盈,可考虑增加买入价固定百分比止损,或跟踪止损。

本文在上一篇文章(DMA之六滑窗网格参数优选)基础上。
增加退出模块,虽然双均线本身可以生成退出信号,但是其信号大多滞后的,无法实现止损,跟踪止损,止盈等功能。
本文增加止损,跟踪止损,止盈等的回测。
本篇文章部分图示有误,以下一篇为准:31DMA之九滑窗网格参数优选

01,基础配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#conda envs:vectorbt_env
import warnings
import vectorbt as vbt
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import pytz
from dateutil.parser import parse
import ipywidgets as widgets
from copy import deepcopy
from tqdm import tqdm
import imageio
from IPython import display
import plotly.graph_objects as go
import itertools
import dateparser
import gc
import math
from 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,时间交易参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# Enter your parameters here
seed = 42
symbol = '002594.XSHE'
metric = 'total_return'

start_date = datetime(2020, 1, 1, tzinfo=pytz.utc) # time period for analysis, must be timezone-aware
end_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 window
freq = '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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Download data with time buffer
cols = ['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 buffer
wobuf_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 data
ohlcv.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)

svg

20,网格参数-指标计算和可视化

仅可视化第一列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 buffer
assert(~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                                                                        45                                                                             
dualma_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
date                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
2020-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.440222
2020-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.555556
2020-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.660222
dualma.slow_ma.head(3)
dualma_fast_window                10                                                                              15                                                                                      20                                                                                25                                                                                 30                                                                                    35                                                                                      40                                                                                   45                                                                             
dualma_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
date                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
2020-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.375822
2020-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.379778
2020-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

svg

21,网格参数-信号计算和可视化

仅可视化第一列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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           10            
dualma_slow_multi           1.5   2.0   2.5
date                                       
2020-01-02 00:00:00+00:00  True  True  True
2020-01-03 00:00:00+00:00  True  True  True
2020-01-06 00:00:00+00:00  True  True  True

svg

svg

Start                       2020-01-02 00:00:00+00:00
End                         2022-12-30 00:00:00+00:00
Period                                            728
Total                                       474.03125
Rate [%]                                    65.114183
First Index                 2020-01-15 16:52:30+00:00
Last Index                  2022-11-07 20:15:00+00:00
Norm Avg Index [-1, 1]                      -0.159967
Distance: Min                                     1.0
Distance: Max                               82.734375
Distance: Mean                               1.464916
Distance: Std                                5.175417
Total Partitions                             6.671875
Partition Rate [%]                           1.510978
Partition Length: Min                       41.671875
Partition Length: Max                      211.171875
Partition Length: Mean                     110.468174
Partition Length: Std                       78.523847
Partition Distance: Min                      26.78125
Partition Distance: Max                     82.734375
Partition Distance: Mean                    51.365493
Partition Distance: Std                     28.015768
Name: agg_func_mean, dtype: object

22,行情,信号的滑窗处理

注意点:
01,训练集和验证集比例3:1,或者2:1,对应:window_len和set_lens为4:1(或3:1),过大了历史包袱沉重,无法及时响应最新行情,过小了则容易参数跳变,形成类似过拟合效果

a,参数设置和效果预览

代码中

1
2
3
4
5
6
7
8
9
10
11
#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:00
2019-03-25 00:00:00+00:00
DatetimeIndex(['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,就是由于切分优先保证了数据的足量,但是数据间隔方面则可能有所重叠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 滚动周期参数设置和大致效果可视化
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}

svg

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       10
0          49.17  58.15  51.20  43.39  48.15  97.90  167.98  239.52  202.00  251.77  253.14
1          48.06  56.16  49.50  43.15  49.73  96.55  164.08  225.00  214.11  252.50  266.49
2          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              0                        
dualma_fast_window    10                        
dualma_slow_multi    1.5   2.0   2.5   3.0   3.5
0                   True  True  True  True  True
1                   True  True  True  True  True
2                   True  True  True  True  True
3                   True  True  True  True  True
4                   True  True  True  True  True

23,滑窗的收益数据计算

a,持有参数收益

在此区间,基础标的物表现

1
2
3
4
5
6
7
8
9
10
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_idx
0    0.235446
1   -1.630616
2    0.598889
3    2.647397
4    4.501923
Name: sharpe_ratio, dtype: float64
split_idx
0   -0.929956
1    2.065991
2    4.100300
3    4.801291
4    0.688785
Name: sharpe_ratio, dtype: float64

b,网格参数收益(训练集和验证集)

in_sharpe.shape: (11264,)
split_idx  dualma_fast_window  dualma_slow_multi  sl_stop  sl_trail  tp_stop
0          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.910520
Name: 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                                                      15                 
dualma_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.5   
0                    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   True   
1                   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  False   
2                   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                                                      30          
dualma_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.0   
0                    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  False   
1                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  ...  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False   
2                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  ...  False  False  False  False  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                                                      35                               
dualma_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.5   
0                   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   True   
1                   False  False  False  False  False  False  False  False  False  False  False  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  False   
2                   False  False  False  False  False  False  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                                                                                                                                                   10                                                                                                                                                                                                                                                                                                                                                                                                                                                           
dualma_fast_window                          40                                                      45                                                      10                                                      15                                                      20                                                      25                                                      30                                                      35                                                      40                                                      45                                                   
dualma_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.0  
0                    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   True  
1                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  
2                   False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  False  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_stop
0          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.197531
Name: sharpe_ratio, Length: 11264, dtype: float64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import pandas as pd
import 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 是上述提供的 DataFrame
comparison_results = compare_true_false_statistics(statistics)
print(comparison_results)
                      mean       var       max       min    median       25%       75%
sl_trail sl_stop                                                                      
False    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.455634
True     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_stop                                       
0.05        0    0    0    0       0    1    0
0.10        0    0    0    1       1    1    0
0.15        0    0    0    0       1    1    0
0.20        0    0    0    1       1    1    0

c,训练集上的最佳参数用于验证集

大致思路:
01,获取各split_idx的最佳收益(sharp_radio)的参数组合idxmax,也就是fast_window,slow_window,split_idx,三维索引元组
02,按照split_idx进行聚类,取得各split_idx对应的最佳参数。实际含义就是各滑动窗口的最佳参数

v1:简单最大值优选法
选取,测试集合的最优参数作为验证集参数,如果sharp_ratio就最大,回撤就最小类似这样的简单优选策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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_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'])

svg

将滚动获取的最佳参数用于验证集,统计收益信息

24,sharp ratio的汇总可视化

basic为例的基础分析视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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()

svg

关注点:

蓝色部分
正常排序是(从上到下):点线,实现,线段,

橘色部分

实线对实线
说明测试集和验证集的周期收益情况,二者同时出现0轴同侧较好(同时上涨,同时下跌,保持行情的稳定性or延续性)

线段对线段
二者一方面随着各自颜色的实线趋势变化(受各自实线影响较大),其他应该无必然联系

点线对点线
蓝色点高于橘色点线,蓝色是训练集内最佳,橘色则是训练集得到最优参数用于验证集结果收益,大概率低于验证集。

测试,验证集时间长度差异,引入偏差
由于测试集一般是验证集的2-3倍(或更多),对于单边行情(假如上涨),则(测试集的)实线收益。蓝色线大概率位于橘色线上方。
如果下跌,则相反。蓝色由于时间长,大概率位于橘色下方。

注意:
01,202406,对于当前case,y周取值为sharp ratio夏普比,而非收益率。所以数据点高低并不反映收益率。
所以,以上结论需要稍斟酌,并不完全准确。

25,滚动回测收益可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 验证集:原始价格变动
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_idx
0    1.007525
1    0.774549
2    1.086914
3    1.933856
4    3.468328
dtype: float64

svg

out_price_org shape: (11,)
out_price_org.head(5)
split_idx
0    0.940574
1    1.315436
2    1.625985
3    2.111239
4    1.059162
dtype: float64

out_test_return_basic shape: (11,)
out_test_return_basic.head(5) + 1
split_idx  dualma_fast_window  dualma_slow_multi  sl_stop  sl_trail  tp_stop
0          35                  2.0                0.10     True      0.1        0.875962
1          15                  4.0                0.05     True      0.1        1.195700
2          10                  3.0                0.05     False     0.1        1.064199
3          25                  3.5                0.05     False     0.1        1.571024
4          40                  5.0                0.05     False     0.1        1.053886
Name: total_return, dtype: float64

svg

上图可见,以上参数优选方法表现基本接近(也符合之前的sharp ratio接近的特征),不论何种参数优选策略,均优于单纯的持有(不过,结论未必通用)。

26,计算正确性验证(略)

a,准备校验数据,数据展示
b,行情->指标 计算正确
c,指标->信号 计算正确
entry record: 48.9220 price: 51.5800

27,回测结果汇总

原始双均线

参数

1
2
fast_windows = np.arange(10, 50,5)
slow_multis = np.arange(1.5, 5.5, 0.5)

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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),所以时间维度难以持续满仓,所以牛市可能不如稳定持有,但是遇到大跌时,规避风险的优势就很明显了。

跟踪止损

参数

1
2
sl_stops = [0.05,0.1,0.15,0.20]
sl_trail=True

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
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

参数

1
2
sl_stops = [np.inf,0.05,0.1,0.15,0.20]
sl_trail=True

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
merged_df[in_test_best_index_basic]
in_sharpe in_return out_sharpe out_return
split_idx dualma_fast_window dualma_slow_multi sl_stop
0 35 2.0 0.10 1.979770 0.319978 -2.876613 -0.124038
1 15 4.0 0.05 0.324361 0.016997 2.231171 0.195700
2 10 2.0 0.05 1.844657 0.303627 2.693463 0.161817
3 25 3.5 inf 3.374967 1.233830 3.996941 0.571024
4 40 5.0 inf 4.501923 2.451051 0.688785 0.053886
5 35 5.0 0.20 2.959891 1.144421 -2.467499 -0.193663
6 10 4.0 0.10 3.029320 0.727907 -1.060044 -0.073690
7 45 3.0 inf 2.296182 0.749108 inf 0.000000
8 40 1.5 inf 2.489317 0.692895 3.678357 0.449287
9 20 1.5 inf 2.926436 0.575073 2.364898 0.128734
10 30 1.5 0.05 2.402594 0.302466 -4.323912 -0.074724

最佳参数样本内网sharp

最佳参数测试集回测表现

最佳参数验证集回测表现

止损止盈

1
2
3
sl_stops = [0.05,0.1,0.15,0.20]
sl_trails = [False, True]
tp_stops = [0.1, 0.2]

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
13
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

参数

1
2
3
sl_stops = [np.inf,0.05,0.1,0.15,0.20]
sl_trails = [False, True]
tp_stops = [np.inf,0.1, 0.2]

最佳参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
merged_df[in_test_best_index_basic]
in_sharpe in_return out_sharpe out_return
split_idx dualma_fast_window dualma_slow_multi sl_stop sl_trail tp_stop
0 45 2.0 inf False 0.1 3.158667 0.237753 -0.929956 -0.064111
1 15 4.0 inf False 0.1 0.664945 0.034948 2.563318 0.145963
2 10 3.0 0.05 False inf 1.978273 0.408342 1.451395 0.064199
3 25 3.5 inf False inf 3.374967 1.233830 3.996941 0.571024
4 15 1.5 inf False 0.2 5.245280 1.127771 2.858175 0.239233
5 35 5.0 0.20 True inf 2.959891 1.144421 -2.467499 -0.193663
6 20 4.0 inf False 0.2 4.039079 0.462475 1.890751 0.207220
7 45 3.0 inf False inf 2.296182 0.749108 inf 0.000000
8 40 1.5 inf False 0.2 2.665200 0.492175 2.449875 0.239941
9 30 5.0 inf False 0.2 3.738112 0.507262 -0.434888 -0.063484
10 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”肯定存在差异。

阶段性回顾汇总之前个文章,步骤的核心功能。

DMA之一基础策略

通过2个vbt.MA.run计算均线,然后计算指标和可视化,计算信号和可视化,最终形成交易序列以及可视化。
svg

DMA之二网格参数优选

在上一篇基础上,使用vbt.MA.run_combs,形成多个参数组合的策略,网格遍历的形式优选出最佳均线参数。获得各参数表现热力图,以及最佳表现。
局限在于:
01,虽然回测但无法实盘,原因是对于今天来讲,使用的均是历史信息,但是对于过去视角看,过去是不知道未来xx月,使用xx参数可以取得较好收益。所以获得回测结果也就只能看看而已,不具备落地条件。
02,由于参数在整个测试区间都是固定,限制了策略表现,现实中参数大概率随时间or行情走势动态变动的。固定参数无法动态的适应行情。
svg
svg

DMA之三滑窗网格参数优选(滚动窗口)

采用滚动窗口+网格参数优选,分析出动态最优参数。
需要对行情,做滚动切分,切分成训练集,预测集
训练集用于测试各参数的表现,选择最优用于预测集合。同时也要尽可能保证
a,训练集和预测集数据比例合适.
b,预测集合尽可能首尾相接连续整合行情区间,避免重复或者间隙过多(重复或者间隙可能导致double/错过 某周期性行情,引入额外测试误差).

svg

验证集的收益计算采用,会面临预热问题,导致验证集信号稀疏。也会破坏最初设计的验证集首尾相接接近整个时间区间

1
2
3
4
5
6
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()

最终效果
svg

DMA之四滑窗网格参数优选(size状态仓位)

处理预热问题,将信号,指标的计算前置,entry+exit机制改为状态机制(否则entry按照滚动周期,切分后会有信号丢失的问题)。
此时得到结果
svg
对应到参数走势图
svg

显然参数不合理,出现(10,11),(43,44)这种参数组合。
不过,这是第一个理论上没有未来数据等硬伤,且可持续滚动进行下去的版本

DMA之五滑窗网格参数优选(参数正交性:快线*慢线乘数)

改进了参数组合方式,采用快线以及慢线相对快线的倍率。大致认为剔除二者相关性了(正交性)。
此时就无法借助run_combs创建组合计算指标了,需基于vbt创建新技术指标DualMA
参数走势
svg

最终效果
svg

DMA之六滑窗网格参数优选(4种参数优选)

之前每个周期(训练集)的最佳参数,都是采用直接优选的方法。可能存在局部最优过拟合陷阱(某个参数效果很好,但是其邻居效果奇差)。新增3种参数优选方法,一定程度上降低参数过拟合的可能。

v1:直接(简单最大值)优选法
选取,测试集合的最优参数作为验证集参数,如果sharp_ratio就最大,回撤就最小类似这样的简单优选策略。
svg

v2:邻近域优选法
在上一个策略中,实际上是选取,测试集合的最优参数作为验证集参数。而有些情况下,测试集得到参数会突然发生较大变化,这可能偶发事件导致的,
比如:之前的双均线最佳参数一直是,(20,40),本期突然变成(80,160),显然不大合理,为了避免这种突变,让参数的变化也具有一定连贯性(当然,增加连贯性也一定程度降低过拟合风险)
svg
v3:邻居权重优选法-均值
在评估一组参数是否最佳时,并不单纯观察此参数本身是否最优,而是综合考虑参数本以及参数的邻居表现。
比如:
0.5 0.7 0.5 0.2 0.2
0.8 0.7 0.6 0.9 0.2
0.5 0.7 0.5 0.2 0.2
按照基础的最大值法,则选择0.9,但是0.9的邻居表现均不佳。
定义:新取值=原值 + (邻居的平均值)
则可以综合考虑参数本身和参数邻居点的表现。
svg
v4:邻居权重优选法-中位数
由于均值受极值影响较大,可以考虑用 median( 多个邻居),代替上面”邻居的平均值”。
svg

参数优选方法对应累积收益图
svg

本文基于上一篇文章(vectorbt学习_17DMA之五滑窗网格参数优选)。
之前每个周期(训练集)的最佳参数,都是采用直接优选的方法。可能存在局部最优过拟合陷阱(某个参数效果很好,但是其邻居效果奇差)。

新增3种参数优选方法,一定程度上降低参数过拟合的可能。

v1:直接(简单最大值)优选法
选取,测试集合的最优参数作为验证集参数,如果sharp_ratio就最大,回撤就最小类似这样的简单优选策略。

v2:邻近域优选法
在上一个策略中,实际上是选取,测试集合的最优参数作为验证集参数。而有些情况下,测试集得到参数会突然发生较大变化,这可能偶发事件导致的,
比如:之前的双均线最佳参数一直是,(20,40),本期突然变成(80,160),显然不大合理,为了避免这种突变,让参数的变化也具有一定连贯性(当然,增加连贯性也一定程度降低过拟合风险)

v3:邻居权重优选法-均值
在评估一组参数是否最佳时,并不单纯观察此参数本身是否最优,而是综合考虑参数本以及参数的邻居表现。
比如:
0.5 0.7 0.5 0.2 0.2
0.8 0.7 0.6 0.9 0.2
0.5 0.7 0.5 0.2 0.2
按照基础的最大值法,则选择0.9,但是0.9的邻居表现均不佳。
定义:新取值=原值 + (邻居的平均值)
则可以综合考虑参数本身和参数邻居点的表现。

v4:邻居权重优选法-中位数
由于均值受极值影响较大,可以考虑用 median( 多个邻居),代替上面”邻居的平均值”。

01,基础配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#conda envs:vectorbt_env
import warnings
import vectorbt as vbt
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import pytz
from dateutil.parser import parse
import ipywidgets as widgets
from copy import deepcopy
from tqdm import tqdm
import imageio
from IPython import display
import plotly.graph_objects as go
import itertools
import dateparser
import gc
import math
from 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,时间交易参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# Enter your parameters here
seed = 42
symbol = '002594.XSHE'
metric = 'total_return'

start_date = datetime(2020, 1, 1, tzinfo=pytz.utc) # time period for analysis, must be timezone-aware
end_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 window
freq = '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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Download data with time buffer
cols = ['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 buffer
wobuf_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 data
ohlcv.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)

svg

20,网格参数-指标计算和可视化

仅可视化第一列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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_wbuf=ohlcv_wbuf['Close']
dualma = vbt.DualMA.run(price_wbuf, fast_window=fast_windows,slow_multi=slow_multis,param_product=True)
dualma = dualma[wobuf_mask]
# there should be no nans after removing time buffer
assert(~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                                                                        45                                                                             
dualma_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
date                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
2020-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.440222
2020-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.555556
2020-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.660222
dualma.slow_ma.head(3)
dualma_fast_window                10                                                                              15                                                                                      20                                                                                25                                                                                 30                                                                                    35                                                                                      40                                                                                   45                                                                             
dualma_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
date                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
2020-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.375822
2020-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.379778
2020-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

svg

21,网格参数-信号计算和可视化

仅可视化第一列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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           10            
dualma_slow_multi           1.5   2.0   2.5
date                                       
2020-01-02 00:00:00+00:00  True  True  True
2020-01-03 00:00:00+00:00  True  True  True
2020-01-06 00:00:00+00:00  True  True  True

svg

svg

Start                       2020-01-02 00:00:00+00:00
End                         2022-12-30 00:00:00+00:00
Period                                            728
Total                                       474.03125
Rate [%]                                    65.114183
First Index                 2020-01-15 16:52:30+00:00
Last Index                  2022-11-07 20:15:00+00:00
Norm Avg Index [-1, 1]                      -0.159967
Distance: Min                                     1.0
Distance: Max                               82.734375
Distance: Mean                               1.464916
Distance: Std                                5.175417
Total Partitions                             6.671875
Partition Rate [%]                           1.510978
Partition Length: Min                       41.671875
Partition Length: Max                      211.171875
Partition Length: Mean                     110.468174
Partition Length: Std                       78.523847
Partition Distance: Min                      26.78125
Partition Distance: Max                     82.734375
Partition Distance: Mean                    51.365493
Partition Distance: Std                     28.015768
Name: agg_func_mean, dtype: object

22,行情,信号的滑窗处理

注意点:
01,训练集和验证集比例3:1,或者2:1,对应:window_len和set_lens为4:1(或3:1),过大了历史包袱沉重,无法及时响应最新行情,过小了则容易参数跳变,形成类似过拟合效果

a,参数设置和效果预览

代码中

1
2
3
4
5
6
7
8
print(in_indexes[0][0])
print(in_indexes[1][0])
print(in_indexes[0][53:55])

2019-01-02 00:00:00+00:00
2019-03-25 00:00:00+00:00
DatetimeIndex(['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,就是由于切分优先保证了数据的足量,但是数据间隔方面则可能有所重叠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 滚动周期参数设置和大致效果可视化
start_end_days=ohlcv.shape[0]
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)
price=ohlcv['Close']
# 验证:单列数据验证,橘黄色验证集连续且无重复
roll_in_and_out_samples(price, **split_kwargs, plot=True, trace_names=['in-sample', 'out-sample']).show_svg()


split_kwargs: {'n': 7, 'window_len': 240, 'set_lens': (80,), 'left_to_right': False}

svg

b,根据滑窗参数切分行情数据和信号

in_price.shape: (160, 7)
out_price.shape: (80, 7)

in_price.index: RangeIndex(start=0, stop=160, step=1)
in_price.columns: Int64Index([0, 1, 2, 3, 4, 5, 6], dtype='int64', name='split_idx')

in_price[0:3]:
split_idx      0      1      2       3       4       5       6
0          48.17  59.78  92.59  219.90  146.56  254.11  250.02
1          48.04  58.88  90.00  216.30  153.73  277.60  246.50
2          48.28  59.13  94.74  225.04  148.99  275.95  246.30

###############################
in_dmac_size.shape: (160, 448)
out_dmac_size.shape: (80, 448)

in_dmac_size.iloc[:5,:5]:
split_idx              0                        
dualma_fast_window    10                        
dualma_slow_multi    1.5   2.0   2.5   3.0   3.5
0                   True  True  True  True  True
1                   True  True  True  True  True
2                   True  True  True  True  True
3                   True  True  True  True  True
4                   True  True  True  True  True

23,滑窗的收益数据计算

a,持有参数收益

在此区间,基础标的物表现

1
2
3
4
5
6
7
8
9
10
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_idx
0    2.315678
1    3.890261
2    1.812302
3    1.122310
4    2.388496
Name: sharpe_ratio, dtype: float64
split_idx
0    4.885519
1   -0.547754
2    4.538256
3   -0.039085
4   -0.527252
Name: sharpe_ratio, dtype: float64

b,网格参数收益(训练集和验证集)

in_sharpe.shape: (448,)

out_sharpe.shape: (448,)

c,训练集上的最佳参数用于验证集

大致思路:
01,获取各split_idx的最佳收益(sharp_radio)的参数组合idxmax,也就是fast_window,slow_window,split_idx,三维索引元组
02,按照split_idx进行聚类,取得各split_idx对应的最佳参数。实际含义就是各滑动窗口的最佳参数

v1:简单最大值优选法
选取,测试集合的最优参数作为验证集参数,如果sharp_ratio就最大,回撤就最小类似这样的简单优选策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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_basic = get_best_index(in_sharpe)

print('in_best_index[:5]')
print(in_best_index_basic[:5])

# 绘图:参数走势图
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([(40, 1.5, 0),
            (20, 3.0, 1),
            (10, 4.0, 2),
            (10, 4.0, 3),
            (40, 5.0, 4)],
           names=['dualma_fast_window', 'dualma_slow_multi', 'split_idx'])

svg

v2:邻近域优选法
有些情况下,测试集得到参数会突然发生较大变化,这可能偶发事件导致的,
比如:之前的双均线最佳参数一直是,(20,40),本期突然变成(80,160),显然不大合理,为了避免这种突变,让参数的变化也具有一定连贯性(当然,增加连贯性也一定程度降低过拟合风险)

in_best_index[:5]
MultiIndex([(40, 1.5, 0),
            (40, 1.5, 1),
            (40, 2.0, 2),
            (30, 1.5, 3),
            (25, 1.5, 4)],
           names=['dualma_fast_window', 'dualma_slow_multi', 'split_idx'])

svg

v3:邻居权重优选法-均值
在评估一组参数是否最佳时,并不单纯观察此参数本身是否最优,而是综合考虑参数本以及参数的邻居表现。
比如:
0.5 0.7 0.5 0.2 0.2
0.8 0.7 0.6 0.9 0.2
0.5 0.7 0.5 0.2 0.2
按照基础的最大值法,则选择0.9,但是0.9的邻居表现均不佳。
定义:新取值=原值 + (邻居的平均值)
则可以综合考虑参数本身和参数邻居点的表现。

in_best_index[:5]
MultiIndex([(10, 1.5, 0),
            (20, 3.0, 1),
            (10, 4.5, 2),
            (10, 4.5, 3),
            (45, 5.0, 4)],
           names=['dualma_fast_window', 'dualma_slow_multi', 'split_idx'])

svg

v4:邻居权重优选法-中位数
由于均值受极值影响较大,可以考虑用 median( 多个邻居),代替上面”邻居的平均值”。

in_best_index[:5]
MultiIndex([(10, 1.5, 0),
            (25, 2.5, 1),
            (10, 4.5, 2),
            (10, 4.5, 3),
            (45, 5.0, 4)],
           names=['dualma_fast_window', 'dualma_slow_multi', 'split_idx'])

svg

将滚动获取的最佳参数用于验证集,统计收益信息

out_dmac_size.shape: (80, 448)

out_dmac_size_reindexed[in_best_index_basic].shape: (80, 7)

dmac_pf_out.trades.records[:5]
   id  col        size  entry_idx  entry_price  entry_fees  exit_idx  exit_price  exit_fees           pnl    return  direction  status  parent_id
0   0    0  117.061022          0    85.212500   24.937656        79  187.930000   0.000000  11999.277880  1.202928          0       0          0
1   1    1   53.495628          0   186.465000   24.937656        46  181.225800  24.236970   -329.448922 -0.033027          0       1          1
2   2    2   56.480598         13   176.610425   24.937656        79  288.150000   0.000000   6274.884195  0.629057          0       0          2
3   3    3   35.921252          0   277.692500   24.937656         7  271.030725  24.339408   -288.576364 -0.028930          0       1          3
4   4    3   33.252058         29   291.326500   24.218014        64  300.038025  24.942204    240.515915  0.024828          0       1          4

out_test_sharpe.head(5)
dualma_fast_window  dualma_slow_multi  split_idx
40                  1.5                0            4.885519
20                  3.0                1            0.059796
10                  4.0                2            3.438258
                                       3            0.153046
40                  5.0                4           -1.016612
Name: sharpe_ratio, dtype: float64

24,sharp ratio的汇总可视化

basic为例的基础分析视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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_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()

svg

关注点:

蓝色部分
正常排序是(从上到下):点线,实现,线段,

橘色部分

实线对实线
说明测试集和验证集的周期收益情况,二者同时出现0轴同侧较好(同时上涨,同时下跌,保持行情的稳定性or延续性)

线段对线段
二者一方面随着各自颜色的实线趋势变化(受各自实线影响较大),其他应该无必然联系

点线对点线
蓝色点高于橘色点线,蓝色是训练集内最佳,橘色则是训练集得到最优参数用于验证集结果收益,大概率低于验证集。

测试,验证集时间长度差异,引入偏差
由于测试集一般是验证集的2-3倍(或更多),对于单边行情(假如上涨),则(测试集的)实线收益。蓝色线大概率位于橘色线上方。
如果下跌,则相反。蓝色由于时间长,大概率位于橘色下方。

注意:
01,202406,对于当前case,y周取值为sharp ratio夏普比,而非收益率。所以数据点高低并不反映收益率。
所以,以上结论需要稍斟酌,并不完全准确。

4种优选方法的训练集夏普sharp ratio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cv_results_df = pd.DataFrame({
'in_sample_hold': in_hold_sharpe.values,
'in_sample_best_basic': in_sharpe[in_best_index_basic].values,
'in_sample_best_coord': in_sharpe[in_best_index_nb_coord].values,
'in_sample_best_mean': in_sharpe[in_best_index_nb_mean].values,
'in_sample_best_median': in_sharpe[in_best_index_nb_median].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['green']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()

svg

可见,训练集上,不论那种筛选方法,其sharp_ratio大致走势接近,均优于简单的持有策略
对于本案例,本质上是因为行情本身基本维持上涨趋势,如果行情较高波动性,则可能会有分化。

4种优选方法的验证集夏普sharp ratio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cv_results_df = pd.DataFrame({
'out_sample_hold': out_hold_sharpe.values,
'out_sample_test_basic': out_test_sharpe_basic.values,
'out_sample_test_coord': out_test_sharpe_coord.values,
'out_sample_test_mean': out_test_sharpe_mean.values,
'out_sample_test_median': out_test_sharpe_median.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['green']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()

svg

可见,验证集上,其sharp_ratio大致走势也接近。

25,滚动回测收益可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 验证集:原始价格变动
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))

# # 验证集:持有收益率, 高度类似,out_price_org,略
# def simulate_holding(price, **kwargs):
# pf = vbt.Portfolio.from_holding(price, **kwargs)
# return pf.total_return()

# out_hold_return = simulate_holding(out_price, **pf_kwargs)
# print()
# print('out_hold_return shape:',out_hold_return.shape)
# print('out_hold_return.head(5) + 1')
# print(out_hold_return.head(5)+1)


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_basic': (out_test_return_basic.values+1).cumprod(),
'out_test_return_coord': (out_test_return_coord.values+1).cumprod(),
'out_test_return_mean': (out_test_return_mean.values+1).cumprod(),
'out_test_return_median': (out_test_return_median.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']),
dict(line_color=color_schema['red']),
dict(line_color=color_schema['cyan']),
dict(line_color=color_schema['orange'])
]
).show_svg()
out_price_org shape: (7,)
out_price_org.head(5)
split_idx
0    2.210941
1    0.876075
2    2.001737
3    0.971119
4    0.902879
dtype: float64

out_test_return_basic shape: (7,)
out_test_return_basic.head(5) + 1
dualma_fast_window  dualma_slow_multi  split_idx
40                  1.5                0            2.199928
20                  3.0                1            0.967055
10                  4.0                2            1.627488
                                       3            0.995194
40                  5.0                4            0.897145
Name: total_return, dtype: float64

svg

上图可见,以上参数优选方法表现基本接近(也符合之前的sharp ratio接近的特征)。
最佳的效果是out_test_return_basic,也就是最简单的取得测试区间最大值表现的参数

为何这种简单朴素的思路反而取得较好的效果?这一点乍一看有点不符常理。
猜测是由于策略并未陷入过拟合,由于均线时间设置合理,倍率步长也设置较为宽泛,所以本身就包含了一定的容错性。避免了过拟合。
上面的那几种优化思路,其本质都是针对过拟合的优化,均值,中位数,以及近邻选择都是避免陷入局部最优。但是如果网格参数设置较宽泛,其本身一定程度就避免了过拟合。
所以,基于以上在过拟合上的优化,可能并无效果。
网格参数宽泛,缺点在于未能有效捕捉行情蕴含信息,比如,均线探测,10,20,30,实际最佳结果为15,或者25,则容易跳过去。
如果非要吹毛求疵,不妨根据上面的图示,进一步减少步长,细化参数(此时大概率出现几条线同时向上走,basic到一定程度陷入局部最优后就会下降),直到basic效果接近优化思路的效果。此时大概率说明达到过拟合临界点。

26,计算正确性验证(略)

a,准备校验数据,数据展示
b,行情->指标 计算正确
c,指标->信号 计算正确
d,信号->交易 计算正确