Skip to content

Reading Outputs

After executing bars, you can inspect the visual outputs via instance.chart() and script metadata via instance.script_info().

For the complete chart data model — every field, enum, and rendering algorithm — see Chart Rendering.

Accessing Graphs

The chart contains series graphs (per-bar data) and graphs (point-in-time drawing objects):

rust
let chart = instance.chart();

// Series graphs: one value per bar
for (id, graph) in chart.series_graphs() {
    match graph {
        SeriesGraph::Plot(plot) => { /* line, area, histogram, etc. */ }
        SeriesGraph::PlotCandle(candle) => { /* OHLC candle overlay */ }
        SeriesGraph::PlotShape(shape) => { /* shape markers */ }
        SeriesGraph::PlotArrow(arrow) => { /* arrow markers */ }
        SeriesGraph::PlotBar(bar) => { /* OHLC bar chart */ }
        SeriesGraph::PlotChar(ch) => { /* character markers */ }
        SeriesGraph::BackgroundColors(bg) => { /* background colors */ }
        SeriesGraph::Fill(fill) => { /* fill between plots */ }
    }
}

// Point-in-time graphs
for (id, graph) in chart.graphs() {
    match graph {
        Graph::Label(label) => { /* text annotation */ }
        Graph::Line(line) => { /* line segment */ }
        Graph::Box(bx) => { /* rectangle */ }
        Graph::Table(table) => { /* cell grid */ }
        Graph::Hline(hline) => { /* horizontal line */ }
        Graph::LineFill(fill) => { /* fill between two lines */ }
        Graph::Polyline(poly) => { /* multi-point line */ }
    }
}

You can also look up individual graphs by ID:

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

Script Info

Inspect the compiled script's metadata:

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);
    }
}

// List all inputs
for input in &info.inputs {
    // Each input variant has: id, title, tooltip, default_value, etc.
}

// Alert conditions
for alert in &info.alert_conditions {
    // Alert condition definitions
}

Output Modes

Navi supports two output modes:

Chart Mode (Default)

In Chart mode, all outputs are accumulated in the Chart struct. After execution, read results via instance.chart() and instance.strategy_report(). See Strategy Report for the full report structure.

Best for: local chart rendering, batch backtesting, one-shot analysis.

rust
let instance = instance.run_to_end(symbol, tf).await?;
let chart = instance.chart();
let report = instance.strategy_report(); // if strategy script

Stream Mode

In Stream mode, drawing instructions and strategy events are emitted as Event variants through the RunHandle returned by instance.run(). Use OutputMode::Stream when building the instance:

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

Best for: real-time push, remote clients, event-driven architectures, extracting only computed values.

Events

Events are produced during execution and yielded by the RunHandle returned from instance.run().

EventDescription
ScriptInfo(Arc<ScriptInfo>)Script metadata emitted once at stream start, before any bar (Stream mode only). Contains declared series, inputs, alerts, and overlay settings.
Warning(Warnings)Compilation warnings, emitted once after ScriptInfo and before any bar (when present).
BarStart(BarStartEvent)Start of a bar. Contains bar_index and candlestick (full OHLCV).
BarEndEnd of a bar's execution.
HistoryEndBoundary between historical and real-time bars.
Log(LogEvent)A log message with level (Info, Warning, Error) and message.
Alert(AlertEvent)An alert with optional id and message.
Draw(DrawEvent)A drawing instruction (Stream mode only). Wraps SeriesEvent or GraphEvent.
Strategy(StrategyEvent)A strategy event (Stream mode only). Orders, trades, equity snapshots.

BarStart, BarEnd, HistoryEnd, Log, Alert, and Warning are emitted in both modes. Draw and Strategy are only emitted in Stream mode.

Draw Events

DrawEvent has four variants:

  • DrawEvent::NewBar { bar_index } — signals the start of a new bar. Emitted once per bar (confirmed or realtime-new), before any Series events for that bar. ChartAccumulator uses this to grow its internal series buffers.
  • DrawEvent::Series(SeriesEvent) — per-bar computed values from plot(), bg_color(), fill(), plot_arrow(), plot_bar(), plot_candle(), plot_char(), plot_shape(), bar_color(). Static config comes from Event::ScriptInfo; subsequent series events only carry per-bar updates.
  • DrawEvent::Graph(GraphEvent) — point-in-time drawing objects (label, line, box, table, hline, linefill, polyline). Add* carries the full struct, Update* uses attribute-level enums, and Remove signals deletion or eviction.
  • DrawEvent::FilledOrder(FilledOrder) — an executed order fill with order_id, price, and quantity.

SeriesEvent Variants

