Post List

2019년 3월 18일 월요일

R을 이용한 텔레그램봇 만들기 (관심종목 공시정보를 실시간으로 전송하기)



지난 포스팅들을 통해 전자공시시스템(DART)의 API를 이용하여 데이터를 받는 방법, R과 텔레그램을 연결하는 방법, 그리고 스케쥴러를 이용하여 자동으로 정보를 제공하는 방법에 대해 알아보았습니다.


이번 시간에는 위의 기능들을 활용하여, 관심종목들의 공시정보를 실시간으로 텔레그램에 전송하는 방법에 대해 배워보도록 하겠습니다.

# 텔레그램 연결
library(telegram.bot)
bot = Bot(token = "Your Telegram API Key")
updates = bot$getUpdates()
chat_id = updates[[1]]$message$chat$id

# dart api 연결
library(jsonlite)
library(readr)

먼저 텔레그램봇의 채팅방 ID를 불러오도록 합니다. chat_id에 제대로 값을 불러오지 못할 경우, 해당 채팅방에서 아무 텍스트나 입력한 후 다시 코드를 실행시키면 제대로 값이 불러와집니다. 그 후 공시정보를 받아오기 위한 jsonlite 패키지와 csv 파일을 읽기위한 readr 패키지를 불러옵니다.

그 후에는 1. 관심종목 리스트, 2. 전송된 공시 리스트 파일을 만들어야 합니다.




먼저 관심종목은 KRX300 지수의 구성종목들을 예시로 하며, 현재 전송된 공시가 없으므로 빈 파일이 됩니다. 해당 파일을 각각 'krx_300.csv''rcp_list.csv' 이름으로 저장합니다.


# Default
# guess_encoding('C:/Users/Henry/Dropbox/R/telegram_bot/krx_300.csv')
my.universe = read_csv('C:/Users/Henry/Dropbox/R/telegram_bot/krx_300.csv',
                       locale = locale(encoding = 'EUC-KR'),
                       col_names = FALSE)
my.universe = unlist(my.universe)

rcp_list = read_csv('C:/Users/Henry/Dropbox/R/telegram_bot/rcp_list.csv',
                    locale = locale(encoding = 'EUC-KR'),
                    col_names = FALSE)
rcp_list = unlist(rcp_list)


먼저 readr 패키지의 guess_encoding() 함수를 이용하여 저장한 파일의 인코딩 정보를 확인합니다. 대부분의 경우 UTF-8 이거나 EUC-KR 형태로 저장되어 있습니다. read_csv() 함수를 이용하여 csv 파일을 불러오며 관심종목은 my.universe에, 전송 리스트는 rcp_list에 저장하도록 합니다. 또한 unlist() 함수를 이용하여 벡터 형태로 변경해 주도록 합니다.

이번에는 공시 정보를 받을 API 주소를 만들도록 합니다. 당일 공시 최근 100건의 API 주소는 다음과 같습니다.

http://dart.fss.or.kr/api/search.json?auth=YOUR_DART_API_KEY&page_set=100


해당 url을 웹페이지에 직접 입력하면 다음과 같이 JSON 형태로 데이터를 보여줍니다.



이제 위의 url 주소를 코드 내에서 만들어 주어야 합니다.


api.key = 'YOUR_DART_API_KEY'
url = paste0('http://dart.fss.or.kr/api/search.json?auth=',
       api.key,'&주요사항보고서&page_set=100')
url
[1] "http://dart.fss.or.kr/api/search.json?auth=YOUR_DART_API_KEY&주요사항보고서&page_set=100"


원래 대로라면 위의 코드를 이용하여 금일 공시에 해당하는 url 정보를 만들 수 있습니다. 그러나 문제는 '주요사항보고서'라는 한글에 있습니다. 코드 내에 한글이 있을 경우 나중에 스케쥴러를 통한 코드가 돌아가는데 에러가 발생하므로, 해당 글자를 UTF-16에 해당하는 문자(\uc8fc\uc694\uc0ac\ud56d\ubcf4\uace0\uc11c)로 바꿔주도록 하겠습니다. 이를 적용한 코드는 다음과 같습니다. 인코딩 컨버팅은 [Link]에서 확인하실 수 있습니다.


