본문 바로가기

[Toy Project] 자동매매

[Toy Project] 자동매매 프로그램 만들기 - 4. RSI 지표 계산하기

반응형

 

[목차]

 

1. RSI 지표란?

 

2. RSI 지표 공식

 

3. 업비트 API를 통해서 RSI 계산하기

 

 

  1. RSI 지표란?

 

RSI(Relative Strength Index)상대강도지수로, 주식, 선물, 옵션 등의 기술적 분석에 사용되는 보조 지표이다. 1978년 미국의 Welles Wilder가 개발했다. RSI는 가격의 상승 압력과 하락 압력 간의 상대적인 강도를 나타내며, 일정 기간 동안 주가가 전일 가격에 비해 상승한 변화량과 하락한 변화량의 평균값을 구하여, 상승한 변화량이 크면 과매수로, 하락한 변화량이 크면 과매도로 판단하는 방식이다. 

 

Welles Wilder는 70% 이상을 과매수, 30% 이하를 과매도 상태로 규정했다. 

 

 

  2. RSI 지표 공식

 

RSI 지표의 식이 어떻게 유도되는지 알아보자. (출처)

 

주어진 기간의 모든 날의 주가에 대해서

1. 가격이 전일 가격보다 상승한 날의 상승분을 U(up) 값이라고 하고, 하락한 날의 하락분은 D(down) 값이라고 한다. 

2. U와 D의 평균값을 각각 AU(average ups), AD(average downs)이라고 한다. 

3. AU를 AD로 나눈 값을 RS(relative strength) 값이라고 한다. RS가 크다는 것은 일정 기간 동안 하락한 폭보다 상승한 폭이 더 크다는 것을 의미한다.

4. RSI = RS / (1+ RS)을 통해서 RSI를 구한다. 보통 RSI는 백분율로 나타내므로 최종적으로 식에 100을 곱해준다. 

 

기간을 며칠 동안으로 할지는 직접 설정할 수 있는데, Welles Wilder는 14일을 사용할 것을 권장했다. 

 

하지만 위 공식만으로는 거래소에서 제공하는 RSI 값과 정확히 일치하지 않는다. 

실제 RSI값은 최근의 값에 조금 더 가중치를 두기 때문에 위 방식으로는 이를 고려하지 못한다. 

 

실제 정확한 RSI(기준 14일)를 구하는 공식을 알아보자. 

 

첫 번째 AU/AD 계산

- AU : 지난 14일 동안의 상승분의 합 / 14

- AD : 지난 14일 동안의 하락분의 합 / 14

 

이후의 AU/AD 계산 

- AU : [13 * 이전 AU + 현재 상승분] / 14

- AD : [13 * 이전 AD + 현재 하락분] / 14

 

제일 처음 계산하는 날의 AU/AD는 단순히 지난 14일 동안의 상승/하락분의 평균이다.

하지만 그다음 날부터의 AU/AD는 이전 날의 AU/AD * 13 + 현재 상승/하락분을 14로 나눈 값으로 갖는다. 

이렇게 계산하면 최근 값에 더 영향을 받게 되고, 실제로 이 값이 거래소에서 제공하는 RSI 값과 일치한다. 

 

따라서 후자의 공식을 이용할 예정이다. 

 

  3. 업비트 API를 통해서 RSI 계산하기

 

나의 매매 전략의 큰 비중을 차지할 예정인 이 실시간 RSI 지표를 업비트 API에서 자체적으로 제공해주지는 않는다. 따라서 업비트에서 제공하는 시세 캔들 조회 기능을 이용하여 직접 계산을 해주어야 한다. 

이제 이 RSI를 어떻게 코드를 통해서 계산하는지 알아보자.

(이 블로그를 참고하였습니다) 

 

우선, RSI를 구하기 위해서는 Pandas 모듈에 대해서 잘 알고 있는 것이 좋다. 시세 캔들 조회를 통해서 반환되는 데이터의 형태가 pandas.DataFrame이므로 이를 다루기 위해서 Pandas 모듈의 여러 메서드를 이용하기 때문이다. 

이 글에서는 Pandas 모듈에 대한 자세한 내용은 다루지 않고, RSI를 계산하는데 필요한 기능만 설명하겠다. 

 

먼저, 원하는 암호화폐와 원하는 주기를 선택하여 시세 캔들을 조회한다. 

 

* 코드는 5분 봉 기준으로 계산한다. 다만 설명할 때에는 편하게 '일' 기준으로 언급하겠다.

 

