# 参数优化 (/zh/features/optimization/strategy-optimization)

哪些参数有效，如何在时间和内存限制内测试数百万种组合？VBT 通过一个装饰器将任意函数转为参数扫描，按条件构建网格，并可将编译函数分块运行于全部核心。从现有 Python
策略开始，从少量参数值扩展到数百万种组合。

```python title="Backtest 1,000,000 parameter combinations and keep only the Sharpe ratio"
>>> close = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2025-01-01").close

>>> @vbt.parameterized(  # (1)
...     merge_func="concat",
...     engine="threadpool",
...     chunk_len="auto",
...     warmup=True,
... )
... @njit(nogil=True)  # (2)
... def sharpe_nb(close, fast_window, slow_window, sl_stops):
...     fast = vbt.nb.rolling_mean_1d_nb(close, fast_window)
...     slow = vbt.nb.rolling_mean_1d_nb(close, slow_window)
...     entries = vbt.nb.crossed_above_1d_nb(fast, slow)
...     exits = vbt.nb.crossed_above_1d_nb(slow, fast)
...     n = len(sl_stops)
...     sim_out = vbt.pf_nb.from_signals_nb(  # (3)
...         target_shape=(len(close), n),
...         group_lens=np.full(n, 1),
...         close=close,
...         long_entries=entries,
...         long_exits=exits,
...         sl_stop=sl_stops.reshape(1, -1),
...         fees=0.001,
...         save_returns=True,
...     )
...     return vbt.ret_nb.sharpe_ratio_nb(sim_out.in_outputs.returns, 365.0)

>>> sl_stops = np.linspace(0.01, 0.30, 100)
>>> sharpe = sharpe_nb(
...     close.values,
...     vbt.Param(np.arange(2, 102), name="fast_window"),
...     vbt.Param(np.arange(105, 305, 2), name="slow_window"),
...     sl_stops,
... )
>>> len(sharpe)  # (4)
1000000

>>> sharpe.index = sharpe.index.set_levels(sl_stops.round(4), level=2)
>>> sharpe.index = sharpe.index.set_names("sl_stop", level=2)
>>> sharpe.nlargest(5).round(3)
fast_window  slow_window  sl_stop
2            107          0.0129     1.608
                          0.0159     1.607
             119          0.0246     1.603
             117          0.0510     1.594
                          0.0539     1.594
dtype: float64

>>> best_per_pair = sharpe.groupby(["fast_window", "slow_window"]).max()
>>> best_per_pair.vbt.heatmap(x_level="fast_window", y_level="slow_window").show()
```

1.  每组窗口在并行线程中运行函数一次，再拼接结果。
2.  释放 GIL 的编译函数，让线程真正并行。
3.  每次调用将一组窗口同时对全部 100 个止损水平回测，作为 100 列。
4.  100 个快速窗口 × 100 个慢速窗口 × 100 个止损水平。Apple M3 上，编译后扫描耗时约 18 秒。

2020 至 2024 年比特币 100 个快速和 100 个慢速均线窗口的最佳夏普比率热图. [Figure data (JSON)](/assets/figures/features/optimization/strategy-optimization-million.ab407f76961c.json)

每次回测仅保留一个数值，因此一百万次回测只需几兆字节。热图显示每组窗口在所有止损水平中的最佳夏普比率。在该牛市中，95% 的组合夏普比率为正，因此孤立的最佳值本身说明不了太多。

## 参数化任意函数 \[#parameterize-any-function]