api.key = 'YOUR_DART_API_KEY'
url = paste0('http://dart.fss.or.kr/api/search.json?auth=',
             api.key,'&','\uc8fc\uc694\uc0ac\ud56d\ubcf4\uace0\uc11c','&page_set=100')
url
[1] "http://dart.fss.or.kr/api/search.json?auth=YOUR_DART_API_KEY&주요사항보고서&page_set=100

UTF-16 형태로 입력하여도 출력되는 형태는 기존 한글과 동일합니다. 이제 위에서 만든 주소를 이용하여 공시 데이터를 다운로드 받도록 하겠습니다.


data = fromJSON(url)
data.df = data$list
head(data.df, 3)
  crp_cls       crp_nm   crp_cd                                 rpt_nm
1       K     유틸렉스   263050 임원ㆍ주요주주특정증권등소유상황보고서
2       E 리젠시빌주택 01026412                             임원의변동
3       K         화진   134780                     [기재정정]참고서류
          rcp_no              flr_nm   rcp_dt rmk
1 20190318000297 DE LA CALLE AGUSTIN 20190318    
2 20190318000296        리젠시빌주택 20190318  공
3 20190318000295              황해용 20190318  

crp_nm은 종목 이름, crp_cd는 종목 티커, rpt_nm은 공시 제목, rcp_no는 공시 번호를 나타냅니다. 이중 rcp_no를 이용하여 해당 공시의 url을 생성할 수 있습니다.




회사별검색을 해보면 가장 상단에 조회되는 내용이 우리가 찾고자 하는 공시입니다.



해당 공시를 클릭하여 url 정보를 확인해보면, 위의 rcpNo= 글자 뒤의 문서번호가 우리가 다운로드 받은 JSON 형식의 데이터에서 rcp_no 컬럼의 데이터와 동일함을 알수 있습니다. 이를 이용하면 모든 공시들의 url 정보를 생성할 수 있습니다.


data.df$name = paste(data.df$crp_nm, data.df$rpt_nm)
data.df$url = paste0('http://dart.fss.or.kr/dsaf001/main.do?rcpNo=',
                     data.df$rcp_no)

먼저 crp_nm과 rpt_nm을 합쳐 '기업명 + 공시제목'의 형태를 name 열에 저장합니다. 또한 url의 공통부분과 rcp_no를 합쳐 공시별 url 정보를 생성합니다.

data.new = data.df
data.new = data.new[data.new$crp_nm %in% my.universe, ]
data.new = data.new[!data.new$rcp_no %in% rcp_list, ]

send.message = paste(data.new$name, data.new$url, sep = '\n')
sapply(send.message, function(x) {bot$sendMessage(chat_id = chat_id, x)})


공시데이터 중 1) 관심 종목에 해당하는 종목, 2) 이미 전송하지 않은 공시에 해당하는 부분만을 추출하도록 합니다. 먼저 crp_nm 즉 종목명이 my.universe에 존재하는 행만을 선택합니다. 그 후 rcp_no 즉 공시번호가 rcp_list에 존재하지 않는, 즉 이전에 전송되지 않은 행만을 선택하도록 합니다. 그 후 name, url  정보를 합쳐 send.message 변수에 저장하며, bot$sendMessage() 함수를 통해 텔레그램에 전송하도록 합니다.




금일 공시 내역 중 관심종목에 해당하는 종목, 그리고 전송되지 않은 공시 내용의 제목과 url이 전송되는 것이 확인됩니다. 해당 url을 클릭하면 각 공시 내용의 웹페이지로 이동하게 됩니다.


# 리스트에 저장
rcp_list = c(rcp_list, data.new$rcp_no)
write_csv(data.frame(rcp_list),
          path ='C:/Users/Henry/Dropbox/R/telegram_bot/rcp_list.csv',
          col_names = FALSE)


공시를 중복으로 전송하는 일을 막기위해, 전송된 공시 리스트를 새로 업데이트 하도록 합니다. 기존 rcp_list와 새로운 rcp_no를 합친 후, 'rcp_list.csv' 파일에 새롭게 저장하도록 합니다.


