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