Skip to content

Executing Scripts

Candlestick

The Candlestick struct represents one OHLCV bar:

rust
use openpine_vm::{Candlestick, market::TradeSession};

let candle = Candlestick::new(
    1700000000000,          // time: epoch milliseconds
    150.0,                  // open
    155.0,                  // high
    149.0,                  // low
    153.0,                  // close
    1_000_000.0,            // volume
    0.0,                    // turnover
    TradeSession::Regular,  // trade session
);

Candlestick also provides derived price calculations:

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 specifies the chart period. It can be created using convenience methods or parsed from a string:

rust
use openpine_vm::TimeFrame;

// Convenience constructors
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);

// Parse from string (TradingView format)
let tf: TimeFrame = "D".parse().unwrap();    // 1 day
let tf: TimeFrame = "5".parse().unwrap();    // 5 minutes
let tf: TimeFrame = "60".parse().unwrap();   // 60 minutes (1 hour)
let tf: TimeFrame = "W".parse().unwrap();    // 1 week
let tf: TimeFrame = "3M".parse().unwrap();   // 3 months

// Inspect
let seconds = tf.in_seconds();  // Option<u64>
let display = tf.to_string();   // "D", "5", "W", etc.

SymbolInfo

SymbolInfo carries metadata about the trading instrument. You no longer construct it manually — just pass the symbol string to Instance::builder and the builder resolves the info automatically (from DataProvider::symbol_info if a provider is configured, otherwise from built-in defaults).

Supported market prefixes: NYSE, NASDAQ, SHSE, SZSE, HKEX, SGX.

To supply custom symbol metadata (description, currency, min tick, etc.), implement DataProvider::symbol_info and return a PartialSymbolInfo. See Instance Builder for details.

Feeding Data

Historical Backtest

Pass a Vec<Candlestick> directly to Instance::builder(), then call instance.run_to_end():

rust
use openpine_vm::TimeFrame;

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

instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;

For lazy or streaming data, collect into a Vec<Candlestick> first, or implement a custom DataProvider:

rust
// Collect from an iterator
let bars: Vec<Candlestick> = load_bars_from_db().collect();
let mut instance = Instance::builder(bars, source, timeframe, "NASDAQ:AAPL")
    .build().await?;

Live Streaming

For live data, implement DataProvider and yield CandlestickItem::Confirmed(candle) for closed bars and CandlestickItem::Realtime(candle) for the current forming bar. Emit CandlestickItem::HistoryEnd after all historical bars to signal that live data follows. The VM automatically handles rollback between realtime updates and confirms the bar when the stream ends.

Event Stream

Instance::run() returns a Stream<Item = Result<Event, Error>> that yields events as the script executes bar by bar.

Event Types

EventWhen emitted
BarStart(BarStartEvent)Before each bar's execution begins. Contains bar_index, timestamp (epoch ms), and bar_state (see below).
BarEndAfter each bar's execution completes (strategy processing, script body, etc.). Always paired with a preceding BarStart.
HistoryEndOnce, when the DataProvider yields CandlestickItem::HistoryEnd. Signals the transition from historical replay to live data. Not preceded by BarStart.
Log(LogEvent)During bar execution, when the script calls log.info(), log.warning(), or log.error(). Emitted between BarStart and BarEnd.
Alert(AlertEvent)During bar execution, when the script calls alert() or an alertcondition() triggers. Emitted between BarStart and BarEnd.
Draw(DrawEvent)During bar execution in OutputMode::Stream only, when the script calls plot(). AddPlot is emitted once on the first bar; UpdatePlot is emitted on every bar.

Bar States

BarStartEvent.bar_state tells you why this bar is being executed:

