并行回测#
策略的随机性#
回测的结果往往带有一定的随机性,即使是同一策略,同一参数,同一时间段,多次回测的结果也会有所不同。这种随机性的成因可能有多种,比如:
策略自身基于概率模型,买卖决策天然具有随机性
同一时间点,触发买入的标的数量,大于当日剩余可开仓数量,需要随机选择实际开仓标的
滑点模拟的自身随机性
单次回测的结果仅仅是一次抽样,其收益率、最大回测、波动率等指标值都具有偶然性,因而并不足以准确评估策略的实际效果。为了更可靠的评估策略的表现,应进行多次回测,并以以各项统计指标来综合衡量策略的预期收益以及稳定性。
[ ]:
并行回测#
[1]:
from tradepy.optimization.schedulers import BacktestRunsScheduler
from tradepy.depot.stocks import StocksDailyBarsDepot
from tradepy.core.conf import TaskConf, BacktestConf, StrategyConf, SlippageConf
[2]:
df = StocksDailyBarsDepot.load()
5042it [00:21, 235.70it/s]
[5]:
conf = TaskConf(
repetition=20, # 使用同样的回测参数重复运行次数
backtest=BacktestConf(
cash_amount=1e6,
broker_commission_rate=0.01,
min_broker_commission_fee=0,
strategy=StrategyConf(
# 与"寻参优化"时一样,策略代码需放到本地可导入的模块内
strategy_class="examples.ma_cross.MovingAverageCrossoverStrategy",
take_profit=8,
stop_loss=5,
take_profit_slip=SlippageConf(
method='max_jump',
params=1
),
stop_loss_slip=SlippageConf(
method='max_pct',
params=0.1
),
max_position_opens=10,
max_position_size=0.25,
min_trade_amount=8000,
custom_params={
"min_stock_price": 3,
"min_volatility": 2,
}
)
)
)
scheduler = BacktestRunsScheduler(conf)
scheduler.run(data_df=df, dask_args={
# dask_args会被直接传递给`dask.distributed.Client`
# 建议根据实际CPU核心数(而非逻辑核心数)来设置worker数量
"n_workers": 4,
"threads_per_worker": 1,
})
2023-09-12 14:49:23.957 | INFO | tradepy.optimization.schedulers:__init__:41 - 任务工作目录: /Users/dilu/.tradepy/optimizer/2023-09-12/14:49:23
[base] [2023-09-12 14:49:24,045] [INFO]: >>> 获取待计算因子
[base] [2023-09-12 14:49:24,046] [INFO]: - 待计算: [ema10, typical_price, sma30, atr, sma120, moving_averages_ref1, sma30_ref1, ema10_ref1]
[base] [2023-09-12 14:49:24,047] [INFO]: >>> 计算每支个股的后复权价格以及技术因子
100%|███████████████████████████████████████████████████████████████████████████████████████████████████| 5042/5042 [01:07<00:00, 74.79it/s]
2023-09-12 14:50:38.900 | INFO | tradepy.optimization.schedulers:_output_indicators_df:64 - 回测数据已保存至: /Users/dilu/.tradepy/optimizer/2023-09-12/14:49:23/dataset.pkl
2023-09-12 14:50:41.498 | INFO | tradepy.optimization.schedulers:run:155 - 启动Dask集群: id=Scheduler-4f21118b-edfe-40ae-b1c5-d219745b3dd7, dashboard port=8787, <Client: 'tcp://127.0.0.1:61924' processes=4 threads=4, memory=16.00 GiB>
2023-09-12 14:50:41.509 | INFO | tradepy.optimization.schedulers:submit_tasks_and_patch_results:100 - 提交20个任务
2023-09-12 14:50:44.457 | INFO | tradepy.optimization.worker:run:58 - 开始执行任务: 60b1196a-8364-4145-aa76-4b74bf7a0163 (第2轮)
2023-09-12 14:50:44.457 | INFO | tradepy.optimization.worker:run:58 - 开始执行任务: e986d5cd-a0c7-4df2-beaa-106e63035730 (第4轮)
2023-09-12 14:50:44.457 | INFO | tradepy.optimization.worker:run:58 - 开始执行任务: 65516f5a-005b-4309-860b-d82e220276b1 (第3轮)
2023-09-12 14:50:44.458 | INFO | tradepy.optimization.worker:run:58 - 开始执行任务: c5ea51c3-cc46-4dbb-bde1-71b185d9b347 (第1轮)
...
[ ]:
结果评估#
首先加载回测结果,将回测任务的工作目录(执行 BacktestRunsScheduler.run
时会打印到日志)提供给 BacktestRunsResult
。TradePy提供了两个内置的多轮回测评估方法
[1]:
import pickle
import pandas as pd
from tradepy.optimization.result import BacktestRunsResult
[2]:
%load_ext autoreload
%autoreload 2
[1]: 计算常用评估指标的统计值:,可提供指定的评估指标(result.tasks_df
内有可选的指标), 默认使用如下:
total_returns: 总收益率%
max_drawdown: 最大回撤
sharpe_ratio: 夏普比率
win_rate: 胜率%
number_of_trades: 开仓次数
number_of_take_profit: 止盈次数
number_of_stop_loss: 止损次数
[3]:
result = BacktestRunsResult('/Users/dilu/.tradepy/optimizer/2023-09-12/14:49:23')
result.describe()
[3]:
total_returns | max_drawdown | sharpe_ratio | win_rate | number_of_trades | number_of_take_profit | number_of_stop_loss | |
---|---|---|---|---|---|---|---|
count | 20.00 | 20.00 | 20.00 | 20.00 | 20.00 | 20.00 | 20.00 |
mean | 187.64 | -18.53 | 0.90 | 40.49 | 8074.65 | 3243.95 | 4235.65 |
std | 17.43 | 1.27 | 0.11 | 0.31 | 80.29 | 49.26 | 43.29 |
min | 157.14 | -20.72 | 0.67 | 40.09 | 7910.00 | 3168.00 | 4132.00 |
25% | 173.64 | -19.29 | 0.81 | 40.28 | 8029.00 | 3210.75 | 4217.00 |
50% | 182.92 | -18.58 | 0.88 | 40.38 | 8078.00 | 3231.00 | 4232.50 |
75% | 198.30 | -17.77 | 0.98 | 40.65 | 8111.75 | 3281.00 | 4269.75 |
max | 224.52 | -16.23 | 1.12 | 41.14 | 8211.00 | 3348.00 | 4298.00 |
[2]: 收益曲线可视化
plot_equity_curves
: 绘制多轮回测的收益曲线plot_equity_curve_bands
: 绘制每日收益的平均值、一倍标准差的上下沿,以及用平均值作为”典型收益曲线”时,对应的移动回撤(underwater plot)
[3]:
# 注: 如果不提供 ``sample_runs``, 则默认读取并展示该回测批次中的所有收益曲线 (本教程中,为20轮回测)
# 由图可见,该策略的收益曲线模式比较稳定.
result.plot_equity_curves(sample_runs=15)
[4]:
# 观察回撤图可见,该策略的最佳稳定表现期为21至22年,。22年9月起有劣化趋势,回撤期的回撤幅度和时间长度,都有明显扩大
result.plot_equity_curve_bands()
[ ]:
[ ]: