Skip to content

读取输出

执行 K 线后,你可以通过 instance.chart() 检查视觉输出,通过 instance.script_info() 检查脚本元数据。

有关完整的图表数据模型(所有字段、枚举和渲染算法),请参阅图表渲染

访问图形

图表包含序列图(逐 K 线数据)和图形(时间点绘图对象):

rust
let chart = instance.chart();

// 序列图:每根 K 线一个值
for (id, graph) in chart.series_graphs() {
    match graph {
        SeriesGraph::Plot(plot) => { /* 线条、面积、柱状图等 */ }
        SeriesGraph::PlotCandle(candle) => { /* OHLC K 线叠加 */ }
        SeriesGraph::PlotShape(shape) => { /* 形状标记 */ }
        SeriesGraph::PlotArrow(arrow) => { /* 箭头标记 */ }
        SeriesGraph::PlotBar(bar) => { /* OHLC 条形图 */ }
        SeriesGraph::PlotChar(ch) => { /* 字符标记 */ }
        SeriesGraph::BackgroundColors(bg) => { /* 背景颜色 */ }
        SeriesGraph::Fill(fill) => { /* 绘图之间的填充 */ }
    }
}

// 时间点图形
for (id, graph) in chart.graphs() {
    match graph {
        Graph::Label(label) => { /* 文本标注 */ }
        Graph::Line(line) => { /* 线段 */ }
        Graph::Box(bx) => { /* 矩形 */ }
        Graph::Table(table) => { /* 单元格网格 */ }
        Graph::Hline(hline) => { /* 水平线 */ }
        Graph::LineFill(fill) => { /* 两条线之间的填充 */ }
        Graph::Polyline(poly) => { /* 多点线 */ }
    }
}

你也可以通过 ID 查找单个图形:

rust
if let Some(sg) = chart.series_graph(id) {
    // ...
}
if let Some(g) = chart.graph(id) {
    // ...
}

脚本信息

检查已编译脚本的元数据:

rust
let info = instance.script_info();

match &info.script_type {
    ScriptType::Indicator(ind) => {
        println!("Indicator: {}", ind.title);
        println!("Overlay: {}", ind.overlay);
    }
    ScriptType::Strategy(strat) => {
        println!("Strategy: {}", strat.title);
        println!("Initial capital: {}", strat.initial_capital);
    }
    ScriptType::Library(lib) => {
        println!("Library: {}", lib.title);
    }
}

// 列出所有输入
for input in &info.inputs {
    // 每个输入变体有:id、title、tooltip、default_value 等
}

// 警报条件
for alert in &info.alert_conditions {
    // 警报条件定义
}

输出模式

Navi 支持两种输出模式:

Chart 模式(默认)

Chart 模式下,所有输出累积在 Chart 结构体中。执行完成后,通过 instance.chart()instance.strategy_report() 读取结果。完整的报告结构请参阅策略报告

适用场景:本地图表渲染、批量回测、一次性分析。

rust
let instance = instance.run_to_end(symbol, tf).await?;
let chart = instance.chart();
let report = instance.strategy_report(); // 策略脚本

Stream 模式

Stream 模式下,绘图指令和策略事件作为 Event 变体通过 instance.run() 返回的 RunHandle 发出。构建实例时使用 OutputMode::Stream

rust
let instance = Instance::builder(candles, source, tf, symbol)
    .with_output_mode(OutputMode::Stream)
    .build().await?;

适用场景:实时推送、远程客户端、事件驱动架构、仅需计算结果。

事件

事件在执行期间产生,通过 handle.next_event().await 逐个获取(handleinstance.run() 返回,并在执行期间持有 Instance)。

事件说明
ScriptInfo(Arc<ScriptInfo>)流开始时发出的脚本元数据(仅 Stream 模式),包含声明的序列、输入、告警和 overlay 设置。
Warning(Warnings)编译警告(在 ScriptInfo 之后、第一根 K 线之前发出一次,有警告时)。
BarStart(BarStartEvent)K 线开始。包含 bar_indexcandlestick(完整 OHLCV)。
BarEndK 线执行结束。
HistoryEnd历史与实时 K 线的分界标记。
Log(LogEvent)日志消息,含级别(Info、Warning、Error)和内容。
Alert(AlertEvent)警报,含可选 id 和消息。
Draw(DrawEvent)绘图指令(仅 Stream 模式)。包装 SeriesEventGraphEvent
Strategy(StrategyEvent)策略事件(仅 Stream 模式)。订单、成交、权益快照等。

BarStartBarEndHistoryEndLogAlertWarning 在两种模式下均发出。DrawStrategy 仅在 Stream 模式下发出。

绘图事件

