寻参优化#
示例策略#
策略参数#
我们继续使用前面教程中的双均线策略,并加入更加两个可优化指标参数:
min_stock_price
: 最低股价。低价股交易时滑点可能更严重,但价格下限过高,会减少开仓机会。min_volatility
: 最低波动率。波动率太低的股票,可能长期占用仓位,降低资金利用率;波动率过高的股票,可能日内同时触发止盈/止损,降低回测结果的可靠性,更可能导致当日买入即出现较大亏损。
[1]:
import pandas as pd
from tradepy.strategy.base import BacktestStrategy, BuyOption
from tradepy.strategy.factors import FactorsMixin
from tradepy.decorators import tag
class MovingAverageCrossoverStrategy(BacktestStrategy, FactorsMixin):
@tag(outputs=["ema10_ref1", "sma30_ref1"], notna=True)
def moving_averages_ref1(self, ema10, sma30) -> pd.Series:
return ema10.shift(1), sma30.shift(1)
def should_buy(self, orig_open, sma120, ema10, sma30, typical_price, atr,
ema10_ref1, sma30_ref1, close, company) -> BuyOption | None:
if "ST" in company:
return
if orig_open < self.min_stock_price:
return
volatility = 100 * atr / typical_price
if volatility < self.min_volatility:
return
if (ema10 > sma120) and (ema10_ref1 < sma30_ref1) and (ema10 > sma30):
return close, 1
def should_sell(self, ema10, sma30, ema10_ref1, sma30_ref1):
return (ema10_ref1 > sma30_ref1) and (ema10 < sma30)
def pre_process(self, df: pd.DataFrame) -> pd.DataFrame:
return df.query('market != "科创板"').copy()
[ ]:
参数空间#
由于该策略使用静态止盈/止损,止盈/止损点位也应加入可调参数。我们可以设计如下的参数搜寻空间:
min_stock_price
: [3, 5, 10]min_volatility
: [2, 4, 7]止盈
: [4.5, 8]止损
: [3, 5]
使用网格搜索时,这一共有 3 x 3 x 2 x 2 = 36组参数。另外,如果策略带有一定随机性(比如当日触发买入信号标的数量,大于最大开仓数量
时,需要随机选择标的),每组参数还应回测多轮,再以平均收益评估。假设每组跑20轮,最终需要运行 36 * 20 = 720
次回测。
[ ]:
寻参计算#
详见如下代码。要特别注意的是, 策略代码需放到Python文件,在这个例子中,我们将其放在当前工作目录下的 ma_cross.py
[3]:
from tradepy.optimization.schedulers import OptimizationScheduler
from tradepy.optimization.result import OptimizationResult
from tradepy.core.conf import BacktestConf, StrategyConf, OptimizationConf, SlippageConf
conf = OptimizationConf(
repetition=20,
backtest=BacktestConf(
cash_amount=1e6,
broker_commission_rate=0.01,
min_broker_commission_fee=0,
strategy=StrategyConf(
strategy_class="ma_cross.MovingAverageCrossoverStrategy",
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,
)
)
)
param_search_ranges = [
{ "name": "min_stock_price", "range": [3, 5, 10] },
{ "name": "min_volatility", "range": [2, 4, 7] },
{ "name": "stop_loss", "range": [3, 5] },
{ "name": "take_profit", "range": [4.5, 8] },
]
scheduler = OptimizationScheduler(conf, param_search_ranges)
result: OptimizationResult = scheduler.run(data_df=df)
2023-08-23 20:27:03.539 | INFO | tradepy.optimization.schedulers:__init__:41 - 任务工作目录: /Users/dilu/.tradepy/optimizer/2023-08-23/20:27:03
- 止损和止盈是两个特殊的可搜参数,名字必须为 stop_loss 和 take_profit
- OptimizationScheduler.run的 data_df 可以是通过 StocksDailyBarsDepot 加载的原始日K数据,也可以是已经包含了策略指标的。如果是原始数据, OptimizationScheduler 会先调用策略类预生成含指标的回测数据,并保存到本次寻参的工作目录中。
部分运行日志如下。默认设置下,计算并行数为 CPU核数 * 0.75 (向下取整),运行时可访问 http://localhost:8787
查看Dask worker的状态等监控信息。
- 请根据可用内存来调整并行数,可通过设置 OptimizationScheduler.run的 dask_args 的worker数量来控制。
- 每次运行寻参都会创建一些工作目录,位置是 ~/.tradepy/optimizer,时间久了请注意自行清理。
tradepy.optimization.schedulers:__init__:40 - 任务工作目录: /Users/dilu/.tradepy/optimizer/2023-08-21/13:56:47
>>> 获取待计算因子
- 待计算: [sma30, sma120, ema10, typical_price, atr, moving_averages_ref1, ema10_ref1, sma30_ref1]
>>> 计算每支个股的后复权价格以及技术因子
100%|█████████████████████████████████| 5042/5042 [01:03<00:00, 79.60it/s]
tradepy.optimization.schedulers:_output_indicators_df:67 - 回测数据已保存至: /Users/dilu/.tradepy/optimizer/2023-08-21/13:56:47/dataset.pkl
tradepy.optimization.schedulers:run:148 - 启动Dask集群: id=Scheduler-ca6ee5e8-8b84-4bad-bf21-e4b2ccb7c383, dashboard port=8787, <Client: 'tcp://127.0.0.1:62136' processes=6 threads=6, memory=16.00 GiB>
tradepy.optimization.schedulers:_run:210 - 第1次执行
tradepy.optimization.schedulers:__run_once:189 - 获取第1个参数批, 批数量 = 36
tradepy.optimization.schedulers:submit_tasks_and_patch_results:100 - 提交36个任务
tradepy.optimization.worker:run:62 - 开始执行任务: 8d9daaa5-8b60-4f0a-9426-22ffe747ff72
tradepy.optimization.worker:run:62 - 开始执行任务: 4e5d4149-b0df-47b3-bc88-496f1481db7b
tradepy.optimization.worker:run:62 - 开始执行任务: 138f81e3-7cf7-49c3-83c1-e65a1bbc9bc8
tradepy.optimization.worker:run:62 - 开始执行任务: cb12e919-8633-4ee1-8141-91657d8cc637
tradepy.optimization.worker:run:62 - 开始执行任务: c3b4de0c-4cc4-462a-8c13-7da35f5313e0
tradepy.optimization.worker:run:62 - 开始执行任务: d3037570-fc75-44c3-b7bf-5507242500d9
[ ]:
分析结果#
如果以收益率为排序,则最佳一组参数为 { min_stock_price: 3, min_volatility: 2, stop_loss: 5, take_profit: 8 }
,按平均值计,1850%,胜率40.15%,最大回测-19.09%,夏普比率0.88。另外,我们也可以观察到到盈亏比和胜率呈负相关,这也是交易策略的一个普遍规律。
[6]:
metrics_df = result.get_total_metrics()
metrics_df[:10]
[6]:
收益率 | 胜率 | 最大回撤 | 开仓数 | 夏普比率 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
mean | std | mean | std | mean | std | mean | std | mean | std | ||||
min_stock_price | min_volatility | stop_loss | take_profit | ||||||||||
3.0 | 2.0 | 5.0 | 8.0 | 185.43 | 21.16 | 40.63 | 0.38 | -19.09 | 1.61 | 8015.25 | 90.13 | 0.88 | 0.15 |
5.0 | 2.0 | 5.0 | 8.0 | 161.03 | 15.55 | 40.30 | 0.31 | -19.35 | 1.60 | 7703.05 | 78.94 | 0.70 | 0.12 |
4.5 | 152.95 | 14.40 | 53.67 | 0.32 | -21.51 | 1.89 | 8321.35 | 44.74 | 0.65 | 0.13 | |||
3.0 | 2.0 | 5.0 | 4.5 | 152.84 | 13.04 | 53.58 | 0.25 | -19.16 | 1.69 | 8523.65 | 33.41 | 0.65 | 0.11 |
10.0 | 2.0 | 5.0 | 4.5 | 122.50 | 11.18 | 53.48 | 0.30 | -30.24 | 3.86 | 7369.90 | 46.04 | 0.34 | 0.12 |
8.0 | 119.58 | 11.58 | 39.84 | 0.33 | -28.54 | 2.33 | 6684.10 | 77.82 | 0.31 | 0.12 | |||
3.0 | 4.0 | 5.0 | 8.0 | 107.82 | 9.43 | 39.75 | 0.31 | -38.16 | 1.82 | 6770.10 | 41.68 | 0.19 | 0.09 |
10.0 | 2.0 | 3.0 | 8.0 | 102.53 | 9.06 | 29.20 | 0.28 | -32.13 | 3.22 | 7086.15 | 41.32 | 0.12 | 0.11 |
3.0 | 2.0 | 3.0 | 8.0 | 98.56 | 11.87 | 29.00 | 0.39 | -28.42 | 3.93 | 8180.95 | 87.92 | 0.06 | 0.16 |
5.0 | 4.0 | 5.0 | 8.0 | 97.76 | 7.94 | 39.47 | 0.19 | -39.02 | 1.96 | 6506.75 | 40.97 | 0.09 | 0.08 |
[ ]:
最后,我们还可通过热力图,来大致评估参数组表现的有效性。 然而问题是,只有当可调参数数量(维度数)2或3时,才能直接生成直观可视的热力图,而在本教程中有4个可调参数。此时,我们可以利用一些降维算法(比如PCA、t-SNE算法),将4个维度的参数数据映射到2维空间,即可在2维热力图上进行分析。 OptimizationResult.plot_performance_metric
集成了t-SNE算法实现,当参数组数量 >= 3,会先将参数集映射到2D再生成热力图,并用给定的指标值着色。
在做映射时,t-SNE算法会在低维空间中保留原高维空间中的数据相似度,这样我们可以很直观地评估“相似的参数组,是否表现也类似”。如果一组最佳参数是有效的,在热力图上它的周围也应该是一片“热力高原”,意味该策略确实存在一个符合其盈利能力的参数空间。
结果如下:虽然存在表现显然不行的参数类型(普遍为波动率较大或最低股价太高),但似乎并没有显著优势的参数类型。
[5]:
result.plot_performance_metric("收益率")
[ ]:
自定义优化算法#
TODO
[ ]: