▶ Full Post Text
Hey r/algotrading,
**pandas-ta-classic** is the community-maintained fork of pandas-ta — a comprehensive technical analysis library for pandas DataFrames. This release is the largest update since the fork, spanning 66 commits and 338 changed files.
GitHub: [github.com/xgboosted/pandas-ta-classic](https://github.com/xgboosted/pandas-ta-classic)
PyPI: `pip install pandas-ta-classic`
# 🎯 TL;DR
* **TA-Lib exact parity** — Wilder smoothing, chained EMA lookbacks, and PSAR reversal checks now match the C library within `float64` precision. All 60 oracle tests pass at `tol=1e-7`.
* **Fluent API chaining** — `df.ta.chain().sma(50).rsi().macd()`. Chain indicators in one expression, call `df.ta.unchain()` to go back to normal mode.
* **Property-based testing** — 55 Hypothesis tests verify mathematical invariants (`SMA(constant) == constant`, RSI ∈ \[0,100\]) across random inputs.
* **Test coverage 78% → 89%** — 1427 tests, zero failures. Every indicator has offset, fill, and None-guard coverage.
* **Code modernized** — Python 3.9–3.14, 370 dead-code instances removed, `tal` alias replaced with `talib` everywhere.
# ⚡ Fluent API Chaining (PR #113)
import pandas_ta_classic as ta
df = df.ta.chain().sma(50).rsi().macd().bbands(20)
# df now has SMA_50, RSI_14, MACD_12_26_9, MACDh_12_26_9, MACDs_12_26_9,
# BBL_20_2.0, BBM_20_2.0, BBU_20_2.0, BBB_20_2.0, BBP_20_2.0
df.ta.unchain() # back to normal append mode
No more repetitive `append=True` on every call. The chain mode accumulates columns in one fluent expression, then `unchain()` returns you to standard usage.
# 🔬 TA-Lib Parity Fixes
# Wilder's Smoothing
The PR #112 remediation extracted `wilder_smooth()` into a shared utility (`utils/_wilder.py`). It implements Wilder's cumulative-sum smoothing with the correct `sum(raw[1:length])` seeding, matching TA-Lib's internal PLUS\_DM / MINUS\_DM calculation exactly. Used by `dm.py` — no more hand-rolled NumPy loop inlined.
# Chained EMA Lookbacks
`DEMA`, `TEMA`, and `T3` now correctly strip leading NaN before feeding EMA output back into EMA. This matches TA-Lib's lookback of `depth*(length-1)`. Extracted into `_ema_chain()` in `overlap/ema.py`, reducing \~70 lines of repetitive boilerplate to \~10.
# PSAR Reversal Check
The SAR guard (`max`/`min` clamp at row-1/row-2) now applies *before* the reversal test, matching TA-Lib's behaviour. Previously, the raw projected SAR was checked, causing off-by-one splits at reversal bars.
# Bug Fixes
* `cdl_doji` — fixed `<` → `<=` threshold and added `shift(1)` to match TA-Lib's look-ahead behavior
* `ichimoku` — `apply_fill` now covers all 5 output series (was 3)
* `pvr` — added None-guard, offset, and fill support
* 13 indicators — added missing `apply_fill` for `fillna`/`fill_method` kwargs
# 🧪 Test Infrastructure
# [assertions.py](http://assertions.py) + IndicatorSpec
assert_indicator_standard(self, IndicatorSpec(
func=ta.rsi,
args=[self.close],
expected_name="RSI_14",
expected_type=Series,
none_arg_idx=0,
))
One call tests: return type, name, columns (DataFrame), offset, fill (fillna, ffill, bfill), None-guard, and length-in-name. Applied uniformly across all indicator test modules.
# Property-Based Testing
55 Hypothesis tests using `@given(price_series(), ...)`. Examples:
* `SMA(constant) == constant` for all window sizes
* `BBANDS: lower ≤ mid ≤ upper` for every row
* `RSI` output always ∈ \[0, 100\]
* `STDEV` always non-negative
* Offset preserves length, fillna removes NaN
* `verify_series(None)` returns None
# Fixture Auto-Regeneration
`tests/__init__.py` now regenerates `expected_values.json` and `regression_snapshots.json` on import when TA-Lib is installed. No more stale fixtures — test data is always in sync with the code.
make test-all # regenerate fixtures + run 1427 tests
make fixtures # regenerate fixture JSONs only (requires TA-Lib)
# Oracle Parity
* **60/60** TA-Lib oracle tests now pass at `tol=1e-7` — exact float64 match achieved by the Wilder smoothing and chained EMA fixes
# 📦 Package & Quality
# Code Modernization
* Removed 279 unnecessary `# -*- coding: utf-8 -*-` declarations (UP009)
* 65 useless `f"..."` prefixes (no placeholders) removed
* 87 unused imports removed across candle/overlap/momentum modules
* `Optional[X]` → `X | None`, `List[Y]` → `list[Y]` (pyupgrade)
* `tal` → `talib` rename — all test files now import `talib` directly
* `ruff` CI-critical checks (E9, F63, F7, F82) — all passed
# Python Support
Tested and passing on **Python 3.10, 3.11, 3.12, 3.13, 3.14**.
# 🔗 Links
* **GitHub**: [github.com/xgboosted/pandas-ta-classic](https://github.com/xgboosted/pandas-ta-classic)
* **PyPI**: `pip install pandas-ta-classic`
* **Changelog**: [CHANGELOG.md](https://github.com/xgboosted/pandas-ta-classic/blob/main/CHANGELOG.md)
* **Contributing**: [CONTRIBUTING.md](https://github.com/xgboosted/pandas-ta-classic/blob/main/CONTRIBUTING.md)