功能

计算后端

使用 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

Time one rolling mean on 50 million values across backends and memory layouts
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

✅ 大多数 Numba 编译函数已重写,使用 @jit 自动并行化并行处理各列。只需一条命令即可启用。此方法最适合应用于宽数组的轻量函数。

Benchmark the rolling mean without and with parallelization
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 函数都全局注册,因此只需一条命令,就能切换不同实现,甚至完全关闭加速。

Run different implementations of the cumulative sum
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: float64
log_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 倍!

Benchmark the rolling Sortino ratio
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 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。