Post List

2019년 2월 21일 목요일

R을 이용한 주식 섹터별 구성종목 확인하기



주식투자 혹은 퀀트투자를 하다보면
특정 섹터에 지나치게 포트폴리오가 쏠리는 위험을 방지하기 위해
섹터별 효과를 어느정도 제거해줄 필요가 있습니다.

그렇다면 문제는 섹터별 구성종목을 확인하는데 있습니다.

거래소에소는 산업분류 페이지를 통해 
섹터별 구성종목을 제공하고 있기는 하지만,
그 수가 무려 47개나 되어 현실적으로 사용하기가 어렵습니다.
(각 섹터별 구성 종목이 지나치게 작아지는 문제)

또한 10개 섹터로 이루어진
GICS 산업분류에 해당하는 종목도 제공하고 있지만
된 S&P와 MSCI의 독점적 지적재산으로 명시했기에
이를 사용하는데 무리가 있습니다.


그러나 다행히 '와이즈인덱스(http://www.wiseindex.com/)' 에서는 
10개 섹터로 이루어진 WICS 산업분류를 발표하고 있으며,
홈페이지를 통해 각 섹터별 구성종목 또한 확인할 수 있습니다.




먼저 홈페이지의 Index 탭을 접속한 후,
좌측의 WICS에서 에너지 항목을 선택합니다.

Components 항목을 보면 구성종목을 확인할 수 있습니다.

해당 페이지가 어떻게 이루어있는지
개발자도구 화면을 열어 확인해보도록 하겠습니다.





네트워크 과정을 확인하기 위해
개발자도구화면 창을 켠채로 Ratio를 클릭한 후
다시 Components를 클릭합니다.

하단의 GetIndexComponents?.. 부분이
구성종목을 받아오는 부분으로 추정할 수 있습니다.

Request URL을 복사하여 새로운 창에서 열어보도록 하겠습니다.


http://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt=20190220&sec_cd=G10



json 형태로 구성종목들이 출력됨이 확인됩니다.

해당 데이터를 R에서 불러오도록 하겠습니다.


library(jsonlite)
library(dplyr)
library(ggplot2)

url = 'http://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt=20190220&sec_cd=G10'
data = fromJSON(url)
data.sector = data$list

먼저 json 형태의 크롤링에 필요한 jsonlite 패키지와
데이터 가공에 필요한 dplyr,
시각화에 필요한 ggplot2 패키지를 불러옵니다.

url에는 위의 주소를 그대로 입력하며,
fromJSON() 함수를 통해 데이터를 읽어옵니다.

우리가 필요한 섹터별 데이터는 list에 위치하므로
해당 데이터만을 뽑아오도록 합니다.


data.sector %>% head()
  IDX_CD  IDX_NM_KOR ALL_MKT_VAL CMP_CD            CMP_KOR  MKT_VAL
1    G10 WICS 에너지    20132121 096770       SK이노베이션 10829567
2    G10 WICS 에너지    20132121 010950              S-Oil  4353014
3    G10 WICS 에너지    20132121 078930                 GS  2659238
4    G10 WICS 에너지    20132121 067630 에이치엘비생명과학   591305
5    G10 WICS 에너지    20132121 006120       SK디스커버리   308889
6    G10 WICS 에너지    20132121 002960         한국쉘석유   205296
    WGT S_WGT CAL_WGT SEC_CD SEC_NM_KOR SEQ TOP60 APT_SHR_CNT
1 53.79 53.79       1    G10     에너지   1     2    56403994
2 21.62 75.41       1    G10     에너지   2     2    41655633
3 13.21 88.62       1    G10     에너지   3     2    49245150
4  2.94 91.56       1    G10     에너지   4     2    32668763
5  1.53 93.10       1    G10     에너지   5     2    10470820
6  1.02 94.11       1    G10     에너지   6     2      611000


에너지 섹터 내에 종목들의 다양한 정보가 손쉽게 불러와집니다.

그렇다면 이번에는 에너지 섹터만이 아닌
10개 섹터 모든 데이터를 가져오도록 하겠습니다.

http://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt=20190220&sec_cd=G10


해당 url은 dt 부분은 기준시점 날짜를,
sec_cd는 섹터의 고유코드를 의미합니다.

10개 페이지를 일일이 확인하면
섹터 고유코드를 어렵지 않게 따올 수 있습니다.


sector.code = c(
  'G10', # 에너지
  'G15', # 소재
  'G20', # 산업재
  'G25', # 경기관소비재
  'G30', # 필수소비재
  'G35', # 건강관리
  'G40', # 금융
  'G45', # IT
  'G50', # 전기통신서비스
  'G55'  # 유틸리
)

data.full = list()
counter = 1
date = '20190220'


먼저 sector.code에는 모든 섹터의 코드를 입력해줍니다.
data.full은 섹터 정보들이 저장될 공간이며,
counter는 작업률 출력을 위한 임시 변수입니다.

또한 date는 2019년 2월 20일을 기준으로 했으며,
원하는 날짜를 입력시 해당 날 기준 데이터가 출력됩니다.
(비영업일 데이터는 출력되지 않습니다.)



