DataFrame Properties and Methods

Programming Conventions

Pandas TA Classic has three primary “styles” of processing Technical Indicators for your use case and/or requirements. They are: Standard, DataFrame Extension, and the Pandas TA Classic Strategy. Each with increasing levels of abstraction for ease of use.

Standard Usage

You explicitly define the input columns and take care of the output.

Note

Column names are case-sensitive in Standard usage. Pass the exact column name from your DataFrame (e.g. df["Close"] or df["close"]).

import pandas_ta_classic as ta

# Simple Moving Average
sma10 = ta.sma(df["Close"], length=10)
# Returns a Series with name: SMA_10

# Donchian Channel
donchiandf = ta.donchian(df["High"], df["Low"], lower_length=10, upper_length=15)
# Returns a DataFrame named DC_10_15 and column names: DCL_10_15, DCM_10_15, DCU_10_15

# Chaining indicators
ema10_ohlc4 = ta.ema(ta.ohlc4(df["Open"], df["High"], df["Low"], df["Close"]), length=10)

DataFrame Extension

By default, df.ta reads the columns open, high, low, close and volume. A column is found by its exact name first, then by the same name in any case (Close, CLOSE); a column that only starts with the name (Open time for open) is not used. Pass close="my_col" (or high=, low=, …) to read another column. A column the indicator requires that the DataFrame does not have raises KeyError listing the available columns; an optional one (cpr’s volume) is simply left out.

The open column is the exception: it is read only where the indicator requires it (bop, ha, ohlc4, the candle patterns, …). ad, adosc, cmf and psl take an optional open_ that switches them to a variant built on close - open, and df.ta computes their classic form even when the DataFrame has an open column. Pass open="open" (or another column name) for the variant; ad then names its column ADo:

df.ta.ad()             # AD:  classic, high-low-close
df.ta.ad(open="open")  # ADo: the open variant, as ta.ad(..., open_=df["open"])

An argument the indicator requires and the DataFrame cannot supply raises ValueError naming it: df.ta.beta() without a benchmark, and df.ta.long_run(), short_run(), tsignals(), xsignals() or mavp() without their Series. Both used to return None, which looked like an ordinary empty result. Input that is merely shorter than the indicator’s window is data rather than a mistake and still yields an all-NaN result.

# Simple usage
sma10 = df.ta.sma(length=10)
# Returns a Series with name: SMA_10

# With chaining and suffix
ema10_ohlc4 = df.ta.ema(close=df.ta.ohlc4(), length=10, suffix="OHLC4")
# Returns a Series with name: EMA_10_OHLC4

# Appending results directly to DataFrame
df.ta.sma(length=10, append=True)
df.ta.donchian(lower_length=10, upper_length=15, append=True)

Properties

adjusted

# Set ta to default to an adjusted column, 'adj_close', overriding default 'close'.
df.ta.adjusted = "adj_close"
df.ta.sma(length=10, append=True)

# To reset back to 'close', set adjusted back to None.
df.ta.adjusted = None

adjusted is stored in df.attrs, which pandas copies into df.copy() and column subsets. Where the adjusted column is missing, reading the default close raises KeyError instead of silently using unadjusted prices; set adjusted = None on that frame, or pass close= explicitly.

categories

# List of Pandas TA categories.
df.ta.categories

cores

# Number of worker processes df.ta.strategy() may start.
# Defaults to 0: strategy() runs serially unless you ask for workers.
df.ta.cores = 4

# Back to serial execution.
df.ta.cores = 0

# Returns the number of cores you set, or 0.
df.ta.cores

Parallelism pays off from roughly 100,000 rows per DataFrame upwards; below that, starting the workers costs more than the indicators do. See Strategy System for when to use it and how to reuse a pool of your own.

datetime_ordered

# The 'datetime_ordered' property returns True if the DataFrame
# index is of Pandas datetime64 and df.index[0] < df.index[-1].
# Otherwise it returns False.
df.ta.datetime_ordered

last_run

df.ta.last_run is when df.ta(kind=...) or df.ta.strategy() last finished on this DataFrame, as a UTC datetime.datetime, or None before the first run. Strategy.created is a UTC datetime too.

from zoneinfo import ZoneInfo

df.ta.strategy(ta.CommonStrategy)
df.ta.last_run                                          # datetime(..., tzinfo=timezone.utc)
df.ta.last_run.astimezone(ZoneInfo("America/New_York"))  # the same instant in New York

Note

df.ta.exchange was removed in 0.9.0. It only chose whose clock the old last_run string showed; reading or setting it raises AttributeError. Convert last_run to the time zone you need instead.

reverse

# The 'reverse' is a helper property that returns the DataFrame
# in reverse order; useful for some indicators
df.ta.reverse

prefix & suffix

prefix and suffix are per-call kwargs, not settable properties on the accessor. Pass them directly to any indicator call to customise the output column name(s).

# Prefix the output column name
df.ta.sma(length=10, prefix="MY")       # column: MY_SMA_10

# Suffix the output column name
df.ta.sma(length=10, suffix="SLOW")     # column: SMA_10_SLOW

# Both together
df.ta.sma(length=10, prefix="MY", suffix="SLOW")  # column: MY_SMA_10_SLOW

# Works with multi-column results too (e.g. MACD)
df.ta.macd(prefix="MY", suffix="v1")   # columns: MY_MACD_12_26_9_v1, …

time_range

time_range controls the time unit used when df.ta.time_range is read back as a numeric value (e.g. for annualisation). Valid values are "years" (default), "months", "weeks", "days", "hours", "minutes", and "seconds". Spans are measured in calendar time: a year is 365.25 days, so "years" is the calendar span, not a count of trading days. Setting any other value raises ValueError.

# Return the span of the DataFrame in years (default)
df.ta.time_range = "years"
print(df.ta.time_range)   # e.g. 2.5

# Switch to months
df.ta.time_range = "months"
print(df.ta.time_range)   # e.g. 30.1

# Reset to the default unit
df.ta.time_range = "years"

to_utc

Note

df.ta.to_utc was removed in 0.9.0 and raises AttributeError. It was a property that converted df’s index when it was read, so hasattr() and IDE introspection changed the frame too.

Use the ta.to_utc function, which returns a copy with a UTC index (a naive index is localised, an aware one converted) and leaves its argument unchanged:

df = ta.to_utc(df)

Methods

indicators

indicators(**kwargs) — prints or returns the list of available indicators.

# Print all available indicators to stdout (returns None)
df.ta.indicators()

# Return indicators as a Python list
ind_list = df.ta.indicators(as_list=True)

# Return the list excluding specific indicators
ind_list = df.ta.indicators(as_list=True, exclude=["vp", "td_seq"])

Fetching data

Note

pandas-ta-classic does not download market data. df.ta.ticker(), ta.yf() and ta.av() were deprecated in 0.8.32 and removed in 0.9.0. Fetch OHLCV with yfinance or alpha-vantage and pass the DataFrame in; examples/fetch_market_data.py shows both.

import yfinance as yf

df = yf.download("AAPL", period="1y", interval="1d")
df.ta.rsi(append=True)

Removed helpers

Note

df.ta.constants(), ta.get_time, ta.EXCHANGE_TZ and ta.CDL_PATTERN_NAMES were deprecated in 0.8.32 and removed in 0.9.0. Add a constant column directly (df["0"] = 0) and use ta.ALL_PATTERNS for the candle pattern names. ta.utils.get_time and df.ta.exchange were removed in 0.9.0 as well; df.ta.last_run is a UTC datetime (see last_run).

chain

chain(append: bool = True) — activates fluent chaining mode, where every indicator call auto-appends to the DataFrame and returns the DataFrame (so .ta is available for the next call). Added in v0.6.

# Chain four indicators in a single expression
df.ta.chain().sma(20).ta.rsi(14).ta.macd().ta.bbands(20)

# With per-indicator kwargs
df.ta.chain().sma(20, prefix="FAST").ta.sma(50, prefix="SLOW")

# Rename output columns inline
df.ta.chain().bbands(
    20, col_names=("LOWER", "MID", "UPPER", "BW", "PCT")
)

Note

Chain state is stored on df.attrs and persists across .ta accessor re-entries. It applies only to the DataFrame chain() was called on: df.copy() and slices such as df.iloc[-100:] are not chained. Use unchain() to exit chain mode.

unchain

unchain() — deactivates fluent chaining mode and returns the DataFrame for normal, non-chained usage.

# Start chaining, then exit mid-expression
df.ta.chain().sma(20).ta.unchain().ta.rsi(14)
#                               ^
#                               rsi() returns a Series (not appended)

# Manual exit
df.ta.chain()
# ... do some chained calls ...
df.ta.unchain()  # chain mode deactivated