# 形态与事件研究 (/zh/features/analytics/patterns-and-event-studies)

某种价格形态或市场事件此前出现后，接下来发生了什么？VBT 在历史价格中搜索与形态相似的窗口，并将任意事件集合转化为可汇总的后续价格路径。只需几行 Python，就能以编译速度开展形态识别和事件研究。

```python title="Find the 10 closest matches to the last 30 days and project the next 30"
>>> close = vbt.YFData.pull("BTC-USD", start="2015-01-01", end="2025-01-01").close
>>> pattern = close.iloc[-30:]
>>> ranges = close.vbt.find_pattern(
...     pattern=pattern.values,
...     rescale_mode="rebase",  # (1)
...     min_similarity=0.75,
... )
>>> past = ranges.end_idx.values < len(close) - 60  # (2)
>>> similarity = np.where(past, ranges.similarity.values, -np.inf)
>>> top10 = ranges.apply_mask(similarity >= np.sort(similarity)[-10])
>>> top10.readable[["Start Index", "End Index", "Similarity"]].round(3)
                Start Index                 End Index  Similarity
0 2015-07-20 00:00:00+00:00 2015-08-19 00:00:00+00:00       0.831
1 2016-07-07 00:00:00+00:00 2016-08-06 00:00:00+00:00       0.810
2 2017-08-18 00:00:00+00:00 2017-09-17 00:00:00+00:00       0.829
3 2017-11-08 00:00:00+00:00 2017-12-08 00:00:00+00:00       0.808
4 2019-03-04 00:00:00+00:00 2019-04-03 00:00:00+00:00       0.809
5 2022-05-18 00:00:00+00:00 2022-06-17 00:00:00+00:00       0.816
6 2022-08-28 00:00:00+00:00 2022-09-27 00:00:00+00:00       0.807
7 2022-11-29 00:00:00+00:00 2022-12-29 00:00:00+00:00       0.801
8 2023-04-21 00:00:00+00:00 2023-05-21 00:00:00+00:00       0.830
9 2024-08-11 00:00:00+00:00 2024-09-10 00:00:00+00:00       0.807

>>> after = top10.with_delta(30)  # (3)
>>> after.get_projections().iloc[-1].quantile([0.1, 0.5, 0.9]).round(3)
0.1    0.989
0.5    1.071
0.9    1.413
Name: 2025-01-30 00:00:00+00:00, dtype: float64

>>> after.plot_projections(plot_past_period=90).show()  # (4)
```

1.  比较形状，而非价格水平：将每个窗口缩放到与形态相同的起点。
2.  仅保留后续 30 天已完全已知的匹配，这也会排除形态自身。
3.  提取每次匹配之后的 30 天。
4.  绘制最近 90 天价格，以及投影路径、中位数和分位数区间。

2024 年最后 90 天比特币价格，以及十个最相似历史窗口之后的路径向前 30 天的投影. [Figure data (JSON)](/assets/figures/features/analytics/patterns-projection.fb92fff9e081.json)

十个最相似窗口结束 30 天后，路径中位数上涨 7.1%，第 10 至第 90 百分位范围从亏损 1.1% 到盈利 41.3%。十个历史案例是对历史的描述，而非预测；分布跨度与中位数同样值得关注。

## 形态搜索 \[#pattern-search]

`find_pattern` 沿序列滑动窗口，将形态拟合到每个窗口，并给出 0 至 1 的拟合评分。形态可以是点列表，例如表示下降三角形的
`[5, 1, 3, 1, 2, 1]`，也可以是一段真实历史价格。每个窗口都会拉伸到形态长度，并按 `rescale_mode` 缩放：`"minmax"`
忽略价格范围比较形状，`"rebase"` 比较百分比变动。设置 `max_window`
后，搜索会尝试不超过该值的每种窗口长度，从而找到不同速度下的同一形态。每增加一种长度就要完整搜索一次，因此也可用 `row_select_prob` 和 `window_select_prob`
抽样一部分行和长度。距离可使用平均绝对误差或均方误差，`max_error` 限制任意单点可偏离的程度。