`@vbt.parameterized` 封装任意函数，从几行 pandas 到完整回测。以 `vbt.Param`
封装的实参成为参数，各组合传入函数，结果合并为以参数值为索引层级的带标签对象。合并可拼接、按列堆叠或返回原始列表；参数可以嵌套、跨调用共享，或本身就是函数。下方[参数化装饰器](#parameterized-decorator)功能介绍在完整策略上展示这一模式。

您可以保留现有 pandas 计算和 Python 策略逻辑。参数值也可以是函数，因此同一流水线可比较不同入场规则及不同回看窗口。为每条规则添加标签，让结果易于识别。

### 跨组合复用计算 \[#reuse-calculations-across-combinations]

100
组窗口共用的一条均线只需计算一次。预先计算指标，再将输出传入搜索。下方[可参数化对象](#paramables)示例先运行全部均线，再将指标对象组合成快慢配对。也可以分阶段搜索：先找到有用的入场设置，复用这些信号，再探索[止损](/zh/features/backtesting/stop-loss-and-take-profit/)和[仓位规模](/zh/features/backtesting/position-sizing/)。

## 笛卡尔、配对与惰性网格 \[#cartesian-zipped-and-lazy-grids]

参数默认按笛卡尔积组合，共享 `level`
时则逐元素配对。条件在运行前移除不合理组合，例如快速窗口长于慢速窗口，如[条件参数](#conditional-parameters)功能介绍所示。大型网格惰性生成，因此从数十亿潜在组合中随机抽取子集前，无需将完整网格装入内存，详见[惰性参数网格](#lazy-parameter-grids)。

## 搜索策略 \[#search-strategies]

穷举搜索只是一个选项。`random_subset`
测试网格的随机样本，通常以一小部分成本找到相同优良区域，如[随机搜索](#random-search)功能介绍所示。分阶段搜索先使用粗网格，再缩小到最佳区域附近的细网格。抽样时设置随机种子，可以重复选择相同参数组合。

### 使用 Optuna 或 Hyperopt 优化 VBT 回测 \[#optimize-a-vbt-backtest-with-optuna-or-hyperopt]

对于贝叶斯或进化搜索，可将 VBT 回测用作 [Optuna](https://optuna.org/) 或
[Hyperopt](https://hyperopt.github.io/hyperopt/)
的目标函数。优化器提出参数组合，您的函数计算信号并运行回测，再返回待优化指标。无需先计算整个网格，就能搜索回看窗口、信号阈值、止损水平或模型设置。

目标由您定义。例如，最大化扣费后收益指标，或将收益和回撤组合为评分。初始网格搜索和后续优化器驱动搜索可使用同一个回测函数。VBT 负责模拟和指标，外部优化器选择接下来尝试哪些参数。

## 大规模扫描 \[#big-sweeps]

三种设置让大规模扫描保持快速：

*   **分块。** `chunk_len` 将组合分为多个块，在并行线程或进程中运行。对于 Numba 函数，`warmup=True` 在分发其余任务前先运行第一个任务，以提前完成编译。
*   **单体分块。** 将多个参数值合并为一次调用，让函数按列处理；上方止损水平示例和下方两个参数的[单体分块](#mono-chunks)功能介绍都使用此方式。
*   **仅返回指标。** 每个组合返回单个指标而非投资组合，并在模拟时保存收益率，让指标无需重建收益率。

长时间运行时，分块缓存可将已完成结果保存到磁盘，在恢复相同搜索时复用。请参阅[并行执行与缓存](/zh/features/performance/parallel-execution-and-caching/)。流水线也可返回
`vbt.NoResult`，从合并结果中排除不需要的组合，例如交易过少而无法评估的策略。

## 在结果中选择 \[#choosing-among-results]

大型网格中最高夏普比率通常是异常值。请根据多个数值排名：返回的 pandas
对象按参数索引，只需几行代码，就能将夏普比率、最大回撤和交易次数等多个指标标准化后平均为评分。优先选择相邻参数也表现良好的区域；需要多个策略时，应选择彼此不同的组合，而非往往近乎重复的排名前几名。比较[交易持续时间和敞口](/zh/features/analytics/trade-analytics/)，判断变体是否真的以不同方式交易。[前向滚动与交叉验证](/zh/features/optimization/time-series-cross-validation/)页面的滚动测试检查选择能否在选择数据之外成立。参数敏感性与选择偏差检验，见[稳健性与过拟合](/zh/features/optimization/robustness-and-overfitting/)。

### 在同一表格比较多个指标 \[#compare-several-metrics-in-one-table]

保留选择所需数值，将完整投资组合留在函数内部。此小示例使用虚构价格和三个均线窗口，信号延后一根 K 线，每次回测均包含手续费。

```python title="Compare return, drawdown, and trade count"
>>> close = pd.Series(
...     [100, 102, 105, 103, 99, 101, 106, 110, 107, 102, 104, 109, 114, 111, 106, 108, 115, 118, 113, 110],
...     index=pd.date_range("2025-01-01", periods=20),
... )

>>> @vbt.parameterized(merge_func="column_stack")
... def compare_windows(close, window):
...     average = close.rolling(window).mean()
...     entries = (close > average).shift(1, fill_value=False)
...     exits = (close < average).shift(1, fill_value=False)
...     pf = vbt.PF.from_signals(close, entries, exits, fees=0.001, freq="1D")
...     return pd.Series({
...         "Return [%]": pf.total_return * 100,
...         "Max drawdown [%]": pf.max_drawdown * 100,
...         "Closed trades": pf.trades.status_closed.count(),
...     })

>>> results = compare_windows(close, vbt.Param([2, 3, 5])).T
>>> results.round(2)
        Return [%]  Max drawdown [%]  Closed trades
window
2           -16.28            -16.28            4.0
3           -23.36            -23.36            4.0
5           -16.92            -16.92            2.0

>>> results.index[results["Closed trades"] >= 3].tolist()
[2, 3]
```

最后一行选择至少三笔已平仓交易的窗口。您可按自己的标准修改筛选，或在返回的 Series
中添加[绩效指标](/zh/features/analytics/performance-metrics/)。一张结果表提供每种设置的收益、回撤和交易次数，可直接用于自己的选择规则。

## 可参数化对象 \[#paramables]

新增于 v2024.4.1

✅ 每个可分析 VBT 对象（例如数据、指标或投资组合）现在都可拆成多个相同类型的小对象，每个仅包含一列或一个分组。这让 VBT
对象可作为独立参数，每次只处理部分信息，例如数据实例中的一个标的，或指标中的一种参数组合。

```python title="Combine outputs of a SMA indicator combinatorially"
>>> @vbt.parameterized(merge_func="column_stack")
... def get_signals(fast_sma, slow_sma):  # (1)
...     entries = fast_sma.crossed_above(slow_sma)
...     exits = fast_sma.crossed_below(slow_sma)
...     return entries, exits

>>> data = vbt.YFData.pull(["BTC-USD", "ETH-USD"])
>>> sma = data.run("talib:sma", timeperiod=range(20, 50, 2))  # (2)
>>> fast_sma = sma.rename_levels({"sma_timeperiod": "fast"})  # (3)
>>> slow_sma = sma.rename_levels({"sma_timeperiod": "slow"})
>>> entries, exits = get_signals(
...     vbt.Param(fast_sma, condition="__fast__ < __slow__"),  # (4)
...     vbt.Param(slow_sma)
... )
>>> entries.columns
MultiIndex([(20, 22, 'BTC-USD'),
            (20, 22, 'ETH-USD'),
            (20, 24, 'BTC-USD'),
            (20, 24, 'ETH-USD'),
            (20, 26, 'BTC-USD'),
            (20, 26, 'ETH-USD'),
            ...
            (44, 46, 'BTC-USD'),
            (44, 46, 'ETH-USD'),
            (44, 48, 'BTC-USD'),
            (44, 48, 'ETH-USD'),
            (46, 48, 'BTC-USD'),
            (46, 48, 'ETH-USD')],
           names=['fast', 'slow', 'symbol'], length=210)
```

1.  接受两个指标并返回信号的普通函数。
2.  在全部时间窗口上一次运行 SMA 指标。
3.  复制指标并重命名参数层级，获得不同指标实例。
4.  将两个指标实例作为参数传入，各自拆为仅一列的小实例。同时移除快速窗口大于或等于慢速窗口的全部列。

## 惰性参数网格 \[#lazy-parameter-grids]

新增于 v2023.12.23

✅ 如果只关注所有参数组合的一个子集，[参数化装饰器](#parameterized-decorator)不再需要将参数网格实体化。无论全部潜在组合有多大，都几乎可以立即生成随机参数组合。

```python title="Test a random subset of a huge number of parameter combinations"
>>> @vbt.parameterized(merge_func="concat")
... def test_combination(data, n, sl_stop, tsl_stop, tp_stop):
...     return data.run(
...         "from_random_signals",
...         n=n,
...         sl_stop=sl_stop,
...         tsl_stop=tsl_stop,
...         tp_stop=tp_stop,
...         seed=42,
...     ).total_return

>>> n = np.arange(10, 100)
>>> sl_stop = np.arange(1, 1000) / 1000
>>> tsl_stop = np.arange(1, 1000) / 1000
>>> tp_stop = np.arange(1, 1000) / 1000
>>> len(n) * len(sl_stop) * len(tsl_stop) * len(tp_stop)
89730269910

>>> test_combination(
...     vbt.YFData.pull("BTC-USD"),
...     n=vbt.Param(n),
...     sl_stop=vbt.Param(sl_stop),
...     tsl_stop=vbt.Param(tsl_stop),
...     tp_stop=vbt.Param(tp_stop),
...     _random_subset=10,
...     _seed=42
... )
n   sl_stop  tsl_stop  tp_stop
18  0.476    0.485     0.862       2.233997
21  0.530    0.697     0.017       0.091041
49  0.499    0.560     0.954      15.987541
50  0.535    0.200     0.477       2.182477
72  0.763    0.360     0.129      -0.040304
78  0.503    0.071     0.533      10.851910
79  0.656    0.388     0.930      11.854461
80  0.746    0.042     0.644       0.702603
87  0.274    0.539     0.434       2.072847
97  0.806    0.206     0.426       7.136395
dtype: float64
```

## 单体分块 \[#mono-chunks]

新增于 1.13.0

✅
[参数化装饰器](#parameterized-decorator)现在支持将参数组合拆成“单体分块”，将每块中的参数值合并为单个值，并通过一次函数调用运行整块。这意味着不再局限于一次仅处理一种参数组合
🌪️ 请注意，函数必须调整为可处理多个参数值，合并函数也应按需修改。

```python title="Test 100 combinations of SL and TP values per thread"
>>> @vbt.parameterized(
...     merge_func="concat",
...     mono_chunk_len=100,  # (1)
...     chunk_len="auto",  # (2)
...     engine="threadpool",  # (3)
...     warmup=True  # (4)
... )
... @njit(nogil=True)
... def test_stops_nb(close, entries, exits, sl_stop, tp_stop):
...     sim_out = vbt.pf_nb.from_signals_nb(
...         target_shape=(close.shape[0], sl_stop.shape[1]),
...         group_lens=np.full(sl_stop.shape[1], 1),
...         close=close,
...         long_entries=entries,
...         short_entries=exits,
...         sl_stop=sl_stop,
...         tp_stop=tp_stop,
...         save_returns=True
...     )
...     return vbt.ret_nb.total_return_nb(sim_out.in_outputs.returns)

>>> data = vbt.YFData.pull("BTC-USD", start="2020")  # (5)
>>> entries, exits = data.run("randnx", n=10, seed=42, hide_params=True, unpack=True)  # (6)
>>> sharpe_ratios = test_stops_nb(
...     vbt.to_2d_array(data.close),
...     vbt.to_2d_array(entries),
...     vbt.to_2d_array(exits),
...     sl_stop=vbt.Param(np.arange(0.01, 1.0, 0.01), mono_merge_func=np.column_stack),  # (7)
...     tp_stop=vbt.Param(np.arange(0.01, 1.0, 0.01), mono_merge_func=np.column_stack)
... )
>>> sharpe_ratios.vbt.heatmap().show()
```

1.  将 100 个值合并为一个数组，形成一个单体分块。
2.  并行执行 N 个单体分块，N 为核心数。
3.  使用多线程。
4.  分发其他块前，先执行一个单体分块完成编译。
5.  上方函数只处理一个标的。
6.  随机选择 10 次入场和退出。
7.  对每个单体分块，将全部值堆叠为二维数组。

BTC-USD 不同止损与止盈组合的总收益热图. [Figure data (JSON)](/assets/figures/features/optimization/chunked-parameters.faa469abe97f.json)

## 条件参数 \[#conditional-parameters]

新增于 1.8.1

✅ 参数可以相互依赖。例如，测试均线交叉时，快速窗口长于慢速窗口没有意义。过滤这类情况后，只需评估大约一半的参数组合。

```python title="Test slow windows being longer than fast windows by at least 5"
>>> @vbt.parameterized(merge_func="column_stack")
... def ma_crossover_signals(data, fast_window, slow_window):
...     fast_sma = data.run("sma", fast_window, short_name="fast_sma")
...     slow_sma = data.run("sma", slow_window, short_name="slow_sma")
...     entries = fast_sma.real_crossed_above(slow_sma.real)
...     exits = fast_sma.real_crossed_below(slow_sma.real)
...     return entries, exits

>>> entries, exits = ma_crossover_signals(
...     vbt.YFData.pull("BTC-USD", start="one year ago UTC"),
...     vbt.Param(np.arange(5, 50), condition="slow_window - fast_window >= 5"),
...     vbt.Param(np.arange(5, 50))
... )
>>> entries.columns
MultiIndex([( 5, 10),
            ( 5, 11),
            ( 5, 12),
            ( 5, 13),
            ( 5, 14),
            ...
            (42, 48),
            (42, 49),
            (43, 48),
            (43, 49),
            (44, 49)],
           names=['fast_window', 'slow_window'], length=820)
```

## 随机搜索 \[#random-search]

新增于 1.7.0

✅
网格搜索测试每种可能的超参数组合，而随机搜索选择并测试随机组合。这在参数组合数量巨大时尤其有用。研究也表明，随机搜索可以更少函数评估找到与网格搜索相当或更好的值。指标工厂、参数化装饰器和所有执行广播的方法，现在都内置支持随机搜索。

```python title="Test a random subset of SL, TSL, and TP combinations"
>>> data = vbt.YFData.pull("BTC-USD", start="2020")
>>> stop_values = np.arange(1, 100) / 100  # (1)
>>> pf = vbt.PF.from_random_signals(
...     data,
...     n=100,
...     seed=42,
...     sl_stop=vbt.Param(stop_values),
...     tsl_stop=vbt.Param(stop_values),
...     tp_stop=vbt.Param(stop_values),
...     broadcast_kwargs=dict(random_subset=1000, seed=42)  # (2)
... )
>>> pf.total_return.sort_values(ascending=False)
sl_stop  tsl_stop  tp_stop
0.02     0.22      0.55       1.093685
0.93     0.60      0.99       1.091450
0.88     0.68      0.99       1.091450
0.79     0.69      0.99       1.091450
         0.62      0.99       1.091450
                                   ...
0.08     0.84      0.14      -0.466277
0.14     0.09      0.12      -0.502329
0.80     0.11      0.11      -0.509395
0.19     0.11      0.09      -0.529528
0.29     0.11      0.06      -0.563992
Name: total_return, Length: 1000, dtype: float64
```

1.  每个参数 99 个值 = 99³ = 970,299 种组合，从中抽取 1,000 种。
2.  指标工厂和参数化装饰器直接接受此参数。

## 参数化装饰器 \[#parameterized-decorator]

新增于 1.7.0

✅ 特殊装饰器让任意 Python
函数接受多种参数组合，即使函数本身只支持一种。装饰器封装函数并访问实参，识别充当参数的全部实参，构建网格，再对网格中每个组合调用底层函数。执行可以轻松并行化。全部输出就绪后，合并为单个对象。用途无穷：从运行无法通过指标工厂封装的指标，到参数化整个流水线！🪄

=== "示例 1：基础 SMA 指标"
    ```python title="Parameterize a basic SMA indicator without Indicator Factory"
    >>> @vbt.parameterized(merge_func="column_stack")  # (1)
    ... def sma(close, window):
    ...     return close.rolling(window).mean()

    >>> data = vbt.YFData.pull("BTC-USD")
    >>> sma(data.close, vbt.Param(range(20, 50)))
    ```

    1.  使用 `column_stack` 合并 DataFrame 形式的时间序列，以及投资组合等复杂 VBT 对象。

    组合 30/30: 100%

    ```text title="Output"
    window                               20            21            22  \
    Date
    2014-09-17 00:00:00+00:00           NaN           NaN           NaN
    2014-09-18 00:00:00+00:00           NaN           NaN           NaN
    2014-09-19 00:00:00+00:00           NaN           NaN           NaN
    ...                                 ...           ...           ...
    2024-03-07 00:00:00+00:00  57657.135156  57395.376488  57147.339134
    2024-03-08 00:00:00+00:00  58488.990039  58163.942708  57891.045455
    2024-03-09 00:00:00+00:00  59297.836523  58956.156064  58624.648793

    ...

    window                               48            49
    Date
    2014-09-17 00:00:00+00:00           NaN           NaN
    2014-09-18 00:00:00+00:00           NaN           NaN
    2014-09-19 00:00:00+00:00           NaN           NaN
    ...                                 ...           ...
    2024-03-07 00:00:00+00:00  49928.186686  49758.599330
    2024-03-08 00:00:00+00:00  50483.072266  50303.123565
    2024-03-09 00:00:00+00:00  51040.440837  50846.672353

    [3462 rows x 30 columns]
    ```

=== "示例 2：布林带流水线"
    ```python title="Parameterize an entire Bollinger Bands pipeline"
    >>> @vbt.parameterized(merge_func="concat")  # (1)
    ... def bbands_sharpe(data, timeperiod=14, nbdevup=2, nbdevdn=2, thup=0.3, thdn=0.1):
    ...     bb = data.run(
    ...         "talib_bbands",
    ...         timeperiod=timeperiod,
    ...         nbdevup=nbdevup,
    ...         nbdevdn=nbdevdn
    ...     )
    ...     bandwidth = (bb.upperband - bb.lowerband) / bb.middleband
    ...     cond1 = data.low < bb.lowerband
    ...     cond2 = bandwidth > thup
    ...     cond3 = data.high > bb.upperband
    ...     cond4 = bandwidth < thdn
    ...     entries = (cond1 & cond2) | (cond3 & cond4)
    ...     exits = (cond1 & cond4) | (cond3 & cond2)
    ...     pf = vbt.PF.from_signals(data, entries, exits)
    ...     return pf.sharpe_ratio

    >>> bbands_sharpe(
    ...     vbt.YFData.pull("BTC-USD"),
    ...     nbdevup=vbt.Param([1, 2]),  # (2)
    ...     nbdevdn=vbt.Param([1, 2]),
    ...     thup=vbt.Param([0.4, 0.5]),
    ...     thdn=vbt.Param([0.1, 0.2])
    ... )
    ```

    1.  使用 `concat` 合并标量和 Series 形式的指标。
    2.  构建 4 个参数的笛卡尔积。

    组合 16/16: 100%

    ```text title="Output"
    nbdevup  nbdevdn  thup  thdn
    1        1        0.4   0.1     1.681532
                            0.2     1.617400
                      0.5   0.1     1.424175
                            0.2     1.563520
             2        0.4   0.1     1.218554
                            0.2     1.520852
                      0.5   0.1     1.242523
                            0.2     1.317883
    2        1        0.4   0.1     1.174562
                            0.2     1.469828
                      0.5   0.1     1.427940
                            0.2     1.460635
             2        0.4   0.1     1.000210
                            0.2     1.378108
                      0.5   0.1     1.196087
                            0.2     1.782502
    dtype: float64
    ```

## 类数组参数 \[#array-like-parameters]

新增于 1.5.0

✅ 广播机制已完全重构，现在支持参数。VBT
中许多参数，例如止损和止盈，都是类数组，可逐行、逐列甚至逐元素提供。内部连标量也视为普通时间序列，与其他真正的时间序列一起广播。此前，为测试多种参数组合，必须将其他时间序列平铺，使形状完全一致。现在平铺过程会自动完成！

```python title="Write a steep slope indicator without indicator factory"
>>> def steep_slope(close, up_th):
...     r = vbt.broadcast(dict(close=close, up_th=up_th))
...     return r["close"].pct_change() >= r["up_th"]

>>> data = vbt.YFData.pull("BTC-USD", start="2020", end="2022")
>>> fig = data.plot(plot_volume=False)
>>> sma = vbt.talib("SMA").run(data.close, timeperiod=50).real
>>> sma.rename("SMA").vbt.plot(fig=fig)
>>> mask = steep_slope(sma, vbt.Param([0.005, 0.01, 0.015]))  # (1)

>>> def plot_mask_ranges(column, color):
...     mask.vbt.ranges.plot_shapes(
...         column=column,
...         plot_close=False,
...         add_shape_kwargs=dict(fillcolor=color),
...         fig=fig
...     )
>>> plot_mask_ranges(0.005, "orangered")
>>> plot_mask_ranges(0.010, "orange")
>>> plot_mask_ranges(0.015, "yellow")
>>> fig.update_xaxes(showgrid=False)
>>> fig.update_yaxes(showgrid=False)
>>> fig.show()
```

1.  测试三个参数，生成三列掩码，每个参数一列。

2020 至 2021 年 BTC-USD OHLC、50 天 SMA 及三个阈值下的陡峭斜率区间. [Figure data (JSON)](/assets/figures/features/optimization/steep-slope.27dbe5be055b.json)

## 参数 \[#parameters]

新增于 1.5.0

✅ 新增用于处理参数的模块。

```python title="Generate 10,000 random parameter combinations for MACD"
>>> from itertools import combinations

>>> window_space = np.arange(100)
>>> fastk_windows, slowk_windows = list(zip(*combinations(window_space, 2)))  # (1)
>>> window_type_space = list(vbt.enums.WType)
>>> param_product = vbt.combine_params(
...     dict(
...         fast_window=vbt.Param(fastk_windows, level=0),  # (2)
...         slow_window=vbt.Param(slowk_windows, level=0),
...         signal_window=vbt.Param(window_space, level=1),
...         macd_wtype=vbt.Param(window_type_space, level=2),  # (3)
...         signal_wtype=vbt.Param(window_type_space, level=2),
...     ),
...     random_subset=10_000,
...     seed=42,
...     build_index=False
... )
>>> pd.DataFrame(param_product)
      fast_window  slow_window  signal_window  macd_wtype  signal_wtype
0               0            1              0           2             2
1               0            3              5           1             1
2               0            3             55           4             4
3               0            3             80           2             2
4               0            4             17           2             2
...           ...          ...            ...         ...           ...
9995           97           99             14           0             0
9996           97           99             35           2             2
9997           98           99              6           2             2
9998           98           99             44           4             4
9999           98           99             78           4             4

[10000 rows x 5 columns]
```

1.  快速窗口应短于慢速窗口。
2.  快慢窗口已组合，因此共享同一乘积层级。
3.  窗口类型无需组合，因此共享同一乘积层级。
