Rust Streaming API
Not yet released
The Rust API is not yet released. Estimated release: Q1 2027.
The streaming API is built for live feeds: open a stream once, then feed it one bar at a time. The stream carries its state from bar to bar, so each new bar costs O(1) — and every value is bit-identical to what the batch method (core.SMA, core.RSI, …) would return by recomputing over the whole slice.
Each streamable function adds two constructors on Core and a handful of methods on its stream:
| Call | When | Does |
|---|---|---|
core.<NAME>_Open(history, params) | once | validate params, consume warm-up history, return (stream, value) |
stream.update(bar) | once per closed bar | commit one bar, return the new value |
stream.peek(bar) | any time on the forming bar | evaluate a provisional bar without committing |
stream.out_range() | any time | the bars this stream has a value for — the batch range over the same bars |
Two more calls, OpenAndFill and update_and_fill, write array output instead of a single value — see Array-Fill Calls below.
There is no Close — dropping the stream closes it (RAII).
Example (SMA)
use ta_lib::Core;
let core = Core::new();
// Seed with warm-up history (>= SMA_Lookback(period) + 1 bars).
let history: Vec<f64> = /* ...your closing prices... */;
let (mut s, last) = core.SMA_Open(&history, 30)?; // stream + value at the last history bar
// Each time a bar closes:
let v = s.update(new_close)?; // Err only for a non-finite bar
// Intra-bar, on the not-yet-closed bar (repeat as the price ticks):
let provisional = s.peek(forming_close)?; // state left unchanged
// dropping `s` closes the streamOpen returns a Result — Err(RetCode::InsufficientHistory) if there is too little history (another bar fixes it, so this is the one worth retrying), Err(RetCode::BadParam) if a parameter is out of range. update and peek return a Result too, and after a successful Open the only thing they reject is a non-finite bar, leaving the handle exactly as it was.
One narrow exception to "the handle is unchanged": a composed indicator drives its sub-stages through their own public update, so a value the library computed internally is re-checked there. If such an intermediate overflowed to an infinity, the rejection would surface after earlier sub-stages had advanced, and would name the sub-stage. It needs input magnitudes around 1e306 and up — the overflow class TA-Lib already treats as out of scope — but the guarantee is stated for the caller-supplied case, which is the one you can provoke. update never allocates.
Rules
- Warm-up.
Opensucceeds only ifhistory.len() >= <NAME>_Lookback(params) + 1— with fewer bars there is no defined value yet. AfterOpen, the history can be dropped — the stream keeps everything it needs. - Closed vs forming bar.
updatecommits state irreversibly, so use it only for closed bars.peekreturns exactly the value the nextupdatewould, without committing; it runs the same transition on a copy. It takes&selfand never writes the handle, so peeks may run concurrently. Where copying the handle means several allocations, the copy is held per thread and reused — only the first peek of that indicator on that thread allocates. That scratch lives as long as the thread: one handle copy per indicator a thread has peeked, holding itsCoreand buffers, which dropping your own handles does not release. - Parameters are fixed at
Open. Changing a parameter means a new stream. Unstable period and candle settings are captured from the immutableCoreatOpenand cannot change during the stream's life. - Threads.
update(&mut self)makes the single-writer rule a compile-time guarantee — one exclusive writer per stream. Streams areSend + Sync + Clone; cloning forks an independent stream. - Don't persist a stream across library versions.
Multi-input / multi-output
Inputs and outputs mirror the batch method. Multi-output functions return a tuple in batch output order; candlestick patterns return i32:
// MACD: one input, three outputs
let (mut s, (macd, signal, hist)) = core.MACD_Open(&history, 12, 26, 9)?;
let (macd, signal, hist) = s.update(new_close)?;
// A candlestick pattern returns i32
let (mut s, _) = core.CDLDOJI_Open(&open, &high, &low, &close)?;
let pattern: i32 = s.update(o, h, l, c)?;Array-Fill Calls
Open and update each write a single value. Two more calls write a full slice instead — the same shape the batch method would produce — while still driving the stream:
| Call | When | Does |
|---|---|---|
core.<NAME>_OpenAndFill(..) | once, instead of Open | like Open, but also fills the output for every history bar, returning (stream, OutRange) |
stream.update_and_fill(bars, outs) | instead of a loop of update | commit n closed bars and write the n values |
OpenAndFill — Open gives you only the value at the last history bar. OpenAndFill also writes the output for every history bar — the same values the batch method would produce — while still returning the live stream, in one pass:
let mut warmup = vec![0.0; history.len()];
let (mut s, filled) = core.SMA_OpenAndFill(&history, 30, &mut warmup)?;
// warmup[..filled.count] is the SMA over all of history; then stream on:
let v = s.update(new_close)?;OpenAndFill takes the batch method's optional parameters and one slice per output, and returns the range it wrote as the same OutRange the batch method returns, beside the live stream. The output slices must not alias the input or each other.
update_and_fill — feeding a gap one update at a time works; update_and_fill does the same thing in one call, writing one value per bar into your slice:
let mut out = vec![0.0; gap.len()];
s.update_and_fill(&gap, &mut out)?; // out[i] is the SMA at gap[i]It is exactly gap.len() back-to-back update calls — same values, same state — with one set of argument checks instead of n. s.out_range() reports the bars the handle has a value for, before and after; there is no second return value for it.
That includes a call that fails partway. A non-finite bar returns Err(RetCode::BadParam) exactly as update does, which means the bars before it are already committed and their values already written; the range tells you how many. Err(RetCode::BadParam) before anything is committed if the input slices differ in length or an output is shorter than the bar count; a zero bar count is a successful no-op.
Discovering streamable functions
When driving TA-Lib through the abstraction layer, streamable functions carry the TA_FUNC_FLG_STREAM flag in their function info.