8 분 소요

Summary

직전 글 에서는 RSI · MACD · Bollinger 같은 지표들을 정의식부터 시작해서 pandas 만으로 직접 풀어봤어요. 그런데 사실 실무에선 거의 한 줄짜리 함수로 뽑아요. ta.rsi(...), talib.RSI(...) 처럼요.

그러면 “왜 직접 짜는 글부터 썼냐” 싶을 텐데, 한 번 직접 짜 본 사람만이 라이브러리가 뱉는 숫자를 신뢰할 수 있어요. 라이브러리마다 RSI 값이 살짝씩 다르거든요. 그 이유를 모르면 백테스트가 엇갈릴 때 어디부터 봐야 할지 막막해집니다.

이번 글은 그 다음 단계예요. 가장 많이 쓰이는 두 라이브러리(ta, TA-Lib)를 초보자 입장에서 처음부터 한 번 따라가 봅니다.

💡 이 글에서 다루는 것

  • ta vs TA-Lib — 두 라이브러리의 차이와 선택 기준
  • 설치 — ta(pip 한 방), TA-Lib(C 코어 함정, macOS/Ubuntu 별 가이드)
  • yfinance 로 데이터 받고 같은 DataFrame 에서 두 라이브러리 비교
  • RSI · MACD · Bollinger · ATR · SMA/EMA — 두 라이브러리로 뽑는 패턴
  • 직접 계산 vs ta vs TA-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

./configuregcc 를 찾지 못하면 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 는 두 가지 스타일을 동시에 제공해요.

  1. 함수형ta.momentum.rsi(close, window=14) 처럼 한 줄
  2. 클래스형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

taTA-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(표본 표준편차 분모 n vs n-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. 정리

  • taTA-Lib같은 일을 하는 두 도구. 다른 점은 설치 난이도, 속도, 입력 타입(Series vs ndarray) 뿐이에요.
  • 설치는 tapip 한 방, 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여 개를 백테스트에 결합해서, “패턴 신호가 정말 알파를 만드냐” 를 데이터로 확인해보는 흐름을 정리해볼게요.