DrawEvent 有四个变体:

  • DrawEvent::NewBar { bar_index } — 新 K 线开始的信号。每根新 K 线(历史或实时新开)发出一次,早于该 K 线的所有 Series 事件。ChartAccumulator 用此事件扩展内部系列缓冲区。
  • DrawEvent::Series(SeriesEvent) — 逐 K 线计算值,来自 plot()bg_color()fill()plot_arrow()plot_bar()plot_candle()plot_char()plot_shape()bar_color()。静态配置来自 Event::ScriptInfo,后续 SeriesEvent 只携带逐 bar 更新。
  • DrawEvent::Graph(GraphEvent) — 时间点绘图对象(labellineboxtablehlinelinefillpolyline)。Add* 携带完整结构体,Update* 使用属性级枚举,Remove 表示删除或淘汰。
  • DrawEvent::FilledOrder(FilledOrder) — 已执行的成交订单,包含 order_idpricequantity

SeriesEvent 变体

事件Navi 函数说明
UpdatePlotplot()线条、面积、柱状图等。携带 valuecolor。静态声明来自 ScriptInfo.series_declarations
UpdateBgColorbg_color()背景颜色带。携带 color。静态声明来自 ScriptInfo.series_declarations
UpdateFillfill()两个 plot 或 hline 之间的填充。携带 color(纯色或渐变)。静态声明来自 ScriptInfo.series_declarations
UpdatePlotArrowplot_arrow()箭头标记。携带 valueup_colordown_color。静态声明来自 ScriptInfo.series_declarations
UpdatePlotBarplot_bar()OHLC 条形图。携带 bar(OHLC)和 color。静态声明来自 ScriptInfo.series_declarations
UpdatePlotCandleplot_candle()OHLC K 线叠加。携带 barcolorwick_colorborder_color。静态声明来自 ScriptInfo.series_declarations
UpdatePlotCharplot_char()字符标记。携带 valuecolortext_color。静态声明来自 ScriptInfo.series_declarations
UpdatePlotShapeplot_shape()形状标记。携带 valuecolortext_color。静态声明来自 ScriptInfo.series_declarations
UpdateBarColorbar_color()K 线着色。携带 color。静态声明来自 ScriptInfo.bar_color_declaration

Event::ScriptInfo 携带脚本声明的全部 series_declarations(按从 0 开始的 series graph id 顺序排列),以及可选的 bar_color_declaration。在 stream 模式下,消费端可以先根据 ScriptInfo 建立声明索引,再把后续 Update* 事件视为按索引更新。

所有公开的 draw event id 现在都以 u64 序列化。对 series 事件来说,这个 u64 就是 series_declarations 中对应的从 0 开始的声明索引。

GraphEvent 变体

事件Navi 函数说明
AddHlinehline()水平线。创建后不可变。
AddLabel / UpdateLabellabel.new()文本标注。Update 使用 LabelAttr 枚举。
AddLine / UpdateLineline.new()线段。Update 使用 LineAttr 枚举。
AddBox / UpdateBoxbox.new()矩形。Update 使用 BoxAttr 枚举。
AddTable / UpdateTableAttr / UpdateTableCell / UpdateTableCellAttrtable.new()单元格网格。三种更新粒度:表级属性、整个单元格、单元格属性。
AddLineFill / UpdateLineFilllinefill.new()两条线之间的填充。
AddPolylinepolyline.new()多点线。创建后不可变。
Remove*.delete() / 淘汰表示删除或超限淘汰。

策略事件

事件说明
Config策略配置快照(初始资金、佣金、保证金等)。首根 K 线发出一次。
OrderSubmitted通过 strategy.entry()strategy.order() 提交订单。
OrderFilled订单成交。包含 iddirectionpricequantitycommission
OrderCancelled订单取消。
TradeOpened成交后新仓位开立。包含入场成交的 bar_indexbar_time(Unix 毫秒时间戳)。
TradeClosed仓位关闭。包含盈亏、最大浮盈、最大回撤、佣金,以及出场成交的 bar_indexbar_time(Unix 毫秒时间戳)。
OpenTradeUpdated逐 K 线更新持仓的盈亏和风险指标。
EquitySnapshot逐 K 线权益和买入持有权益。
DailyReturn每日回报率,用于 Sharpe/Sortino 计算。
MarginCall保证金追缴事件。
RiskFlatten风控触发平仓。

基本事件消费

rust
use navi_vm::{Event, LogLevel};

let mut handle = instance.run(symbol, tf);
while let Some(result) = handle.next_event().await {
    match result? {
        Event::Log(log) => {
            match log.level {
                LogLevel::Info => println!("INFO: {}", log.message),
                LogLevel::Warning => println!("WARN: {}", log.message),
                LogLevel::Error => println!("ERROR: {}", log.message),
            }
        }
        Event::Alert(alert) => {
            println!("ALERT [id={:?}]: {}", alert.id, alert.message);
        }
        Event::HistoryEnd => {
            println!("History replay complete");
        }
        _ => {}
    }
}

提取计算值(Stream 模式)

