퀀트 지표 라이브러리 입문 — ta / TA-Lib 로 RSI · MACD · Bollinger 한 줄에
Summary
직전 글 에서는 RSI · MACD · Bollinger 같은 지표들을 정의식부터 시작해서 pandas 만으로 직접 풀어봤어요. 그런데 사실 실무에선 거의 한 줄짜리 함수로 뽑아요. ta.rsi(...), talib.RSI(...) 처럼요.
그러면 “왜 직접 짜는 글부터 썼냐” 싶을 텐데, 한 번 직접 짜 본 사람만이 라이브러리가 뱉는 숫자를 신뢰할 수 있어요. 라이브러리마다 RSI 값이 살짝씩 다르거든요. 그 이유를 모르면 백테스트가 엇갈릴 때 어디부터 봐야 할지 막막해집니다.
이번 글은 그 다음 단계예요. 가장 많이 쓰이는 두 라이브러리(ta, TA-Lib)를 초보자 입장에서 처음부터 한 번 따라가 봅니다.
💡 이 글에서 다루는 것
tavsTA-Lib— 두 라이브러리의 차이와 선택 기준- 설치 —
ta(pip 한 방),TA-Lib(C 코어 함정, macOS/Ubuntu 별 가이드)yfinance로 데이터 받고 같은 DataFrame 에서 두 라이브러리 비교- RSI · MACD · Bollinger · ATR · SMA/EMA — 두 라이브러리로 뽑는 패턴
- 직접 계산 vs
tavsTA-Lib— 같은 RSI 인데 값이 살짝씩 다른 이유- 언제 직접 짤지, 언제
ta, 언제TA-Lib인지 선택 가이드
0. 두 라이브러리 한눈에
| 항목 | ta |
TA-Lib (talib) |
|---|---|---|
| 언어 | 순수 Python | C 코어 + Python 래퍼 |
| 설치 | pip install ta 한 방 |
C 라이브러리 먼저 깔고 → pip install TA-Lib |
| 입력 | pd.Series / pd.DataFrame 친화 |
np.ndarray 입력/출력 (변환 필요) |
| 출력 | 같은 인덱스의 pd.Series |
같은 길이의 np.ndarray |
| 속도 | 보통 | 압도적으로 빠름 (수십~수백배) |
| 지표 수 | 약 40+ | 150+ (캔들 패턴 60+개 포함) |
| 디버깅 | 파이썬이라 코드 까보기 쉬움 | C 코어라 까보기 어려움 |
| 정확성 | 원작자 정의 대체로 따름 | 업계 표준으로 통용 |
요약하면 ta 는 빠르게 깔고 빠르게 쓰기 좋고, TA-Lib 는 한 번 깔면 프로덕션까지 가져갈 만한 친구 예요. 처음 배울 땐 ta 부터, 진지하게 굴릴 거면 TA-Lib 도 같이 알아두는 걸 추천드려요.
1. 설치
1-1. ta — pip 한 방
pip install ta
끝이에요. 의존성도 numpy, pandas 정도라 따로 신경 쓸 게 없어요.
✅ 확인
import ta print(ta.__version__)
1-2. TA-Lib — C 코어 먼저, 그 다음 pip
TA-Lib 은 1990년대 후반에 만들어진 C 라이브러리(libta-lib)의 파이썬 래퍼예요. 그래서 시스템에 C 라이브러리가 먼저 깔려 있어야 pip install TA-Lib 가 컴파일에 성공해요. 여기서 입문자분들이 가장 많이 막혀요.
macOS (Intel)
brew install ta-lib
pip install TA-Lib
macOS (Apple Silicon — M1/M2/M3)
기본은 위와 같아요.
brew install ta-lib
pip install TA-Lib
⚠️ 그런데 종종 이런 에러가 떠요.
ta_libc.h: No such file or directory
brew가 arm64(/opt/homebrew) 에 깔렸는데pip이 x86_64 Python 으로 찾으러 가는 경우예요. 두 방법 중 하나로 해결합니다.
방법 1 — Python 자체를 arm64 로 통일.
file $(which python)
# Mach-O 64-bit executable arm64 ← 이게 떠야 함
방법 2 — 컴파일러에 brew prefix 를 명시.
TA_LIBRARY_PATH=$(brew --prefix ta-lib)/lib \
TA_INCLUDE_PATH=$(brew --prefix ta-lib)/include \
pip install TA-Lib
Ubuntu / Debian
apt 저장소에 ta-lib 이 없어서 소스에서 빌드 해야 해요.
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
tar -xzf ta-lib-0.4.0-src.tar.gz
cd ta-lib/
./configure --prefix=/usr
make
sudo make install
pip install TA-Lib
./configure 가 gcc 를 찾지 못하면 sudo apt install build-essential 먼저 깔아주세요.
Windows
요즘은 pre-built wheel 이 PyPI 에 올라와 있어서 그냥 pip install TA-Lib 만 해도 대부분 됩니다. 안 되면 unofficial wheel 페이지 에서 본인 Python 버전(cp311, cp312 등)에 맞는 wheel 받아서 pip install <파일>.whl.
✅ 확인
import talib print(talib.__version__) print(talib.get_functions()[:10]) # 깔린 함수 목록 일부
2. 공통 데이터 준비 — yfinance 로 1년치
두 라이브러리 모두 똑같은 OHLCV 데이터를 입력으로 받아요. 무료로 받기 가장 편한 게 yfinance 예요.
pip install yfinance
import yfinance as yf
import pandas as pd
df = yf.download("AAPL", start="2025-01-01", end="2026-01-01", auto_adjust=True)
# 최근 yfinance 는 멀티레벨 컬럼을 반환해요. 한 번 풀어줍니다.
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
df.columns = df.columns.str.lower()
df = df[["open", "high", "low", "close", "volume"]]
print(df.head(3))
출력은 이런 모양이에요.
open high low close volume
Date
2025-01-02 248.93 249.10 241.82 243.85 55740700
2025-01-03 243.36 244.18 241.89 243.36 40244600
2025-01-06 244.31 247.33 243.20 245.00 45045600
💡 자기 데이터(증권사 CSV, FDR, KRX 다운로드 등)가 이미 있다면 컬럼명만
open / high / low / close / volume으로 맞추고 인덱스를DatetimeIndex로 만들면 그대로 이어집니다.
3. ta 로 지표 뽑기
ta 는 두 가지 스타일을 동시에 제공해요.
- 함수형 —
ta.momentum.rsi(close, window=14)처럼 한 줄 - 클래스형 —
RSIIndicator(close, window=14).rsi()처럼 한 번 만들어두고 메서드로 꺼냄
클래스형이 인자 이름이 명확해서 IDE 자동완성도 잘 되고, 같은 객체에서 여러 결과(MACD 의 main/signal/hist) 를 꺼낼 때 깔끔해요. 이 글은 클래스형으로 통일합니다.
3-1. RSI · MACD · Bollinger · ATR · SMA/EMA
from ta.momentum import RSIIndicator
from ta.trend import MACD, SMAIndicator, EMAIndicator
from ta.volatility import BollingerBands, AverageTrueRange
# RSI(14)
df["rsi14"] = RSIIndicator(close=df["close"], window=14).rsi()
# MACD(12, 26, 9)
macd = MACD(close=df["close"], window_slow=26, window_fast=12, window_sign=9)
df["macd"] = macd.macd()
df["macd_signal"] = macd.macd_signal()
df["macd_hist"] = macd.macd_diff()
# Bollinger Bands(20, 2σ)
bb = BollingerBands(close=df["close"], window=20, window_dev=2)
df["bb_upper"] = bb.bollinger_hband()
df["bb_mid"] = bb.bollinger_mavg()
df["bb_lower"] = bb.bollinger_lband()
# ATR(14)
df["atr14"] = AverageTrueRange(
high=df["high"], low=df["low"], close=df["close"], window=14
).average_true_range()
# SMA / EMA(20)
df["sma20"] = SMAIndicator(close=df["close"], window=20).sma_indicator()
df["ema20"] = EMAIndicator(close=df["close"], window=20).ema_indicator()
df.tail(3) 찍어보면 지표 컬럼들이 그대로 붙어있어요. 인덱스(Date) 가 보존돼서 그대로 백테스트로 넘기기 좋아요.
3-2. 한방에 다 — add_all_ta_features
지표를 한꺼번에 다 보고 싶을 땐 이런 단축도 있어요.
from ta import add_all_ta_features
df_all = add_all_ta_features(
df.copy(),
open="open", high="high", low="low", close="close", volume="volume",
fillna=False,
)
print(df_all.shape)
# 대략 (252, 89) — 89개 지표 컬럼이 생겨요
⚠️
fillna=True는 시계열 앞쪽 NaN 을 0 이나 forward fill 로 채워줘요. 백테스트에서는 이게 거짓 신호로 작동 할 수 있어요(아직 충분히 안 모인 구간을 “값이 있다” 고 착각함). 학습/EDA 용엔 켜도, 백테스트엔False추천 드려요.
⚠️ 컬럼명은 정확히 소문자 여야 해요.
Open / High / Low같이 대문자가 섞이면KeyError가 떨어집니다. 위에서df.columns.str.lower()한 게 이걸 막아주는 부분이에요.
4. TA-Lib 로 지표 뽑기
TA-Lib 은 입력이 np.ndarray 라서 살짝 다른 패턴이에요. pd.Series.values 또는 .to_numpy() 로 변환해서 넣고, 결과(np.ndarray)를 다시 DataFrame 컬럼에 꽂아요.
4-1. 같은 5종 — RSI · MACD · Bollinger · ATR · SMA/EMA
import talib
close = df["close"].to_numpy()
high = df["high"].to_numpy()
low = df["low"].to_numpy()
# RSI(14)
df["rsi14_talib"] = talib.RSI(close, timeperiod=14)
# MACD(12, 26, 9) → 세 개를 한 번에 반환
macd, signal, hist = talib.MACD(close, fastperiod=12, slowperiod=26, signalperiod=9)
df["macd_talib"] = macd
df["macd_signal_talib"] = signal
df["macd_hist_talib"] = hist
# Bollinger Bands(20, 2σ) — matype=0 은 SMA 기반
upper, mid, lower = talib.BBANDS(close, timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)
df["bb_upper_talib"] = upper
df["bb_mid_talib"] = mid
df["bb_lower_talib"] = lower
# ATR(14)
df["atr14_talib"] = talib.ATR(high, low, close, timeperiod=14)
# SMA / EMA(20)
df["sma20_talib"] = talib.SMA(close, timeperiod=20)
df["ema20_talib"] = talib.EMA(close, timeperiod=20)
함수 시그니처에서 인자 이름이 timeperiod, fastperiod, slowperiod 처럼 한 단어로 붙어 있는 게 ta 와 다른 점이에요. 이건 C 라이브러리 시절부터 내려온 이름이라 못 바꾸는 거니까 외워두면 편해요.
4-2. DataFrame 친화 — talib.abstract
매번 .to_numpy() 가 번거롭다면 abstract 모드가 있어요. DataFrame 을 통째로 넘기면 알아서 OHLCV 컬럼을 찾아요.
from talib import abstract
df["rsi14_abs"] = abstract.RSI(df, timeperiod=14)
bb_df = abstract.BBANDS(df, timeperiod=20, nbdevup=2, nbdevdn=2)
print(bb_df.head())
# upperband / middleband / lowerband 컬럼이 들어있는 DataFrame
abstract 모드는 df 안에 open/high/low/close/volume (소문자) 컬럼이 있다고 가정해요. 우리가 2. 에서 이미 그렇게 만들어둬서 바로 돌아갑니다.
💡 캔들 패턴(
CDLDOJI,CDLHAMMER,CDLENGULFING등) 60여 개는TA-Lib의 강점이에요.ta에는 없어요.df["doji"] = talib.CDLDOJI(df["open"].to_numpy(), df["high"].to_numpy(), df["low"].to_numpy(), df["close"].to_numpy()) df["hammer"] = talib.CDLHAMMER(df["open"].to_numpy(), df["high"].to_numpy(), df["low"].to_numpy(), df["close"].to_numpy()) # 값 0 = 패턴 없음, 100/-100 = 강세/약세 패턴
5. 직접 계산 vs ta vs TA-Lib — 같은 RSI 인데 왜 값이 다르냐
여기가 이 글에서 가장 짚고 가고 싶은 부분이에요. 같은 RSI(14) 인데 세 가지 값이 살짝씩 달라요.
5-1. 단순 평균으로 짠 RSI
직전 글에서 짠 식으로 한 번 RSI 를 만들어봐요.
import numpy as np
def rsi_sma(close: pd.Series, period: int = 14) -> pd.Series:
delta = close.diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.rolling(period).mean() # ← 단순 이동평균
avg_loss = loss.rolling(period).mean()
rs = avg_gain / avg_loss
return 100 - (100 / (1 + rs))
df["rsi14_manual_sma"] = rsi_sma(df["close"], 14)
이걸 ta, TA-Lib 값과 같이 찍어봐요.
cols = ["rsi14_manual_sma", "rsi14", "rsi14_talib"]
print(df[cols].dropna().tail(5).round(2))
rsi14_manual_sma rsi14 rsi14_talib
Date
2025-12-23 64.31 58.92 58.92
2025-12-24 66.07 60.41 60.41
2025-12-26 60.55 56.83 56.83
2025-12-29 58.71 55.14 55.14
2025-12-30 55.02 53.30 53.30
ta 와 TA-Lib 은 소수점까지 일치하는데, 직접 짠 SMA 버전만 4~6 정도씩 다르게 나옵니다.
5-2. 원인 — Wilder smoothing
RSI 원작자인 J. Welles Wilder 가 1978년 책에서 제안한 원래 정의는 단순 평균이 아니라 자기가 따로 정의한 평활식 이에요.
\[\overline{G}_t = \frac{(n-1)\,\overline{G}_{t-1} + G_t}{n}\]이게 사실상 α = 1/n 짜리 지수가중이동평균(EWMA) 이에요. 그래서 ta, TA-Lib 둘 다 Wilder 의 원래 정의를 따라가요. 단순 SMA 로 짜면 원작자 정의와 다른 값이 나오는 거고요.
💡 정리하면
- 직접 짠
rsi_sma— 14일 SMA 기반 (Wilder 정의가 아님)ta,TA-Lib— Wilder smoothing 기반 (원작자 정의 그대로)
5-3. Wilder 식으로 다시 짜면 라이브러리와 일치
pandas 로는 ewm(alpha=1/period, adjust=False) 가 정확히 Wilder smoothing 이에요.
def rsi_wilder(close: pd.Series, period: int = 14) -> pd.Series:
delta = close.diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.ewm(alpha=1/period, adjust=False).mean()
avg_loss = loss.ewm(alpha=1/period, adjust=False).mean()
rs = avg_gain / avg_loss
return 100 - (100 / (1 + rs))
df["rsi14_manual_wilder"] = rsi_wilder(df["close"], 14)
print(df[["rsi14_manual_wilder", "rsi14", "rsi14_talib"]].dropna().tail(5).round(4))
이러면 세 값이 (초반 워밍업 구간을 제외하고) 거의 똑같이 나옵니다. 차이가 사라지는 게 아니라, 정의를 맞춰주니까 같아지는 거예요.
⚠️ 같은 지표인데 라이브러리/구현마다 값이 다른 경우는 RSI 말고도 많아요. ATR(Wilder smoothing vs SMA), Bollinger(표본 표준편차 분모
nvsn-1), Stochastic(slow %K 의 추가 평활 여부) 등. 백테스트가 비교 글과 안 맞을 때 알고리즘 자체가 아니라 평활 방식이 다른 경우가 70% 더라고요.
6. 그래서 언제 무엇을 쓰나
직접 짤지, ta 를 쓸지, TA-Lib 을 쓸지 한 표로 정리.
| 상황 | 추천 | 이유 |
|---|---|---|
| 지표를 처음 배우는 단계 | 직접 짜기 | 정의식을 손으로 따라가야 라이브러리 출력의 의미가 잡힘 |
| 빠른 EDA / 프로토타입 | ta |
pip 한 방, pandas 친화, IDE 자동완성 잘 됨 |
| 횡단면 백테스트 (수천 종목 × 수년) | TA-Lib |
속도 차이가 체감됨. ta 로 돌리면 분 단위가 걸릴 일이 초 단위로 |
캔들 패턴(CDL...) 필요 |
TA-Lib |
60+개 패턴 함수 내장, ta 엔 없음 |
| Windows / 설치 환경 제약 | ta |
C 라이브러리 의존성 없어서 사고가 안 남 |
| 디버깅 / 알고리즘 커스터마이즈 | 직접 + ta |
둘 다 파이썬이라 한 줄씩 뜯어보기 좋음 |
| 사내 표준 / 외부와 결과 일치 중요 | TA-Lib |
업계에서 가장 많이 쓰이는 구현이라 비교 기준이 됨 |
저는 보통 ta 로 빠르게 짜다가, 같은 코드를 그대로 TA-Lib 로 옮겨 속도 튜닝 하는 식으로 굴려요. 두 라이브러리 모두 같은 입력(OHLCV DataFrame) 을 쓰니까 인덱서/평활 방식만 맞추면 결과가 일치하거든요.
7. 정리
ta와TA-Lib은 같은 일을 하는 두 도구. 다른 점은 설치 난이도, 속도, 입력 타입(Seriesvsndarray) 뿐이에요.- 설치는
ta는pip한 방,TA-Lib은 C 라이브러리 먼저(brew install ta-lib/ 소스 빌드) →pip install TA-Lib. - 라이브러리마다 RSI · ATR 같은 지표 값이 살짝씩 다른 가장 큰 이유는 Wilder smoothing 적용 여부. 직접 짤 때
ewm(alpha=1/n, adjust=False)로 맞추면 라이브러리와 일치합니다. - 직전 글에서 손으로 짜본 정의가 있으니까, 라이브러리가 뱉는 숫자도 이제 “왜 그렇게 나오는지” 가 보여요. 거기까지 가야 백테스트 결과를 의심 없이 신뢰할 수 있습니다.
일단 오늘은 여기까지…..
다음 글에서는 TA-Lib 의 캔들 패턴(CDLDOJI, CDLHAMMER, CDLENGULFING …) 60여 개를 백테스트에 결합해서, “패턴 신호가 정말 알파를 만드냐” 를 데이터로 확인해보는 흐름을 정리해볼게요.