Skip to content

执行脚本

Candlestick

Candlestick 结构体表示一根 OHLCV K 线:

rust
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 还提供派生价格计算:

rust
candle.hl2();    // (high + low) / 2
candle.hlc3();   // (high + low + close) / 3
candle.ohlc4();  // (open + high + low + close) / 4
candle.hlcc4();  // (high + low + close + close) / 4

TimeFrame

TimeFrame 指定图表周期。可以使用便捷方法创建或从字符串解析:

rust
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,否则使用内置默认值)。

支持的市场前缀:NYSENASDAQSHSESZSEHKEXSGX

如需提供自定义标的元数据(描述、货币、最小变动单位等),实现 DataProvider::symbol_info 并返回 PartialSymbolInfo。详情参见实例构建器

提供数据

历史回测

直接将 Vec<Candlestick> 传给 Instance::builder(),调用 run_to_end() 时 VM 会主动从中拉取数据:

rust
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

rust
// 从迭代器收集
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::HistoryEndHistoryEnd 之后的 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_indexcandlestick(完整 OHLCV)以及 bar_stateHistoryRealtimeNewRealtimeUpdateRealtimeConfirmed)。
BarEnd每根 K 线执行完成后(策略处理、脚本体等)。始终与前一个 BarStart 配对。
HistoryEnd仅一次,当 DataProvider yield CandlestickItem::HistoryEnd 时。标记从历史回放到实时数据的转换。不跟随 BarStart
Log(LogEvent)K 线执行期间,脚本调用 log.info()log.warning()log.error() 时。在 BarStartBarEnd 之间发出。
Alert(AlertEvent)K 线执行期间,脚本调用 alert()alert_condition() 触发时。在 BarStartBarEnd 之间发出。
Draw(DrawEvent)OutputMode::Stream 模式下。包装 SeriesEventGraphEventFilledOrder。详见读取输出
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)

用法

rust
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(),它会在内部消费完整个流并返回执行完成后的实例:

rust
let instance = instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;
// 执行完毕后通过 instance.chart()、instance.strategy_report() 等读取结果

重置状态

instance.reset() 丢弃所有运行时状态,将实例恢复到构建完成后的初始状态——就好像从未执行过任何 K 线——而无需重新编译脚本或重新创建对象。

rust
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)。

下一步

基于 MIT 许可证发布。