RSI 지표 산출
RSI 지표에 대해 알아보고 업비트 시세 API 응답을 기반으로 RSI 지표를 산출하는 방법을 알아봅니다.
전체 코드 예제는 Recipes 메뉴에서 확인하실 수 있습니다.
위 버튼을 클릭하면 본 튜토리얼의 전체 코드 Recipe 페이지로 이동합니다.
시작하기
RSI(Relative Strength Index, 상대강도지수)는 주식과 선물 등 전통 금융 시장에서 널리 사용되는 기술적 분석 지표로서 디지털 자산 거래에서도 유의미한 지표로 사용됩니다. RSI의 개념과 계산 방법을 알아보고, 업비트 API를 활용하여 Python으로 디지털 자산의 RSI를 산출하는 방법을 단계별로 안내합니다.
RSI(상대강도지수)란?
RSI (Relative Strength Index, 상대강도지수)란 자산의 과매수, 과매도 상태를 판단하는 지표입니다. 0 ~ 100 사이의 값을 가질 수 있으며, 70 이상을 과매수 상태, 30 이하를 과매도 상태로 해석합니다. 과매수 또는 과매도 상태의 정도는 자산의 추세 전환 가능성 예측에 참고할 수 있습니다. RSI 계산 공식은 다음과 같습니다. RS는 상대강도(Relative Strength)를 의미합니다.
RSI = 100 - (100 / (1 + RS))
RS(상대강도) 계산 공식
RSI를 계산하기 위해서는 먼저 RS(상대강도)와 평균 상승폭, 평균 하락폭을 계산해야 합니다. RS를 계산하는 공식은 다음과 같습니다.
- RS 계산 공식
RS = 누적 평균 상승폭 / 누적 평균 하락폭
- 누적 평균 상승폭, 누적 평균 하락폭 계산 공식
누적 평균 상승폭 = (누적 평균 상승폭 * (기간 - 1) + 전일 종가 대비 변화 금액(양수)) / 기간
누적 평균 하락폭 = (누적 평균 하락폭 * (기간 - 1) + 전일 종가 대비 변화 금액(음수)) / 기간
이제 실제 디지털 자산의 누적 평균 상승폭, 누적 평균 하락폭을 계산해 RSI를 산출해 보겠습니다.
RSI 계산하기
디지털 자산의 전일 종가는 일(Day) 캔들 조회 API를 호출하여 쉽게 확인할 수 있습니다. 이 가이드에서는 비트코인의 캔들 데이터를 활용해 14일 동안의 평균 상승폭과 평균 하락폭을 구하고 RS와 RSI를 계산합니다.
def list_days_candles(market: str, count: int)-> Sequence:
params = {
"market": market,
"count": count,
}
url = "https://api.upbit.com/v1/candles/days"
headers = {
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
reversed_candle_data = response[::-1]
return reversed_candle_data
이 API는 market
과 to
, count
를 파라미터로 받습니다. 각 인자에 대한 설명은 다음과 같습니다.
market
: 조회할 마켓의 디지털 자산을 특정합니다. 이 가이드에서는 원화 마켓의 비트코인으로 특정합니다.to
: 캔들 데이터 조회 기간의 종료 시각을 설정합니다. count와 조합하여 조회할 캔들의 기간과 개수를 특정할 수 있습니다. ISO 8601 포맷(UTC)으로 입력합니다. 입력하지 않은 경우, 가장 최근 캔들 데이터 생성 시간을 기준으로 설정합니다. 이 가이드에서는 이 파라미터를 입력하지 않고 가장 최근 캔들 데이터를 기준으로 조회합니다.count
: 응답으로 받을 캔들 데이터의 개수를 설정합니다. 일반적으로 RSI는 14일의 캔들 데이터를 기준으로 계산합니다.
과거 시점으로 부터 변화된 종가를 계산해야 하므로 최신 순(내림차순)으로 반환된 캔들 데이터를 오름차순으로 재정렬합니다.
[
{
market: 'KRW-BTC',
candle_date_time_utc: '2025-06-23T00:00:00',
candle_date_time_kst: '2025-06-23T09:00:00',
opening_price: 140908000,
high_price: 147101000,
low_price: 139698000,
trade_price: 146182000,
timestamp: 1750723199727,
candle_acc_trade_price: 346783036638.01794,
candle_acc_trade_volume: 2422.95238077,
prev_closing_price: 140908000,
change_price: 5274000,
change_rate: 0.0374286769
},
...
{
market: 'KRW-BTC',
candle_date_time_utc: '2025-07-07T00:00:00',
candle_date_time_kst: '2025-07-07T09:00:00',
opening_price: 148500000,
high_price: 149100000,
low_price: 147196000,
trade_price: 148100000,
timestamp: 1751932799103,
candle_acc_trade_price: 123245812310.68657,
candle_acc_trade_volume: 832.29461691,
prev_closing_price: 148500000,
change_price: -400000,
change_rate: -0.0026936027
}
]
평균 상승폭, 평균 하락폭 계산하기
응답의 change_price
필드는 전일 종가 대비 가격의 변화량을 의미합니다. 이 값으로 아래와 같이 평균 상승폭과 평균 하락폭을 계산할 수 있습니다.
def calculate(candle_data: Sequence, period: int = 14) -> Mapping:
if len(candle_data) < period:
raise ValueError("At least {period} candle data are required.".format(period=period))
gains = []
losses = []
for item in candle_data:
change = item.get('change_price')
gains.append(change if change > 0 else 0)
losses.append(abs(change) if change < 0 else 0)
initial_au = sum(gains[:period]) / period
initial_ad = sum(losses[:period]) / period
au = initial_au
ad = initial_ad
for i in range(period, len(gains)):
au = (au * (period - 1) + gains[i]) / period
ad = (ad * (period - 1) + losses[i]) / period
RSI 계산에 앞서 먼저 충분한 개수의 일봉 캔들 데이터가 존재하는지 확인합니다. 이후 각 캔들 데이터의 change_price
값을 확인하여 두 개의 배열(gains, losses)에 값을 추가합니다.
• gains
배열: change_price
가 양수인 경우 해당 값을 추가하고, 음수이거나 0인 경우 0을 추가합니다.
• losses
배열: change_price
가 음수인 경우 해당 값을 절대값으로 변환해 추가하고, 양수이거나 0인 경우 0을 추가합니다.
이 과정을 거치면 gains
와 losses
배열은 캔들 데이터 개수와 동일한 길이를 가지게 됩니다.
if len(candle_data) < period:
raise ValueError("At least {period} candle data are required.".format(period=period))
gains = []
losses = []
for item in candle_data:
change = item.get('change_price')
gains.append(change if change > 0 else 0)
losses.append(abs(change) if change < 0 else 0)
gains
배열과 losses
배열의 첫 값과 period 기간의 값 까지의 합을 구하고 이를 period 기간의 수로 나누어 초기 평균 상승폭, 평균 하락폭을 계산합니다. 이후, gains 배열과 losses 배열의 값을 사용해 전체 캔들 데이터의 누적 평균 상승폭, 누적 평균 하락폭을 계산합니다.
initial_au = sum(gains[:period]) / period
initial_ad = sum(losses[:period]) / period
au = initial_au
ad = initial_ad
for i in range(period, len(gains)):
au = (au * (period - 1) + gains[i]) / period
ad = (ad * (period - 1) + losses[i]) / period
RS와 RSI 계산하기
누적 평균 상승폭과 누적 평균 하락폭을 통해 상대강도(RS)와 상대강도지수(RSI)를 계산할 수 있습니다. calculate
함수에 RS와 RSI를 계산하는 공식을 추가합니다.
RS를 계산할 때, 누적 평균 하락폭이 0인 경우 하락이 전혀 없었다는 의미이므로 RSI는 100으로 처리합니다. 누적 하락폭이 0이 아닌 경우, 누적 평균 상승폭을 누적 평균 하락폭으로 나누어 RS를 계산하고 공식에 대입해 RSI를 계산합니다.
def calculate(candle_data: Sequence[Mapping[str, Any]], period: int = 14) -> Mapping[str, Any]:
...
rs = float('inf') if ad == 0 else au / ad
rsi = (100 - 100 / (1 + rs))
return {
"AU": str(Decimal(au).quantize(Decimal("1e-4"), rounding=ROUND_DOWN)),
"AD": str(Decimal(ad).quantize(Decimal("1e-4"), rounding=ROUND_DOWN)),
"RS": str(Decimal(rs).quantize(Decimal("1e-4"), rounding=ROUND_DOWN)),
"RSI": str(Decimal(rsi).quantize(Decimal("1e-4"), rounding=ROUND_DOWN))
}
결과 확인
코드를 실행해 KRW-BTC
페어의 RSI를 조회할 수 있습니다.
if __name__ == "__main__":
candle_data = list_days_candles("KRW-BTC", 200)
rsi_data = calculate(candle_data, 14)
print(rsi_data)
{
"AU": "626931.7147",
"AD": "531397.2181",
"RS": "1.1797",
"RSI": "54.1238"
}
전체 코드 예제
RSI를 산출하는 전체 코드는 다음과 같습니다. 200일 간의 캔들 데이터로 누적 평균 상승,하락폭을 구하고 이를 사용해 14일간의 RSI를 계산할 수 있습니다.
from typing import Any
from collections.abc import Mapping, Sequence
import requests
from decimal import Decimal, ROUND_DOWN
def list_days_candles(market: str, count: int)-> Sequence:
params = {
"market": market,
"count": count,
}
url = "https://api.upbit.com/v1/candles/days"
headers = {
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
reversed_candle_data = response[::-1]
return reversed_candle_data
def calculate(candle_data: Sequence, period: int = 14) -> Mapping:
if len(candle_data) < period:
raise ValueError("At least {period} candle data are required.".format(period=period))
gains = []
losses = []
for item in candle_data:
change = item.get('change_price')
gains.append(change if change > 0 else 0)
losses.append(abs(change) if change < 0 else 0)
initial_au = sum(gains[:period]) / period
initial_ad = sum(losses[:period]) / period
au = initial_au
ad = initial_ad
for i in range(period, len(gains)):
au = (au * (period - 1) + gains[i]) / period
ad = (ad * (period - 1) + losses[i]) / period
rs = float('inf') if ad == 0 else au / ad
rsi = (100 - 100 / (1 + rs))
return {
"AU": str(Decimal(au).quantize(Decimal("1e-4"), rounding=ROUND_DOWN)),
"AD": str(Decimal(ad).quantize(Decimal("1e-4"), rounding=ROUND_DOWN)),
"RS": str(Decimal(rs).quantize(Decimal("1e-4"), rounding=ROUND_DOWN)),
"RSI": str(Decimal(rsi).quantize(Decimal("1e-4"), rounding=ROUND_DOWN))
}
if __name__ == "__main__":
candle_data = list_days_candles("KRW-BTC", 200)
rsi_data = calculate(candle_data, 14)
print(rsi_data)
Updated 5 days ago