StateMeaning
HistoryA closed historical bar from CandlestickItem::Confirmed. Each bar executes exactly once. bar_index advances after each bar. This is the state during the initial backtest replay.
RealtimeNewThe first tick of a new real-time bar. Either the very first CandlestickItem::Realtime after HistoryEnd, or a Realtime whose timestamp is later than the previous one (which implicitly confirms the previous bar first). bar_index does not advance until the bar is confirmed.
RealtimeUpdateA subsequent tick for the same real-time bar (same timestamp as the previous Realtime). The VM rolls back variable state to the bar's initial snapshot, then re-executes the script with updated OHLCV data. Same bar_index as the preceding RealtimeNew.
RealtimeConfirmedThe current real-time bar is being confirmed (closed). This happens in two cases: (1) a new Realtime tick arrives with a later timestamp — the VM auto-confirms the previous bar before opening the new one; (2) the data stream ends while a real-time bar is still open — the VM confirms it as the final step. After confirmation, bar_index advances.

Event Ordering

For a typical historical-then-realtime session, the stream yields:

BarStart(0, History)  → Log/Alert/Draw... → BarEnd
BarStart(1, History)  → Log/Alert/Draw... → BarEnd
...
HistoryEnd
BarStart(N, RealtimeNew)       → ... → BarEnd    ← first tick of bar N
BarStart(N, RealtimeUpdate)    → ... → BarEnd    ← same-timestamp tick update
BarStart(N, RealtimeConfirmed) → ... → BarEnd    ← bar N confirmed (new timestamp arrived)
BarStart(N+1, RealtimeNew)     → ... → BarEnd    ← first tick of bar N+1
BarStart(N+1, RealtimeConfirmed) → ... → BarEnd  ← stream ended, final confirm

Usage

rust
use std::pin::pin;
use futures_util::StreamExt;
use openpine_vm::{Event, Instance, TimeFrame};

let mut stream = pin!(instance.run("NASDAQ:AAPL", TimeFrame::days(1)));
while let Some(result) = stream.next().await {
    match result? {
        Event::BarStart(bs) => {
            println!("bar {} started (state={:?})", bs.bar_index, bs.bar_state);
        }
        Event::BarEnd => { /* bar finished */ }
        Event::HistoryEnd => {
            println!("history replay complete, live bars follow");
        }
        Event::Log(log) => println!("[{:?}] {}", log.level, log.message),
        Event::Alert(alert) => println!("ALERT: {}", alert.message),
        Event::Draw(draw) => { /* plot data, only in Stream mode */ }
    }
}

If you don't need the event stream, use the convenience method run_to_end() which consumes the stream internally:

rust
instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await?;
// Results available via instance.chart(), instance.strategy_report(), etc.

Output Mode

OutputMode controls how plot() data is produced. Set it on the builder:

rust
use openpine_vm::{Instance, OutputMode, TimeFrame};

let mut instance = Instance::builder(provider, source, TimeFrame::days(1), "NASDAQ:AAPL")
    .with_output_mode(OutputMode::Stream)
    .build().await?;
Modeplot() behaviorUse case
OutputMode::Chart (default)Writes to instance.chart()Read the full chart after execution
OutputMode::StreamEmits DrawEvent in the event streamStream plot data without building a chart

Stream Mode Example

In OutputMode::Stream, each plot() call emits events instead of writing to the chart:

rust
use std::pin::pin;
use futures_util::StreamExt;
use openpine_vm::{DrawEvent, Event, Instance, OutputMode, TimeFrame};

let mut instance = Instance::builder(bars, source, TimeFrame::days(1), "NASDAQ:AAPL")
    .with_output_mode(OutputMode::Stream)
    .build().await?;

let mut stream = pin!(instance.run("NASDAQ:AAPL", TimeFrame::days(1)));
while let Some(result) = stream.next().await {
    match result? {
        Event::Draw(DrawEvent::AddPlot { id, title }) => {
            println!("new plot: id={}, title={:?}", id, title);
        }
        Event::Draw(DrawEvent::UpdatePlot { id, bar_index, timestamp, value }) => {
            println!("plot {} bar {}: {:?}", id, bar_index, value);
        }
        _ => {}
    }
}

BarStart, BarEnd, HistoryEnd, Log, and Alert events are emitted in both modes. Only Draw events are affected by the output mode.

Next Steps

Released under the MIT License.