功能
时间序列操作
在带标签的 pandas 数据上运行编译滚动窗口、归约和日期逻辑
如何在数百列上快速运行滚动窗口、归约和日期逻辑,同时保留 pandas 标签?VBT 通过 .vbt 访问器,为每个 Series 和 DataFrame 添加编译后的时间序列操作。在 Numba
中计算,再获得带标签的 pandas 对象,可直接用于指标、信号与绩效分析。
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.0best_ms(lambda: df.vbt.rolling_zscore(20))12.0best_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,并在需要第二个输入时对其广播:
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 线可能已经超过一小时前。选择与待测规则匹配的回看方式:
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 函数无法直接接收 pandas 日期时间索引。那么如何将数据与时间关联?VBT 提供一组处理整数时间戳的函数,可从每个时间戳提取当前时间、星期等信息,以判断 K 线是否位于交易时段。
@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()教程
更多内容参见信号开发教程。
✅ 比较不同 K 线上的值,也可使用 Pandas 的向量化方式,无须编写 Numba 函数。VBT 添加便捷的访问器方法,用于按时间差回看,并检查过去一段时间内是否发生过事件。
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()教程
更多内容参见信号开发教程。
✅ 滚动应用等许多方法现在有两种版本:普通方法(实例方法)和元方法(类方法)。普通方法绑定单个数组,不需要元数据;元方法不绑定任何数组。借助广播与模板逻辑,自定义函数可以使用 VBT 提供的窗口边界和列索引,同时处理多个命名数组。
@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: float64data.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!⌨️
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 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。