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):
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:
if let Some(sg) = chart.series_graph(id) {
// ...
}
if let Some(g) = chart.graph(id) {
// ...
}Script Info
Inspect the compiled script's metadata:
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.
let instance = instance.run_to_end(symbol, tf).await?;
let chart = instance.chart();
let report = instance.strategy_report(); // if strategy scriptStream 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:
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().
| Event | Description |
|---|---|
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). |
BarEnd | End of a bar's execution. |
HistoryEnd | Boundary 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 anySeriesevents for that bar.ChartAccumulatoruses this to grow its internal series buffers.DrawEvent::Series(SeriesEvent)— per-bar computed values fromplot(),bg_color(),fill(),plot_arrow(),plot_bar(),plot_candle(),plot_char(),plot_shape(),bar_color(). Static config comes fromEvent::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, andRemovesignals deletion or eviction.DrawEvent::FilledOrder(FilledOrder)— an executed order fill withorder_id,price, andquantity.
SeriesEvent Variants
| Event | Navi function | Description |
|---|---|---|
UpdatePlot | plot() | Line, area, histogram, etc. Carries value and color. Static metadata comes from ScriptInfo.series_declarations. |
UpdateBgColor | bg_color() | Background color band. Carries color. Static metadata comes from ScriptInfo.series_declarations. |
UpdateFill | fill() | Fill between two plots or hlines. Carries color (solid or gradient). Static metadata comes from ScriptInfo.series_declarations. |
UpdatePlotArrow | plot_arrow() | Arrow markers. Carries value, up_color, down_color. Static metadata comes from ScriptInfo.series_declarations. |
UpdatePlotBar | plot_bar() | OHLC bar chart. Carries bar (OHLC) and color. Static metadata comes from ScriptInfo.series_declarations. |
UpdatePlotCandle | plot_candle() | OHLC candle overlay. Carries bar, color, wick_color, border_color. Static metadata comes from ScriptInfo.series_declarations. |
UpdatePlotChar | plot_char() | Character markers. Carries value, color, text_color. Static metadata comes from ScriptInfo.series_declarations. |
UpdatePlotShape | plot_shape() | Shape markers. Carries value, color, text_color. Static metadata comes from ScriptInfo.series_declarations. |
UpdateBarColor | bar_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
| Event | Navi function | Description |
|---|---|---|
AddHline | hline() | Horizontal line. Immutable after creation. |
AddLabel / UpdateLabel | label.new() | Text annotation. Update uses LabelAttr enum. |
AddLine / UpdateLine | line.new() | Line segment. Update uses LineAttr enum. |
AddBox / UpdateBox | box.new() | Rectangle. Update uses BoxAttr enum. |
AddTable / UpdateTableAttr / UpdateTableCell / UpdateTableCellAttr | table.new() | Cell grid. Three update granularities: table-level, whole cell, or cell attribute. |
AddLineFill / UpdateLineFill | linefill.new() | Fill between two lines. |
AddPolyline | polyline.new() | Multi-point line. Immutable after creation. |
Remove | *.delete() / eviction | Signals deletion or max-object eviction. |
Strategy Events
| Event | Description |
|---|---|
Config | Strategy configuration snapshot (initial capital, commission, margin, etc.). Emitted once on the first bar. |
OrderSubmitted | An order is submitted via strategy.entry() or strategy.order(). |
OrderFilled | An order is filled. Contains id, direction, price, quantity, commission. |
OrderCancelled | An order is cancelled. |
TradeOpened | A new position is opened after a fill. Contains bar_index and bar_time (epoch ms) of the entry fill. |
TradeClosed | A position is closed. Contains P&L, runup, drawdown, commission, plus bar_index and bar_time (epoch ms) of the exit fill. |
OpenTradeUpdated | Per-bar update of an open position's P&L and risk metrics. |
EquitySnapshot | Per-bar equity and buy-hold equity values. |
DailyReturn | Daily return percentage for Sharpe/Sortino calculation. |
MarginCall | Margin call event. |
RiskFlatten | Risk-triggered position flattening. |
Basic Event Consumption
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.
Recommended: plot_rows()
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:
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:
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.
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():
let instance = instance.run_to_end(symbol, tf).await?;
// Events are discarded; read results from instance.chart(), etc.Complete Example
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
- Chart Rendering — complete chart data model and rendering guide
- Error Handling — handling compile and runtime errors