EventNavi functionDescription
UpdatePlotplot()Line, area, histogram, etc. Carries value and color. Static metadata comes from ScriptInfo.series_declarations.
UpdateBgColorbg_color()Background color band. Carries color. Static metadata comes from ScriptInfo.series_declarations.
UpdateFillfill()Fill between two plots or hlines. Carries color (solid or gradient). Static metadata comes from ScriptInfo.series_declarations.
UpdatePlotArrowplot_arrow()Arrow markers. Carries value, up_color, down_color. Static metadata comes from ScriptInfo.series_declarations.
UpdatePlotBarplot_bar()OHLC bar chart. Carries bar (OHLC) and color. Static metadata comes from ScriptInfo.series_declarations.
UpdatePlotCandleplot_candle()OHLC candle overlay. Carries bar, color, wick_color, border_color. Static metadata comes from ScriptInfo.series_declarations.
UpdatePlotCharplot_char()Character markers. Carries value, color, text_color. Static metadata comes from ScriptInfo.series_declarations.
UpdatePlotShapeplot_shape()Shape markers. Carries value, color, text_color. Static metadata comes from ScriptInfo.series_declarations.
UpdateBarColorbar_color()Bar coloring. Carries color. Static metadata comes from ScriptInfo.bar_color_declaration.

Event::ScriptInfo carries series_declarations for all series graphs declared by the script, in zero-based id order, plus an optional bar_color_declaration. In stream mode, consumers can build their declaration lookup from ScriptInfo first, then treat later Update* events as direct indexed updates.

All public draw-event ids are serialized as u64. For series events, that u64 is the zero-based declaration index in series_declarations.

GraphEvent Variants

EventNavi functionDescription
AddHlinehline()Horizontal line. Immutable after creation.
AddLabel / UpdateLabellabel.new()Text annotation. Update uses LabelAttr enum.
AddLine / UpdateLineline.new()Line segment. Update uses LineAttr enum.
AddBox / UpdateBoxbox.new()Rectangle. Update uses BoxAttr enum.
AddTable / UpdateTableAttr / UpdateTableCell / UpdateTableCellAttrtable.new()Cell grid. Three update granularities: table-level, whole cell, or cell attribute.
AddLineFill / UpdateLineFilllinefill.new()Fill between two lines.
AddPolylinepolyline.new()Multi-point line. Immutable after creation.
Remove*.delete() / evictionSignals deletion or max-object eviction.

Strategy Events

EventDescription
ConfigStrategy configuration snapshot (initial capital, commission, margin, etc.). Emitted once on the first bar.
OrderSubmittedAn order is submitted via strategy.entry() or strategy.order().
OrderFilledAn order is filled. Contains id, direction, price, quantity, commission.
OrderCancelledAn order is cancelled.
TradeOpenedA new position is opened after a fill. Contains bar_index and bar_time (epoch ms) of the entry fill.
TradeClosedA position is closed. Contains P&L, runup, drawdown, commission, plus bar_index and bar_time (epoch ms) of the exit fill.
OpenTradeUpdatedPer-bar update of an open position's P&L and risk metrics.
EquitySnapshotPer-bar equity and buy-hold equity values.
DailyReturnDaily return percentage for Sharpe/Sortino calculation.
MarginCallMargin call event.
RiskFlattenRisk-triggered position flattening.

Basic Event Consumption

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");
        }
        _ => {}
    }
}

Extracting Computed Values (Stream Mode)

For most integrations, prefer Instance::plot_rows(). It is the dedicated helper for extracting plot() values in stream mode, and keeps the API aligned to plot columns only. It requires OutputMode::Stream and will panic otherwise.

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();

Manual stream handling

If you need lower-level control, you can still extract plot() values directly from the event stream:

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 {
                    // New bar: append a slot
                    macd_values.push(value);
                    new_bar = false;
                } else {
                    // Realtime re-execution of the same bar — replace the last value
                    if let Some(last) = macd_values.last_mut() {
                        *last = value;
                    }
                }
            }
        }
        _ => {}
    }
}

This pattern resolves the series id once from script_info() and reuses it for the whole stream.

Rebuilding Chart from Events

Use ChartAccumulator to reconstruct a full Chart from the event stream. Convert each event to ChartEvent with TryFrom — it handles ScriptInfo, HistoryEnd, NewBar, Series, Graph, and FilledOrder. Non-chart events (like Log, Alert, Strategy) return Err and can be handled separately:

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();

Rebuilding Strategy Report from Events

Use StrategyAccumulator to reconstruct a StrategyReport from strategy events. For the complete report structure and all available fields, see Strategy Report.

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();

If you don't need to process events individually, use run_to_end():

rust
let instance = instance.run_to_end(symbol, tf).await?;
// Events are discarded; read results from instance.chart(), etc.

Complete Example

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?;

    // Consume the event stream
    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();

    // Print final RSI values
    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(())
}

Next Steps

Released under the MIT License.