Docs
Open Studio

Guides

Technical indicators

On this page7

data.indicator() supports TA-Lib and pandas_ta with hundreds of technical indicators. One line calculates thousands of stocks over long time periods, for individual stock analysis or machine learning. The result is a date-by-ticker table, like data.get(), so you can use it directly in a stock-selection rule.

Install TA-Lib

!pip install ta-lib

For more parameters and functions, see the TA-Lib official documentation.

Compute RSI for every stock

Pass the indicator's name and parameters to data.indicator(), for example RSI (timeperiod=14):

from finlab import data

data.set_market('us')
rsi = data.indicator('RSI', timeperiod=14)
rsi[['AAPL', 'MSFT', 'NVDA']].tail()
  • It computes every stock in the active market. The result is a DataFrame indexed by date with one column per ticker.
  • The first 14 days are NaN because there is not enough data yet.
  • By default the indicator uses unadjusted prices; pass adjust_price=True for adjusted prices, or resample='W' for weekly bars.

Compute a two-line indicator: Stochastic (KD)

data.indicator('STOCH') computes the Stochastic Oscillator (KD values) and returns two DataFrames (K and D):

from finlab import data

data.set_market('us')
k, d = data.indicator('STOCH')
k

Pick stocks where K is above D

List stocks where K > D on the most recent day:

signal = (k > d).iloc[-1]
signal[signal].index  # tickers with K above D on the latest date

(k > d).iloc[-1] is a boolean Series indexed by ticker for the latest date.

Indicators beyond the classic set

data.indicator() looks up TA-Lib first, by upper-case name. Newer TA-Lib releases include indicators such as SuperTrend, which returns the trend line and its direction (1 up, -1 down):

from finlab import data

data.set_market('us')
supertrend, direction = data.indicator('SUPERTREND')

When TA-Lib has no function of that name, a lower-case name is computed with pandas_ta if it is installed (it may be slower).

Build your own indicators with Pandas

Build custom indicators directly with Pandas operations, no TA-Lib required.

Momentum, moving-average deviation and revenue growth

from finlab import data

data.set_market('us')
close = data.get('price:adj_close')
volume = data.get('price:volume')
revenue = data.get('us_income_statement:revenue')  # quarterly

# Momentum: N-day return
momentum_20 = close / close.shift(20) - 1

# Moving average deviation
ma60 = close.average(60)
bias = (close - ma60) / ma60

# Price-volume divergence: price at high but volume shrinking
price_high = close == close.rolling(60).max()
volume_low = volume < volume.average(20)
divergence = price_high & volume_low

# Quarterly revenue growth
rev_yoy = revenue / revenue.shift(4) - 1  # year-over-year
rev_qoq = revenue / revenue.shift(1) - 1  # quarter-over-quarter
rev_trend = (
    revenue.rolling(2).mean() / revenue.rolling(4).mean() - 1
)  # last 2 vs last 4 quarters

Combine factors into one score (.cs)

.cs computes across all stocks on each date. Standardize each factor, then add them with equal weight:

eps = data.get('us_income_statement:eps_diluted')
net_income = data.get('us_income_statement:net_income')
equity = data.get('us_balance_sheet:total_stockholders_equity')

raw_close = data.get('price:close')  # unadjusted price for valuation
pe = raw_close / eps.rolling(4).sum()  # trailing-12-month P/E
roe = net_income / equity[equity > 0]  # quarterly ROE, positive equity only

# Multi-factor composite: standardize then equal-weight sum
score = roe.cs.zscore() + (-pe).cs.zscore()  # higher ROE, lower PE = better

# Select top 10% by composite score
position = score.cs.rank() > 0.9

Neutralize by sector (.sector)

Selecting on raw ROE overweights sectors where ROE is high anyway. Standardizing within each sector picks the standouts of each sector instead:

# Problem: raw ROE selection overweights high-ROE sectors (e.g., technology)
# Solution: within-sector standardization selects "sector standouts"
profile = data.get('us_company_profile')
sectors = dict(zip(profile['symbol'], profile['sector']))

roe_neutral = roe.sector(sectors).zscore()  # sector-neutralized
rev_neutral = rev_yoy.sector(sectors).zscore()  # revenue growth also neutralized

# Composite factor
score = roe_neutral + rev_neutral
position = score.cs.rank() > 0.8

# Or use within-sector ranking directly
position = roe.sector(sectors).rank() > 0.7  # top 30% ROE within each sector

Without a mapping, .sector uses the Taiwan industry classification; for U.S. stocks always pass one.

Set weights and backtest (.weight)

Once you have a selection, .weight sets each holding's weight; then hand the weights to sim():

from finlab.backtest import sim

position = score.cs.rank() > 0.8

# Refined weights: inverse volatility + turnover limit
weights = (
    position.weight.inverse_volatility(window=60, price=close)
    .rebalance('QE')  # keep rebalance dates only, so turnover is per rebalance
    .weight.limit_turnover(max_turnover=0.3)
)

report = sim(weights, resample='Q')

inverse_volatility() reads Taiwan prices unless you pass price. weight.cap_industry() caps Taiwan industry weights and is Taiwan market only.

See the finlab.dataframe reference for all .cs, .sector, and .weight methods.

Next steps

↑↓ Move Enter Open Esc Close