import pyupbit

data = pyupbit.get_ohlcv(ticker="KRW-XRP", interval="minute5")

print(data)
# OUTPUT
                       open    high     low   close        volume         value
2021-05-16 19:15:00  1940.0  1945.0  1935.0  1935.0  1.897473e+06  3.678875e+09
2021-05-16 19:20:00  1935.0  1940.0  1930.0  1930.0  3.161945e+06  6.116492e+09
2021-05-16 19:25:00  1930.0  1940.0  1930.0  1935.0  1.367690e+06  2.646188e+09
2021-05-16 19:30:00  1935.0  1940.0  1935.0  1935.0  5.738666e+05  1.111318e+09
2021-05-16 19:35:00  1940.0  1940.0  1925.0  1925.0  3.439253e+06  6.645471e+09
...                     ...     ...     ...     ...           ...           ...
2021-05-17 11:30:00  1710.0  1715.0  1695.0  1700.0  4.279192e+06  7.276236e+09
2021-05-17 11:35:00  1695.0  1710.0  1695.0  1705.0  2.603152e+06  4.432095e+09
2021-05-17 11:40:00  1700.0  1705.0  1690.0  1695.0  3.524970e+06  5.985498e+09
2021-05-17 11:45:00  1695.0  1705.0  1690.0  1695.0  2.598497e+06  4.413472e+09
2021-05-17 11:50:00  1695.0  1700.0  1690.0  1695.0  1.189717e+06  2.015690e+09

 

우리가 계산할 RSI는 '종가(close)' 기준이므로 close 열의 데이터만 따로 뽑아낸다. 

import pyupbit

data = pyupbit.get_ohlcv(ticker="KRW-XRP", interval="minute5")
closedata = data["close"]

print(closedata)
# OUTPUT 
2021-05-16 19:20:00    1930.0
2021-05-16 19:25:00    1935.0
2021-05-16 19:30:00    1935.0
2021-05-16 19:35:00    1925.0
2021-05-16 19:40:00    1930.0
                        ...
2021-05-17 11:35:00    1705.0
2021-05-17 11:40:00    1695.0
2021-05-17 11:45:00    1695.0
2021-05-17 11:50:00    1690.0
2021-05-17 11:55:00    1700.0
Name: close, Length: 200, dtype: float64

 

closedata를 출력해보면 종가(close) 열만 남은 것을 볼 수 있다. 

data의 타입을 출력하면 DataFrame으로 나오지만, closedata의 타입을 출력하면 Series로 나오게 된다.

즉, 2차원인 DataFrame에서 하나의 열을 뽑아내어 1차원인 Series로 반환하게 되는 것이다. 

 

이제 앞에서 설명한 RSI 공식에서, U와 D값을 계산해주어야 한다. 이를 위해서는 당일 종가가 전일 종가와 비교해서 얼마나 상승/하락했는지가 필요한데, 이는 Series의 diff 메서드를 이용하면 간편하게 구할 수 있다. 

 

diff 메서드의 형태는 Series.diff(period=1)을 갖는다.  

period는 얼마나 앞에 있는 원소와 비교할지를 의미한다. period = 1인 경우는 바로 앞의 원소와의 차이를 구한다. 

예를 들어서 [1, 3, 4, 6, 10]의 series가 있다고 하자. 

여기에 diff(1)을 적용하면 [NaN, 2, 1, 2, 4]가 구해진다. 

만약 diff(2)를 적용하면 [NaN, NaN, 3, 3, 6]이 나오게 된다. period만큼의 앞에 원소가 존재하지 않는 경우는 NaN이 들어간다. 

 

따라서 diff 메서드를 이용하면 U와 D를 쉽게 구할 수 있다. 

import pyupbit

data = pyupbit.get_ohlcv(ticker="KRW-XRP", interval="minute5")
closedata = data["close"]
delta = closedata.diff()

print(delta)
# OUTPUT

2021-05-16 20:15:00     NaN
2021-05-16 20:20:00     5.0
2021-05-16 20:25:00    -5.0
2021-05-16 20:30:00     5.0
2021-05-16 20:35:00   -10.0
                       ...
2021-05-17 12:30:00    35.0
2021-05-17 12:35:00    10.0
2021-05-17 12:40:00   -10.0
2021-05-17 12:45:00    -5.0
2021-05-17 12:50:00    10.0
Name: close, Length: 200, dtype: float64

 

