RSI(상대강도지수)란 무엇인가?
RSI(Relative Strength Index)는 J. Welles Wilder가 1978년 개발한 모멘텀 오실레이터로, 가격의 상승 압력과 하락 압력의 상대적 강도를 0~100 사이의 수치로 표현합니다. 퀀트 트레이딩과 자동매매 시스템에서 가장 널리 사용되는 기술적 지표 중 하나이며, 특히 과매수(overbought)와 과매도(oversold) 구간을 판별하는 데 핵심적인 역할을 합니다.
RSI 공식은 다음과 같습니다:
RSI = 100 - (100 / (1 + RS))
RS = 평균 상승폭 / 평균 하락폭
= Average Gain over N periods / Average Loss over N periods
일반적으로 N=14(14일)를 기본 기간으로 사용하며, RSI 값이 70 이상이면 과매수, 30 이하면 과매도로 해석합니다. 이 단순한 규칙만으로도 상당히 효과적인 자동매매 전략을 구현할 수 있습니다.
RSI 기반 자동매매 전략의 핵심 로직
RSI 자동매매 전략은 크게 세 가지 접근 방식이 있습니다:
1. 기본 과매수/과매도 전략
가장 직관적인 방법입니다. RSI가 30 이하로 내려가면 매수하고, 70 이상으로 올라가면 매도합니다. 횡보장(레인지 마켓)에서 특히 효과적이며, 강한 추세장에서는 거짓 신호(false signal)가 발생할 수 있다는 점을 주의해야 합니다.
- 매수 조건: RSI가 30 아래에서 30 위로 교차(상향 돌파)
- 매도 조건: RSI가 70 위에서 70 아래로 교차(하향 돌파)
- 장점: 구현이 간단하고 백테스팅이 용이
- 단점: 추세장에서 조기 청산 가능성
2. RSI 다이버전스 전략
가격은 신고가를 경신하는데 RSI는 전고점을 넘지 못하는 약세 다이버전스(bearish divergence), 반대로 가격은 신저가를 기록하는데 RSI는 전저점보다 높은 강세 다이버전스(bullish divergence)를 활용합니다. 추세 전환 시점을 포착하는 데 매우 강력하지만, 자동화 구현 난이도가 높습니다.
3. RSI + 이동평균 복합 전략
RSI 단독 사용의 한계를 보완하기 위해 이동평균(MA)과 결합합니다. 예를 들어 200일 이동평균 위에서만 RSI 매수 신호를 따르고, 아래에서만 매도 신호를 따르는 방식입니다. 이렇게 하면 추세 방향과 일치하는 거래만 실행하여 승률을 크게 높일 수 있습니다.
파이썬으로 RSI 계산하기
파이썬의 pandas 라이브러리를 사용하면 RSI를 몇 줄로 구현할 수 있습니다. Wilder의 원래 방식인 지수이동평균(EMA) 기반 계산법을 사용합니다.
import pandas as pd
import numpy as np
def calculate_rsi(prices: pd.Series, period: int = 14) -> pd.Series:
"""Wilder's RSI 계산 (지수이동평균 방식)"""
delta = prices.diff()
gain = delta.where(delta > 0, 0.0)
loss = (-delta).where(delta < 0, 0.0)
# Wilder's smoothing (EMA with alpha = 1/period)
avg_gain = gain.ewm(alpha=1/period, min_periods=period).mean()
avg_loss = loss.ewm(alpha=1/period, min_periods=period).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
위 함수에서 ewm(alpha=1/period)는 Wilder의 스무딩 방식과 동일한 결과를 제공합니다. 일반 SMA 방식보다 최근 데이터에 더 큰 가중치를 부여하므로 시장 변화에 더 민감하게 반응합니다.
자동매매 시스템 전체 구현
이제 실제로 작동하는 RSI 자동매매 백테스팅 시스템을 구축해 보겠습니다. 이동평균 크로스오버 백테스팅 가이드에서 다룬 기본 프레임워크를 확장하는 형태입니다.
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
class RSIStrategy:
"""RSI 기반 자동매매 백테스팅 엔진"""
def __init__(self, ticker: str, period: int = 14,
oversold: float = 30, overbought: float = 70):
self.ticker = ticker
self.period = period
self.oversold = oversold
self.overbought = overbought
def fetch_data(self, start: str, end: str) -> pd.DataFrame:
"""Yahoo Finance에서 주가 데이터 다운로드"""
df = yf.download(self.ticker, start=start, end=end)
df['RSI'] = calculate_rsi(df['Close'], self.period)
return df
def generate_signals(self, df: pd.DataFrame) -> pd.DataFrame:
"""매매 신호 생성"""
df = df.copy()
df['Signal'] = 0
# 과매도 탈출 → 매수 (RSI가 30 아래에서 위로)
df.loc[(df['RSI'] > self.oversold) &
(df['RSI'].shift(1) <= self.oversold), 'Signal'] = 1
# 과매수 탈출 → 매도 (RSI가 70 위에서 아래로)
df.loc[(df['RSI'] < self.overbought) &
(df['RSI'].shift(1) >= self.overbought), 'Signal'] = -1
return df
def backtest(self, df: pd.DataFrame,
initial_capital: float = 10_000_000) -> dict:
"""백테스팅 실행 및 성과 분석"""
capital = initial_capital
position = 0
shares = 0
trades = []
for i, row in df.iterrows():
if row['Signal'] == 1 and position == 0:
shares = capital // row['Close']
capital -= shares * row['Close']
position = 1
trades.append({
'date': i, 'type': 'BUY',
'price': row['Close'], 'shares': shares
})
elif row['Signal'] == -1 and position == 1:
capital += shares * row['Close']
trades.append({
'date': i, 'type': 'SELL',
'price': row['Close'], 'shares': shares
})
shares = 0
position = 0
# 최종 포트폴리오 가치
final_value = capital + (shares * df.iloc[-1]['Close'])
total_return = (final_value - initial_capital) / initial_capital * 100
return {
'initial_capital': initial_capital,
'final_value': round(final_value),
'total_return': round(total_return, 2),
'num_trades': len(trades),
'trades': trades
}
이 코드의 핵심은 generate_signals() 메서드입니다. 단순히 RSI 값이 임계치를 넘는 것이 아니라, 교차(crossover) 시점을 포착합니다. shift(1)로 이전 봉의 RSI와 비교하여 정확한 돌파 시점에서만 신호를 발생시킵니다.
실전 최적화: RSI 파라미터 튜닝
RSI 전략의 성과는 파라미터 설정에 크게 좌우됩니다. 기본값(14, 30, 70) 대신 종목과 시장 특성에 맞는 최적값을 찾아야 합니다.
def optimize_parameters(ticker: str, start: str, end: str):
"""그리드 서치로 최적 RSI 파라미터 탐색"""
best_return = -float('inf')
best_params = {}
for period in [7, 10, 14, 21]:
for oversold in [20, 25, 30, 35]:
for overbought in [65, 70, 75, 80]:
strategy = RSIStrategy(
ticker, period, oversold, overbought
)
df = strategy.fetch_data(start, end)
df = strategy.generate_signals(df)
result = strategy.backtest(df)
if result['total_return'] > best_return:
best_return = result['total_return']
best_params = {
'period': period,
'oversold': oversold,
'overbought': overbought,
'return': best_return
}
return best_params
파라미터 최적화 시 주의할 점이 있습니다:
- 과적합(overfitting) 경계: 과거 데이터에 완벽하게 맞는 파라미터는 미래에 통하지 않을 수 있습니다. 반드시 학습 기간과 검증 기간을 분리하세요.
- Walk-Forward Analysis: 전체 기간을 여러 구간으로 나누어 순차적으로 최적화→검증을 반복하면 더 견고한 결과를 얻을 수 있습니다.
- 거래 비용 반영: 매매 횟수가 많아지면 수수료와 슬리피지가 수익을 크게 깎습니다. 파라미터 비교 시 반드시 거래 비용을 포함하세요.
리스크 관리: 손절매와 포지션 사이징
아무리 좋은 전략이라도 리스크 관리 없이는 큰 손실을 피할 수 없습니다. RSI 자동매매에 적용할 수 있는 핵심 리스크 관리 기법을 살펴봅니다.
고정 비율 손절매(Stop-Loss)
# 매수가 대비 -3% 손절
stop_loss_pct = 0.03
if position == 1:
unrealized_loss = (entry_price - current_price) / entry_price
if unrealized_loss >= stop_loss_pct:
# 강제 청산
execute_sell(current_price)
켈리 기준(Kelly Criterion) 포지션 사이징
전략의 승률과 평균 손익비를 기반으로 최적 투자 비율을 계산합니다:
def kelly_fraction(win_rate: float, avg_win: float, avg_loss: float) -> float:
"""켈리 비율 계산 — 과도한 레버리지 방지를 위해 Half-Kelly 적용"""
b = avg_win / avg_loss # 손익비
kelly = (win_rate * b - (1 - win_rate)) / b
return max(0, kelly * 0.5) # Half-Kelly
켈리 기준의 100%를 그대로 적용하면 변동성이 매우 커지므로, 실무에서는 Half-Kelly(50%)를 사용하는 것이 일반적입니다.
RSI 전략의 한계와 보완 방법
RSI 자동매매 전략을 실전에 적용하기 전에 반드시 알아야 할 한계점들이 있습니다:
- 추세장 약점: 강한 상승 추세에서 RSI는 70 이상에서 장기간 머물 수 있습니다. 이때 매도 신호를 따르면 큰 수익 기회를 놓칩니다. 해결책으로 이동평균 필터를 추가하여 추세 방향과 반대되는 신호를 걸러낼 수 있습니다.
- 횡보장 의존성: RSI는 본질적으로 평균회귀(mean reversion) 전략이므로, 레인지 바운드 시장에서 성과가 좋고 추세 시장에서는 부진합니다.
- 지표 후행성: RSI는 과거 가격 데이터를 기반으로 계산되므로 필연적으로 후행합니다. 급격한 가격 변동에 즉각 반응하지 못할 수 있습니다.
- 단일 지표 한계: RSI만으로 모든 시장 상황을 커버할 수 없습니다. MACD, 볼린저 밴드, 거래량 등 다른 지표와 결합하면 신호의 신뢰도를 높일 수 있습니다.
실시간 자동매매 시스템으로 확장하기
백테스팅에서 검증된 전략을 실시간 매매로 전환하려면 추가적인 인프라가 필요합니다:
- 데이터 피드: 실시간 가격 데이터를 받아오는 API 연동 (한국투자증권 OpenAPI, 업비트 WebSocket 등)
- 주문 실행: 브로커 API를 통한 자동 주문 발행 및 체결 확인
- 모니터링: 전략 실행 상태, 포지션, 손익을 실시간으로 추적하는 대시보드
- 알림 시스템: 이상 상황(API 오류, 급격한 손실 등) 발생 시 즉시 알림
# 실시간 자동매매 루프 (의사 코드)
import schedule
import time
def trading_loop():
"""매 분마다 실행되는 트레이딩 루프"""
price = broker_api.get_current_price(ticker)
prices_series = update_price_history(price)
rsi = calculate_rsi(prices_series, period=14).iloc[-1]
if rsi < 30 and not has_position():
size = kelly_fraction(win_rate, avg_win, avg_loss) * capital
broker_api.buy(ticker, size)
send_alert(f"매수 실행: RSI={rsi:.1f}")
elif rsi > 70 and has_position():
broker_api.sell(ticker, get_position_size())
send_alert(f"매도 실행: RSI={rsi:.1f}")
schedule.every(1).minutes.do(trading_loop)
while True:
schedule.run_pending()
time.sleep(1)
마무리: RSI 자동매매 체크리스트
RSI 기반 자동매매 전략을 성공적으로 운영하기 위한 핵심 체크리스트입니다:
- ✅ RSI 기간과 임계값을 종목 특성에 맞게 최적화했는가?
- ✅ 충분한 기간(최소 3~5년)의 백테스팅을 수행했는가?
- ✅ Walk-Forward Analysis로 과적합 여부를 검증했는가?
- ✅ 손절매와 포지션 사이징 규칙을 설정했는가?
- ✅ 거래 비용(수수료 + 슬리피지)을 반영한 수익률인가?
- ✅ 추세 필터(이동평균 등)로 거짓 신호를 줄였는가?
- ✅ 실시간 전환 시 오류 처리와 모니터링을 구축했는가?
RSI는 단순하지만 강력한 지표입니다. 핵심은 단독 사용이 아닌 복합 전략의 구성 요소로 활용하고, 철저한 리스크 관리와 함께 운영하는 것입니다. 백테스팅으로 전략을 검증하고, 소액으로 실전 테스트를 거친 후 점진적으로 규모를 키워 나가세요.