for (i in sector.code) {
  
  url = paste0(
    'http://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt=',
    date,'&sec_cd=',i)
  data = fromJSON(url)
  data.sector = data$list
  
  data.sector = data.sector %>%
    select(IDX_NM_KOR, CMP_CD, CMP_KOR) %>%
    rename(섹터 = IDX_NM_KOR, 종목코드 = CMP_CD, 종목명 = CMP_KOR) %>%
    mutate(섹터 = stringr::str_split(섹터, " ", simplify = TRUE)[,2])
  
  data.full[[i]] = data.sector
  
  print(paste(i, ",",counter / length(sector.code) * 100, "%"))
  counter = counter + 1
  Sys.sleep(2)
}

for loop 구문을 통해 손쉽게 모든 섹터의 구성종목을
다운로드 받을 수 있습니다.

먼저 해당 날짜와 섹터에 해당하는 url을 생성한 후,
fromJSON() 함수를 통해 다운로드 받습니다.

그 후 IDX_NM_KOR, CMP_CD, CMP_KOR 열만 선택한 후,
이들의 이름을 각각 섹터, 종목코드, 종목명으로 바꿉니다.

또한 섹터 이름이 'WICS xxx' 형태로 되어 있으므로,
str_split() 함수를 이용해 xxx 부분만을 추출하여 줍니다.

그 후 data.full 변수에 저장해주도록 합니다.

data.full = do.call(rbind, data.full)
rownames(data.full) = NULL

head(data.full)
    섹터 종목코드             종목명
1 에너지   096770       SK이노베이션
2 에너지   010950              S-Oil
3 에너지   078930                 GS
4 에너지   067630 에이치엘비생명과학
5 에너지   006120       SK디스커버리
6 에너지   002960         한국쉘석유

tail(data.full)
         섹터 종목코드       종목명
1980 유틸리티   012320 경동인베스트
1981 유틸리티   017390     서울가스
1982 유틸리티   117580   대성에너지
1983 유틸리티   034590 인천도시가스
1984 유틸리티   016710   대성홀딩스
1985 유틸리티   053050     지에스이



작업이 완료되면 do.call() 함수를 통해
리스트 형식을 데이터테이블로 변환해주도록 하며,
행이름은 NULL로 지워주도록 합니다.

우리가 원하는 섹터, 종목코드, 종목명 열만 
깔끔하게 추출됨이 보이며,
총 1985개 종목 정보가 들어와 있습니다.


data.full %>% group_by(섹터) %>% summarize(n = n())

# A tibble: 10 x 2
   섹터               n
   <chr>          <int>
 1 IT               583
 2 건강관리         228
 3 경기관련소비재   395
 4 금융              77
 5 산업재           335
 6 소재             218
 7 에너지            25
 8 유틸리티          20
 9 전기통신서비스     7
10 필수소비재        97


group_by() 함수를 통해 섹터별로 그룹을 나눈 후,
summarize() 함수를 통해 갯수를 출력하도록 합니다.

총 10개 섹터 기준, 섹터 별 구성종목수가 나타납니다.

이번에는 좀 더 알아보기 쉽게 도식화를 해보도록 하겠습니다.



data.full %>% group_by(섹터) %>% summarize(n = n()) %>%
  ggplot(aes(x = 섹터, y = n)) + geom_bar(stat = 'identity')




그림으로 표현하니 그 차이를 확인하기 한결 편합니다.
그러나 가독성을 높이려면 오름차순 혹은 내림차순으로
다시 정렬하는 것이 좋습니다.


data.full %>% group_by(섹터) %>% summarize(n = n()) %>%
  ggplot(aes(x = reorder(섹터, n), y = n)) + geom_bar(stat = 'identity') +
  xlab(NULL) + ylab(NULL) +
  coord_flip() + theme_bw()



aes() 내부의 x축을 설정해줄 때,
reorder() 함수를 통해 오름차순으로 정리를 해줍니다.
또한 coord_flip()를 통해 좌우 축을 바꿔주기도 합니다.

마지막으로 그래프의 끝에 종목수가 글자로 표기되면
훨씬 이해하기가 쉬울듯 합니다.



data.full %>% group_by(섹터) %>% summarize(n = n()) %>%
  ggplot(aes(x = reorder(섹터, n), y = n, label = n)) +
  geom_bar(stat = 'identity', fill = 'springgreen3',) + 
  geom_text(color = 'black', size = 4, hjust = -0.3) +
  xlab(NULL) + ylab(NULL) +
  scale_y_continuous(expand = c(0, 0, 0.1, 0)) +
  theme_classic() +
  coord_flip()



막대바 + 숫자를 통해 훨씬 데이터를 이해하기가 쉬워집니다.

해당 데이터를 이용한다면
섹터 중립화 팩터 포트폴리오를 구성할 수도 있겠습니다.

댓글 1개:

  1. 좋은 내용 감사합니다. 한가지 질문이 있는데, KOSPI와 KOSDAQ을 나눠서 섹터별로 정리를 할 수 있을까요? 야후 파이넨스나 한국 거래소의 kOSPI와 KOSDAQ주식 정보와 wiseindex의 섹터 정보 기준으로 병합해서 정리하려하는데 merge할때 데이터가 패널형태라 병합이 어렵네요

    답글삭제