Release v0.8.1 - #769
Merged
Merged
Release v0.8.1#769
Conversation
The dev line requires a TA-Lib C that has no release yet, so a PR into dev could not build -- and in fact ran nothing at all, since tests.yml triggers on master only. This adds tests-dev.yml, which triggers on dev, builds the C library from source at the tip of its dev branch and caches it by commit, then runs the same steps tests.yml does. tests.yml is untouched: master keeps installing the published release. It resolves the commit with git ls-remote rather than api.github.com, which is rate limited per runner IP and answers 403 often enough to redden a job for a reason unrelated to the code under test.
talib.stream.SMA(close) returns a handle, not a value: .value is the value at the last history bar, .update(bar) is O(1) and returns that bar's, .peek(bar) evaluates a forming bar without committing, .copy() forks it, and .open_and_fill() returns the handle plus the Function API's series in one pass. Multi-output functions answer with a named tuple; .out_range and .advance() carry the C contract's bar range. BREAKING: talib.stream_X and the old value-returning talib.stream.X are gone, along with the stream_* stubs in _ta_lib.pyi -- talib/stream.pyi types the handles instead. Migrating is stream.X(...) -> stream.X(...).value, and the compiler will not find the sites: `if stream.CDLDOJI(o, h, l, c):` used to test the pattern and now tests a handle, which is always true. Adds talib.InsufficientHistory, raised when an open gets too little history. talib/_stream.pxi and talib/stream.pyi are generated by tools/generate_stream.py (--stub for the second).
SUPERTREND's streaming outputs are outSupertrend/outTrend in the C header. The committed _stream.pxi predates that rename and names them real/integer, so its namedtuple fields disagree with abstract's output_names and test_open_and_fill_matches_batch[SUPERTREND] fails on dev. _ta_lib.c regenerated with Cython 3.2.8, the version that produced the committed file. Claude-Session: https://claude.ai/code/session_01RQsaLFjtMXaVE4UHQKVaeu
update() on a multi-output handle spent ~125 ns/bar in namedtuple.__new__ against a 6 ns C call. MACD update() goes from 182 to 57 ns/bar with every candidate measured in one binary, and from 170 to 37 ns/bar end to end; BBANDS, STOCH, AROON and MINMAXINDEX all land in the -73% to -75% band. The Function API already returns a plain tuple, so the two tiers now agree. tuple -> NamedTuple stays available later as a non-breaking upgrade, since every tuple idiom keeps working; the reverse would not be. Single-output handles are untouched: 178 of 178 single-output classes are byte-identical before and after. test_open_and_fill_matches_batch loses its _fields check and gains a type and arity check that reaches all 23 multi-output and 178 single-output functions; order stays pinned by the element-wise comparison above it. Claude-Session: https://claude.ai/code/session_01RQsaLFjtMXaVE4UHQKVaeu
Cython emitted 58 warnings, all of the form Global name __PANDAS_SERIES matched from within class scope in contradiction to Python 'class private name' rules. This may change in a future release. The Function class reads seven module globals whose leading double underscore makes them mangling-eligible, so Python's own rules say those references should resolve to _Function__PANDAS_SERIES and fail. Cython resolves them to the module global instead and warns that it may stop. Dropping one underscore makes the code mean what it already did. The seven are private by every convention -- absent from __all__, undocumented -- and __TA_FUNCTION_NAMES__ is untouched: its trailing underscores put it outside the mangling rule. tools/generate_stream.py referenced them too, so _stream.pxi is regenerated. Nothing else moves. dir() of talib, talib._ta_lib, talib.abstract and talib.stream differs only by those seven names, and the generated C, normalised for the rename, differs only in the interned string table -- no function body, no ABI, no code path.
v0.8.0 shipped without the streaming API, so its entries move out of the released section. Claude-Session: https://claude.ai/code/session_01TuVjx2Q5urdmwi5opzfCp8
TA-Lib C 0.8.1 released SUPERTREND with outputs outSupertrend and outTrend.
_func.pxi was generated against a pre-release C that still called them
outReal and outInteger, so help(talib.SUPERTREND) and the abstract stub
listed "real" and "integer", while abstract.Function('SUPERTREND').output_names
already reported the released names at runtime.
_ta_lib.pxd now declares the header's parameter names, and _func.pxi,
abstract.pyi and _ta_lib.c are regenerated against the released 0.8.1
header. Return values, dtypes and argument order do not change.
Claude-Session: https://claude.ai/code/session_01TuVjx2Q5urdmwi5opzfCp8
An empty array made the Function API call TA-Lib with pointers one element before its buffers: check_begidx answers length - 1, which is -1, so the wrapper passed startIdx = endIdx = 0 and data + begidx. A function whose lookback is zero reads and writes that element. That is 34 functions at their default parameters, ACOS, ADD and OBV among them, plus any function whose parameters give a zero lookback, such as MA(x, timeperiod=1), through the Function, Abstract, pandas and polars paths alike. The heap corruption may abort the interpreter later, on an unrelated free, or go unnoticed. Stream opens are not affected; they raise InsufficientHistory first. When it started: 0.4.27, tagged 2023-07-13. Commit 02de3a3 ("don't throw exceptions on all nan input", CHANGELOG "[FIX]: Don't throw exceptions when inputs are all NaN") replaced check_begidx's `raise Exception("inputs are all NaN")` with `return length - 1`. An empty array takes that same branch, so until then it raised; since then it has reached TA-Lib with begidx -1. Every release since has it, through 0.8.0. Every wrapper now returns its outputs as empty arrays, before check_begidx, the lookback or TA-Lib are reached. Nothing else changes for an empty input: types, dtypes and tuple layout match the non-empty path, and for valid parameters the results are identical to before across func, the pandas and polars wrappers, and abstract with dict, pandas and polars DataFrame inputs, for all 201 functions. An invalid parameter on an empty input now returns empty outputs instead of TA_BAD_PARAM: there is nothing to compute. The test runs in a child process with the empty inputs right after an inaccessible page, so a read before the buffer faults at once. It fails on the previous code with SIGSEGV. It is skipped on Windows, where the guard would need VirtualProtect and no CI runs the tests. Claude-Session: https://claude.ai/code/session_01TuVjx2Q5urdmwi5opzfCp8
dev tracks TA-Lib C's dev branch, whose version moves ahead of the release.
Member
|
Awesome!! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tested against the released TA-Lib C v0.8.1. Carries the compatibility changes for it and the new streaming API.