执行脚本
Candlestick
Candlestick 结构体表示一根 OHLCV K 线:
use navi_vm::{Candlestick, market::TradeSession};
let candle = Candlestick::new(
1700000000000, // time: 毫秒级时间戳
150.0, // open
155.0, // high
149.0, // low
153.0, // close
1_000_000.0, // volume
0.0, // turnover
TradeSession::Regular, // 交易时段
);Candlestick 还提供派生价格计算:
candle.hl2(); // (high + low) / 2
candle.hlc3(); // (high + low + close) / 3
candle.ohlc4(); // (open + high + low + close) / 4
candle.hlcc4(); // (high + low + close + close) / 4TimeFrame
TimeFrame 指定图表周期。可以使用便捷方法创建或从字符串解析:
use navi_vm::TimeFrame;
// 便捷构造函数
let tf = TimeFrame::days(1);
let tf = TimeFrame::minutes(5);
let tf = TimeFrame::weeks(1);
let tf = TimeFrame::months(1);
let tf = TimeFrame::seconds(30);
// 从字符串简写解析
let tf: TimeFrame = "D".parse().unwrap(); // 1 天
let tf: TimeFrame = "5".parse().unwrap(); // 5 分钟
let tf: TimeFrame = "60".parse().unwrap(); // 60 分钟(1 小时)
let tf: TimeFrame = "W".parse().unwrap(); // 1 周
let tf: TimeFrame = "3M".parse().unwrap(); // 3 个月
// 检查
let seconds = tf.in_seconds(); // Option<u64>
let display = tf.to_string(); // "D", "5", "W" 等SymbolInfo
SymbolInfo 携带交易标的的元数据。你不再需要手动构造它——只需将标的字符串传给 Instance::builder,构建器会自动解析(若配置了 provider 则调用 DataProvider::symbol_info,否则使用内置默认值)。
支持的市场前缀:NYSE、NASDAQ、SHSE、SZSE、HKEX、SGX。
如需提供自定义标的元数据(描述、货币、最小变动单位等),实现 DataProvider::symbol_info 并返回 PartialSymbolInfo。详情参见实例构建器。
提供数据
历史回测
直接将 Vec<Candlestick> 传给 Instance::builder(),调用 run_to_end() 时 VM 会主动从中拉取数据:
use navi_vm::TimeFrame;
let instance = Instance::builder(historical_data, source, TimeFrame::days(1), "NASDAQ:AAPL")
.build().await?
.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;对于懒加载/流式数据,先收集为 Vec<Candlestick>,或实现自定义 DataProvider:
// 从迭代器收集
let bars: Vec<Candlestick> = load_bars_from_db().collect();
let mut instance = Instance::builder(bars, source, timeframe, "NASDAQ:AAPL")
.build().await?;实时流
实现 DataProvider 并 yield CandlestickItem::Bar(candle)。HistoryEnd 之前的所有 bar 必须是已完全收盘的历史 K 线——HistoryEnd 是一条硬边界,意味着"所有历史数据均已确认"。在所有已确认的历史 bar 发送完毕后 yield CandlestickItem::HistoryEnd。HistoryEnd 之后的 Bar 为实时 K 线——时间戳可能与最后一根历史 bar 相同(若查询时该周期仍在形成中),也可能是更晚的新时间戳(新 bar)。VM 会自动处理回滚和确认逻辑。
事件流
Instance::run() 返回 RunHandle,在脚本逐 K 线执行时逐个产出事件。
RunHandle
RunHandle 在执行期间持有 Instance。你可以通过 handle.next_event().await 迭代事件,并借助 DerefMut 直接在 handle 上调用实例方法;执行结束后再通过 handle.into_instance() 取回实例。
当实时 K 线发生 rollover(先收到 Bar(tA),再收到时间戳更晚的 Bar(tB),其中 tB > tA),流保持与之前相同的事件顺序——先确认 tA,再开始 tB——但这两步分开暴露,以便调用方在确认边界处执行 checkpoint。
事件类型
| 事件 | 何时发出 |
|---|---|
ScriptInfo(Arc<ScriptInfo>) | 流开始:脚本元数据(标题、overlay 标志、输入、声明的序列、告警)。在任何 K 线之前发出一次(仅 Stream 模式)。 |
Warning(Warnings) | 编译警告,在 ScriptInfo 之后、任何 K 线之前发出一次(有警告时)。 |
BarStart(BarStartEvent) | 每根 K 线执行前。包含 bar_index、candlestick(完整 OHLCV)以及 bar_state(History、RealtimeNew、RealtimeUpdate 或 RealtimeConfirmed)。 |
BarEnd | 每根 K 线执行完成后(策略处理、脚本体等)。始终与前一个 BarStart 配对。 |
HistoryEnd | 仅一次,当 DataProvider yield CandlestickItem::HistoryEnd 时。标记从历史回放到实时数据的转换。不跟随 BarStart。 |
Log(LogEvent) | K 线执行期间,脚本调用 log.info()、log.warning() 或 log.error() 时。在 BarStart 和 BarEnd 之间发出。 |
Alert(AlertEvent) | K 线执行期间,脚本调用 alert() 或 alert_condition() 触发时。在 BarStart 和 BarEnd 之间发出。 |
Draw(DrawEvent) | 仅 OutputMode::Stream 模式下。包装 SeriesEvent、GraphEvent 或 FilledOrder。详见读取输出。 |
Strategy(StrategyEvent) | 仅 OutputMode::Stream 模式下。订单、成交、权益快照等。详见读取输出。 |
事件顺序
典型的历史 + 实时会话中,流的产出顺序:
BarStart(0) → Log/Alert/Draw... → BarEnd ← 历史 K 线 0
BarStart(1) → Log/Alert/Draw... → BarEnd ← 历史 K 线 1
...
HistoryEnd
BarStart(N) → ... → BarEnd ← K 线 N 的第一个 tick(RealtimeNew)
BarStart(N) → ... → BarEnd ← 同时间戳 tick 更新(RealtimeUpdate)
BarStart(N) → ... → BarEnd ← K 线 N 确认(RealtimeConfirmed)
BarStart(N+1) → ... → BarEnd ← K 线 N+1 的第一个 tick(RealtimeNew)
BarStart(N+1) → ... → BarEnd ← 流结束,最终确认(RealtimeConfirmed)用法
use navi_vm::{Event, Instance, TimeFrame};
let mut handle = instance.run("NASDAQ:AAPL", TimeFrame::days(1));
while let Some(result) = handle.next_event().await {
match result? {
Event::ScriptInfo(info) => {
println!("脚本类型:{:?}", info.script_type);
}
Event::BarStart(bs) => {
println!("K 线 {} 开始,阶段为 {:?}", bs.bar_index, bs.bar_state);
}
Event::BarEnd => { /* K 线结束 */ }
Event::HistoryEnd => {
println!("历史回放完成,后续为实时 K 线");
}
Event::Log(log) => println!("[{:?}] {}", log.level, log.message),
Event::Alert(alert) => println!("ALERT: {}", alert.message),
Event::Draw(draw) => { /* 绘图数据,仅 Stream 模式 */ }
Event::Strategy(strat) => { /* 策略事件,仅 Stream 模式 */ }
_ => {}
}
}如果不需要事件流,使用便捷方法 run_to_end(),它会在内部消费完整个流并返回执行完成后的实例:
let instance = instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;
// 执行完毕后通过 instance.chart()、instance.strategy_report() 等读取结果重置状态
instance.reset() 丢弃所有运行时状态,将实例恢复到构建完成后的初始状态——就好像从未执行过任何 K 线——而无需重新编译脚本或重新创建对象。
let mut instance = instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;
// 重置:清除所有 K 线、图表、策略状态等
instance.reset();
// 用相同的脚本和数据源从第 0 根 K 线重新运行
let instance = instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;何时用 reset() vs. 新建 Instance
reset()— 适合在同一编译程序和数据源上反复运行(例如参数扫描、调整输入后重跑)。省去重新编译和 Arena 分配的开销。- 新建
Instance— 脚本源码、标的或周期变化时必须新建,或需要在构建时传入不同的输入值覆盖时使用。 - 快照 — 希望后续启动时跳过历史回放、保留已累积状态时使用。参见快照。
注意事项
通过构建器 .with_input_value() 设置的输入值覆盖在 build() 时写入 GC Arena,之后不再保存在实例上。reset() 后,输入会恢复为脚本中声明的默认值(例如 input.int(14, "Length") 恢复为 14)。