# 多时间周期分析 (/zh/features/data/multi-timeframe-analysis)

如何在小时 K 线上使用日频指标，同时避免窥探未来？日 K 线以开盘时间标注时，其收盘价仍要到结束时才可获知。VBT 按每个数值实际可用的时间进行重采样和重新对齐，帮助您在 Python
多时间周期回测中避免前视偏差。

```python title="Filter hourly entries with a daily trend, aligned naively and safely"
>>> h1_data = vbt.GBMOHLCData.pull(  # (1)
...     "SYN",
...     start="2024-01-01",
...     end="2025-01-01",
...     timeframe="1h",
...     std=0.002,
...     seed=7,
... )
>>> d1_data = h1_data.resample("1d")  # (2)
>>> d1_uptrend = d1_data.close > d1_data.close.rolling(10).mean()

>>> naive_filter = d1_uptrend.reindex(h1_data.index, method="ffill")  # (3)
>>> safe_filter = (
...     d1_uptrend.vbt.realign_closing(h1_data.index)  # (4)
...     .fillna(False)
...     .astype(bool)
... )

>>> h1_sma = h1_data.close.rolling(24).mean()
>>> entries = h1_data.close.vbt.crossed_above(h1_sma)
>>> exits = h1_data.close.vbt.crossed_below(h1_sma)
>>> pf = vbt.PF.from_signals(
...     h1_data,
...     entries=pd.concat({
...         "naive": entries & naive_filter,
...         "safe": entries & safe_filter,
...     }, axis=1),
...     exits=exits,
...     fees=0.0005,
... )
>>> pf.stats(["total_return", "sharpe_ratio", "win_rate", "total_trades"], agg_func=None)
       Total Return [%]  Sharpe Ratio  Win Rate [%]  Total Trades
naive         42.179045      0.863159     26.050420           239
safe         -56.766534     -1.172668     18.552036           222

>>> pf.value.vbt.plot().show()
```

1.  使用固定随机种子的一年合成小时 K 线，让结果不依赖数据提供商。
2.  将小时 K 线聚合为日 K 线：第一个开盘价、最高的最高价、最低的最低价、最后一个收盘价。
3.  pandas 方式：从当天标签起，将每个日频值复制到之后每个小时。
4.  安全方式：每个日频值在当天收盘对应的小时 K 线上才变得可用。

2024 年使用日频趋势过滤器的小时策略，比较简单对齐与安全对齐的投资组合价值. [Figure data (JSON)](/assets/figures/features/data/mtf-naive-vs-safe.ead37995507f.json)

简单版本看似盈利，但它是错误的。当日收盘价上穿均线时，简单过滤器在午夜就已启用，比该收盘价出现早了 23 小时：

```python title="Compare when each filter turns on"
>>> filters = pd.DataFrame({"naive": naive_filter, "safe": safe_filter})
>>> filters.loc["2024-01-28 23:00":"2024-01-30 00:00"].iloc[[0, 1, 2, 22, 23, 24, 25]]
                           naive   safe
2024-01-28 23:00:00+00:00  False  False
2024-01-29 00:00:00+00:00   True  False
2024-01-29 01:00:00+00:00   True  False
2024-01-29 21:00:00+00:00   True  False
2024-01-29 22:00:00+00:00   True  False
2024-01-29 23:00:00+00:00   True   True
2024-01-30 00:00:00+00:00   True   True
```

按信息实际存在的时间对齐过滤器后，同样的策略亏损。两条曲线之间的差距完全来自前视偏差。

标记为 `23:00` 的小时行表示从 23:00 到午夜的 K 线。其收盘价和日收盘价在该 K 线结束时同时可获知。这一行的安全过滤器用于在 K 线收盘时作出决策。若在下一根 K
线执行，[信号回测页面](/zh/features/backtesting/signal-backtesting/)介绍了延迟信号的方法。

## 在一个策略中组合时间周期 \[#combine-timeframes-in-one-strategy]

为每个时间周期分配任务，再将结果对齐到作出决策的 K 线：

| 研究想法               | 如何构建                          |
| ------------------ | ----------------------------- |
| 仅在日频趋势一致时执行小时入场    | 如上所示，计算日频过滤器，并对齐到小时收盘         |
| 使用四小时动量信号和 15 分钟入场 | 在四小时 K 线上计算，再将输出重新对齐到 15 分钟索引 |
| 比较同一指标的多个时间周期      | 向 TA-Lib 的 `timeframe` 参数传入列表 |
| 将逐分钟模拟以月收益形式查看     | 对已完成投资组合重采样以生成报告              |

从策略需要的最细分辨率数据开始。一份分钟 K 线数据可以提供五分钟、小时和日频视图。也可通过将各结果重新对齐到共同目标索引，组合下载分辨率不同的数据。

## 开盘与收盘对齐 \[#opening-and-closing-alignment]

两条规则涵盖大多数情况。由 K 线开盘价得到的值在起点已知，用 `realign_opening` 重新对齐。由收盘、最高、最低或成交量得到的值在结束时已知，用 `realign_closing`
对齐。基于收盘价计算的指标遵循收盘规则。两者均支持双向操作：将小时数据降采样为日 K 线，或将日频值带回小时 K 线。

重新对齐的掩码以浮点数返回，第一个值可获知前的 K 线为 `NaN`。转换类型前先用 `False` 填充，如上方 `safe_filter` 所示，因为 `NaN` 转为布尔值会成为
`True`，从而从第一根 K 线开始启用过滤器。

`data.resample()` 使用适合各列的函数聚合完整 OHLCV 数据集，`vbt.Resampler`
在任意两个索引之间建立映射，包括不规则索引和交易时段等自定义边界。下方[安全重采样](#safe-resampling)功能介绍在日、周、月收盘价上运行同一指标，并将三者显示在日 K 线上。

从日内数据构建日 K 线，或将已完成的日频水平带入小时策略。VBT 同时处理聚合和对齐，让每个时间周期在同一回测中发挥自己的作用。

对于带额外字段的数据集，可以通过自定义聚合规则指定这些字段重采样时的行为。VBT 的提供商类已包含 Binance 计价资产成交量等字段的规则。

## 对完整对象重采样 \[#resampling-whole-objects]

可改变时间周期的不仅是数据。指标、记录和完整投资组合也能重采样，并对各部分使用相应规则：价格取最后值，现金流求和，订单保留时间戳。常见模式是在细 K 线上模拟以准确成交，再在粗 K
线上分析以提高速度和可读性，如下方[可重采样对象](#resamplable-objects)功能介绍所示。对已完成投资组合重采样只改变报告频率。若要测试另一时间周期上的交易决策，请对输入重采样，并在该周期重新运行模拟。

## 频率 \[#frequencies]

年化指标和重采样需要 K 线频率。VBT 从连续时间戳样本中推断频率，即使偶尔有缺口也能工作。数据开头不规则或没有固定间隔时，请显式传入 `freq`，或通过
`vbt.settings.wrapping["freq"]` 统一设置。毫秒和纳秒等日期时间精度不同的索引，应先转换为相同精度再重新对齐。

## 其他时间周期上的指标 \[#indicators-on-other-timeframes]

每个 TA-Lib 指标都接受 `timeframe` 参数。先将输入重采样到该周期，在该周期计算指标，再安全地将输出对齐回原始 K
线，因此时间周期列表就成为与其他参数相同的一个参数。其他指标可以手动完成相同三步：重采样输入、计算指标、用 `realign_closing` 重新对齐输出。下方
[TA-Lib 时间周期](#ta-lib-timeframes)功能介绍在三个周期上运行同一指标。

指标窗口以计算时间周期的 K 线数衡量。14 周期日频 SMA 使用 14 个日收盘价，14 周期小时 SMA 使用 14
个小时收盘价。日频结果随后带回小时索引，并在每个新日频值可获知时更新。您可以通过[参数优化](/zh/features/optimization/strategy-optimization/)，将时间周期与其他参数一起比较。

完整的高时间周期 K 线只有在结束时才已知。有些平台使用仍在形成的 K 线，在每根低周期 K 线上更新指标，得到更早但持续变化至收盘的数值。如果需要这种行为，请在每一步从部分 K
线显式计算，并在回测中使用当时真实存在的数值。

!!! info "教程"
    会员专属的[多时间周期分析（英文）](https://members.vectorbt.pro/tutorials/mtf-analysis/resampling/)教程深入介绍重采样、对齐和聚合。

## 安全重采样 \[#safe-resampling]

新增于 1.1.2

✅ 处理数组数据时，[前视偏差](https://www.investopedia.com/terms/l/lookaheadbias.asp)始终存在风险，尤其涉及多时间周期时。强烈不建议仅使用
Pandas，因为它无法识别金融数据主要由开盘时间标记的 K 线组成，事件可能发生在 K 线之间任意时刻。因此，Pandas 会错误地假设时间戳就是事件准确发生时间。VBT
提供完整的函数和类集合，用于安全重采样和分析数据！

```python title="Calculate SMA on multiple timeframes and display on the same chart"
>>> def mtf_sma(close, close_freq, target_freq, timeperiod=5):
...     target_close = close.vbt.realign_closing(target_freq)  # (1)
...     target_sma = vbt.talib("SMA").run(target_close, timeperiod=timeperiod).real  # (2)
...     target_sma = target_sma.rename(f"SMA ({target_freq})")
...     return target_sma.vbt.realign_closing(close.index, freq=close_freq)  # (3)

>>> data = vbt.YFData.pull("BTC-USD", start="2020", end="2023")
>>> fig = mtf_sma(data.close, "D", "daily").vbt.plot()
>>> mtf_sma(data.close, "D", "weekly").vbt.plot(fig=fig)
>>> mtf_sma(data.close, "D", "monthly").vbt.plot(fig=fig)
>>> fig.show()
```

1.  从源频率重采样到目标频率。由于收盘发生在 K 线末尾，请将其作为“收盘事件”重采样。
2.  在目标频率计算 SMA。
3.  从目标频率重采样回源频率，以在同一图表显示多个周期。由于 `close` 包含缺口，不能简单重采样到 `close_freq`，否则可能产生未对齐序列。应直接重采样到 `close` 的索引。

在日、周、月周期上计算的比特币简单移动平均线. [Figure data (JSON)](/assets/figures/features/productivity/safe-resampling.5bdd558681a0.json)

!!! info "教程"
    更多内容请参阅[多时间周期分析](/zh/tutorials/mtf-analysis)教程。

## 可重采样对象 \[#resamplable-objects]

新增于 1.1.2

✅ 您不仅可以重采样时间序列，也能重采样复杂 VBT
对象！底层每个对象都由一组类数组属性组成，因此重采样意味着将所有相关信息一起聚合。如果希望以更高频率模拟以获得最高精度，再以较低频率分析以提高速度，这尤其有帮助。

```python title="Plot the monthly return heatmap of a random portfolio"
>>> import calendar

>>> data = vbt.YFData.pull("BTC-USD", start="2018", end="2023")
>>> pf = vbt.PF.from_random_signals(data, n=100, direction="both", seed=42)
>>> mo_returns = pf.resample("M").returns  # (1)
>>> mo_return_matrix = pd.Series(
...     mo_returns.values,
...     index=pd.MultiIndex.from_arrays([
...         mo_returns.index.year,
...         mo_returns.index.month
...     ], names=["year", "month"])
... ).unstack("month")
>>> mo_return_matrix.columns = mo_return_matrix.columns.map(lambda x: calendar.month_abbr[x])
>>> mo_return_matrix.vbt.heatmap(
...     is_x_category=True,
...     trace_kwargs=dict(zmid=0, colorscale="Spectral")
... ).show()
```

1.  将完整投资组合重采样为月频，再计算收益率。

2018 至 2022 年固定随机种子的比特币随机投资组合月收益热图. [Figure data (JSON)](/assets/figures/features/productivity/monthly-return-heatmap.36c65fb0009f.json)

!!! info "教程"
    更多内容请参阅[多时间周期分析](/zh/tutorials/mtf-analysis)教程。

## TA-Lib 时间周期 \[#ta-lib-timeframes]

新增于 1.2.0

✅ 比较不同时间周期的指标涉及许多细节。现在，所有 TA-Lib 指标都支持一个参数，将输入数组重采样到目标周期、计算指标，再将输出数组重采样回原始周期。参数化多时间周期分析因此更加轻松！

```python title="Run SMA on multiple timeframes and display the whole thing as a heatmap"
>>> h1_data = vbt.BinanceData.pull(
...     "BTCUSDT",
...     start="3 months ago UTC",
...     timeframe="1h"
... )
>>> mtf_sma = vbt.talib("SMA").run(
...     h1_data.close,
...     timeperiod=14,
...     timeframe=["1d", "4h", "1h"],
...     skipna=True
... )
>>> mtf_sma.real.vbt.ts_heatmap().show()
```

BTCUSDT 小时、四小时和日频上的简单移动平均线. [Figure data (JSON)](/assets/figures/features/indicators/ta-lib-time-frames.a70363fe5509.json)

!!! info "教程"
    更多内容请参阅[多时间周期分析](/zh/tutorials/mtf-analysis)教程。
