TAlib
Technical analysis library for Elixir.
Indicators
All 133 TA-Lib functions are covered — see PARITY.md for the function-by-function map. The Hilbert-transform family and MAMA are faithful translations of the TA-Lib C reference, cross-verified series for series.
- Overlap studies: SMA, EMA, WMA, CMA, Wilder RMA, DEMA, TEMA, TRIMA, KAMA,
T3, Midpoint, Midprice, Bollinger Bands, Keltner Channels, Donchian
Channels, Parabolic SAR — scalars and full series, plus streaming
init/nextstate for O(1) updates as new prices arrive (see Streaming below) - Momentum: RSI (Wilder), MACD, Stochastic (fast and slow), Stochastic RSI, Williams %R, ROC/ROCP/ROCR/ROCR100, Momentum, TRIX, CMO, APO, PPO, Aroon, Aroon Oscillator, BOP, CCI, ADX/DMI (+DI, -DI, DX, ADXR)
- Volatility: StdDev, Variance, ATR, NATR, True Range
- Volume: OBV, MFI, rolling VWAP, Chaikin A/D line and oscillator
- Statistics: linear regression (value/slope/intercept/angle), Time Series Forecast, Beta, Correlation
- Candlestick patterns: all 61 TA-Lib CDL functions via
TAlib.Indicators.CDL, scored per bar (+100 bullish / -100 bearish / 0 none, with TA-Lib's ±200 hikkake confirmations and ±80 equal-touch levels), verified integer-exact against the C library;CDL.scan/2scores many patterns over one shared candle context - Generic moving-average dispatch (
MA.ma/3), variable-period MA (mavp),MACD.macd_ext/macd_fix, extended Parabolic SAR - Cycle indicators: the full Hilbert-transform family (
HT.ht_trendline,ht_dcperiod,ht_dcphase,ht_phasor,ht_sine,ht_trendmode) andMAMA.mama(with FAMA) — value-matched against the real TA-Lib C library
See docs/BENCHMARKS.md for a performance and numerical-agreement comparison against TA-Lib.
Bar-based indicators take maps such as
%{open: o, high: h, low: l, close: c, volume: v} with the keys they need —
extra keys are ignored.
Installation
The package can be installed by adding talib to your list of dependencies
in mix.exs:
def deps do
[
{:talib, "~> 0.7"}
]
end
Conventions
- Prices are always given in chronological order, oldest price first.
Series functions return results aligned with the input list,
nil-padded while the lookback window fills up; scalar functions return the value at the latest point. - Lookback periods must be positive integers; anything else raises
ArgumentError. - Every function returns
nil— never0— when there is not enough data, since0is a legitimate reading for several indicators.
Examples
iex> TAlib.Indicators.MA.sma([1330.95, 1334.65, 1340.0], 3)
1335.2
iex> TAlib.Indicators.MA.sma_list([1, 2, 3, 4, 5], 3)
[nil, nil, 2.0, 3.0, 4.0]
iex> TAlib.Indicators.RSI.rsi([6, 1, 4, 2, 5, 3], 3) |> Float.round(4)
42.8571
iex> TAlib.Indicators.MACD.macd_all(prices)
%{macd: 20.36, signal: 12.28, histogram: 8.08}
More examples can be found in the module documentation (Hexdocs) and in the test suite.
Streaming
Batch functions walk their whole input — the wrong shape for a live feed. Fourteen indicator families (the MAs, StdDev, Bollinger Bands, ATR, RSI, MACD, Stochastic fast and slow, Williams %R, Donchian, Aroon) also come as O(1) streaming state: warm up once from history, then advance one bar at a time.
# Warm up from history; values equals the batch series exactly
{state, values} = TAlib.Indicators.MA.sma_init(history, 20)
# One new price, one constant-time step — ~0.1 µs, vs recomputing
# the whole series per tick
{state, value} = TAlib.Indicators.MA.sma_next(state, price)
In a GenServer, keep the state in the process and advance it in
handle_info/2 as ticks arrive; the states are small immutable
structs, safe to keep and pass around. Because init replays the
batch arithmetic operation for operation, the streamed series
continues the batch series bit for bit — the same values the audit
checked against the C library (see
docs/BENCHMARKS.md, "Streaming: O(1) per tick").
Reflections
This library slept from 2019 until late 2026, then went from a handful of indicators to full TA-Lib parity in a dozen releases. A few things the journey taught, in case they're useful:
- Translating reference code is not verifying it. Every algorithm
here was rewritten from the TA-Lib C source — and translations that
"looked right" still shipped real bugs: DEMA combining its EMA
stages head-to-tail, KAMA with the wrong slow constant, candlestick
thresholds approximated from textbooks instead of the C settings
table. What caught them was not care but measurement: a value audit
comparing our output, bar for bar, against the real C library on
shared fixtures (
bench/audit_dump.exs). That audit became the project's most valuable file — it also caught bugs in our own performance optimizations (an inverted deque comparison, an off-by-one tuple layout) that every unit test happily passed. - Exactness is a feature, not pedantry. A 1e-3 drift is invisible on a chart and very visible in a backtest: thresholds flip candlestick patterns and seeding differences compound through exponential averages. Every audited function is now bit-exact or within 1e-12 of the C library, and all 61 candlestick patterns are integer-exact — including the ±200/±80 score levels most reimplementations skip.
- Immutable lists are slower than C arrays, predictably. Single
functions run 25-100x the C time; that is the cost of walking
linked lists instead of mutating arrays. The remedy isn't magic but
the same algorithmic care C gets — sliding sums, monotonic deques,
single-pass walks. Nothing here is quadratic, and the concurrency
story is the BEAM's: one
Task.async_streamscales a 16-symbol panel 6.1x on 6 cores with zero plumbing, where Python needs a process pool to escape its GIL. C wins any single computation; Elixir wins the runtime around it — the feeds, the timeouts, the supervision, the traffic around the numbers.
The deliberate trade-offs: nil instead of 0 for missing data (0 is
a legitimate reading), chronological inputs oldest-first, and no
unstable-period machinery — outputs start where the math starts.