Files
QuanTAlib/docs/python.md
T
Miha Kralj f10baa6dfb docs: add license rationale, Python/PineScript guides, API updates
- Add docs/license.md with Apache 2.0 rationale and patent protection analysis
- Add docs/python.md and docs/pinescript.md platform guides
- Expand README license section with disclosure and link to rationale
- Update docs/api.md and docs/architecture.md
- Update Python bindings: helpers, all indicator modules, pyproject.toml
- Add Python tests for Arrow and Polars integration
- Update TValue core type and documentation
- Add fix_length_to_period tooling script
2026-03-03 22:11:35 -08:00

11 KiB
Raw Blame History

Python Guide: quantalib for Data Scientists

"You chose Python because life is short. QuanTAlib talks raw machine code directly to the CPU because nanoseconds are shorter."

What This Is (and What It Is Not)

quantalib is not a Python library. It is a pre-compiled native binary wearing a Python trench coat. The math runs as raw machine code — the kind that talks directly to CPU vector registers and crunches 8 numbers simultaneously per clock tick. Think NumPy speed, except the entire indicator algorithm is fused into one native call with zero Python loops. Half a million bars of SMA in 328 microseconds. That is faster than your monitor can refresh a single frame.

The pip install delivers a ready-to-run native binary for your platform. No compilation step, no build tools, no waiting. Just import and go.

