实例构建器
InstanceBuilder 配置并将 Navi 脚本编译为可执行的 Instance。
基本用法
use navi_vm::{Instance, TimeFrame};
let mut instance = Instance::builder(bars, source, TimeFrame::days(1), "NASDAQ:AAPL")
.build().await?;
instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;数据提供者
Instance::builder() 的第一个参数是 DataProvider,服务于两个目的:
- 主 chart K 线 —
Instance::run()从中消费数据驱动逐 bar 执行 request.security()数据 — 多周期和跨标的 K 线流
Vec<Candlestick> 直接实现了 DataProvider,无需任何包装即可传入:
let mut instance = Instance::builder(bars, source, timeframe, "NASDAQ:AAPL")
.build().await?;
instance.run_to_end("NASDAQ:AAPL", timeframe).await?;多标的或实时数据,实现 DataProvider:
let mut instance = Instance::builder(MyProvider::new(), source, timeframe, "NASDAQ:AAPL")
.build().await?;
instance.run_to_end("NASDAQ:AAPL", timeframe).await?;标的字符串作为最后一个参数传入;标的元数据由 provider 自动解析。
检查脚本信息
使用独立函数 script_info() 检查脚本的元数据,无需创建可执行实例,也不需要数据提供者、品种或周期:
use navi_vm::script_info;
let info = script_info(source)?;
println!("Script type: {:?}", info.script_type);
println!("Is overlay: {}", info.overlay());
println!("Inputs: {} total", info.inputs.len());
for input in &info.inputs {
// 检查输入定义
}在构建实例之前可用此函数查询输入 ID(详见输入配置)。
构建器方法
InstanceBuilder(来自 Instance::builder)在 build() 时编译源码。 InstanceBuilderFromProgram(来自 Instance::builder_from_program)跳过编译。 编译期选项仅在 InstanceBuilder 上可用。
| 方法 | 说明 | builder | builder_from_program |
|---|---|---|---|
with_locale | 数字/日期格式化的区域设置 | ✓ | ✓ |
with_input_value | 按 ID 覆盖脚本输入 | ✓ | ✓ |
with_input_sessions | 过滤处理的交易时段 | ✓ | ✓ |
with_strategy_config_override | 在实例层面覆盖 strategy() 参数 | ✓ | ✓ |
with_background_color | 自定义图表背景颜色 | ✓ | ✓ |
with_last_info | 最后一根 K 线的索引和时间戳 | ✓ | ✓ |
with_execution_limits | 循环迭代次数和 security 调用次数限制 | ✓ | ✓ |
with_mtf_wait | 实时阶段等待 request.security() MTF 数据就绪的最大时长 | ✓ | ✓ |
with_output_mode | 系列图形输出模式(Chart 或 Stream) | ✓ | ✓ |
with_jit | 启用 JIT 编译(需要 jit feature) | ✓ | ✓ |
with_library_loader | use 语句的自定义加载器,支持链式 fallback | ✓ | — |
脚本元数据
with_path
为脚本设置文件路径(用于错误信息):
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.build().await?;with_locale
设置数字和日期格式化的区域设置:
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_locale("zh")
.build().await?;输入配置
with_input_value
按 ID 覆盖默认输入值。输入 ID 从 0 开始按顺序分配,与脚本中 input.*() 调用的出现顺序一致。
如需查询各输入的 ID,可使用 script_info()(详见检查脚本信息):inputs 是按声明顺序排列的 Vec<Input>,索引即为 ID。
// Navi 脚本:
// length = input.int(14, "RSI Length") // input id 0
// src = input.source(close, "Source") // input id 1
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_input_value(0, 21) // 将 RSI Length 覆盖为 21
.build().await?;with_input_sessions
控制处理哪些交易时段:
use navi_vm::InputSessions;
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_input_sessions(InputSessions::REGULAR | InputSessions::EXTENDED)
.build().await?;| 标志 | 描述 |
|---|---|
InputSessions::REGULAR | 常规交易时段 |
InputSessions::EXTENDED | 延长/盘前/盘后交易 |
InputSessions::OVERNIGHT | 隔夜交易时段 |
InputSessions::ALL | 所有交易时段 |
策略配置
with_strategy_config_override
在不修改脚本源码的情况下,覆盖 strategy() 的部分参数。设为 Some(…) 的字段优先于脚本中声明的值;设为 None 的字段回退到脚本默认值。对非策略脚本无效。
use navi_vm::{Instance, StrategyConfigOverride, TimeFrame};
let instance = Instance::builder(bars, source, TimeFrame::days(1), "NASDAQ:AAPL")
.with_strategy_config_override(StrategyConfigOverride {
initial_capital: Some(50_000.0),
commission_value: Some(0.1),
..Default::default()
})
.build().await?;StrategyConfigOverride 字段(均为 Option<T>,默认 None):
| 字段 | 说明 | 默认值 |
|---|---|---|
initial_capital | 初始账户资金 | 1_000_000 |
commission_type | 佣金计算方式:percent、cash_per_contract 或 cash_per_order | percent |
commission_value | 佣金金额。percent 模式下为百分比数值(1 = 1%);cash_per_contract 或 cash_per_order 模式下为每合约或每笔订单的绝对金额。 | 0 |
margin_long | 多头保证金比率(%) | 100 |
margin_short | 空头保证金比率(%) | 100 |
slippage | 每笔成交滑点,单位:tick | 0 |
pyramiding | 同方向最大开仓次数 | 0 |
risk_free_rate | 年化无风险利率(%),用于计算夏普/索提诺比率 | 2 |
process_orders_on_close | 在 bar 收盘价成交,而非下一根 bar 开盘价 | false |
calc_on_order_fills | 订单成交后重新执行脚本 | false |
backtest_fill_limits_assumption | 限价单成交所需超出限价的额外 tick 数 | 0 |
close_entries_rule | 优先平仓规则:FIFO 或 ANY | FIFO |
覆盖值存储在实例上,每次 reset() 时自动重新应用,不会因重置而丢失。
显示与环境
with_background_color
设置自定义背景颜色:
use navi_vm::ast::Color;
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_background_color(Color::new(255, 255, 255, 255))
.build().await?;with_last_info
提供最后一根可用 K 线的信息(用于 barstate.is_last):
use navi_vm::LastInfo;
let last = LastInfo::new(499, 1700000000000);
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_last_info(last)
.build().await?;执行限制
with_execution_limits
配置运行时限制,以防止脚本失控:
use navi_vm::ExecutionLimits;
let limits = ExecutionLimits::default()
.with_max_loop_iterations_per_bar(1_000_000);
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_execution_limits(limits)
.build().await?;若未调用此方法,默认每 Bar 最多 500,000 次循环迭代。
还可以通过 with_max_bars_back() 控制内存中保留多少根历史 Bar(默认:1000)。详情请参阅安全与执行限制。
with_mtf_wait
设置每根实时 K 线执行前的固定等待时长(脚本含 request.security() 时生效):
use std::time::Duration;
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_mtf_wait(Duration::from_secs(3))
.build().await?;实时模式下,主 chart 流与 request.security() 子流由不同的异步任务驱动。新的 chart bar 到达时, 子流数据可能因网络延迟尚未就绪。若不等待,VM 会使用最后一个缓存值。
启用 with_mtf_wait 后,VM 在每根实时 bar 执行前先等待指定时长,让子流有时间推送最新数据,再执行脚本。
| 值 | 行为 |
|---|---|
Duration::ZERO(默认值) | 不等待——非阻塞行为 |
Duration::from_secs(3) | 每根实时 bar 前 sleep 3 秒——适合 3 秒内到达的数据源 |
| 其他时长 | 固定 sleep,对所有 request.security() 子流统一生效 |
脚本中没有 request.security() 调用时,函数立即返回不执行 sleep,零性能开销。
库加载
with_library_loader
为 use 语句添加自定义库加载器。新 loader 被插入到解析链的最前面,失败后依次 回退到之前注册的 loader,内置 prelude/stdlib 始终作为最后的 fallback。
use navi_loader::LibraryLoader;
struct MyLoader;
impl LibraryLoader for MyLoader {
type Source = String;
type Error = String;
fn load(&self, path: &str) -> Result<(String, String), Self::Error> {
// 返回 (源代码, 模块名) 或 Err
todo!()
}
}
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_library_loader(MyLoader)
.build().await?;多次调用 with_library_loader 可构建优先级栈,每个新 loader 优先于之前注册的 loader:
// 解析顺序:loader_b → loader_a → 内置 prelude/stdlib
let instance = Instance::builder(provider, source, timeframe, "NASDAQ:AAPL")
.with_library_loader(loader_a)
.with_library_loader(loader_b)
.build().await?;高级编译
编译一次,复用 Program
Instance::builder() 每次 build().await 都会重新编译源码。如果需要把同一个 Program 用于多个实例(例如不同的 provider 或不同的输入值),可先编译一次, 再用 Instance::builder_from_program 构造每个实例,避免重复编译:
use navi_compiler::CompileOptions;
use navi_vm::{Instance, NativeFuncs, TimeFrame};
// 编译一次。
let program = CompileOptions::new(source)
.with_library_loader(navi_builtins::builtins_loader())
.with_load_options(navi_builtins::load_options())
.with_native_func_registry(&NativeFuncs)
.compile()?;
// 多个实例共享同一个 program,不再重新编译。
let instance_a = Instance::builder_from_program(
program.clone(), provider_a, TimeFrame::days(1), "NASDAQ:AAPL",
)
.with_input_value(0, 14)
.build().await?;
let instance_b = Instance::builder_from_program(
program.clone(), provider_b, TimeFrame::days(1), "NASDAQ:AAPL",
)
.with_input_value(0, 21)
.build().await?;Program 内部用 Arc 持有,克隆开销很小。注意 builder_from_program 不暴露 with_library_loader——这是编译期选项,对已编译的 program 无效。
CompileOptions 的 setter
直接用 navi_compiler::CompileOptions 时可以使用下列 setter —— Instance::builder 没有暴露它们,因为它内部已经配好了合理的默认值:
| Setter | 说明 |
|---|---|
with_load_options(opts) | 覆盖 prelude/stdlib 解析配置。一般以 navi_builtins::load_options() 为起点再做定制。 |
with_native_func_registry(®istry) | 替换 native 函数注册表。必须包含脚本用到的所有 native 函数;传 &navi_vm::NativeFuncs 即可使用 vm 自带的全套。 |
with_locale(locale) | 与 InstanceBuilder 上同名方法行为一致。 |
with_library_loader(loader) | 同上 —— 把额外的 loader 链到现有 loader 之前。 |
with_optimize(enable) | 切换优化开关(默认 true)。 |
输出模式
with_output_mode
控制 plot() 数据的产出方式。
| 模式 | 行为 |
|---|---|
OutputMode::Chart(默认) | 系列图形数据写入 instance.chart() |
OutputMode::Stream | 系列图形数据以 DrawEvent 形式在事件流中发出 |
use navi_vm::{Instance, OutputMode, TimeFrame};
let mut instance = Instance::builder(provider, source, TimeFrame::days(1), "NASDAQ:AAPL")
.with_output_mode(OutputMode::Stream)
.build().await?;详见 执行脚本。
完整示例
use navi_vm::{Instance, OutputMode, StrategyConfigOverride, TimeFrame};
let mut instance = Instance::builder(bars, source, TimeFrame::minutes(5), "NASDAQ:AAPL")
.with_locale("en")
.with_input_value(0, 20) // 覆盖第一个输入
.with_input_value(1, 2.5) // 覆盖第二个输入
.with_input_sessions(InputSessions::REGULAR)
.with_last_info(LastInfo::new(999, last_time))
.with_output_mode(OutputMode::Stream)
// 仅策略脚本有效:覆盖执行参数
.with_strategy_config_override(StrategyConfigOverride {
initial_capital: Some(50_000.0),
commission_value: Some(0.1),
..Default::default()
})
.build().await?;
instance.run_to_end("NASDAQ:AAPL", TimeFrame::minutes(5)).await?;