匹配结果以带相似度评分的区间记录返回，可筛选、绘图并转换为信号。记录的结束索引是匹配之后的 K 线，`last_pd_mask` 标记每个匹配完成时的 K 线。同一评分也可作为滚动指标，每根 K
线一个值：

```python title="Compute pattern similarity as an indicator"
>>> patsim = vbt.PATSIM.run(
...     close,
...     pattern=pattern.values,
...     window=30,
...     rescale_mode="rebase",
... )
>>> patsim.similarity.iloc[-5:].round(3)
Date
2024-12-27 00:00:00+00:00    0.682
2024-12-28 00:00:00+00:00    0.682
2024-12-29 00:00:00+00:00    0.730
2024-12-30 00:00:00+00:00    0.796
2024-12-31 00:00:00+00:00    1.000
Freq: D, Name: (array, 30, rebase), dtype: float64
```

最后一个值为 1，因为形态正是取自这 30 根 K 线。

!!! warning "将匹配用作交易信号"
    默认情况下，重叠匹配通过保留最相似者来处理，后面找到的更好匹配可能移除较早的匹配。这会引入前视。用于信号时，请以 `overlap_mode="allowall"`
    搜索，或使用滚动相似度指标。匹配在最后一根 K 线收盘时完成，因此应在下一根 K 线上交易。

## 事件之后的价格路径 \[#forward-paths-after-events]

投影适用于任意事件，而非仅限形态。将信号掩码转换为从每个事件起始的区间，VBT 就会收集每次事件之后的价格路径，并将起点重设为 1。下面是 SPY 自 2010 年以来所有幅度不低于 1%
的开盘跳空：

```python title="Study what SPY did in the five days after a gap down"
>>> data = vbt.YFData.pull("SPY", start="2010-01-01", end="2025-01-01", auto_adjust=False)
>>> gap_down = data.open / data.close.shift(1) - 1 <= -0.01
>>> events = gap_down.vbt.signals.delta_ranges(5, close=data.close)
>>> paths = events.get_projections()
>>> paths.shape  # (1)
(6, 176)
>>> paths.iloc[-1].quantile([0.1, 0.5, 0.9]).round(3)
0.1    0.960
0.5    1.010
0.9    1.041
Name: 2025-01-05 00:00:00-05:00, dtype: float64
>>> print((paths.iloc[-1] > 1).mean().round(3))
0.631
```

1.  六行，包括事件 K 线及其后五根 K 线；176 次跳空各对应一列。

向下跳空五天后，SPY 有 63% 的情况上涨，中位涨幅为 1.0%。投影可以汇总为分位数区间，或绘制为扇形路径。同样的方法也适用于财报日期、宏观数据发布或任意其他带日期的事件。

### 使用自己的事件日历 \[#bring-your-own-event-calendar]

如果已有事件日期列表，可标记对应 K 线，并提取每次事件之后的路径。这里使用两个示例日期，从一个小型价格序列中选取两根 K 线的变动：

```python title="Compare price moves after your own event dates"
>>> event_close = pd.Series([100.0, 102.0, 103.0, 104.0, 200.0, 198.0, 196.0, 198.0], index=pd.date_range("2026-01-01", periods=8))
>>> event_dates = pd.to_datetime(["2026-01-01", "2026-01-05"])
>>> event_mask = pd.Series(event_close.index.isin(event_dates), index=event_close.index)
>>> event_ranges = event_mask.vbt.signals.delta_ranges(2, close=event_close)
>>> event_paths = event_ranges.get_projections(id_level="start_idx")
>>> ((event_paths.iloc[-1] - 1) * 100).rename("Return [%]").round(1)
start_idx
2026-01-01    3.0
2026-01-05   -2.0
Name: Return [%], dtype: float64
```