This means:

  • Batch only. You pass in an array, you get back an array. No streaming, no bar-by-bar updates. (The .NET version does streaming at 0.4 μs per update. Python's function-call overhead would eat that alive.)
  • Same numbers. The results are identical to the C# core library, bit for bit. Cross-validated against TA-Lib, Tulip, Skender, and half a dozen other implementations nobody remembers.
  • 393 indicators. Not 12. Not "the popular ones." All of them. From SMA to Yang-Zhang Volatility Adaptive Moving Average.

Installation

pip install quantalib

Pre-built wheels ship for all 6 combinations of Windows, Linux, and macOS on both x64 and ARM64. If your platform is missing, you are doing something creative.

Optional backends

pip install quantalib[pandas]     # pd.Series round-trip
pip install quantalib[polars]     # pl.Series round-trip
pip install quantalib[pyarrow]    # pa.Array round-trip
pip install quantalib[all]        # all three, because indecision is valid

Without any optional backend, plain NumPy arrays work. Always have. Always will.

Finding Your Indicator

Every indicator lives in one of 15 category modules. You do not need to know which module contains what: the top-level quantalib namespace re-exports everything.

import quantalib as qtl

# These are identical:
result = qtl.sma(prices, period=20)
result = qtl.trends_fir.sma(prices, period=20)

If you know the indicator name, call it. If you do not, here is the map:

Category Module What It Measures Examples
Core core Price transforms, building blocks avgprice, medprice, typprice, ha
Trends (FIR) trends_fir Finite impulse response averages sma, wma, hma, alma, trima
Trends (IIR) trends_iir Infinite impulse response averages ema, dema, tema, kama, jma
Filters filters Signal processing, noise reduction kalman, sgf, butter2, gauss
Oscillators oscillators Bounded/centered oscillators stoch, rsi, cci, fisher, willr
Dynamics dynamics Trend strength and direction adx, aroon, supertrend, ichimoku
Momentum momentum Speed of price changes roc, mom, macd, tsi, vel
Volatility volatility Price variability atr, bbw, stddev, hv, tr
Volume volume Trading activity obv, vwma, mfi, cmf, adl
Statistics statistics Statistical measures zscore, correlation, entropy
Channels channels Price boundaries bbands, kchannel, dchannel
Cycles cycles Cycle analysis ht_dcperiod, ht_sine, cg, dsp
Reversals reversals Pattern detection psar, pivot, fractals, swings
Errors errors Error metrics, loss functions rmse, mae, mape, smape
Numerics numerics Mathematical transforms fft, normalize, sigmoid, slope

Full indicator catalog with descriptions: 393 indicators

Calling Convention

Simple indicators follow this pattern:

result = qtl.indicator_name(source_data, period=N, offset=0)

Many indicators have their own parameters — multiple periods, multipliers, smoothing factors, phase controls. Use your IDE's autocomplete or help() to see what each function accepts. A few representative examples:

# Simple: one period
sma = qtl.sma(close, period=20)

# Multiple periods
macd_line, signal, hist = qtl.macd(close, fastPeriod=12, slowPeriod=26)

# Period + multiplier
upper, mid, lower = qtl.bbands(close, bbPeriod=20, bbMult=2.0)

# Complex: many named parameters
gator_upper, gator_lower = qtl.gator(close, jawPeriod=13, jawShift=8,
    teethPeriod=8, teethShift=5, lipsPeriod=5, lipsShift=3)

# Filter with float controls
filtered = qtl.kalman(close, q=0.01, r=0.1)

Common parameters

  • source_data: NumPy array, pandas Series, polars Series, or PyArrow Array. The library detects the type and returns the same type.
  • period: The primary lookback window (where applicable). This is the canonical name. If you are coming from pandas-ta, length= works too — it is silently aliased.
  • offset: Shift the output by N bars. Default 0. Positive values shift right.
  • **kwargs: Every function accepts **kwargs for forward compatibility and aliasing.

Input patterns

Most indicators take a single source series. Some need OHLCV data:

# Single source (Pattern A): most indicators
sma = qtl.sma(close, period=20)

# High-Low-Close (Pattern E): ATR, channels
atr = qtl.atr(high, low, close, period=14)

# OHLCV (Pattern B): volume indicators, dynamics
adx = qtl.adx(open, high, low, close, volume, period=14)

# Dual source (Pattern F): error metrics
rmse = qtl.rmse(actual, predicted, period=20)

# Source + Volume (Pattern G): volume-weighted indicators
vwma = qtl.vwma(close, volume, period=20)

Return types

Input type Single output Multi output
np.ndarray np.ndarray tuple[np.ndarray, ...]
pd.Series pd.Series (preserves index) pd.DataFrame
pl.Series pl.Series pl.DataFrame
pa.Array pa.Array dict[str, pa.Array]

Multi-output indicators (Bollinger Bands, Stochastic, MACD, Ichimoku) return multiple arrays. Unpack them:

upper, mid, lower = qtl.bbands(close, period=20, std=2.0)
k, d = qtl.stoch(high, low, close, kLength=14, dPeriod=3)

Working with DataFrames

pandas

import pandas as pd
import quantalib as qtl

df = pd.read_csv("ohlcv.csv", parse_dates=["date"], index_col="date")

df["sma_20"] = qtl.sma(df["close"], period=20)
df["rsi_14"] = qtl.rsi(df["close"], period=14)
df["atr_14"] = qtl.atr(df["high"], df["low"], df["close"], period=14)

# Multi-output unpacks into separate columns
df["bb_upper"], df["bb_mid"], df["bb_lower"] = qtl.bbands(
    df["close"], period=20, std=2.0
)

The pandas index survives the round-trip. The output Series inherits the input's index, gets a name like "SMA_20", and stores the category in .attrs["category"].

polars

import polars as pl
import quantalib as qtl

df = pl.read_csv("ohlcv.csv")

df = df.with_columns(
    qtl.sma(df["close"], period=20).alias("sma_20"),
    qtl.rsi(df["close"], period=14).alias("rsi_14"),
)

pyarrow

import pyarrow as pa
import pyarrow.parquet as pq
import quantalib as qtl

table = pq.read_table("ohlcv.parquet")
close = table.column("close").combine_chunks()

rsi = qtl.rsi(close, period=14)   # pa.Array

Warmup Behavior

QuanTAlib produces output from bar 1. There are no NaN gaps at the beginning. A 20-period SMA with only 5 bars returns the average of those 5 bars — not the 20-period average (that would require prescience), but a mathematically defensible estimate that improves as data accumulates.

sma = qtl.sma(prices, period=20)
# sma[0] has a value — the best estimate given 1 bar
# sma[19] is the first fully-converged 20-period SMA
# All 20 values are finite numbers, not NaN

Early values are usable approximations, not garbage. This is a deliberate design decision. Other libraries leave NaN gaps. QuanTAlib fills them.

If your input data itself contains NaN (e.g., missing bars), those propagate through as the last valid value — QuanTAlib substitutes rather than spreading the disease.

pandas-ta Migration

Coming from pandas-ta? Two things changed:

  1. Parameter name: period is canonical. length still works as an alias.
  2. Function names: Most are identical. For ambiguous cases, use the compatibility layer:
from quantalib._compat import get_compat

# Resolve a pandas-ta name to a quantalib function
fn = get_compat("bbands")
if fn:
    result = fn(close, period=20)

Performance Reality Check

The native engine bypasses Python entirely for the math. No interpreter loop, no garbage collector pauses, no GIL contention. Your data goes in as a memory pointer, the CPU grinds through it at hardware speed, and the result comes back as a NumPy array. The Python overhead (marshaling the pointer, wrapping the result) adds about 10 μs — roughly the time it takes to blink, divided by 10,000.

To put the numbers in perspective:

Indicator quantalib (500K bars) pandas-ta (500K bars) How much faster
SMA 328 μs (⅓ of a millisecond) ~50 ms 150× faster
EMA 421 μs ~45 ms 107× faster
RSI 517 μs ~80 ms 155× faster

What does 150× mean in practice? If your pandas-ta backtest over 2,000 symbols takes 8 hours, quantalib finishes the same work in 3 minutes. That is the difference between "run it overnight and hope" and "run it while the coffee brews."

Error Handling

The C ABI returns status codes. The Python bridge converts them to exceptions:

from quantalib._bridge import QtlInvalidLengthError, QtlInvalidParamError

try:
    qtl.sma(prices, period=0)     # period must be > 0
except QtlInvalidLengthError:
    print("Period must be positive")

try:
    qtl.sma(np.array([]))         # empty array
except QtlInvalidLengthError:
    print("Array too short")

Platform Support

Platform Architecture Status
Windows x64 Pre-built wheel
Windows ARM64 Pre-built wheel
Linux x64 Pre-built wheel
Linux ARM64 (aarch64) Pre-built wheel
macOS x64 Pre-built wheel
macOS ARM64 (Apple Silicon) Pre-built wheel

The native shared library (quantalib.dll / libquantalib.so / libquantalib.dylib) is bundled inside the wheel. No separate installation, no system dependencies, no cmake rituals.

Going Deeper