여기서 AU와 AD를 구하기 위해서는 상승한 날의 값과 하락한 날의 값을 각각 따로 뽑아내어 평균값을 계산해주어야 하는데, 굳이 직접 반복문을 통해서 계산할 필요 없이 delta를 그대로 하나 더 카피하여 하나는 양수만, 하나는 음수만 남도록 만들어주면 평균을 구해주는 메서드를 통해서 바로 계산할 수 있다. 

import pyupbit

data = pyupbit.get_ohlcv(ticker="KRW-XRP", interval="minute5")
closedata = data["close"]
delta = closedata.diff()

ups, downs = delta.copy(), delta.copy()
ups[ups < 0] = 0
downs[downs > 0] = 0

print(ups)
print(downs)

# OUTPUT
2021-05-16 20:20:00     NaN
2021-05-16 20:25:00     0.0
2021-05-16 20:30:00     5.0
2021-05-16 20:35:00     0.0
2021-05-16 20:40:00     0.0
                       ...
2021-05-17 12:35:00    10.0
2021-05-17 12:40:00     0.0
2021-05-17 12:45:00     0.0
2021-05-17 12:50:00     5.0
2021-05-17 12:55:00     0.0
Name: close, Length: 200, dtype: float64
2021-05-16 20:20:00     NaN
2021-05-16 20:25:00    -5.0
2021-05-16 20:30:00     0.0
2021-05-16 20:35:00   -10.0
2021-05-16 20:40:00   -10.0
                       ...
2021-05-17 12:35:00     0.0
2021-05-17 12:40:00   -10.0
2021-05-17 12:45:00    -5.0
2021-05-17 12:50:00     0.0
2021-05-17 12:55:00     0.0
Name: close, Length: 200, dtype: float64

 

ups에서는 양수인 값만 남은 것을 볼 수 있고, downs에서는 음수인 값만 남은 것을 볼 수 있다. 

 

이제 RS를 구하기 위해서 AU와 AD를 계산해야 한다. 

앞서 말했듯이 최근에 가중치를 더 주어 평균을 계산하는 계산법을 이용하는데, 이를 지수 이동 평균 (EMA, Exponential Moving Average)라고 한다. 

pandas 모듈에서, 지수 이동 평균을 구해주는 함수인 ewm 함수(레퍼런스)가 존재하므로 이를 이용하면 간편하다. 

 

import pyupbit

data = pyupbit.get_ohlcv(ticker="KRW-XRP", interval="minute5")
closedata = data["close"]
delta = closedata.diff()

ups, downs = delta.copy(), delta.copy()
ups[ups < 0] = 0
downs[downs > 0] = 0

period = 14
au = ups.ewm(com = period-1, min_periods = period).mean()
ad = downs.abs().ewm(com = period-1, min_periods = period).mean()

print(au)
print(ad)

# OUTPUT
2021-05-16 21:40:00         NaN
2021-05-16 21:45:00         NaN
2021-05-16 21:50:00         NaN
2021-05-16 21:55:00         NaN
2021-05-16 22:00:00         NaN
                         ...
2021-05-17 13:55:00    7.566141
2021-05-17 14:00:00    7.025703
2021-05-17 14:05:00    6.523866
2021-05-17 14:10:00    6.057876
2021-05-17 14:15:00    5.625170
Name: close, Length: 200, dtype: float64
2021-05-16 21:40:00         NaN
2021-05-16 21:45:00         NaN
2021-05-16 21:50:00         NaN
2021-05-16 21:55:00         NaN
2021-05-16 22:00:00         NaN
                         ...
2021-05-17 13:55:00    4.985107
2021-05-17 14:00:00    4.629028
2021-05-17 14:05:00    4.298383
2021-05-17 14:10:00    4.348499
2021-05-17 14:15:00    4.395034
Name: close, Length: 200, dtype: float64

 

com 매개변수는 비율을 얼마나 감소시킬지를 결정한다. 1/(1+com)의 비율로 현재 값을 반영하기 때문에 com자리에는 '주기 - 1'만큼의 값을 넣어주어야 한다. 

그리고 min_periods값을 갖기 위해서 최소 얼마만큼의 관측 개수가 있어야 하는지를 말한다. AU와 AD를 계산하기 위해서 최소 14일의 데이터는 존재해야 하기 때문에 min_periods에 14를 넣어준다. 

만약 RSI를 14일이 아니라 a일 기준으로 한다면 period = a로 바꿔주면 된다. 

 

그렇게 계산한 후 평균을 구해주는 mean() 함수를 이용하고 나서 au와 ad를 출력해보면 처음 13일은 NaN이, 나머지는 각 날짜별로 잘 계산된 것을 볼 수 있다. 

