功能
计算后端
使用 Numba、并行 Numba、Rust 或 NumPy 运行内核,并逐次选择后端
VBT 为什么快,又如何控制计算在哪里执行?VBT PRO 将繁重的回测和分析操作放入 Numba 编译函数。受支持的函数还可以并行运行、使用可选 Rust 扩展,或使用您注册的 NumPy 实现。您可以逐次调用或全局选择后端,同时保留相同的 Python 研究工作流。
从 pandas 开始加速计算
从 pandas Series 或 DataFrame 出发,即可调用 VBT 的滚动统计、指标和投资组合模拟器,无须自行编写编译函数。VBT 将数值数组传给计算,再用原有标签封装结果。日期、交易品种和参数组合都保留下来供分析使用。
对于自定义逻辑,Numba 让您用 Python 编写数值循环并编译。这适用于依赖未平仓头寸的信号,或逐根 K 线更新状态的指标。编译函数处理 NumPy 数组,数据准备和报告仍可保留在 pandas 中。示例见自定义指标和事件驱动回测。
比较 NumPy、Numba 和 Rust
import timeit
@vbt.register_jitted(task_id_or_func=vbt.nb.rolling_mean_nb)
def rolling_mean_np(arr, window, minp=None):
csum = np.cumsum(arr, axis=0)
out = np.full(arr.shape, np.nan)
out[window - 1] = csum[window - 1]
out[window:] = csum[window:] - csum[:-window]
return out / window
np.random.seed(42)
values = 100 + np.random.normal(0, 0.01, size=(100_000, 500)).cumsum(axis=0)
layouts = {
"row-major": pd.DataFrame(np.ascontiguousarray(values)),
"column-major": pd.DataFrame(np.asfortranarray(values)),
}
backends = {
"pandas": None,
"NumPy": "np",
"Numba": "nb",
"Numba parallel": dict(jitter="nb", parallel=True),
"Rust": "rs",
"Rust parallel": dict(jitter="rs", parallel=True),
}
def ms(func):
return min(timeit.repeat(func, number=3, repeat=5)) / 3 * 1000
timings = {}
for layout, prices in layouts.items():
for backend, jitted in backends.items():
if jitted is None:
func = lambda: prices.rolling(50).mean()
else:
func = lambda: prices.vbt.rolling_mean(50, jitted=jitted)
timings[(backend, layout)] = ms(func)
pd.Series(timings).unstack().loc[list(backends), list(layouts)].round(0).astype(int) row-major column-major
pandas 693 360
NumPy 489 204
Numba 561 163
Numba parallel 379 32
Rust 87 145
Rust parallel 71 67表中显示 Apple M3 上每次调用的毫秒数,每个后端返回的数值均一致。最快运行耗时 32 毫秒,pandas 在相同数组上耗时 360 毫秒,但领先者随布局而变化:列优先数据以并行 Numba 最快,行优先数据则以并行 Rust 最快。Rust 的滚动均值逐行遍历数组,会先将列优先输入复制为行优先;Numba 的实现逐列遍历,列连续时受益最大。因此,VBT 允许逐次选择后端、全局设置后端,或让实测耗时决定。
哪个后端适合您的任务?
| 您的任务 | 可以尝试 |
|---|---|
| 使用自定义 Python 交易规则的回测 | Numba 编译数值回调与模拟循环。 |
| 跨大量交易品种或参数组合的滚动计算 | 并行 Numba 或 Rust 可将受支持计算分配到多个 CPU 核心。 |
| 反复调用受支持的内置函数 | Rust 直接运行原生代码,无须等待 Numba 编译该函数。 |
| 复用已有 NumPy 实现 | 将它与内置实现一起注册,再通过 jitted 选择。 |
| 计算形状各异的研究流程 | 测量关键调用,逐次选择后端或使用 AutoBench。 |
列既可代表交易品种,也可代表策略配置。模拟会并行处理独立列或组,同时按顺序处理各策略的 K 线。若要将完整回测分配给多个工作进程,或将大数组分块,参见并行执行与缓存。
Numba 编译
VBT 的大多数数值函数都由 Numba 编译,从滚动统计、信号生成器,到 vbt.PF.from_signals 背后的模拟器。模拟器是遍历 K
线的编译循环,而非整数组操作,因此止损、根据当前余额调整仓位等路径依赖规则也能以编译速度运行。编译函数释放 GIL,使线程能够并行执行;启用缓存的内置函数会将编译代码存入磁盘,以便后续 Python
会话复用。只要参数类型不变,仅改变数据或参数值本身不会触发重新编译,因此很适合反复研究。
全新环境中的首次调用仍需编译,可能耗时数秒。自定义回调,例如 from_signals 的信号函数,也会编译,而且每个新回调都会使模拟器重新编译,在构建本页的机器上约耗时 20
秒。重新定义回调,例如重运行定义它的 notebook
单元格,也被视为新回调,因此应将回调放在独立单元格或模块中。staticized 选项可将调用回调的模拟转化为也能在磁盘缓存的函数,使其跨重启和新工作进程复用。缓存的是编译后的模拟器,因此仍可用于新的市场数据和参数。
调试时,jitted=False 会运行底层 Python 函数,如下方累加和示例所示。您也可以在导入 VBT 前,通过 numba.disable 配置关闭 Numba。这控制 Numba
编译,而后端选择决定调用是否使用 Rust。
后端与 jitter
VBT 中的 jitted 函数是一个注册表条目,拥有一个或多个实现:nb 表示 Numba,rs 表示可选 Rust 扩展,np 表示向量化 NumPy,也可以是您注册的任意
jitter。jitted 参数为每次调用选择实现,并传递 parallel=True
等选项,详见下方即时编译与并行 Numba功能介绍。并行版本在多个核心上处理列,宽数组受益明显,窄数组则有少量额外开销。滚动夏普和
Sortino 比率等收益滚动度量通过单次遍历更新累计和,无须重新计算每个窗口,详见下方超高速滚动度量。
后端选择
安装 Rust 扩展后,兼容调用默认交给 Rust;Rust 无法处理输入时回退到 Numba。上例默认调用在行优先数据上耗时 86 毫秒,在列优先数据上耗时 147 毫秒,与 Rust 的耗时一致。以下三种设置可改变选择:
- 逐次调用: 使用
jitted="nb"、jitted="rs",或包含选项的字典。 - 按模块: 解析覆盖规则可为路径匹配的全部函数设置后端,例如
vectorbtpro.returns下的所有函数。 - 按实测: AutoBench 读取在本机生成的基准缓存,根据测量结果,以及必要时的输入形状,将每次调用分配给符合条件的后端。
Rust 引擎页的 Rust 后端功能介绍展示各个选项。后端选择发生在计算内部,因此切换受支持实现时,可以保留相同的 pandas 输入和结果处理方式。
找到最快的后端
使用 VBT 基准工具,在自己的数组上比较后端,再依据耗时为各项计算选择后端。Python 输入和带标签的结果保持不变。
✅ 大多数 Numba 编译函数已重写,使用
@jit 自动并行化并行处理各列。只需一条命令即可启用。此方法最适合应用于宽数组的轻量函数。
np.random.seed(42)
df = pd.DataFrame(np.random.uniform(size=(1000, 1000)))
%timeit df.rolling(10).mean() 45.6 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)%timeit df.vbt.rolling_mean(10) 5.33 ms ± 302 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)%timeit df.vbt.rolling_mean(10, jitted=dict(parallel=True)) 1.82 ms ± 5.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)✅ Jitting 原指即时编译,但在 VBT 中,它泛指加速。Numba 仍是主要 jitter,VBT 现在也允许实现自定义 jitter 类,例如向量化 NumPy,甚至支持 GPU 的 JAX。所有 jitted 函数都全局注册,因此只需一条命令,就能切换不同实现,甚至完全关闭加速。
data = vbt.YFData.pull("BTC-USD", start="7 days ago")
log_returns = np.log1p(data.close.pct_change())
log_returns.vbt.cumsum() Date
2023-01-31 00:00:00+00:00 0.000000
2023-02-01 00:00:00+00:00 0.024946
2023-02-02 00:00:00+00:00 0.014271
2023-02-03 00:00:00+00:00 0.013310
2023-02-04 00:00:00+00:00 0.008288
2023-02-05 00:00:00+00:00 -0.007967
2023-02-06 00:00:00+00:00 -0.010087
Freq: D, Name: Close, dtype: float64log_returns.vbt.cumsum(jitted=False) Date
2023-01-31 00:00:00+00:00 0.000000
2023-02-01 00:00:00+00:00 0.024946
2023-02-02 00:00:00+00:00 0.014271
2023-02-03 00:00:00+00:00 0.013310
2023-02-04 00:00:00+00:00 0.008288
2023-02-05 00:00:00+00:00 -0.007967
2023-02-06 00:00:00+00:00 -0.010087
Freq: D, Name: Close, dtype: float64@vbt.register_jitted(task_id_or_func=vbt.nb.nancumsum_nb)
def nancumsum_np(arr):
return np.nancumsum(arr, axis=0)
log_returns.vbt.cumsum(jitted="np") Date
2023-01-31 00:00:00+00:00 0.000000
2023-02-01 00:00:00+00:00 0.024946
2023-02-02 00:00:00+00:00 0.014271
2023-02-03 00:00:00+00:00 0.013310
2023-02-04 00:00:00+00:00 0.008288
2023-02-05 00:00:00+00:00 -0.007967
2023-02-06 00:00:00+00:00 -0.010087
Freq: D, Name: Close, dtype: float64✅ 基于收益率的滚动度量已进行极致性能优化,最高可加速 1000 倍!
import quantstats as qs
index = vbt.date_range("2020", periods=100000, freq="1min")
np.random.seed(42)
returns = pd.Series(np.random.normal(0, 0.001, size=len(index)), index=index)
%timeit qs.stats.rolling_sortino(returns, rolling_period=10) 2.79 s ± 24.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)%timeit returns.vbt.returns.rolling_sortino_ratio(window=10) 8.12 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)版权所有 © 2021–2026 Oleg Polakow。保留所有权利。
本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。