Proposed breaking change. talib.stream would be replaced, not extended — the
current last-value functions go away.
Why
TA-Lib C 0.8.1 has a real streaming API: O(1) cost per bar. talib.stream predates it and works a
different way — it takes the whole array on every call and asks the batch function for a single bar (often O(lookback)).
The propose C 0.8.1 changes bring two things at once:
- High-performance (adding a bar is a few nanoseconds on modern CPU).
- streaming output bit-exact with batch processing over the same data.
Shape
from talib import stream
s = stream.SMA(close, timeperiod=30) # same arguments as talib.SMA
s.value # value at the last history bar
for price in feed:
v = s.update(price) # one closed bar in, its value out
s.peek(provisional) # what update would return; commits nothing
s.copy() # independent fork
stream.SMA takes exactly the arguments talib.SMA takes — one returns the
series, the other a handle positioned at its end.
A multi-output function answers with a plain tuple, the same type and order
talib.MACD returns:
m = stream.MACD(close) # same arguments as talib.MACD
macd, signal, hist = m.update(price)
m.value[2] # last 'hist' value again, no recompute
abstract.Function('MACD').output_names # ['macd', 'macdsignal', 'macdhist']
A single-output function returns a bare float, not a 1-tuple.
Warm-Up
At stream creation, an history of at least lookback + 1 bars must be provided, which abstract knows:
need = abstract.Function('RSI', timeperiod=14).lookback + 1 # 15
# ... build 'history' here, with at least 'need' bars
s = stream.RSI(history, timeperiod=14) # open once
emit(s.value) # its value, at the last history bar
for bar in feed: # then only updates
emit(s.update(bar))
With fewer than need bars the open raises talib.InsufficientHistory. You can alternatively design your warm-up to keep re-trying opening until success.
If you already hold the history and want the batch series too, one pass gives
both:
s, rsi = stream.RSI.open_and_fill(history, timeperiod=14)
# rsi is what talib.RSI(history) returns, and s is positioned at its end
open_and_fill is an alternate constructor, the stream created has the same capability (update/peel/value etc...).
Migration
Three names go away: talib.stream.X, talib.stream_X (also exported at top
level), and the stream_* stubs in _ta_lib.pyi.
Details
- Pickling must raise. A handle is a pointer into the C library and never
crosses a process boundary; __reduce__ should say so rather than let
multiprocessing find out.
InsufficientHistory wants its own exception class. It is the library's one
recoverable condition, and _ta_check_success raises a bare Exception today,
so it cannot be caught narrowly inside a bar loop.
- pandas/polars. Handled in Cython by
_stream_input / _stream_like, per
argument. Not __init__.py's _wrapper, which costs ~900 ns per call whether
or not a Series is passed.
- Requires ta-lib C >= 0.8.1, so this lands with the 0.8.1 support work.
Proposed breaking change.
talib.streamwould be replaced, not extended — thecurrent last-value functions go away.
Why
TA-Lib C 0.8.1 has a real streaming API: O(1) cost per bar.
talib.streampredates it and works adifferent way — it takes the whole array on every call and asks the batch function for a single bar (often O(lookback)).
The propose C 0.8.1 changes bring two things at once:
Shape
stream.SMAtakes exactly the argumentstalib.SMAtakes — one returns theseries, the other a handle positioned at its end.
A multi-output function answers with a plain tuple, the same type and order
talib.MACDreturns:A single-output function returns a bare
float, not a 1-tuple.Warm-Up
At stream creation, an history of at least
lookback + 1bars must be provided, whichabstractknows:With fewer than
needbars the open raisestalib.InsufficientHistory. You can alternatively design your warm-up to keep re-trying opening until success.If you already hold the history and want the batch series too, one pass gives
both:
open_and_fillis an alternate constructor, the stream created has the same capability (update/peel/value etc...).Migration
Three names go away:
talib.stream.X,talib.stream_X(also exported at toplevel), and the
stream_*stubs in_ta_lib.pyi.Details
crosses a process boundary;
__reduce__should say so rather than letmultiprocessingfind out.InsufficientHistorywants its own exception class. It is the library's onerecoverable condition, and
_ta_check_successraises a bareExceptiontoday,so it cannot be caught narrowly inside a bar loop.
_stream_input/_stream_like, perargument. Not
__init__.py's_wrapper, which costs ~900 ns per call whetheror not a Series is passed.