이제 AU와 AD를 구했으니 RSI를 계산할 일만 남았다. 

 

import pyupbit
import pandas

data = pyupbit.get_ohlcv(ticker="KRW-XRP", interval="minute5")
closedata = data["close"]
delta = closedata.diff()

ups, downs = delta.copy(), delta.copy()
ups[ups < 0] = 0
downs[downs > 0] = 0

period = 14
au = ups.ewm(com = period-1, min_periods = period).mean()
ad = downs.abs().ewm(com = period-1, min_periods = period).mean()

RS = au/ad
RSI = pandas.Series(100 - (100/(1+RS)))
print(RSI)

# OUTPUT
2021-05-16 22:00:00          NaN
2021-05-16 22:05:00          NaN
2021-05-16 22:10:00          NaN
2021-05-16 22:15:00          NaN
2021-05-16 22:20:00          NaN
                         ...
2021-05-17 14:15:00    52.402765
2021-05-17 14:20:00    54.049178
2021-05-17 14:25:00    48.616133
2021-05-17 14:30:00    53.635245
2021-05-17 14:35:00    51.818280
Name: close, Length: 200, dtype: float64

 

RSI를 Series 타입으로 저장하기 위해서 pandas 모듈을 import 해준다. 

RS = AU / AD 식을 통해 RS를 구하고, RSI = RS / (1 + RS) 공식을 통해서 RSI를 계산해준다. 

백분율이므로 100을 곱해주는 점을 잊지 말자. 

마지막으로 RSI를 출력해보면 위와 같이 나온다.

 

 

마지막으로 14:35:00에 구한 RSI값이 실제로 업비트에서 제공하는 RSI 값과 동일한 것을 볼 수 있다. 

 

결국 우리가 RSI를 참고할 땐 현재의 RSI를 이용하기 때문에, 최종적으로 1초마다 현재의 RSI를 구하는 코드를 작성해보자. 

RSI를 구하는 부분은 함수로 별도로 작성하였다. 

 

import pyupbit
import pandas
import datetime
import time

def rsi(ohlc: pandas.DataFrame, period: int = 14):
    delta = ohlc["close"].diff()
    ups, downs = delta.copy(), delta.copy()
    ups[ups < 0] = 0
    downs[downs > 0] = 0

    AU = ups.ewm(com = period-1, min_periods = period).mean()
    AD = downs.abs().ewm(com = period-1, min_periods = period).mean()
    RS = AU/AD

    return pandas.Series(100 - (100/(1 + RS)), name = "RSI")  


while True:
    data = pyupbit.get_ohlcv(ticker="KRW-XRP", interval="minute5")
    now_rsi = rsi(data, 14).iloc[-1]
    print(datetime.datetime.now(), now_rsi)
    time.sleep(1)
    
    
# OUTPUT
2021-05-17 14:46:48.125921 46.27291512881062
2021-05-17 14:46:49.268340 44.58472080023427
2021-05-17 14:46:50.396784 46.27291512881062
2021-05-17 14:46:51.585416 46.27291512881062
2021-05-17 14:46:53.148593 46.27291512881062
2021-05-17 14:46:54.443165 46.27291512881062
2021-05-17 14:46:55.824253 44.58472080023427
2021-05-17 14:46:57.033643 44.58472080023427
2021-05-17 14:46:58.205494 44.58472080023427
2021-05-17 14:46:59.358971 44.58472080023427
2021-05-17 14:47:00.496720 46.27291512881062
2021-05-17 14:47:01.709961 46.27291512881062
2021-05-17 14:47:02.878125 46.27291512881062
2021-05-17 14:47:04.496573 46.27291512881062

 

iloc은 입력한 인덱스에 들어있는 값을 반환하는 함수이다. -1은 뒤에서 첫 번째의 원소를 반환하므로 현재 RSI의 값을 구할 수 있게 된다. 

그리고 1초마다 구하기 위해서 time.sleep 함수를 이용한다. 1초 동안 아무 작업도 하지 않도록 하여, 1초마다 RSI를 출력하도록 한다. 

API를 호출하는데 걸리는 시간으로 인해 정확히는 1초가 살짝 넘는 시간마다 RSI 값을 출력하는 것을 볼 수 있다. 

 

 

PC로 보시는 것을 권장합니다. 

피드백은 언제나 환영입니다. 댓글로 달아주세요 ^-^

 

 

반응형