Repository navigation
0.8.0 — 2026-11-08
Streaming (incremental update) API for live and paper trading: append a newly
closed bar and get updated values without recomputing the series. Ten
families (SMA, EMA, WMA, RSI, ATR, NATR, OBV, AD, MACD, BBANDS), single-series
and portfolio-wide on CPU or GPU, with Python, Node.js and C# wrappers. The 52
new C ABI symbols are appended only, so code built against 0.7.x keeps
working; streaming needs a 0.8.0 native library and 0.8.0 bindings together.
Known limits: the compatibility baseline is still TA-Lib 0.7.1 (161
functions), not 0.8.1. In the 2026-10-07 Windows run the AUTO portfolio path
exceeded 1 ms p99 from 2048 symbols (see docs/STREAMING_API.md).
HT_DCPHASE, HT_SINE and HT_TRENDMODE through the device and batch API
disagree with TA-Lib on constant input, where the phase is rounding noise;
the host API matches (see docs/IMPLEMENTATION_NOTES.md).
Release preparation, 2026-11-01:
- Fixed:
*_batch_dwith stream 0 raced the caller's default stream.
Stream 0 dispatches to the internal pool, whose streams are
cudaStreamNonBlocking, and nothing ordered them after the legacy
default stream. That stream is PyTorch's and CuPy's default
(torch.cuda.current_stream().cuda_stream == 0, the BATCH_API.md
example), so a producer kernel still in flight there raced the batch.
Reported from PyTorch as an intermittent all-NaN row 0 ofsum_batch_d
(SUM NaN-fills its output before computing it); stale but finite rows are
the more frequent outcome. Reproduced with the reporter's code under a
random GPU backlog: 60 of 200 iterations returned a wrong row 0 (7
all-NaN, 53 stale); with the fix 0 of 400 (two seeds), with no
synchronization on the caller's side. - Not only SUM: with the producer held on the legacy stream, every one of
the 143 differential-corpus specs reachable through a*_batch_dfamily
failed the TA-Lib oracle without the fix — in the first series for
families whose kernels happen to synchronize the device, in every series
for the rest. With the fix 140/143 pass in every row; the three
exceptions are a pre-existing device-path deviation, not the batch path
(see Known limits). - The pool now waits on an event recorded on
cudaStreamLegacybefore any
series is dispatched — explicitly the legacy stream, so a
--default-stream per-threadbuild behaves the same. Non-null streams
and the host*_batchvariants are unchanged. Regression test
BatchAPI.DeviceBatchOnStreamZeroWaitsForLegacyStreamholds the legacy
stream with a host function so the race is deterministic. - Linux GPU validation of
a5c8f35(RTX 3090 Ti, CUDA 12.8): CTest 426/426,
corpus 165/165, streaming corpus 699/699, Python/Node/C# pipelines green,
plus the batch-path sweep above. Evidence indocs/validation/2026-11-01/,
details in OPERATIONS.md. - Repository history rewritten on 2026-11-01 to remove infrastructure
details (host addresses, SSH ports, account and workstation paths) from the
operations log and the evidence archives; code is unchanged. Commit ids
quoted in this section are pre-rewrite ids — the mapping is in
OPERATIONS.md, "History rewrite".
Release preparation, 2026-10-14:
- Packaging: the Windows wheel no longer inherits the Linux
libtacuda.so.
setuptools never prunesbuild/lib, so the second platform wheel carried
both natives. The validation step missed it because bashset -eignores
a failing! cmd; it now checks the exact native set of every wheel and a
source-only sdist. - Publishing moved from the three package jobs into one
publishjob that
runs after all packages validate, checks secrets and the npm token first,
and skips versions a registry already has, so a failed push is completed
by a rerun instead of leaving a partial release. - Linux GPU validation of
4e8edd1(RTX 3090 Ti, CUDA 12.8): CTest 425/425,
corpus 165/165, streaming corpus 699/699, Python/Node/C# pipelines green.
Evidence indocs/validation/2026-10-14/, details in OPERATIONS.md.
Release preparation, 2026-10-07:
- Replace dereferenced raw stream handles with non-reused registry tokens;
destroyed handles cannot alias new allocations. Covers single and batch
APIs, with regression checks for allocation churn and wrong handle kinds. - Include string-returning introspection in the binding audit: 811 declared
symbols, not 808. Fix Python error-message return types and expose native
status/last-error helpers in Python and Node. - Align native/package versions to 0.8.0. Publication remains gated on a
numbered release section, an exact matching tag and GPU correctness. - Refresh CUDA installation, binding/test workflow triggers and the pinned
TA-Lib reference. Validate actual PE/ELF exports instead of binary strings. - Correct documentation counts, per-indicator warm-up, and benchmark baselines.
Coverage means native TA-Lib 0.7.1 (161 functions); 0.8.1 migration remains
open (40 missing corpus specs and IMI/WILLR edge semantics).
Streaming (incremental update) API, milestones M1, M2 and M3 — single-series
streams, batch (portfolio) streams, and language bindings with corpus-level
parity verification. User guide: docs/STREAMING_API.md, rationale:
docs/DESIGN_STREAMING_API.md.
M3 — bindings and the streaming corpus:
- Python (
tacuda.stream), Node.js (tacuda.RsiStream, ...) and C#
(RsiStream, ...) wrappers for all 20 stream types. Hand-written rather
than generated: the binding generator models array-in/array-out calls and
cannot express a handle with a lifetime. Idiomatic per language — context
managers in Python,destroy()/Symbol.disposein Node,IDisposable
with tuple returns in C#; a released handle is rejected everywhere instead
of reaching freed native state. bindings/audit_bindings.pygrew a streaming bucket: all 52 streaming
symbols must be reachable from every language, expanding the family tables
the Python and Node wrappers are built from rather than trusting a literal
name scan. Verified to actually fail (exit 1, 4 gaps) when a family is
removed. Wired into ctest asbindings_coverage.bindings/generate_stream_bindings.pyemits the repetitive C# per-family
wrappers with a--checkstaleness gate, wired into ctest as
stream_bindings_up_to_date.- New
benchmarks/streaming_corpus.py: all ten families over the seven
differential-corpus datasets under the same strict oracle, asserting that
chunked appends (1/3/16/all) reproduce the full-series tacuda call, that
the chunkings are bit-identical to each other, that the result matches the
installed TA-Lib, and that batch rows equal single streams on both
execution paths. 699/699 cells pass. The single exclusion is NATR on
the zero-crossing dataset, where TA-Lib's own ta_NATR.c corrupts its first
output bar — the same exclusion the differential corpus already makes. - Streaming cases added to all three binding pipelines: Python 34/34,
Node 201/201, C# 36/36. C++ suite plus binding gates: 419/419.
M2 — batch (portfolio) streams:
- 27 more appended C ABI symbols:
ctStreamBatch_tcarriesnumSeries
independent series of one indicator, one append advances all of them
(ct_<family>_stream_batch_create/appendfor the same ten families, plus
ct_stream_batch_{destroy,reset,lookback,series,outputs,bars,set_exec}).
Matrices are series-major like the batch API. - The state machines moved to
include/utils/StreamStates.has POD structs
with__host__ __device__step functions, so the single-series path, the
CPU batch path and the CUDA kernel run the same recurrence. A batch of N
series is therefore bit-identical to N separate streams — asserted per
family, per execution path, intests/cpp/test_stream_batch.cpp(8 cases,
including reset invalidating resident device state and policy changes
mid-stream migrating state rather than restarting it). ctStreamExec_tselects CPU or GPU;CT_STREAM_EXEC_AUTOswitches at the
measured crossover of 2048 series. 7-indicator pipeline, one bar per tick,
i3-12100F + RTX 3090 Ti (benchmarks/bench_stream_portfolio.cpp):
256 symbols p99 19.5 µs against the 1 ms acceptance target; CPU wins to
1024 symbols (67.6 µs vs 124 µs), the paths meet at 2048 (141 vs 139 µs),
and the GPU wins above (65 536 symbols: 764 µs vs 4 721 µs). AUTO keeps p99
under 1 ms through 65 536 symbols.- Dropped before shipping: a library-managed CPU thread pool. Spawning
workers per append measured 595 µs at every portfolio size — thread
creation swamped the work. Series are independent, so callers who want CPU
parallelism shard across several handles with their own threads. - Persistent batch plans are deliberately not built yet. Batch streams
already keep device state and I/O buffers resident, so what a plan would
still buy is one OHLCV upload per tick instead of one per indicator; that
is worth much of the GPU tick cost at tens of thousands of symbols, but the
acceptance target is met without it. Rationale recorded in
DESIGN_STREAMING_API.md D3.
M1 — single-series streams:
- New public header
include/tacuda_stream.hand 25 appended C ABI
symbols: an opaquectStream_thandle with shared lifetime calls
(ct_stream_destroy/reset/lookback/bars/outputs) plus
create/appendpairs for ten families — SMA, EMA, WMA, RSI, ATR,
NATR, OBV, AD, MACD (3 lines) and BBANDS (3 lines). Append-only, so the
existing ABI is untouched. - Streams carry their state on the host in double precision. Measured on
the RTX 3090 Ti (RSI(14),benchmarks/bench_stream_latency.cpp): an
append costs p50 0.04 µs regardless of history length, against p50
142 µs (100K bars) and 1 234 µs (1M bars) for a fullct_rsi
recompute — roughly 3 500x and 31 000x per tick. History replay runs at
~55 M bars/s. This measurement is what settled the CPU-vs-GPU choice for
single-series updates; portfolio-wide appends stay the GPU's job and are
the next milestone. - Each state machine mirrors the exact recurrence of its full-series
counterpart, including the load-bearing details: TA-Lib's flat-window
RSI convention and NaN poisoning, the ATR seed as the mean of
TR[1..period] landing at indexperiod, NATR'sTA_IS_ZEROclose
guard, MACD's two EMA seeds taken atslowPeriod-1(the fast one is the
fast-length SMA ending there) with the signal EMA consuming the
float-rounded MACD line and all three lines sharing oneoutBegIdx, and
BBANDS' population variance clamped at zero. - Parity is verified against the full-series host API, not asserted:
tests/cpp/test_stream_api.cpp(18 cases) compares every family at four
chunk sizes (1, 3, 16, whole series) under the corpus oracle. NaN masks
match exactly; MACD, OBV, AD and SMA are bit-identical, the rest agree
within 1.2e-07 (EMA/WMA/BBANDS) to 7.4e-07 (NATR) relative — streams are
double where some full-series kernels recurse in float, so they are at
least as accurate, not bit-equal. - Lifetime and error contract match the host API:
api_guardequivalent
on every entry point, thread-localct_get_last_error(), NULL-safe
destroy, use-after-destroy rejected withCT_STATUS_INVALID_ARGUMENT
instead of undefined behavior, and an append through the wrong family's
function rejected rather than silently misread. - Window families (SMA/WMA/BBANDS) re-sum their ring buffer per bar
(O(period), independent of history) rather than keeping a rolling
accumulator, which would drift away from the full-series kernels.
Not yet: persistent batch plans, _ex dtype variants, and state serialization.
88582742c248f91a128a0a7cc77413e3b116e71347bac8fc3df457340811d171 libtacuda.so
ee9797e7fdeb2e31f4c27ffa193da26dfc119803f92280ca50c707a5acbaa6dc tacuda.dll