TA-Lib is the most widely used technical analysis library in quantitative trading. 158 functions covering technical indicators and candlestick pattern recognition, all backed by a C implementation. If you do any kind of systematic trading in Python, you’ll run into it sooner or later.
This tutorial covers everything from installation to a working strategy: the two API styles and when to use each, parameter selection tips for core indicators, candlestick pattern recognition, Pandas/Polars integration, and how TA-Lib compares to pandas-ta. If you’ve already set up yfinance for data, TA-Lib is the next step in turning that data into trading signals.
What Is TA-Lib
TA-Lib (Technical Analysis Library) is an open-source C library originally written by Mario Fortier for computing technical analysis indicators. The Python wrapper uses Cython (not the older SWIG bindings), and on large arrays it’s one to two orders of magnitude faster than pure Python implementations.
It provides three categories of functionality:
- ~100 technical indicators: moving averages, MACD, RSI, Bollinger Bands, ATR, and more — covering trend, momentum, volatility, and volume
- 61 candlestick pattern recognizers: hammer, doji, engulfing, morning star, etc.
- Math and statistical functions: linear regression, standard deviation, beta, and other helper calculations
One thing to watch out for: the upstream C library released version 0.6.x in late 2024, changing the shared library name from -lta_lib to -lta-lib. The Python wrapper split into three branches accordingly:
| Python Wrapper | C Library | NumPy |
|---|---|---|
| 0.4.x | ta-lib 0.4.x | NumPy 1 |
| 0.5.x | ta-lib 0.4.x | NumPy 2 |
| 0.6.x | ta-lib 0.6.x | NumPy 2 (works with NumPy 1 in practice) |
Stick with 0.4.x or 0.5.x if your project is locked to the old C library (0.4.x). For new projects, go with 0.6.x.
Installation
Installing TA-Lib used to be the first hurdle that drove beginners away — a C library dependency meant pip install would fail with compilation errors. Since version 0.6.5, official pre-built wheels cover most platforms, so the process is much smoother now.
pip (Recommended)
pip install TA-Lib
Pre-built wheels are available for Linux (x86_64, arm64), macOS (x86_64, arm64), and Windows (x86_64, x86, arm64), supporting Python 3.9 through 3.14. If you’re on one of these platforms, this single command is all you need.
conda
conda install -c conda-forge ta-lib
conda-forge bundles both the Python wrapper and the underlying C library (libta-lib), resolving dependencies automatically.
Building from Source
Only necessary when no pre-built wheel exists for your platform.
macOS:
brew install ta-lib
pip install TA-Lib
On Apple Silicon (M1/M2/M3/M4), if pip can’t find the library path after Homebrew installation:
export TA_INCLUDE_PATH="$(brew --prefix ta-lib)/include"
export TA_LIBRARY_PATH="$(brew --prefix ta-lib)/lib"
pip install --no-cache-dir TA-Lib
Linux:
wget https://github.com/TA-Lib/ta-lib/releases/download/v0.6.4/ta-lib-0.6.4-src.tar.gz
tar -xzf ta-lib-0.6.4-src.tar.gz
cd ta-lib-0.6.4/
./configure --prefix=/usr
make
sudo make install
pip install TA-Lib
make -jN may fail on the first run — just rerun make -jN followed by make install. Also, directory paths with spaces will cause errors.
Windows (without wheel): Download and run ta-lib-0.6.4-windows-x86_64.msi, then pip install TA-Lib.
Common Errors
“Cannot find ta-lib library”: pip can find the Python source but not the underlying C library. Verify the C library is installed and its headers/shared libraries are on your system’s search paths. On Linux, run ldconfig to refresh the cache. On macOS, check your Homebrew prefix.
Version mismatch: The 0.6.x Python wrapper expects the 0.6.x C library. If you have the old C library (0.4.x) but installed the new wrapper (0.6.x), linking will fail. Use talib.__version__ to check the Python wrapper version and talib.__ta_version__ for the underlying C library version.
Two APIs: Function vs Abstract
TA-Lib Python offers two calling conventions. The Function API is direct and explicit; the Abstract API uses dictionary input and supports runtime introspection.
Function API
The most common approach. Each indicator is a standalone function that takes NumPy arrays:
import numpy as np
import talib
close = np.random.random(100)
# Simple moving average
sma = talib.SMA(close, timeperiod=30)
# MACD
macd, signal, hist = talib.MACD(close, fastperiod=12, slowperiod=26, signalperiod=9)
# Bollinger Bands
upper, middle, lower = talib.BBANDS(close, timeperiod=20, nbdevup=2, nbdevdn=2)
Function names are all uppercase, and parameters have sensible defaults. The first N elements of any output are NaN — that’s the lookback period, the minimum number of data points required before the indicator can produce a value.
Abstract API
Pass OHLCV data as a dictionary, and the function automatically picks the columns it needs:
import numpy as np
from talib.abstract import SMA, MACD, STOCH
inputs = {
'open': np.random.random(100),
'high': np.random.random(100),
'low': np.random.random(100),
'close': np.random.random(100),
'volume': np.random.random(100)
}
# Defaults to close
sma = SMA(inputs, timeperiod=25)
# Calculate on open instead
sma_open = SMA(inputs, timeperiod=25, price='open')
# Stochastic automatically pulls high, low, close from inputs
slowk, slowd = STOCH(inputs, 5, 3, 0, 3, 0)
A feature most tutorials skip: parameter introspection via the info attribute.
from talib.abstract import Function
stoch = Function('STOCH')
print(stoch.info)
# Shows: parameter names, defaults, required input columns, output names
# Invaluable when programmatically generating strategies
Which to Choose
Quick single-indicator calculations → Function API, cleaner code. Batch computing across many indicators, or dynamically switching indicators at runtime → Abstract API, with its unified dict input and configurable parameters. In practice, mixing both is common.
Core Indicators in Practice
TA-Lib’s indicators fall into four categories: trend, momentum, volatility, and volume. The emphasis here is on parameter selection rather than just showing API calls.
Trend Indicators
SMA and EMA
import talib
import numpy as np
close = np.array([...]) # your close prices
sma_20 = talib.SMA(close, timeperiod=20)
ema_20 = talib.EMA(close, timeperiod=20)
The 20/50/200-day periods are conventions from US equities (roughly one month, one quarter, one year of trading days). Crypto markets trade 24/7, so a “one month” equivalent is closer to 30, not 20. Copying these numbers without adjusting for your market’s trading calendar defeats the purpose.
EMA weights recent prices more heavily, reacting faster to trend changes at the cost of more noise. In choppy markets, EMA crossover signals produce more false breakouts than SMA.
MACD
macd, signal, hist = talib.MACD(close, fastperiod=12, slowperiod=26, signalperiod=9)
The 12/26/9 defaults come from Gerald Appel’s 1970s work, calibrated to weekly US stock trading. For intraday or crypto, 5/13/4 or 8/21/5 are common alternatives. Shorter parameters mean faster signals but more false positives — there is no universally “best” setting.
The three return values: macd is the fast-slow EMA difference, signal is an EMA of MACD itself, hist is their difference (the histogram). The standard trading signal is hist crossing from negative to positive (bullish) or positive to negative (bearish).
Bollinger Bands
upper, middle, lower = talib.BBANDS(close, timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)
Two standard deviations cover about 95% of price action under a normal distribution. Real price distributions are fat-tailed, so “sell when price touches the upper band” gets stopped out repeatedly in trending markets. Bollinger Bands are most useful for detecting volatility contraction (narrowing bandwidth → breakout incoming), not as support/resistance.
The matype parameter controls the middle band’s moving average type: 0=SMA, 1=EMA, 2=WMA, etc. Switching to EMA makes the bands more responsive to recent volatility.
Momentum Indicators
RSI
rsi = talib.RSI(close, timeperiod=14)
The 14-period default is from Welles Wilder’s 1978 work. The 70/30 overbought/oversold thresholds break down in strong trends — RSI can stay above 70 for weeks during a bull run. A more practical approach: use 80/40 in uptrends, 60/20 in downtrends, and the classic 70/30 only in ranging markets.
Stochastic
slowk, slowd = talib.STOCH(high, low, close,
fastk_period=5, slowk_period=3, slowk_matype=0,
slowd_period=3, slowd_matype=0)
Raw fast stochastic (%K) is too noisy for actual trading. Almost everyone uses slow stochastic, which smooths %K. The slowk_period=3 is the standard smoothing window. TA-Lib directly returns the slow K and D values.
ADX
adx = talib.ADX(high, low, close, timeperiod=14)
ADX doesn’t tell you direction — only trend strength. Below 20 typically means a ranging market (favor mean-reversion strategies); above 40 means a strong trend (favor trend-following). Many traders focus on MACD and RSI signals while ignoring ADX, skipping the crucial first step of identifying the current market regime.
Volatility Indicators
ATR
atr = talib.ATR(high, low, close, timeperiod=14)
ATR (Average True Range) measures price volatility without directional bias. It’s a core input for position sizing and stop-loss placement. A common stop-loss rule: stop = entry price - 2 × ATR. This adapts to each instrument’s volatility — far more reasonable than a fixed percentage stop.
# Dynamic stop-loss using ATR
entry_price = 100.0
current_atr = atr[-1]
stop_loss = entry_price - 2 * current_atr
Volume Indicators
OBV
obv = talib.OBV(close, volume)
OBV (On Balance Volume) adds today’s volume when price closes up, subtracts it when price closes down. When price makes a new high but OBV doesn’t follow (divergence), it often signals weakening momentum. The absolute value of OBV is meaningless — watch the trend and divergences.
Candlestick Pattern Recognition
TA-Lib includes 61 candlestick pattern functions. Each takes OHLC data and returns an integer array: positive values are bullish patterns, negative are bearish, zero means no pattern detected. Most functions only return -100, 0, or +100.
# Detect hammer
hammer = talib.CDLHAMMER(op, hi, lo, cl)
# Non-zero positions in the array are where patterns were detected
# Detect engulfing
engulfing = talib.CDLENGULFING(op, hi, lo, cl)
Scanning for all patterns at once:
import talib
candle_names = talib.get_function_groups()['Pattern Recognition']
results = {}
for name in candle_names:
func = getattr(talib, name)
results[name] = func(op, hi, lo, cl)
# Patterns detected on the most recent bar
last_signals = {name: val[-1] for name, val in results.items() if val[-1] != 0}
print(last_signals)
An important caveat: standalone candlestick pattern win rates hover around 50-55%, barely better than a coin flip. Their real value comes from combining with trend indicators — for example, only trading bullish engulfing patterns when ADX > 25 and in the direction of the trend. Treat patterns as filters, not signal generators.
| Type | Examples | TA-Lib Functions |
|---|---|---|
| Single bullish | Hammer, Inverted Hammer | CDLHAMMER, CDLINVERTEDHAMMER |
| Single bearish | Hanging Man, Shooting Star | CDLHANGINGMAN, CDLSHOOTINGSTAR |
| Two-bar reversal | Engulfing, Piercing | CDLENGULFING, CDLPIERCING |
| Three-bar reversal | Morning Star, Evening Star | CDLMORNINGSTAR, CDLEVENINGSTAR |
| Continuation | Rising/Falling Three Methods | CDLRISEFALL3METHODS |
Pandas and Polars Integration
In real projects, data usually lives in a Pandas DataFrame. TA-Lib’s Function API accepts NumPy arrays, so extract columns accordingly:
import pandas as pd
import talib
df = pd.read_csv('stock_data.csv')
df['SMA_20'] = talib.SMA(df['close'].values, timeperiod=20)
df['RSI_14'] = talib.RSI(df['close'].values, timeperiod=14)
df['MACD'], df['MACD_signal'], df['MACD_hist'] = talib.MACD(
df['close'].values, fastperiod=12, slowperiod=26, signalperiod=9
)
Pass .values (the underlying NumPy array) rather than the Series itself. Newer versions accept Series directly, but explicit .values avoids index alignment surprises.
Efficient batch computation:
indicators = {
'SMA_10': lambda c: talib.SMA(c, 10),
'SMA_20': lambda c: talib.SMA(c, 20),
'EMA_12': lambda c: talib.EMA(c, 12),
'RSI_14': lambda c: talib.RSI(c, 14),
}
close_values = df['close'].values
for name, func in indicators.items():
df[name] = func(close_values)
Version 0.6.x added native Polars support. If your data pipeline uses Polars (which is considerably faster than Pandas), you can pass Polars Series directly:
import polars as pl
import talib
df = pl.read_csv('stock_data.csv')
rsi = talib.RSI(df['close'], timeperiod=14)
TA-Lib vs pandas-ta vs ta
TA-Lib isn’t the only technical analysis library for Python. pandas-ta and ta are pure-Python alternatives worth considering.
| Dimension | TA-Lib | pandas-ta | ta |
|---|---|---|---|
| Implementation | C + Cython | Pure Python | Pure Python |
| Indicator Count | 158 (incl. 61 patterns) | 150+ (incl. utilities) | 40+ |
| Candlestick Patterns | 61 | Requires TA-Lib installed | None |
| Install Difficulty | Medium (C dep; better since 0.6.5) | Easy (pip install) | Easy (pip install) |
| Performance | Fast (C) | 3-10x slower | 5-15x slower |
| Pandas Integration | Needs .values | Native DataFrame methods | Native DataFrame |
| Maintenance | Active | Uncertain (repo inaccessible) | Low-frequency |
| Polars Support | 0.6.x native | No | No |
The performance gap is negligible on small datasets (< 1,000 rows) but significant when computing indicators across thousands of stocks with minute-level data. In a backtesting pipeline scanning the entire market, TA-Lib’s C implementation makes a real difference.
Recommendations: Need candlestick pattern recognition → TA-Lib is your only option. Large-scale batch computation → TA-Lib. Quick prototyping without installation hassle → pandas-ta. Existing Pandas codebase and you prefer df.ta.sma() syntax → pandas-ta.
Full Strategy Example
Putting it all together: grab data with yfinance, compute indicators with TA-Lib, generate signals, and run a basic backtest.
import yfinance as yf
import talib
import numpy as np
import pandas as pd
# Get data
df = yf.download('AAPL', start='2023-01-01', end='2025-12-31')
close = df['Close'].values.flatten()
high = df['High'].values.flatten()
low = df['Low'].values.flatten()
# Compute indicators
df['RSI'] = talib.RSI(close, timeperiod=14)
df['MACD'], df['MACD_signal'], df['MACD_hist'] = talib.MACD(close)
df['ADX'] = talib.ADX(high, low, close, timeperiod=14)
df['upper'], df['middle'], df['lower'] = talib.BBANDS(close, timeperiod=20)
# Generate signals: MACD bullish crossover + RSI not overbought + trending market
df['signal'] = 0
buy_condition = (
(df['MACD_hist'] > 0) &
(df['MACD_hist'].shift(1) <= 0) & # Histogram turns positive
(df['RSI'] < 70) & # Not overbought
(df['ADX'] > 20) # Market is trending
)
sell_condition = (
(df['MACD_hist'] < 0) &
(df['MACD_hist'].shift(1) >= 0) # Histogram turns negative
)
df.loc[buy_condition, 'signal'] = 1
df.loc[sell_condition, 'signal'] = -1
# Simple backtest
df['position'] = df['signal'].replace(0, np.nan).ffill().fillna(0)
df['returns'] = df['Close'].pct_change()
df['strategy_returns'] = df['position'].shift(1) * df['returns']
total_return = (1 + df['strategy_returns'].dropna()).prod() - 1
print(f"Strategy total return: {total_return:.2%}")
This example demonstrates TA-Lib usage, not a production-ready strategy. Real trading requires transaction costs, slippage modeling, and proper position sizing. For common backtesting pitfalls (overfitting, look-ahead bias, survivorship bias), see Backtesting Pitfalls: The Complete Guide. For evaluating strategy quality beyond raw returns, check the Quant Metrics Guide.
FAQ
“Exception: Function not found”
TA-Lib function names are all uppercase: talib.SMA, not talib.sma. Run talib.get_functions() to see all available names.
Results start with a bunch of NaN values
Expected behavior. Every indicator has a lookback period — the minimum data points required before it can produce output. SMA(30) has 29 leading NaNs, MACD with default parameters has 33. Either reduce timeperiod or use longer historical data.
Is it thread-safe?
The underlying C library is not thread-safe. Calling the same indicator function from multiple threads may produce incorrect results. Use multiprocessing instead of threading, or serialize TA-Lib calls with a lock.
What is the Stream API?
The talib.stream module computes only the latest single data point for an indicator, ideal for real-time market data. Instead of recomputing the entire series every time a new price arrives, Stream API is far more efficient for live trading:
from talib import stream
# Compute only the latest RSI value
latest_rsi = stream.RSI(close_array, timeperiod=14)
PyInstaller packaging fails
The talib.stream module may not be auto-detected during packaging. Add --hidden-import talib.stream to your PyInstaller command.
What does set_unstable_period do?
Recursive indicators like EMA, RSI, and ADX are sensitive to their initial seed values — the first N outputs depend on where your data starts. set_unstable_period tells TA-Lib to discard those early unstable values and replace them with NaN. If your indicator values don’t match another platform, this is often the cause.
import talib
# Discard the first 100 unstable values for all EMA-type indicators
talib.set_unstable_period('EMA', 100)