第一条路径从 100 到 103，第二条从 200 到 196。每条都从各自事件的收盘价起始，让不同价格水平转化为可比较的百分比变动。将示例日期替换为您自己的日历，并对齐价格 K
线，即可比较财报公告、定期发布事件或自己的信号日期。完整的 `event_paths` DataFrame 还让您比较每个结果的形成路径，而非仅看最终收益。

当事件提示了某种交易规则，[信号回测](/zh/features/backtesting/signal-backtesting/)可加入仓位规模、退出和交易成本。使用[前向滚动与交叉验证](/zh/features/optimization/time-series-cross-validation/)，在未用于选择规则的区间上进行测试。

## 市场结构事件 \[#market-structure-events]

许多事件只是简单掩码。价格跳空是开盘价与前收盘价的比较。标记达到最小幅度反转的方向变化，只有在价格充分移动后才被确认，因此随着新 K 线到来，暂定转折点可能改变。`vbt.PIVOTINFO`
使用高低价和独立的上涨、下跌阈值查找它们，并报告最近已确认拐点、最新暂定拐点及各自发生的 K 线。在回测中使用拐点前，请考虑确认延迟。

## 绘制匹配与投影 \[#plotting-matches-and-projections]

`ranges.plot()` 在价格上显示每次匹配及相似度，`plot_projections()`
则在当前价格旁绘制后续路径，并可选显示分位数区间。下方[形态](#patterns)和[投影](#projections)功能介绍展示了两者。

!!! info "教程"
    会员专属的[形态与投影（英文）](https://members.vectorbt.pro/tutorials/patterns-and-projections/patterns/)教程深入介绍拟合、距离度量和基于形态的信号。

## 形态 \[#patterns]

新增于 1.5.0

✅
形态是图表中价格运动形成的独特结构，也是技术分析的核心。现在提供专门的函数和类，可在任意时间序列数据中检测任意复杂度的形态。思路很简单：将形态拟合到所选数据窗口的尺度和周期，再计算逐元素距离，得到单个相似度评分。您可调整评分阈值，决定超过哪个值时将窗口标记为“匹配”。借助
Numba，此操作每秒可以执行数十万次！🔎

```python title="Find and plot a descending triangle pattern"
>>> data = vbt.YFData.pull("BTC-USD")
>>> data.hlc3.vbt.find_pattern(
...     pattern=[5, 1, 3, 1, 2, 1],
...     window=100,
...     max_window=700,
... ).loc["2017":"2019"].plot().show()
```

2017 至 2019 年 BTC-USD 中的下降三角形匹配. [Figure data (JSON)](/assets/figures/features/analysis/patterns.0fd1d09b4584.json)

!!! info "教程"
    更多内容请参阅[形态与投影](/zh/tutorials/patterns-and-projections)教程。

## 投影 \[#projections]

新增于 1.5.0

✅ 除了传统回测，还有更简洁的方法来分析事件及其对价格的影响。认识一下投影！👋
它不仅帮助您直观、定量地评估事件表现，还可以将事件投影到未来以辅助交易。具体做法是提取每次事件之后的价格区间，将所有区间收集到多维数组，再从数组计算置信区间及其他有用统计。与形态结合后，这些工具堪称量化分析师的理想助手！🌟

```python title="Find occurrences of the price moving similarly to the last week and project them"
>>> data = vbt.YFData.pull("ETH-USD")
>>> pattern_ranges = data.hlc3.vbt.find_pattern(
...     pattern=data.close.iloc[-7:],
...     rescale_mode="rebase"
... )
>>> delta_ranges = pattern_ranges.with_delta(7)
>>> fig = data.iloc[-7:].plot(plot_volume=False)
>>> delta_ranges.plot_projections(fig=fig)
>>> fig.show()
```

ETH-USD 上周形态的历史匹配及投影置信区间. [Figure data (JSON)](/assets/figures/features/analysis/projections.0b45eca04db2.json)

!!! info "教程"
    更多内容请参阅[形态与投影](/zh/tutorials/patterns-and-projections)教程。
