Features
Streaming indicators
Update indicators bar by bar with stateful accumulators that match batch results
Can an indicator update with each new bar without recomputing its whole history? VBT computes streaming indicators in Python with accumulators: small functions that take the previous state and one new value and return the next state. They use the same formulas as the batch versions, so you can match batch results with the same inputs, settings, and initialization, without recomputing the full history on each update.
close = vbt.YFData.pull("BTC-USD", start="2024-01-01", end="2025-01-01").close
span = 20
alpha = 2 / (span + 1)
old_wt, weighted_avg, nobs = 1.0, np.nan, 0
streamed = []
for i, price in enumerate(close.values):
if i == 0:
weighted_avg = price
state = vbt.nb.ewm_mean_acc_nb(
vbt.nb.EWMMeanAIS(i, price, old_wt, weighted_avg, nobs, alpha, 1, True)
)
old_wt, weighted_avg, nobs = state.old_wt, state.weighted_avg, state.nobs
streamed.append(state.value)
batch = close.vbt.ewm_mean(span, minp=1)
print(np.allclose(streamed, batch), round(streamed[-1], 2), round(batch.iloc[-1], 2))True 96421.8 96421.8The streamed values match the batch EMA on every bar. Keeping the state instead of the history is what makes the update cost constant:
import timeit
in_state = vbt.nb.EWMMeanAIS(1, 100.0, 1.0, 100.0, 1, alpha, 1, True)
rows = {}
for n in [1_000, 100_000]:
arr = np.random.default_rng(42).normal(size=n).cumsum() + 1000
batch_us = min(timeit.repeat(
lambda: vbt.nb.ewm_mean_1d_nb(arr, span, 1, True), number=100, repeat=5
)) / 100 * 1e6
stream_us = min(timeit.repeat(
lambda: vbt.nb.ewm_mean_acc_nb(in_state), number=10000, repeat=5
)) / 10000 * 1e6
rows[n] = {
"recompute history (µs)": round(batch_us, 1),
"one streaming update (µs)": round(stream_us, 1),
}
pd.DataFrame(rows).T recompute history (µs) one streaming update (µs)
1000 4.9 0.8
100000 450.8 0.9Measured on an Apple M3. Recomputing grows with the history, while a streaming update stays under a microsecond, most of it Python's call overhead.
Where streaming indicators help
| Research task | How accumulators help |
|---|---|
| Update a signal as each completed bar arrives | Calculate the next value from the retained state |
| Follow many symbols or parameter settings | Keep a separate state for each calculation instead of rerunning every history |
| Calculate indicators inside a simulation callback | Update the indicator alongside the strategy's bar-by-bar decisions |
| Process history in successive batches | Carry the state across batches so calculations continue from the same starting point |
You supply observations from your data workflow or simulation. For example, feed each completed bar into an EMA and RSI, combine their outputs into an entry condition, and pass that condition to your strategy. Data pipelines covers collecting and updating the data, while Live simulation covers advancing a portfolio as observations arrive.
Streaming state
Every streaming indicator is defined by its state. For an EMA it is the running average and its weight, for a rolling mean the running sum and the values about to leave the window, and for RSI the average gains and losses. Save the state, and the indicator can continue later, in another process, or after new data arrives, without replaying the full history it was computed from. Keep the settings and current position with that state. For rolling calculations in Numba, also retain the recent values needed as observations leave the window. Rust accumulators hold those buffers internally.
Each symbol and parameter setting has its own state. This lets you track, for example, a fast and slow average on several assets without mixing their histories.
A streaming indicator still needs a warm-up. Rolling windows return NaN until they have enough
observations, and exponential averages depend on where they start: an EMA started a year ago and one
started yesterday give different values today. Start streaming from the same point as the batch
calculation with the same settings when the two must agree. Feeding earlier bars first also lets you
warm up the indicator before the period you want to trade or evaluate, as described in
Walk-forward and cross-validation.
Accumulator building blocks
Accumulators exist for rolling sums, means, standard deviations, z-scores, minimums and maximums, exponential means and standard deviations, crossovers, and indicators such as RSI, ADX, on-balance volume, and SuperTrend. Because each one works with the current inputs and its retained state or window values, several can be combined in a single pass over the data, which is how complex indicators are built from simple ones. The Accumulators highlight below combines two of them into a one-pass rolling z-score.
Streaming keeps each update focused on the new observation, making accumulators especially useful for long histories, many symbols, and calculations inside a simulation loop. Combine them with batch calculations for the rest of your indicators. The EMA benchmark above shows both approaches on the same data.
Python, Numba, and Rust
The same accumulators run in a Python loop, inside a Numba-compiled loop or simulation callback, and in Rust. The Rust versions keep their own rolling buffers, so a Rust program only feeds in new values, as the Streaming indicators highlight below shows. That makes it possible to prototype in Python and move the same logic into a compiled live system without changing the math.
Rust accumulators can also be cloned to branch from the same warmed-up state. Feed different future
observations into each copy to compare scenarios without replaying the shared history. Use reset()
to start a fresh calculation with the same configuration. These are useful building blocks for
custom indicators and native research programs, as
well as live calculations.
Tutorial
The members-only Superfast SuperTrend tutorial builds a streaming SuperTrend from accumulators and compares it with the batch version.
✅ Keep your indicators moving with the market. VBT's Numba and Rust accumulators update one observation at a time, using the same formulas as batch execution. Rust accumulators even manage their own rolling buffers, so you can feed in new data and get straight to the next value.
prev_close = np.nan
obv = 0.0
for close, volume in [(100.0, 10.0), (102.0, 20.0), (101.0, 5.0)]:
out = vbt.ind_nb.obv_acc_nb(
vbt.ind_enums.OBVAIS(close, prev_close, volume, obv)
)
prev_close, obv = close, out.cumsum
print(out.value)10.0
30.0
25.0Tutorial
Learn more in the Live simulation tutorial.
✅ Most rolling indicators implemented with Pandas and NumPy require multiple passes over the data. For example, calculating the sum of three arrays requires at least two passes. If you want to calculate such an indicator iteratively (bar by bar), you either need to pre-calculate everything and store it in memory or re-calculate each window, which can significantly impact performance. Accumulators, however, maintain an internal state that allows you to compute an indicator value as soon as a new data point arrives, resulting in the best possible performance.
@njit
def fastest_rolling_zscore_1d_nb(arr, window, minp=None, ddof=1):
if minp is None:
minp = window
out = np.full(arr.shape, np.nan)
cumsum = 0.0
cumsum_sq = 0.0
nancnt = 0
for i in range(len(arr)):
pre_window_value = arr[i - window] if i - window >= 0 else np.nan
mean_in_state = vbt.nb.RollMeanAIS(
i, arr[i], pre_window_value, cumsum, nancnt, window, minp
)
mean_out_state = vbt.nb.rolling_mean_acc_nb(mean_in_state)
_, _, _, mean = mean_out_state
std_in_state = vbt.nb.RollStdAIS(
i, arr[i], pre_window_value, cumsum, cumsum_sq, nancnt, window, minp, ddof
)
std_out_state = vbt.nb.rolling_std_acc_nb(std_in_state)
cumsum, cumsum_sq, nancnt, _, std = std_out_state
out[i] = (arr[i] - mean) / std
return out
data = vbt.YFData.pull("BTC-USD")
rolling_zscore = fastest_rolling_zscore_1d_nb(data.returns.values, 14)
data.symbol_wrapper.wrap(rolling_zscore)Date
2014-09-17 00:00:00+00:00 NaN
2014-09-18 00:00:00+00:00 NaN
2014-09-19 00:00:00+00:00 NaN
...
2023-02-01 00:00:00+00:00 0.582381
2023-02-02 00:00:00+00:00 -0.705441
2023-02-03 00:00:00+00:00 -0.217880
Freq: D, Name: BTC-USD, Length: 3062, dtype: float64(data.returns - data.returns.rolling(14).mean()) / data.returns.rolling(14).std()Date
2014-09-17 00:00:00+00:00 NaN
2014-09-18 00:00:00+00:00 NaN
2014-09-19 00:00:00+00:00 NaN
...
2023-02-01 00:00:00+00:00 0.582381
2023-02-02 00:00:00+00:00 -0.705441
2023-02-03 00:00:00+00:00 -0.217880
Freq: D, Name: Close, Length: 3062, dtype: float64Related pages
- Technical indicatorsRun TA-Lib, pandas-ta, ta, SMC, WorldQuant Alphas, and native indicators at scale
- Research toolkit › Time-series operationsRun compiled rolling windows, reductions, and date logic on labeled pandas data
- Custom indicatorsTurn Python, pandas, or Numba calculations into reusable indicators with parameter grids
- Backtesting › Live simulationContinue backtests on new bars, chain runs, and feed an external trading system
Copyright © 2021–2026 Oleg Polakow. All rights reserved.
Site content and documentation are provided for using and evaluating VectorBT PRO and for educational purposes. Any other use, including building or supporting competing products or services, requires prior written consent.