功能
Rust 引擎
在原生 Rust 引擎上模拟,并用 Rust 编写策略回调
为 Python 研究引入 Rust 速度,或用 Rust 编写整个策略。VBT PRO 提供原生 Rust 回测引擎,支持这两种工作流。Python 用户通过可选扩展获得预编译计算;Rust 用户还可以编写有状态策略回调,并跨 CPU 核心运行独立模拟。
选择使用 Rust 的方式
| 您想做什么 | 从哪里开始 |
|---|---|
| 加速已有 Python notebook 或脚本中受支持的计算 | 安装可选扩展,继续使用 Python API。 |
| 保留用 Python 编写的自定义交易规则 | 使用 Numba 回调,流程中其他受支持的计算仍可用 Rust。 |
| 编写根据成交、现金或持仓作出反应的 Rust 策略 | 使用原生模拟器,让策略拥有自己的状态。 |
| 在 Rust 程序中逐根处理新到达的 K 线 | 使用流式步进器,延续投资组合与策略状态。 |
Python 扩展和原生 Rust 库共享计算代码。您可以无须学习 Rust 就采用扩展,也可以用原生库构建独立 Rust 策略。原生 Rust 程序不依赖 Python 运行时即可使用该库。
Python 与 Rust 中的有状态策略
下面同一策略运行两次:一次通过 Python API 使用 Numba 回调,一次作为 Rust 策略。它在 50,000 根小时 K 线上交易 100 个合成资产,均线上穿时入场,反向交叉、3% 止损或 6% 止盈时离场;任一止损或止盈触发后,该资产等待 24 根 K 线再允许入场。是否需要等待取决于模拟实际产生的成交,因此无法预先计算为数组。
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):
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,
)const COOLDOWN_BARS: usize = 24;
struct Cooldown<'a> {
entries: ArrayView2<'a, bool>,
exits: ArrayView2<'a, bool>,
blocked_until: Vec<usize>,
}
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(())
}
}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)?;
let parallel = simulator.run_parallel(new_cooldown)?;两个版本都生成相同的 65,278 个订单,每列、每根 K 线、数量和价格均一致。在 8 核 Apple M3 上测量,取五次中的最短耗时:
| 运行方式 | 毫秒 |
|---|---|
通过 vbt.PF.from_signals 运行 Numba 回调 | 1,129 |
| Rust 策略,单核 | 538 |
| Rust 策略,全部核心 | 201 |
Python 时间包括参数准备和投资组合构建,Rust 时间只包括模拟。因此,表中测量的是两个工作流的不同层级。生成结果的研究脚本会构建 Rust 程序,运行两个版本,并比较订单。
可直接替换的 Rust 内核
Python 用户无须编写 Rust 即可使用 Rust 引擎。安装可选 vectorbtpro-rust 包后,滚动统计、信号辅助函数、收益度量,以及 from_signals 和
from_orders 模拟器等受支持函数,作为预编译 Rust 内核运行;Rust 未覆盖的部分回退到
Numba。按照安装指南(英文),先安装对应版本的平台 wheel,再安装
vectorbtpro。只有版本完全匹配时,VBT 才会使用它。jitted=dict(jitter="rs", parallel=True)
可按列或独立投资组合组,将受支持计算分配到多个核心。
内核已提前编译,因此会话中的首次调用无须等待 Numba。在构建本页的机器上,新进程首次计算滚动标准差,Rust 约耗时 0.08 秒。Numba 首次接触该输入时需编译,耗时 1.3 秒;后续进程从磁盘加载已编译版本,约耗时 0.17 秒。同一进程中的后续调用,两者均不到一毫秒,因此短脚本、定时任务和新工作进程受益最大。计算后端页展示 Rust 何时领先及如何逐次选择后端,下方 Rust 后端功能介绍展示具体控制方式。
原生模拟器
同一套模拟器也作为 Rust 库提供。Rust 程序可用 run_single、run 或 run_parallel 一次运行完整历史,也可向 SignalStepper
等步进器逐根输入 K 线,并即时接收每根的成交,使用检查点保存和恢复状态。可以先回放历史,再随着新 K
线到达继续处理,保留现金、持仓和上述冷却期等策略记忆。此工作流详见实时模拟。
ensure_simulation_eq 可比较批量与流式运行,检查记录及最终投资组合状态,并报告首个不一致处。因此可以验证,策略从完整历史运行迁移至逐根 K
线输入后,模拟交易是否保持一致。crate 的测试套件将其模拟器与 Numba 版本比较,如同上例对订单进行比较。
Rust 回调
Rust 策略是实现某个 trait 的类型,例如信号使用 SignalStrategy,每个元素一个订单使用 OrderStrategy,每根 K 线任意数量订单使用
FlexOrderStrategy。其方法接收包含当前 K 线、列、现金和持仓的上下文,并在每个订单后收到执行报告。其他原生引擎也接受信号生成、应用与归约操作以及投资组合配置回调。
每个并行模拟组各有独立策略实例。一个组可包含多个共享现金的资产,因此既能将投资组合层面的决策放在一起,也能并行运行独立投资组合。各组内部的行和回调仍按顺序执行。
Rust 回调运行于 Rust 程序中。在 Python 中,策略逻辑使用上例那样的 Numba 回调,并在 Numba 模拟器上运行。Rust 错误带有类别,例如无效值或订单被拒绝,在 Python 中表现为对应异常类型。
将 Rust 结果带回 Python
原生 Rust 模拟可用 save_npz 将输出保存为兼容 NumPy 的归档。通过 vbt.load_simulation_npz 加载,并使用与模拟相同的价格、标签、初始现金和分组构建
Portfolio。随后可对 Rust 结果使用 VBT
的绩效分析和交易分析。这样,模拟在
Rust 中运行,Python 分析与报告工作流则得以保留。
教程
会员专享的从 Python 到 Rust(英文)教程分别使用高层 API、Numba 内核、Python 绑定及原生 Rust 构建同一策略,再迁移至流式执行。
✅ 将策略直接带到 Rust。VBT 原生模拟器可让同一策略处理完整价格数组,也可逐根处理新到达的 K 线。策略回调能在执行过程中检查现金、持仓与订单,让交易逻辑随时访问投资组合状态。
说明
下方示例共用策略定义,需要 vectorbtpro-rust 和 ndarray = "0.16"。安装方式参见 Rust 设置指南。
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.0
} else if price >= 110.0 && position > 0.0 {
-position
} else {
return Ok(None);
};
Ok(Some(Order::builder().size(size).build()))
})
}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(())
}row 0: 1 share at $100
row 3: 1 share at $112教程
更多内容参见从 Python 到 Rust教程。
✅ 安装可选 vectorbtpro-rust 扩展后,兼容的 jitted 调用便可自动使用 Rust 加速。VBT 通过 vbt.rs 提供扩展,将 Rust 内核注册为
jitted="rs",并在 Rust 不可用或不受支持时回退到普通实现,通常是 Numba。
data = vbt.YFData.pull("BTC-USD", start="2024")
fast_ma = data.close.vbt.rolling_mean(20)
slow_ma = data.close.vbt.rolling_mean(50)
entries = fast_ma.vbt.crossed_above(slow_ma)
exits = fast_ma.vbt.crossed_below(slow_ma)
pf = vbt.Portfolio.from_signals(
data,
entries=entries,
exits=exits,
sl_stop=0.05,
tp_stop=0.15,
fees=0.001,
)版权所有 © 2021–2026 Oleg Polakow。保留所有权利。
本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。