大多数集成场景里,更推荐直接使用 Instance::plot_rows()。这是专门为 stream 模式提取 plot() 值准备的迭代器,只暴露 plot 列。它要求 OutputMode::Stream,否则会 panic。

推荐:plot_rows()

rust
use navi_vm::{Instance, OutputMode};

let instance = Instance::builder(candles, source, tf, symbol)
    .with_output_mode(OutputMode::Stream)
    .build().await?;

let mut stream = instance.plot_rows(symbol, tf);
let columns = stream.columns().to_vec();
let mut rows = Vec::<Vec<Option<f64>>>::new();
while let Some(row) = stream.next_row().await {
    rows.push(row?.to_vec());
}

let macd_index = columns
    .iter()
    .position(|title| title.as_deref() == Some("MACD"));
let macd_values: Vec<Option<f64>> = macd_index
    .map(|index| rows.iter().map(|row| row[index]).collect())
    .unwrap_or_default();

手动处理 stream 事件

如果你需要更底层的控制,也可以继续手动处理事件流来提取 plot() 值:

rust
use navi_vm::{Event, OutputMode, DrawEvent};
use navi_vm::visuals::SeriesEvent;

let instance = Instance::builder(candles, source, tf, symbol)
    .with_output_mode(OutputMode::Stream)
    .build().await?;

let macd_id = instance.script_info().series_id_by_title("MACD");

let mut macd_values: Vec<Option<f64>> = Vec::new();
let mut new_bar = false;

let mut handle = instance.run(symbol, tf);
while let Some(result) = handle.next_event().await {
    match result? {
        Event::Draw(DrawEvent::NewBar { .. }) => {
            new_bar = true;
        }
        Event::Draw(DrawEvent::Series(SeriesEvent::UpdatePlot { id, value, .. })) => {
            if macd_id == Some(id) {
                if new_bar {
                    // 新 K 线:追加槽位
                    macd_values.push(value);
                    new_bar = false;
                } else {
                    // 同一根 K 线的实时重算——替换最后一个值
                    if let Some(last) = macd_values.last_mut() {
                        *last = value;
                    }
                }
            }
        }
        _ => {}
    }
}

这种写法会先通过 script_info() 解析一次 series id,再在整个 stream 生命周期里复用,开销更低。

从事件重建 Chart

使用 ChartAccumulator 从事件流重建完整的 Chart。通过 TryFrom 将事件转为 ChartEvent——处理 ScriptInfoHistoryEndNewBarSeriesGraphFilledOrder。非图表事件(如 LogAlertStrategy)返回 Err,可单独处理:

rust
use navi_vm::{Event, visuals::{ChartAccumulator, ChartEvent}};

let mut acc = ChartAccumulator::new();
let mut handle = instance.run(symbol, tf);
while let Some(result) = handle.next_event().await {
    match ChartEvent::try_from(result?) {
        Ok(chart_event) => acc.apply(chart_event),
        Err(_other) => { /* Log、Alert、Strategy、Warning、BarStart、BarEnd */ }
    }
}
let chart = acc.into_chart();

从事件重建策略报告

使用 StrategyAccumulator 从策略事件重建 StrategyReport。报告的完整结构和所有可用字段请参阅策略报告

rust
use navi_vm::{Event, StrategyAccumulator};

let mut acc = StrategyAccumulator::new();
let mut handle = instance.run(symbol, tf);
while let Some(result) = handle.next_event().await {
    if let Event::Strategy(e) = result? {
        acc.apply(e);
    }
}
let report = acc.report();

如不需要逐个处理事件,使用 run_to_end()

rust
let instance = instance.run_to_end(symbol, tf).await?;
// 事件被丢弃;通过 instance.chart() 等读取结果

完整示例

rust
use navi_vm::{Candlestick, Event, Instance, TimeFrame};

async fn run(candles: Vec<Candlestick>) -> Result<(), navi_vm::Error> {
    let source = r#"
indicator("RSI Monitor")
rsi = ta.rsi(close, 14)
plot(rsi, "RSI")
hline(70, "Overbought")
hline(30, "Oversold")
alert_condition(ta.crossover(rsi, 70), "RSI Overbought")
"#;

    let tf = TimeFrame::days(1);
    let symbol = "NASDAQ:AAPL";
    let instance = Instance::builder(candles, source, tf, symbol)
        .build().await?;

    // 消费事件流
    let mut handle = instance.run(symbol, tf);
    while let Some(result) = handle.next_event().await {
        if let Event::Alert(alert) = result? {
            println!("Alert: {}", alert.message);
        }
    }
    let instance = handle.into_instance();

    // 打印最终 RSI 值
    for (_id, graph) in instance.chart().series_graphs() {
        if let Some(plot) = graph.as_plot() {
            if plot.title.as_deref() == Some("RSI") {
                if let Some(last) = plot.series.last() {
                    println!("Final RSI: {:?}", last);
                }
            }
        }
    }

    Ok(())
}

下一步

基于 MIT 许可证发布。