# Rust 引擎 (/zh/features/performance/native-rust)

为 Python 研究引入 Rust 速度，或用 Rust 编写整个策略。VBT PRO 提供原生 Rust 回测引擎，支持这两种工作流。Python 用户通过可选扩展获得预编译计算；Rust
用户还可以编写有状态策略回调，并跨 CPU 核心运行独立模拟。

## 选择使用 Rust 的方式 \[#choose-your-route-to-rust]

| 您想做什么                           | 从哪里开始                            |
| ------------------------------- | -------------------------------- |
| 加速已有 Python notebook 或脚本中受支持的计算 | 安装可选扩展，继续使用 Python API。          |
| 保留用 Python 编写的自定义交易规则           | 使用 Numba 回调，流程中其他受支持的计算仍可用 Rust。 |
| 编写根据成交、现金或持仓作出反应的 Rust 策略       | 使用原生模拟器，让策略拥有自己的状态。              |
| 在 Rust 程序中逐根处理新到达的 K 线          | 使用流式步进器，延续投资组合与策略状态。             |

Python 扩展和原生 Rust 库共享计算代码。您可以无须学习 Rust 就采用扩展，也可以用原生库构建独立 Rust 策略。原生 Rust 程序不依赖 Python 运行时即可使用该库。

## Python 与 Rust 中的有状态策略 \[#a-stateful-strategy-in-python-and-rust]

下面同一策略运行两次：一次通过 Python API 使用 Numba 回调，一次作为 Rust 策略。它在 50,000 根小时 K 线上交易 100 个合成资产，均线上穿时入场，反向交叉、3%
止损或 6% 止盈时离场；任一止损或止盈触发后，该资产等待 24 根 K 线再允许入场。是否需要等待取决于模拟实际产生的成交，因此无法预先计算为数组。

```python title="Write the cooldown as Numba callbacks and run it through the Python API"
>>> from collections import namedtuple

>>> COOLDOWN_BARS = 24
>>> np.random.seed(42)
>>> returns = np.random.normal(0, 0.01, size=(50_000, 100))
>>> close = pd.DataFrame(100 * np.exp(returns.cumsum(axis=0)))
>>> fast = close.rolling(20).mean()
>>> slow = close.rolling(100).mean()
>>> entries = fast.vbt.crossed_above(slow)
>>> exits = fast.vbt.crossed_below(slow)
>>> Memory = namedtuple("Memory", ["blocked_until"])

>>> @njit
... def cooldown_signal_func_nb(c, entries, exits, memory):
...     cooling = c.i < memory.blocked_until[c.col]
...     entry = vbt.nb.flex_select_nb(entries, c.i, c.col) and not cooling
...     exit = vbt.nb.flex_select_nb(exits, c.i, c.col)
...     return entry, exit, False, False

>>> @njit
... def cooldown_post_order_func_nb(c, memory):
...     if vbt.pf_nb.order_closed_position_nb(c):  # (1)
...         if vbt.pf_nb.get_last_order_nb(c)["stop_type"] >= 0:
...             memory.blocked_until[c.col] = c.i + 1 + COOLDOWN_BARS

>>> memory = Memory(blocked_until=np.zeros(close.shape[1], dtype=int_))
>>> pf = vbt.PF.from_signals(
...     close,
...     signal_func_nb=cooldown_signal_func_nb,
...     signal_args=(vbt.Rep("entries"), vbt.Rep("exits"), memory),
...     broadcast_named_args=dict(entries=entries, exits=exits),
...     post_order_func_nb=cooldown_post_order_func_nb,
...     post_order_args=(memory,),
...     size=1.0,
...     fees=0.001,
...     sl_stop=0.03,
...     tp_stop=0.06,
... )
```

1.  每个订单后运行。若订单平仓且记录带有止损类型，则阻止该资产在之后 24 根 K 线内入场。

```rust title="Write the same cooldown as a Rust strategy"
const COOLDOWN_BARS: usize = 24;

struct Cooldown<'a> {
    entries: ArrayView2<'a, bool>,
    exits: ArrayView2<'a, bool>,
    blocked_until: Vec<usize>, // (1)
}

impl SignalStrategy for Cooldown<'_> {
    fn signals(&mut self, ctx: &mut SignalContext<'_, '_>) -> VbtResult<Signals> {
        let cell = [ctx.i(), ctx.col()];
        let cooling = ctx.i() < self.blocked_until[ctx.local_col()];
        Ok(Signals {
            long_entry: self.entries[cell] && !cooling,
            long_exit: self.exits[cell],
            ..Signals::default()
        })
    }

    fn on_order_end(
        &mut self,
        ctx: &mut OrderEndContext<'_, '_, SignalOrderRecord>,
        report: &ExecutionReport,
    ) -> VbtResult<()> {
        if report.before.position != 0.0 && report.after.position == 0.0 {
            let record = ctx.order_record_now().expect("a close emits a record");
            if record.stop_type >= 0 {
                self.blocked_until[ctx.local_col()] = ctx.i() + 1 + COOLDOWN_BARS;
            }
        }
        Ok(())
    }
}
```

1.  Rust 策略拥有自己的状态，因此记忆元组成为一个字段。

```rust title="Run it on one core and on all cores"
let simulator = SignalSimulator::builder()
    .config(
        SimulationConfig::builder()
            .target_shape(close.dim())
            .group_lens(group_lens.view())
            .close(close.view())
            .build()?,
    )
    .signal_config(
        SignalConfig::builder().size(1.0).fees(0.001).sl_stop(0.03).tp_stop(0.06).build(),
    )
    .build()?;
let new_cooldown = |info: GroupInfo| {
    Ok(Cooldown {
        entries: entries.view(),
        exits: exits.view(),
        blocked_until: vec![0; info.to_col - info.from_col],
    })
};
let serial = simulator.run(new_cooldown)?; // (1)
let parallel = simulator.run_parallel(new_cooldown)?;
```

1.  `run` 为每组创建一个策略，并按顺序运行各组。`run_parallel` 将独立组分配到多个 CPU 核心，合并输出保持原有顺序。

两个版本都生成相同的 65,278 个订单，每列、每根 K 线、数量和价格均一致。在 8 核 Apple M3 上测量，取五次中的最短耗时：

| 运行方式                                 |    毫秒 |
| ------------------------------------ | ----: |
| 通过 `vbt.PF.from_signals` 运行 Numba 回调 | 1,129 |
| Rust 策略，单核                           |   538 |
| Rust 策略，全部核心                         |   201 |

Python 时间包括参数准备和投资组合构建，Rust 时间只包括模拟。因此，表中测量的是两个工作流的不同层级。生成结果的研究脚本会构建 Rust 程序，运行两个版本，并比较订单。

## 可直接替换的 Rust 内核 \[#drop-in-rust-kernels]

Python 用户无须编写 Rust 即可使用 Rust 引擎。安装可选 `vectorbtpro-rust` 包后，滚动统计、信号辅助函数、收益度量，以及 `from_signals` 和
`from_orders` 模拟器等受支持函数，作为预编译 Rust 内核运行；Rust 未覆盖的部分回退到
Numba。按照[安装指南（英文）](https://members.vectorbt.pro/installation/#rust-backend)，先安装对应版本的平台 wheel，再安装
`vectorbtpro`。只有版本完全匹配时，VBT 才会使用它。`jitted=dict(jitter="rs", parallel=True)`
可按列或独立投资组合组，将受支持计算分配到多个核心。

内核已提前编译，因此会话中的首次调用无须等待 Numba。在构建本页的机器上，新进程首次计算滚动标准差，Rust 约耗时 0.08 秒。Numba 首次接触该输入时需编译，耗时 1.3
秒；后续进程从磁盘加载已编译版本，约耗时 0.17
秒。同一进程中的后续调用，两者均不到一毫秒，因此短脚本、定时任务和新工作进程受益最大。[计算后端](/zh/features/performance/compute-backends/)页展示
Rust 何时领先及如何逐次选择后端，下方 [Rust 后端](#rust-backend)功能介绍展示具体控制方式。

## 原生模拟器 \[#native-simulators]

同一套模拟器也作为 Rust 库提供。Rust 程序可用 `run_single`、`run` 或 `run_parallel` 一次运行完整历史，也可向 `SignalStepper`
等步进器逐根输入 K 线，并即时接收每根的成交，使用检查点保存和恢复状态。可以先回放历史，再随着新 K
线到达继续处理，保留现金、持仓和上述冷却期等策略记忆。此工作流详见[实时模拟](/zh/features/backtesting/live-simulation/)。

`ensure_simulation_eq` 可比较批量与流式运行，检查记录及最终投资组合状态，并报告首个不一致处。因此可以验证，策略从完整历史运行迁移至逐根 K
线输入后，模拟交易是否保持一致。crate 的测试套件将其模拟器与 Numba 版本比较，如同上例对订单进行比较。

## Rust 回调 \[#rust-callbacks]

Rust 策略是实现某个 trait 的类型，例如信号使用 `SignalStrategy`，每个元素一个订单使用 `OrderStrategy`，每根 K 线任意数量订单使用
`FlexOrderStrategy`。其方法接收包含当前 K 线、列、现金和持仓的上下文，并在每个订单后收到执行报告。其他原生引擎也接受信号生成、应用与归约操作以及投资组合配置回调。

每个并行模拟组各有独立策略实例。一个组可包含多个共享现金的资产，因此既能将投资组合层面的决策放在一起，也能并行运行独立投资组合。各组内部的行和回调仍按顺序执行。

Rust 回调运行于 Rust 程序中。在 Python 中，策略逻辑使用上例那样的 Numba 回调，并在 Numba 模拟器上运行。Rust 错误带有类别，例如无效值或订单被拒绝，在 Python
中表现为对应异常类型。

## 将 Rust 结果带回 Python \[#bring-rust-results-back-to-python]

原生 Rust 模拟可用 `save_npz` 将输出保存为兼容 NumPy 的归档。通过 `vbt.load_simulation_npz` 加载，并使用与模拟相同的价格、标签、初始现金和分组构建
Portfolio。随后可对 Rust 结果使用 VBT
的[绩效分析](/zh/features/analytics/performance-metrics/)和[交易分析](/zh/features/analytics/trade-analytics/)。这样，模拟在
Rust 中运行，Python 分析与报告工作流则得以保留。

!!! info "教程"
    会员专享的[从 Python 到 Rust（英文）](https://members.vectorbt.pro/tutorials/from-python-to-rust/)教程分别使用高层 API、Numba
    内核、Python 绑定及原生 Rust 构建同一策略，再迁移至流式执行。

## 原生 Rust 模拟器 \[#native-rust-simulators]

新增于 v2026.9.5

✅ 将策略直接带到 Rust。VBT 原生模拟器可让同一策略处理完整价格数组，也可逐根处理新到达的 K 线。策略回调能在执行过程中检查现金、持仓与订单，让交易逻辑随时访问投资组合状态。

!!! note "说明"
    下方示例共用策略定义，需要 `vectorbtpro-rust` 和 `ndarray = "0.16"`。安装方式参见 [Rust 设置指南](/zh/documentation/rust/)。

```rust title="Define a strategy with price and position conditions"
use vectorbtpro_rust::error::VbtResult;
use vectorbtpro_rust::portfolio::enums::Order;
use vectorbtpro_rust::portfolio::simulator::{
    FnOrderStrategy, OrderContext, OrderStrategy,
};

fn buy_the_dip() -> impl OrderStrategy {
    FnOrderStrategy::new(|ctx: &OrderContext<'_, '_>| {
        let price = ctx.close(ctx.col());
        let position = ctx.position(ctx.col());
        let size = if price <= 100.0 && position == 0.0 { // (1)
            1.0
        } else if price >= 110.0 && position > 0.0 {
            -position
        } else {
            return Ok(None);
        };
        Ok(Some(Order::builder().size(size).build()))
    })
}
```

1.  仅在空仓时买入一股。持仓检查可防止在 98 美元时再次买入。

=== "批量"
    ```rust title="Run a batch simulation"
    use ndarray::array;
    use vectorbtpro_rust::portfolio::simulator::{OrderSimulator, SimulationConfig};

    fn main() -> VbtResult<()> {
        let close = array![[100.0], [98.0], [105.0], [112.0]];
        let groups = array![1];
        let config = SimulationConfig::builder()
            .target_shape(close.dim())
            .group_lens(groups.view())
            .close(close.view())
            .init_cash(1000.0)
            .build()?;
        let simulator = OrderSimulator::builder().config(config).build()?;
        let output = simulator.run_single(&mut buy_the_dip())?;
        for order in &output.order_records {
            println!("row {}: {:.0} share at ${:.0}", order.idx, order.size, order.price);
        }
        Ok(())
    }
    ```

    ```text title="Output"
    row 0: 1 share at $100
    row 3: 1 share at $112
    ```

=== "流式"
    ```rust title="Run a streaming simulation"
    use ndarray::array;
    use vectorbtpro_rust::portfolio::simulator::{OrderStepper, StreamConfig, StreamRow};

    fn main() -> VbtResult<()> {
        let close = array![[100.0], [98.0], [105.0], [112.0]];
        let groups = array![1];
        let config = StreamConfig::builder()
            .group_lens(groups.view())
            .init_cash(1000.0)
            .build()?;
        let mut live = OrderStepper::new_single(config, None, buy_the_dip())?;
        for row in close.rows() {
            let step = live.step(StreamRow::builder().close(row).build())?; // (1)
            for order in step.order_records() {
                println!("row {}: {:.0} share at ${:.0}", order.idx, order.size, order.price);
            }
        }
        Ok(())
    }
    ```

    1.  在调用之间保留投资组合状态，并返回当前 K 线的成交。步进器默认不保留完整历史记录。

    ```text title="Output"
    row 0: 1 share at $100
    row 3: 1 share at $112
    ```

!!! info "教程"
    更多内容参见[从 Python 到 Rust](/zh/tutorials/from-python-to-rust/)教程。

## Rust 后端 \[#rust-backend]

新增于 v2026.6.27

✅ 安装可选 `vectorbtpro-rust` 扩展后，兼容的 jitted 调用便可自动使用 Rust 加速。VBT 通过 `vbt.rs` 提供扩展，将 Rust 内核注册为
`jitted="rs"`，并在 Rust 不可用或不受支持时回退到普通实现，通常是 Numba。

=== "开箱即用"
    ```python title="Keep your workflow, get the Rust lane"
    >>> data = vbt.YFData.pull("BTC-USD", start="2024")

    >>> fast_ma = data.close.vbt.rolling_mean(20)  # (1)
    >>> slow_ma = data.close.vbt.rolling_mean(50)
    >>> entries = fast_ma.vbt.crossed_above(slow_ma)  # (2)
    >>> exits = fast_ma.vbt.crossed_below(slow_ma)

    >>> pf = vbt.Portfolio.from_signals(  # (3)
    ...     data,
    ...     entries=entries,
    ...     exits=exits,
    ...     sl_stop=0.05,
    ...     tp_stop=0.15,
    ...     fees=0.001,
    ... )
    ```

    1.  `rolling_mean_nb` 路由至 `vbt.rs.generic.rolling.rolling_mean_rs`。
    2.  `crossed_above_nb` 路由至 `vbt.rs.generic.base.crossed_above_rs`。
    3.  `from_signals_nb` 路由至 `vbt.rs.portfolio.from_signals.from_signals_rs`。

=== "速度检查"
    ```python title="Compare Numba, parallel Numba, Rust, and parallel Rust"
    >>> np.random.seed(42)
    >>> returns = pd.DataFrame(np.random.normal(0, 0.01, size=(200_000, 64)))  # (1)

    >>> nb = lambda: returns.vbt.returns.rolling_profit_factor(
    ...     window=50,
    ...     jitted="nb"
    ... )
    >>> nb_parallel = lambda: returns.vbt.returns.rolling_profit_factor(
    ...     window=50,
    ...     jitted=dict(jitter="nb", parallel=True)
    ... )
    >>> rs = lambda: returns.vbt.returns.rolling_profit_factor(
    ...     window=50,
    ...     jitted="rs"
    ... )
    >>> rs_parallel = lambda: returns.vbt.returns.rolling_profit_factor(
    ...     window=50,
    ...     jitted=dict(jitter="rs", parallel=True)
    ... )

    >>> nb(); nb_parallel(); rs(); rs_parallel()  # (2)

    >>> %timeit nb()
    1.21 s ± 655 μs per loop (mean ± std. dev. of 7 runs, 1 loop each)

    >>> %timeit nb_parallel()
    214 ms ± 15.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

    >>> %timeit rs()
    272 ms ± 736 μs per loop (mean ± std. dev. of 7 runs, 1 loop each)

    >>> %timeit rs_parallel()
    54.6 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    ```

    1.  使用足够宽的数组，为并行内核提供发挥空间。
    2.  先预热，避免将 Numba 编译计入比较。这些耗时在 Apple M3 上测得，会因机器而异。

=== "AutoBench"
    ```bash title="Build the benchmark cache first"
    python -m vectorbtpro.benchmarks.bench_matrix_cli --full
    ```

    !!! info "说明"
        完整基准矩阵可能需要数小时，具体取决于机器。

    ```python title="Let local timings pick the lane"
    >>> vbt.settings.jitting["backends"]["auto_bench_path"] = "benchmarks/cache.json"  # (1)
    >>> vbt.settings.jitting["backends"]["auto_mode"] = "bench"  # (2)

    >>> data = vbt.YFData.pull("BTC-USD", start="2024")
    >>> sharpe_ratio = data.returns.vbt.returns.sharpe_ratio()  # (3)
    ```

    1.  启用 AutoBench 前先运行基准测试。矩阵命令默认写出此缓存。若使用自定义 `--output-dir` 或 `--cache`，请相应修改路径。
    2.  AutoBench 使用 `"bench"`，AutoBenchMixed 使用 `"bench_mixed"`。AutoBenchMixed 可自动在串行与并行候选之间选择。
    3.  不设置 `jitted`。兼容调用会自动查询本机基准缓存。

=== "控制面板"
    ```python title="Tune Rust preference"
    >>> vbt.settings.jitting["backends"]["auto_mode"] = False  # (1)
    >>> vbt.settings.jitting["resolve_overrides"]["returns_module"] = dict(  # (2)
    ...     match="vectorbtpro.returns",
    ...     resolve_kwargs=dict(auto_mode=True),
    ... )

    >>> data = vbt.YFData.pull("BTC-USD", start="2024")
    >>> sharpe_ratio = data.returns.vbt.returns.sharpe_ratio()
    ```

    1.  全局关闭优先使用 Rust 的自动分发。
    2.  仅为收益模块调用重新启用自动后端分发。

=== "原始 Rust，最快路径"
    ```python title="Resolve or call the Rust function directly"
    >>> total_return_rs = vbt.resolve_jitted(  # (1)
    ...     vbt.ret_nb.total_return_nb,
    ...     jitted="rs",
    ...     use_backend_wrapper=False,
    ... )

    >>> data = vbt.YFData.pull("BTC-USD", start="2024")
    >>> returns = data.returns.vbt.to_2d_array()  # (2)

    >>> total_return_rs(returns)  # (3)
    array([0.652341])

    >>> vbt.rs.returns.total_return_rs(returns)  # (4)
    array([0.652341])
    ```

    1.  让 VBT 查找为 `total_return_nb` 注册的 Rust 后端。设置 `use_backend_wrapper=False` 后，返回原始 Rust 函数本身。
    2.  准备参数。原始内核需要原始数组，而非封装后的 Series/DataFrame。
    3.  使用原始数组参数调用解析出的 Rust 函数。
    4.  或从模块路径直接导入同一 Rust 函数。各 Numba 函数的 API 页会在存在对应 Rust 函数时列出其路径。

    !!! info
        原始 Rust 函数绕过注册表的兼容性检查、参数准备和回退机制。仅在参数已按 Rust 函数要求的精确格式准备好时使用。
