并行回测#

策略的随机性#

回测的结果往往带有一定的随机性,即使是同一策略,同一参数,同一时间段,多次回测的结果也会有所不同。这种随机性的成因可能有多种,比如:

  • 策略自身基于概率模型,买卖决策天然具有随机性

  • 同一时间点,触发买入的标的数量,大于当日剩余可开仓数量,需要随机选择实际开仓标的

  • 滑点模拟的自身随机性

单次回测的结果仅仅是一次抽样,其收益率、最大回测、波动率等指标值都具有偶然性,因而并不足以准确评估策略的实际效果。为了更可靠的评估策略的表现,应进行多次回测,并以以各项统计指标来综合衡量策略的预期收益以及稳定性。

return-curves
[ ]:

并行回测#

[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]: 收益曲线可视化

  1. plot_equity_curves: 绘制多轮回测的收益曲线

  2. 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()
[ ]:

[ ]: