功能

时间序列操作

在带标签的 pandas 数据上运行编译滚动窗口、归约和日期逻辑

如何在数百列上快速运行滚动窗口、归约和日期逻辑,同时保留 pandas 标签?VBT 通过 .vbt 访问器,为每个 Series 和 DataFrame 添加编译后的时间序列操作。在 Numba 中计算,再获得带标签的 pandas 对象,可直接用于指标、信号与绩效分析。

Compute a rolling z-score on 500 columns with pandas and with VBT
import timeit

np.random.seed(42)
df = pd.DataFrame(
    np.random.normal(size=(5000, 500)).cumsum(axis=0),
    index=pd.date_range("2020-01-01", periods=5000, freq="h"),
)
pd_z = (df - df.rolling(20).mean()) / df.rolling(20).std()
vbt_z = df.vbt.rolling_zscore(20)
np.allclose(pd_z, vbt_z, equal_nan=True), vbt_z.shape
(True, (5000, 500))
def best_ms(func):  
    return min(timeit.repeat(func, number=1, repeat=7)) * 1000

best_ms(lambda: (df - df.rolling(20).mean()) / df.rolling(20).std())
67.0
best_ms(lambda: df.vbt.rolling_zscore(20))
12.0
best_ms(lambda: df.vbt.rolling_zscore(20, jitted=dict(parallel=True)))  
8.2

输出与 pandas 完全一致。在构建本页的 Apple M3 上,编译版本耗时 12 毫秒,而非 67 毫秒;使用八核时为 8 毫秒。耗时取决于机器和数据,请在自己的环境中测量。

这些操作可用于标准化价格序列、估计滚动波动率、比较资产相关性,或从多个输入数组构建信号。它们直接处理已有 pandas 数据,因此可以在 notebook 中加入 VBT 计算,无须迁移整个工作流。

窗口

均值、和、标准差、最小值、最大值、最值位置、排名、z 分数、协方差、相关性和回归的滚动与扩展版本均已编译,指数加权均值、标准差和线性加权均值也是如此。中位数和分位数未编译,可用 rolling_apply 配合自己的函数,或通过 pandas 计算。所有窗口函数都接受最少观测数,适用于 Series 和 DataFrame,并在需要第二个输入时对其广播:

Compute a 30-day rolling correlation between Bitcoin and Ether
data = vbt.YFData.pull(["BTC-USD", "ETH-USD"], start="2024-01-01", end="2025-01-01")
rets = data.close.pct_change()
rets["BTC-USD"].vbt.rolling_corr(rets["ETH-USD"], 30).describe().round(3)
count    336.000
mean       0.796
std        0.094
min        0.443
25%        0.749
50%        0.818
75%        0.861
max        0.948
dtype: float64

其他计算可用 rolling_apply、expanding_apply 和 groupby_apply,在各窗口或组中运行自己的编译函数。近期行为适合固定窗口,自起点以来的统计适合扩展窗口,希望新观测权重更高时则可用指数权重。

使用 minp,可要求指数计算具备足够多的近期观测才返回值,控制数据缺口对信号的影响。

日期与时间

数据实例保留带时区时间戳。默认将无时区时间戳视为 UTC,所有数据均存为 UTC。tz_localize 指定无时区时间戳原本记录的时区,tz_convert 指定最终保留的时区,例如交易所当地时间。一个数据实例只保留一个时区,因此不同交易所的品种共用该时区。所有接受日期的位置均可解析日期字符串、"3 months ago" 等相对日期及时间差。编译代码内部使用整数表示日期时间,辅助函数可提取小时、星期或月份,并寻找下一个交易时段边界,详见下方 Numba 日期时间。.vbt.xloc["09:30":"16:00"] 跨所有日期选择指定日内时间。vbt.wait 和日历区间索引掩码有助于制定计划;.vbt.ago 按固定时间距离而非固定 K 线数量取值,参见过去时段。

夏令时

当地时间的日内规则相对 UTC 每年会发生两次一小时偏移。规则涉及交易时段时,应将索引保留在交易所时区。

按 K 线数量或经过时间回看

存在观测缺失时,上一根 K 线可能已经超过一小时前。选择与待测规则匹配的回看方式:

Compare the previous bar with exactly one hour ago
price = pd.Series(
    [100.0, 101.0, 104.0, 102.0],
    index=pd.to_datetime([
        "2025-01-01 09:00", "2025-01-01 10:00",
        "2025-01-01 12:00", "2025-01-01 13:00",
    ]),
)
lookbacks = pd.DataFrame({
    "price": price,
    "previous_bar": price.vbt.ago(1),
    "one_hour_ago": price.vbt.ago("1h"),
})
lookbacks
                     price  previous_bar  one_hour_ago
2025-01-01 09:00:00  100.0           NaN           NaN
2025-01-01 10:00:00  101.0         100.0         100.0
2025-01-01 12:00:00  104.0         101.0           NaN
2025-01-01 13:00:00  102.0         104.0         104.0

中午时,上一根 K 线来自 10:00,而精确回看一小时没有观测可返回。any_ago 和 all_ago 将此思路扩展到近期事件,例如过去一小时内是否曾满足条件,或过去五根 K 线是否始终满足条件。

日历规则也可选择周期性时点和区间,例如某个工作日的入场时间或交易时段窗口。将这些掩码与价格条件组合成交易信号,或按日内回测页的方法比较不同时段表现。

清理与精度

fillna、ffill、bfill 和位移逐列在编译代码中执行。用 fshift 和 bshift 位移时,可选择新缺口的填充值,因此整数或布尔数组能保持原有类型。交叉检查可用 skipna=True 跳过缺失值,而非将每个缺口视为中断。

自定义编译规则中,is_close_nb 等数值辅助函数可按显式绝对与相对容差比较值。当舍入噪声可能将原本相等的值变成不同决策时,这很有用。

应用、映射与归约

map 逐值转换,apply_along_axis 逐列或逐行运行函数,reduce 将各列归约为一个值或数组,均可使用自己的编译函数。元方法更进一步,可对多个已对齐数组一次运行一个函数,自定义指标和度量正是借此无须编写 Python 循环。下方元方法功能介绍展示此模式,数组表达式则在命名数组上求公式字符串的值。

分组操作可按日历周期或其他标签汇总观测,转换操作则可为信号去均值或标准化。自定义滚动计算可用同一元方法模式结合价格、成交量与基准。当计算成为可复用指标时,指标开发可为其添加命名输入、参数和输出。

其他工具

rebase 将序列起点重设为指定值,proximity_apply 归约各元素周围的窗口,shuffle 在列内打乱数值以进行随机检验,squeeze_grouped 和 flatten_grouped 将各组的多列合并为一列。底层内核还提供使用自定义系数的因果 FIR 滤波,以及用于自定义数值计算的多项式拟合。

Numba 日期时间

✅ Numba 函数无法直接接收 pandas 日期时间索引。那么如何将数据与时间关联?VBT 提供一组处理整数时间戳的函数,可从每个时间戳提取当前时间、星期等信息,以判断 K 线是否位于交易时段。

Plot the percentage change from the start of the month to now
@njit
def month_start_pct_change_nb(arr, index):
    out = np.full(arr.shape, np.nan)
    for col in range(arr.shape[1]):
        for i in range(arr.shape[0]):
            if i == 0 or vbt.dt_nb.month_nb(index[i - 1]) != vbt.dt_nb.month_nb(index[i]):
                month_start_value = arr[i, col]
            else:
                out[i, col] = (arr[i, col] - month_start_value) / month_start_value
    return out

data = vbt.YFData.pull(["BTC-USD", "ETH-USD"], start="2022", end="2023")
pct_change = month_start_pct_change_nb(
    vbt.to_2d_array(data.close),
    data.index.vbt.to_ns()  
)
pct_change = data.symbol_wrapper.wrap(pct_change)
pct_change.vbt.plot().show()
2022 年比特币和以太坊相对每月月初的百分比变化 图表数据(JSON)

教程

更多内容参见信号开发教程。

过去时段

✅ 比较不同 K 线上的值,也可使用 Pandas 的向量化方式,无须编写 Numba 函数。VBT 添加便捷的访问器方法,用于按时间差回看,并检查过去一段时间内是否发生过事件。

Check whether the price dropped for 5 consecutive bars
data = vbt.YFData.pull("BTC-USD", start="2022-05", end="2022-08")
mask = (data.close < data.close.vbt.ago(1)).vbt.all_ago(5)
fig = data.plot(plot_volume=False)
mask.vbt.signals.ranges.plot_shapes(
    plot_close=False,
    fig=fig,
    add_shape_kwargs=dict(fillcolor="orangered")
)
fig.show()
比特币价格,突出显示连续五根 K 线下跌的区间 图表数据(JSON)

教程

更多内容参见信号开发教程。

元方法

✅ 滚动应用等许多方法现在有两种版本:普通方法(实例方法)和元方法(类方法)。普通方法绑定单个数组,不需要元数据;元方法不绑定任何数组。借助广播与模板逻辑,自定义函数可以使用 VBT 提供的窗口边界和列索引,同时处理多个命名数组。

Compute the rolling z-score on one array and the rolling correlation coefficient on two arrays
@njit
def zscore_nb(x):  
    return (x[-1] - np.mean(x)) / np.std(x)

data = vbt.YFData.pull("BTC-USD", start="2020", end="2021")
data.close.rolling(14).apply(zscore_nb, raw=True)  
Date
2020-01-01 00:00:00+00:00         NaN
                                  ...
2020-12-27 00:00:00+00:00    1.543527
2020-12-28 00:00:00+00:00    1.734715
2020-12-29 00:00:00+00:00    1.755125
2020-12-30 00:00:00+00:00    2.107147
2020-12-31 00:00:00+00:00    1.781800
Freq: D, Name: Close, Length: 366, dtype: float64
data.close.vbt.rolling_apply(14, zscore_nb)  
2020-01-01 00:00:00+00:00         NaN
                                  ...
2020-12-27 00:00:00+00:00    1.543527
2020-12-28 00:00:00+00:00    1.734715
2020-12-29 00:00:00+00:00    1.755125
2020-12-30 00:00:00+00:00    2.107147
2020-12-31 00:00:00+00:00    1.781800
Freq: D, Name: Close, Length: 366, dtype: float64
@njit
def corr_meta_nb(from_i, to_i, col, a, b):  
    a_window = a[from_i:to_i, col]
    b_window = b[from_i:to_i, col]
    return np.corrcoef(a_window, b_window)[1, 0]

data2 = vbt.YFData.pull(["ETH-USD", "XRP-USD"], start="2020", end="2021")
vbt.pd_acc.rolling_apply(  
    14,
    corr_meta_nb,
    vbt.Rep("a"),
    vbt.Rep("b"),
    broadcast_named_args=dict(a=data.close, b=data2.close)
)
symbol                      ETH-USD   XRP-USD
Date
2020-01-01 00:00:00+00:00       NaN       NaN
...                             ...       ...
2020-12-27 00:00:00+00:00  0.636862 -0.511303
2020-12-28 00:00:00+00:00  0.674514 -0.622894
2020-12-29 00:00:00+00:00  0.712531 -0.773791
2020-12-30 00:00:00+00:00  0.839355 -0.772295
2020-12-31 00:00:00+00:00  0.878897 -0.764446

[366 rows x 2 columns]

数组表达式

✅ 组合多个数组时,通常需先对齐并广播,再执行运算。单用 Pandas 往往过于严格,难以满足需求。VBT 提供访问器类方法,可接收普通 Python 表达式、识别全部变量名、从当前上下文提取数组、广播它们,再求表达式值,还支持 NumExpr!⌨️

Evaluate a multiline array expression based on a Bollinger Bands indicator
data = vbt.YFData.pull(["BTC-USD", "ETH-USD"])

low = data.low
high = data.high
bb = vbt.talib("BBANDS").run(data.close)
upperband = bb.upperband
lowerband = bb.lowerband
bandwidth = (bb.upperband - bb.lowerband) / bb.middleband
up_th = vbt.Param([0.3, 0.4])
low_th = vbt.Param([0.1, 0.2])

expr = """
narrow_bands = bandwidth < low_th
above_upperband = high > upperband
wide_bands = bandwidth > up_th
below_lowerband = low < lowerband
(narrow_bands & above_upperband) | (wide_bands & below_lowerband)
"""
mask = vbt.pd_acc.eval(expr)
mask.sum()
low_th  up_th  symbol
0.1     0.3    BTC-USD    344
               ETH-USD    171
        0.4    BTC-USD    334
               ETH-USD    158
0.2     0.3    BTC-USD    444
               ETH-USD    253
        0.4    BTC-USD    434
               ETH-USD    240
dtype: int64

版权所有 © 2021–2026 Oleg Polakow。保留所有权利。

本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。