rcp_list.csv 파일을 확인해보면, 방금 전송된 공시들의 문서번호가 저장되어 있습니다. 이렇게 조건에 해당하는 공시가 발송될때마다 해당 파일에 전송된 내역들이 쌓이게 됩니다. 위의 코드들을 연결하면 아래와 같으며, 해당 코드를 'dart_to_telegram.R'로 저장하도록 합니다.


# 텔레그램 연결
library(telegram.bot)
bot = Bot(token = "Your Telegram API Key")
updates = bot$getUpdates()
chat_id = updates[[1]]$message$chat$id

# dart api 연결
library(jsonlite)
library(readr)

# Default
# guess_encoding('C:/Users/Henry/Dropbox/R/telegram_bot/krx_300.csv')
my.universe = read_csv('C:/Users/Henry/Dropbox/R/telegram_bot/krx_300.csv',
                       locale = locale(encoding = 'EUC-KR'),
                       col_names = FALSE)
my.universe = unlist(my.universe)

rcp_list = read_csv('C:/Users/Henry/Dropbox/R/telegram_bot/rcp_list.csv',
                    locale = locale(encoding = 'EUC-KR'),
                    col_names = FALSE)
rcp_list = unlist(rcp_list)

api.key = 'YOUR_DART_API_KEY'
url = paste0('http://dart.fss.or.kr/api/search.json?auth=',
             api.key,'&','\uc8fc\uc694\uc0ac\ud56d\ubcf4\uace0\uc11c','&page_set=100')


# 주요사항보고서 최근 100 종목 다운로드
data = fromJSON(url)
data.df = data$list
data.df$name = paste(data.df$crp_nm, data.df$rpt_nm)
data.df$url = paste0('http://dart.fss.or.kr/dsaf001/main.do?rcpNo=',
                     data.df$rcp_no)

# 선택한 유니버스 공시만 원하는 경우
data.new = data.df
data.new = data.new[data.new$crp_nm %in% my.universe, ]
data.new = data.new[!data.new$rcp_no %in% rcp_list, ]

send.message = paste(data.new$name, data.new$url, sep = '\n')

sapply(send.message, function(x) {bot$sendMessage(chat_id = chat_id, x)})

# 리스트에 저장
rcp_list = c(rcp_list, data.new$rcp_no)
write_csv(data.frame(rcp_list),
          path ='C:/Users/Henry/Dropbox/R/telegram_bot/rcp_list.csv',
          col_names = FALSE)


이제 해당 파일을 이용하여 1분마다 공시내역을 전송하는 스케쥴러를 만들도록 하겠습니다. DART는 하루에 10,000번의 API 접근을 허용하며, 1분마다 API에 접근할 경우 총 60 * 24 = 1440번 API에 접근하므로 무리가 없습니다.


library(taskscheduleR)
tele_auto = file.path("C:/Users/Henry/Dropbox/R/telegram_bot/dart_to_telegram.R")
taskscheduler_create(taskname = "dart_api_bot", rscript = tele_auto,
                     schedule = "MINUTE", 
                     starttime = format(Sys.time() + 61, "%H:%M"), 
                     startdate = format(Sys.time(), "%Y/%m/%d"),
                     modifier = 1)


앞서 저장한 dart_to_telegram.R 코드를 실행하는 스케쥴러를 dart_api_bot 이라는 이름으로 저장하여, 매 1분마다 코드를 실행하게 됩니다. 즉 1분마다 최근 공시 100개를 다운로드 받고, 관심종목 중 아직 전송되지 않은 공시 내역을 자동으로 텔레그램에 전송하게 됩니다.




관심종목들의 공시가 있을때 마다 텔레그램을 통해 내역이 전송되는 것이 확인됩니다. 공시의 많은 분량이 증권사의 ELS/DLS와 같은 증권발행보고서 이므로, 공시 제목에 '증권실적발행실적보고서'가 있는 경우는 제외한다면 더욱 중요한 공시만 받아볼 수 있습니다.

댓글 없음:

댓글 쓰기