diff --git a/CORPCODE.xml b/CORPCODE.xml index ac1c3f4e..de94205a 100644 --- a/CORPCODE.xml +++ b/CORPCODE.xml @@ -278124,12 +278124,6 @@ 20200110 - - 00106669 - 세아베스틸 - 001430 - 20200114 - 00868149 맥파크원코리아 @@ -279024,12 +279018,6 @@ 20190902 - - 01118467 - 케이리얼티재팬제1호위탁관리부동산투자회사 - - 20190902 - 01398975 뉴새한렌트카 @@ -290280,12 +290268,6 @@ 20200128 - - 01295298 - 에스이엠케이 - - 20200128 - 00106368 금호석유화학 @@ -295494,12 +295476,6 @@ 20200224 - - 00863719 - 에이취아이엠테크 - - 20200224 - 00986764 성신산업 @@ -295524,12 +295500,6 @@ 20200224 - - 00871286 - 엘티에스코리아 - - 20200224 - 00784801 웨이브씨티개발 @@ -296676,12 +296646,6 @@ 20200226 - - 01385263 - 배재통상 - - 20200226 - 01413450 유현하우징 @@ -296850,12 +296814,6 @@ 20200218 - - 00159111 - 마그나파워트레인코리아 - - 20200218 - 01306707 디에스이엔티 @@ -296940,12 +296898,6 @@ 20200224 - - 01134003 - 남문 - - 20200224 - 00202802 일광이앤씨 @@ -296964,12 +296916,6 @@ 20200224 - - 00807245 - 남이섬 - - 20200224 - 01140077 빅토리아도시개발 @@ -298290,12 +298236,6 @@ 20200224 - - 00129226 - 삼화 - - 20200224 - 00870843 헵스켐 @@ -301938,12 +301878,6 @@ 20200225 - - 01142075 - 핑거 - - 20200225 - 01212930 씨제이비엔터컴 @@ -310668,12 +310602,6 @@ 20200225 - - 00521868 - 글로벌텍 - - 20200225 - 01270396 화창토산 @@ -315210,24 +315138,12 @@ 20200205 - - 00957513 - 간석목재산업 - - 20200205 - 00918301 진영철강 20200205 - - 01365658 - 한양기술공업 - - 20200205 - 01023062 원동판넬 @@ -316734,12 +316650,6 @@ 20200219 - - 01014134 - 보광씨티 - - 20200219 - 01169337 롯데울산개발 @@ -320202,12 +320112,6 @@ 20200213 - - 00525323 - 대한강재 - - 20200213 - 00569105 건우기업 @@ -332964,12 +332868,6 @@ 20200224 - - 00911982 - 삼화제지 - - 20200224 - 00895444 솔라루체 @@ -334530,12 +334428,6 @@ 20200224 - - 01243046 - 윈덤피엘파트너스 - - 20200224 - 01323892 포틀랜드아시아 @@ -337152,12 +337044,6 @@ 20200221 - - 01199994 - 에프앤디인프라 - - 20200221 - 00807555 메가엠디 @@ -339786,12 +339672,6 @@ 20200221 - - 00913281 - 에스엠플라텍 - - 20200129 - 00630953 롯데제이티비 @@ -344364,12 +344244,6 @@ 20200224 - - 01137851 - 도타이 - - 20200224 - 01394119 엔아이티 @@ -353244,12 +353118,6 @@ 20200226 - - 00317104 - 라이온켐텍 - 171120 - 20200226 - 00159333 니카코리아 @@ -354402,12 +354270,6 @@ 20200214 - - 01366806 - 형제산업 - - 20200226 - 00655536 울산꿈나무 @@ -356166,12 +356028,6 @@ 20200226 - - 01372278 - 담터에프엔비 - - 20200226 - 00568559 부성개발 @@ -361164,12 +361020,6 @@ 20200227 - - 01188730 - 시그넷이브이 - 260870 - 20200227 - 00353230 프리젠 @@ -369066,12 +368916,6 @@ 20200227 - - 00160010 - 한국큐빅 - 021650 - 20200227 - 00447247 삼인 @@ -388236,12 +388080,6 @@ 20200512 - - 00263371 - 한국경제TV - 039340 - 20200512 - 00997812 코미코 @@ -391554,12 +391392,6 @@ 20200512 - - 00577089 - 대전열병합발전 - - 20200512 - 01468618 베네핏대부 @@ -405780,12 +405612,6 @@ 20200428 - - 00878890 - 울산아로마틱스 - - 20200428 - 01465329 미강티엔에스 @@ -412992,12 +412818,6 @@ 20200513 - - 01415458 - 그랑플레이스 - - 20200513 - 01476884 케이디비비즈 @@ -422142,12 +421962,6 @@ 20200513 - - 00297208 - 에이치디씨아이파크몰 - - 20200513 - 01478828 세석건설 @@ -425574,12 +425388,6 @@ 20200428 - - 01466957 - 조일감속기 - - 20200428 - 01458116 경기리츠공공임대제1호위탁관리부동산투자회사 @@ -428106,12 +427914,6 @@ 20200421 - - 00682390 - 케이비손해사정 - - 20200421 - 00905796 큰길엔터프라이즈 @@ -439824,12 +439626,6 @@ 20200309 - - 01173710 - 건호종합건설 - - 20200309 - 01482575 엠에너지제이차 @@ -445482,12 +445278,6 @@ 20200228 - - 01214044 - 빗썸코리아 - - 20200228 - 00487555 삼성포장 @@ -451038,12 +450828,6 @@ 20201215 - - 00144720 - 유양디앤유 - 011690 - 20201215 - 01153293 제이엘케이 @@ -454242,12 +454026,6 @@ 20200723 - - 00220057 - 유비케어 - 032620 - 20200727 - 00159625 한국중천전화산업 @@ -455874,12 +455652,6 @@ 025320 20210105 - - 01384477 - 엠에프엠코리아 - 323230 - 20210105 - 01492323 신한알파광교위탁관리부동산투자회사 @@ -460134,12 +459906,6 @@ 010120 20210114 - - 01517127 - 아이메디신 - - 20201231 - 01520149 에스엘홀딩스컴퍼니 @@ -460230,12 +459996,6 @@ 220110 20200713 - - 00223799 - 씨제이올리브네트웍스 - - 20200713 - 01080997 디엔에프스틸 @@ -461106,12 +460866,6 @@ 20201110 - - 01511062 - 디멘션 - - 20201110 - 01511929 드림스완제이차유동화전문유한회사 @@ -461934,12 +461688,6 @@ 20201118 - - 00666329 - 디에이티신소재 - - 20201118 - 01508493 남성버스 @@ -464718,12 +464466,6 @@ 20201030 - - 01493456 - 아이씨엠 - - 20201030 - 01509261 예주철강 @@ -465918,12 +465660,6 @@ 208640 20201203 - - 01514607 - 투르코사수출입트랜짓 - - 20201203 - 01514883 리치월드제18차 @@ -468780,12 +468516,6 @@ 051160 20201130 - - 00985686 - 큐브엔터 - 182360 - 20201130 - 00137915 에스엠코어 @@ -469902,6 +469632,72 @@ 021040 20210116 + + 00263371 + 한국경제TV + 039340 + 20210118 + + + 01524385 + 테라젠지놈케어 + + 20210118 + + + 01014134 + 보광씨티 + + 20210118 + + + 00317104 + 라이온켐텍 + 171120 + 20210118 + + + 01366806 + 형제산업 + + 20210118 + + + 01524455 + BKMEDICALGROUPPTE.LTD. + + 20210118 + + + 01524473 + 한신대한제1호위탁관리부동산투자회사 + + 20210118 + + + 01372278 + 담터에프엔비 + + 20210118 + + + 01415458 + 그랑플레이스 + + 20210118 + + + 00496465 + 아웃백스테이크하우스코리아 + + 20210118 + + + 01118467 + 케이리얼티재팬제1호위탁관리부동산투자회사 + + 20210118 + 01180163 더뉴클래스제이차 @@ -472074,6 +471870,42 @@ 20210115 + + 00666329 + 디에이티신소재 + + 20210118 + + + 01511062 + 디멘션 + + 20210118 + + + 01173710 + 건호종합건설 + + 20210118 + + + 01142075 + 핑거 + + 20210118 + + + 01214044 + 빗썸코리아 + + 20210118 + + + 01514607 + 투르코사수출입트랜짓 + + 20210118 + 01397569 칼제이십사차유동화전문 @@ -472548,6 +472380,72 @@ 20200805 + + 01229462 + 무전도시개발 + + 20210118 + + + 00976633 + 쎄코 + + 20210118 + + + 01384477 + 엠에프엠코리아 + 323230 + 20210118 + + + 00797364 + KC코트렐 + 119650 + 20210118 + + + 01182790 + 두산메카텍 + + 20210118 + + + 00159971 + KC그린홀딩스 + 009440 + 20210118 + + + 01137851 + 도타이 + + 20210118 + + + 00871286 + 엘티에스코리아 + + 20210118 + + + 01243046 + 윈덤피엘파트너스 + + 20210118 + + + 01524543 + 케이비오토제사차유동화전문유한회사 + + 20210118 + + + 01188730 + 시그넷이브이 + 260870 + 20210118 + 00158015 한국선재 @@ -473814,6 +473712,36 @@ 050120 20201216 + + 00223799 + 씨제이올리브네트웍스 + + 20210118 + + + 01518542 + 비젠 + + 20210118 + + + 00159111 + 한온시스템이에프피코리아 + + 20210118 + + + 00106669 + 세아베스틸 + 001430 + 20210118 + + + 00109116 + 용산화학 + + 20210118 + 01412442 남도풍력발전 @@ -474366,12 +474294,6 @@ 20200911 - - 00519395 - 씨제이파워캐스트 - - 20200911 - 01383441 지구종합건설 @@ -477822,12 +477744,6 @@ 123010 20201229 - - 01182790 - 두산메카텍 - - 20201229 - 00164973 현대해상 @@ -478920,12 +478836,6 @@ 067000 20201231 - - 00496225 - 이엠네트웍스 - 087730 - 20201231 - 00656289 코스맥스엔비티 @@ -484806,12 +484716,6 @@ 20201209 - - 00159971 - KC그린홀딩스 - 009440 - 20201209 - 01350869 우리금융지주 @@ -487188,12 +487092,6 @@ 280360 20210114 - - 00160047 - 한국테크놀로지그룹 - 000240 - 20210114 - 00134015 세안 @@ -487578,12 +487476,6 @@ 20201222 - - 01518542 - 비젠테크 - - 20201222 - 00542001 토요타파이낸셜서비스코리아 @@ -487824,12 +487716,6 @@ 20200910 - - 01229462 - 무전도시개발 - - 20200910 - 00462194 버버리코리아 @@ -490674,6 +490560,84 @@ 20210115 + + 00144720 + 유양디앤유 + 011690 + 20210118 + + + 00220057 + 유비케어 + 032620 + 20210118 + + + 00496225 + 이엠네트웍스 + 087730 + 20210118 + + + 00160047 + 한국앤컴퍼니 + 000240 + 20210118 + + + 00985686 + 큐브엔터 + 182360 + 20210118 + + + 00297208 + 에이치디씨아이파크몰 + + 20210118 + + + 00521868 + 글로벌텍 + + 20210118 + + + 00577089 + 대전열병합발전 + + 20210118 + + + 01480780 + 엔에이치올원위탁관리부동산투자회사 + + 20210118 + + + 01385263 + 배재통상 + + 20210118 + + + 01518463 + 신홀딩스 + + 20210118 + + + 00462990 + 대량산업 + + 20210118 + + + 01493456 + 아이씨엠 + + 20210118 + 01487686 더시너지1 @@ -491844,12 +491808,6 @@ 20201021 - - 00109116 - 용산화학 - - 20201126 - 01411601 농업회사법인한미종묘 @@ -492474,12 +492432,6 @@ 20200819 - - 00909376 - 케이알제5호위탁관리부동산투자회사 - - 20200824 - 01017797 케이클라비스자산운용 @@ -493080,12 +493032,6 @@ 20200908 - - 00265324 - CJ ENM - 035760 - 20200908 - 01497753 재상팜 @@ -495552,12 +495498,6 @@ 20200604 - - 00462990 - 대량산업 - - 20200604 - 00253736 대원상호저축은행 @@ -495774,12 +495714,6 @@ 20201222 - - 01518463 - 신홀딩스 - - 20201222 - 01344372 엔브이씨파트너스 @@ -495936,12 +495870,6 @@ 20200604 - - 00976633 - 쎄코 - - 20200604 - 00969101 에스엠지 @@ -496254,12 +496182,6 @@ 015260 20201229 - - 00797364 - KC코트렐 - 119650 - 20201229 - 00939331 한국콜마 @@ -496608,6 +496530,30 @@ 20210116 + + 00265324 + CJ ENM + 035760 + 20210118 + + + 00682390 + 케이비손해사정 + + 20210118 + + + 00957513 + 간석목재산업 + + 20210118 + + + 01466957 + 조일감속기 + + 20210118 + 00566001 엘에스글로벌인코퍼레이티드 @@ -496740,6 +496686,102 @@ 20210105 + + 00525323 + 대한강재 + + 20210118 + + + 01365658 + 한양기술공업 + + 20210118 + + + 01524491 + 누날 + + 20210118 + + + 01524507 + 핀텔 + + 20210118 + + + 01517127 + 아이메디신 + + 20210118 + + + 00878890 + 울산아로마틱스 + + 20210118 + + + 01295298 + 에스이엠케이 + + 20210118 + + + 00911982 + 삼화제지 + + 20210118 + + + 00129226 + 삼화 + + 20210118 + + + 00863719 + 에이취아이엠테크 + + 20210118 + + + 00807245 + 남이섬 + + 20210118 + + + 01134003 + 남문 + + 20210118 + + + 00909376 + 케이알제5호위탁관리부동산투자회사 + + 20210118 + + + 01375822 + 에이프릴바이오 + + 20210118 + + + 00519395 + 씨제이파워캐스트 + + 20210118 + + + 00913281 + 에스엠플라텍 + + 20210118 + 01160512 헝셩그룹 @@ -496752,12 +496794,6 @@ 20201231 - - 01480780 - 엔에이치올원위탁관리부동산투자회사 - - 20201231 - 00607478 공영흥업 @@ -496878,6 +496914,18 @@ 20200630 + + 01199994 + 에프앤디인프라 + + 20210118 + + + 00160010 + 한국큐빅 + 021650 + 20210118 + 00187770 한국파마 @@ -498066,12 +498114,6 @@ 20201117 - - 01375822 - 에이프릴바이오 - - 20201117 - 00606770 참좋은여행 @@ -499014,12 +499056,6 @@ 318410 20200921 - - 00496465 - 아웃백스테이크하우스코리아 - - 20200921 - 00615246 농협목우촌 diff --git a/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-12-1.png b/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-12-1.png index cdcb316b..ba51e35e 100644 Binary files a/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-12-1.png and b/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-12-1.png differ diff --git a/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-15-1.png b/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-15-1.png index f0b2dbdc..d985cacf 100644 Binary files a/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-15-1.png and b/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-15-1.png differ diff --git a/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-4-1.png b/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-4-1.png index 04de41ed..7564c595 100644 Binary files a/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-4-1.png and b/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-4-1.png differ diff --git a/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-9-1.png b/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-9-1.png index 854a76b5..2a2535de 100644 Binary files a/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-9-1.png and b/_bookdown_files/13-evaluation_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/data/corp_list.csv b/data/corp_list.csv index 8489d918..a25666c6 100644 --- a/data/corp_list.csv +++ b/data/corp_list.csv @@ -720,2561 +720,2561 @@ "46165","00353018","슈펙스비앤피","058530" "46196","00393618","제노텍","066830" "46337","00100717","아이이","023430" -"46355","00106669","세아베스틸","001430" -"46417","00261285","한국가스공사","036460" -"46449","00503899","사조해표","079660" -"46628","00133317","에스티씨라이프","026220" -"46666","01018802","엔솔바이오사이언스","140610" -"46747","00311216","에이치엔에스하이텍","044990" -"46797","01201581","한화수성기업인수목적","265920" -"46825","00200956","현대정보기술","026180" -"46900","01111570","에스케이제3호기업인수목적","232330" -"47033","01113781","영현무역","242850" -"47040","01212383","엔케이맥스","262760" -"47055","00365758","아이앤씨","052860" -"47231","01063556","질경이","233990" -"47392","01194759","대신밸런스제4호기업인수목적","262830" -"47402","00165583","E1","017940" -"47410","00118451","동일제강","002690" -"47416","00125646","삼목에스폼","018310" -"47419","00119140","핸즈코퍼레이션","143210" -"47441","01261893","케이씨텍","281820" -"47449","00926522","테고사이언스","191420" -"47532","01396676","미래에셋대우스팩4호","333430" -"47612","00964595","엔바이오니아","317870" -"47659","00606293","나이스디앤비","130580" -"47672","00654175","마이크로프랜드","147760" -"47691","00107303","피엔에스커튼월","033220" -"47696","01159853","휴온스","243070" -"47779","00657756","지오씨","135160" -"47826","00138516","아남전자","008700" -"47891","00664288","스페이스솔루션","245030" -"47895","00231354","파워넷","037030" -"47965","00409672","티비에이치글로벌","084870" -"48028","00472890","동국알앤에스","075970" -"48059","00481223","넥스피안","079560" -"48107","01414370","하이제5호스팩","340120" -"48134","00175650","케이씨더블류","068060" -"48202","00131328","서주산업개발","016140" -"48289","00260879","카페24","042000" -"48291","00614089","에이원알폼","234070" -"48349","01069736","한독크린텍","256150" -"48358","01117422","나눔테크","244880" -"48382","00106368","금호석유화학","011780" -"48435","00145109","유한양행","000100" -"48513","00330044","캠시스","050110" -"48585","00101664","경보제약","214390" -"48675","00276834","KMH하이텍","052900" -"48696","00385363","빅텍","065450" -"48792","00491053","진바이오텍","086060" -"48939","00614593","넥스트아이","137940" -"48999","01363924","한국미라클피플사","331660" -"49043","01398151","교보9호스팩","331520" -"49111","00218575","황금에스티","032560" -"49160","00416654","유엔젤","072130" -"49211","00123523","창해에탄올","004650" -"49235","00541163","이엠텍","091120" -"49287","00361381","제이브이엠","054950" -"49304","00118044","동원수산","030720" -"49348","00220686","에스피지","058610" -"49400","01037542","바이옵트로","222160" -"49555","00972293","파멥신","208340" -"49587","00127200","삼영전자공업","005680" -"49616","00838005","서진시스템","178320" -"49718","00398668","휴비츠","065510" -"49720","00606664","케이엔더블유","105330" -"49728","00133511","SG세계물산","004060" -"49742","00167004","흥구석유","024060" -"49813","00493510","뉴프렉스","085670" -"49913","01011438","에이치엘사이언스","239610" -"49922","00599489","청광건설","140290" -"49942","01276901","삼성스팩2호","291230" -"49943","01309388","티라유텍","322180" -"49944","01251489","라온피플","300120" -"49945","00216498","성도이엔지","037350" -"49946","01416457","하나금융15호스팩","341160" -"49961","00497631","티플랙스","081150" -"49967","01113499","시큐센","232830" -"49974","00365457","제이티","089790" -"50083","00872850","비지스틸","179440" -"50267","00869272","서전기전","189860" -"50346","00493185","제이스텍","090470" -"50354","01246034","네오펙트","290660" -"50387","00115472","미주제강","002670" -"50402","00416414","넥스턴","089140" -"50419","00340096","미래에셋벤처투자","100790" -"50536","01222432","에스제이그룹","306040" -"50547","00156910","SBI인베스트먼트","019550" -"50576","01116344","이비테크","208850" -"50581","01035289","모두투어리츠","204210" -"50609","00910947","바다로19호","155900" -"50648","00646732","진양홀딩스","100250" -"50655","00761059","펌텍코리아","251970" -"50666","00198378","녹십자셀","031390" -"50688","01124680","보라티알","250000" -"50699","00138206","쌍용건설","012650" -"50709","00957735","클리오","237880" -"50716","00407975","엔터미디어","068420" -"50756","00579980","아이티엠반도체","084850" -"50798","00159740","KISCO홀딩스","001940" -"50809","01138993","원바이오젠","278380" -"50836","01137903","디케이티","290550" -"50843","00405320","웹젠","069080" -"50864","01199550","현대에너지솔루션","322000" -"50874","01068658","디딤","217620" -"50937","00608316","대한과학","131220" -"50941","00991191","앱클론","174900" -"50988","00249982","솔본","035610" -"50994","00107066","남광토건","001260" -"51042","00687711","한국철강","104700" -"51054","00870861","와이엠씨","155650" -"51065","00163114","서연","007860" -"51079","01108099","현대코퍼레이션홀딩스","227840" -"51103","00962223","지니언스","263860" -"51213","00148939","조광피혁","004700" -"51238","00158963","유나이티드","033270" -"51251","01108248","지노믹트리","228760" -"51284","00669450","바이오프로테크","199290" -"51326","00490151","파트론","091700" -"51405","00121154","엠에스씨","009780" -"51524","00445869","유니포인트","121060" -"51533","00521433","엔지브이아이","093510" -"51535","00398808","디지털대성","068930" -"51550","00137252","사조동아원","008040" -"51584","00140946","한솔로지스틱스","009180" -"51605","00101628","경방","000050" -"51607","00113508","노루홀딩스","000320" -"51653","00147222","전방","000950" -"51664","00113261","대한광통신","010170" -"51706","00129387","삼화페인트공업","000390" -"51773","00148531","제일제강","023440" -"51943","00104722","국제약품","002720" -"52003","00269612","파워로직스","047310" -"52025","01196313","유에스티","263770" -"52052","00133089","성원","015200" -"52070","01109070","비피도","238200" -"52108","01135057","녹십자웰빙","234690" -"52116","01182578","대유에이피","290120" -"52240","00317681","메타바이오메드","059210" -"52263","00540571","엘디티","096870" -"52335","00112679","대창단조","015230" -"52346","00225706","JW생명과학","234080" -"52350","00146214","일성신약","003120" -"52366","00929778","세기리텍","199870" -"52386","00164706","현대약품","004310" -"52451","01335930","티움바이오","321550" -"52460","00525864","피앤씨테크","237750" -"52472","00321204","관악산업","076340" -"52487","01020524","자이글","234920" -"52532","00788773","씨젠","096530" -"52573","00136864","신원","009270" -"52577","00132628","성문전자","014910" -"52624","01231786","슈프리마아이디","317770" -"52626","00531829","엠아이텍","179290" -"52635","00548519","에스엔피제네틱스","086460" -"52684","00442154","제이엔케이히터","126880" -"52756","01187148","케어랩스","263700" -"52806","00352000","하우리","049130" -"52814","00109781","대림제지","017650" -"52837","00506294","가비아","079940" -"52931","00867098","티로보틱스","117730" -"52937","00823429","한국화장품","123690" -"52958","00536286","윈팩","097800" -"52965","01390399","미래에셋대우스팩3호","328380" -"53036","00250137","전파기지국","065530" -"53048","00486097","나스미디어","089600" -"53051","01325429","네온테크","306620" -"53058","00554352","아이큐어","175250" -"53059","01035942","메디안디노스틱","233250" -"53124","00122551","백산","035150" -"53236","00109453","STX조선해양","067250" -"53364","00428321","해태제과식품","101530" -"53378","00560982","바이오솔루션","086820" -"53426","00914040","씨엠에스에듀","225330" -"53452","00332927","제이에스코퍼레이션","194370" -"53494","00989619","알테오젠","196170" -"53578","00140168","지투알","035000" -"53658","00609324","뷰웍스","100120" -"53703","00151298","DSR제강","069730" -"53780","00899459","레이언스","228850" -"53793","00917861","슈피겐코리아","192440" -"53797","00410915","서울옥션","063170" -"53804","01109955","링크제니시스","219420" -"53859","01316227","효성티앤씨","298020" -"53877","01211232","코윈테크","282880" -"53965","00541349","셀트리온제약","068760" -"53972","00264255","바텍","043150" -"53976","00617314","타이거일렉","219130" -"53984","00449254","쎄트렉아이","099320" -"53998","00598198","컴퍼니케이","307930" -"54042","00858124","알에스오토메이션","140670" -"54094","00172936","동일산업","004890" -"54125","00132725","성보화학","003080" -"54207","00675594","디티앤씨","187220" -"54216","01203808","AP시스템","265520" -"54263","00101488","경동나비엔","009450" -"54270","01076550","주노콜렉션","221670" -"54359","00527880","썬테크놀로지스","122800" -"54392","00977641","하이로닉","149980" -"54456","00161709","퍼시스","016800" -"54503","00231044","우리기술","032820" -"54558","00450995","대모","317850" -"54566","00962922","팬젠","222110" -"54595","01116274","엠앤씨생명과학","225860" -"54646","00219848","서희건설","035890" -"54662","00897752","천보","278280" -"54669","00291231","우전","052270" -"54711","00118026","동원산업","006040" -"54718","00360577","엑사이엔씨","054940" -"54750","00167031","흥국","010240" -"54774","00127857","삼익악기","002450" -"54846","00861720","이즈미디어","181340" -"54850","00219440","휴맥스홀딩스","028080" -"54873","00605328","씨앤지하이테크","264660" -"54880","00137207","유니켐","011330" -"54893","00351995","지에스이","053050" -"54935","00242712","엘컴텍","037950" -"54937","00163196","한일철강","002220" -"54960","00101433","경농","002100" -"54962","00301112","삼화네트웍스","046390" -"55017","00131692","서흥","008490" -"55018","00129271","삼화전기","009470" -"55045","00883980","아이디스","143160" -"55084","00106456","지앤엘","014590" -"55111","00162072","한신기계공업","011700" -"55115","00370918","케이디켐","221980" -"55116","00863038","윌링스","313760" -"55137","00145260","율촌화학","008730" -"55196","00671376","티웨이항공","091810" -"55202","01203507","에스엠비나","299670" -"55206","01071944","미래생명자원","218150" -"55231","01336443","한국제8호스팩","310870" -"55256","00131504","서한","011370" -"55509","00819374","나무가","190510" -"55552","00491336","잘만테크","090120" -"55556","00787057","휴맥스","115160" -"55572","00403766","피피아이","062970" -"55683","00111193","대양전기공업","108380" -"55760","00238153","파세코","037070" -"55771","01078178","RFHIC","218410" -"55783","00409140","이원컴포텍","088290" -"55863","00128227","삼정펄프","009770" -"55948","01276327","위지윅스튜디오","299900" -"55999","01093007","LS전선아시아","229640" -"56098","01095704","케이엠제약","225430" -"56113","00298340","에스티아이","039440" -"56162","01063990","로보로보","215100" -"56194","00807555","메가엠디","133750" -"56242","01101041","패션플랫폼","225590" -"56291","00130046","다함이텍","009280" -"56339","01235296","셀리드","299660" -"56365","00366845","탑엔지니어링","065130" -"56377","00471068","덕산하이메탈","077360" -"56403","00410997","누리플랜","069140" -"56444","00226866","인탑스","049070" -"56540","01011562","우성아이비","194610" -"56566","00397191","팬엔터테인먼트","068050" -"56568","00543107","오킨스전자","080580" -"56590","00404701","한국자산신탁","123890" -"56641","00871833","에스티팜","237690" -"56660","00965813","미투온","201490" -"56687","01077577","우리산업","215360" -"56712","00117638","동양텔레콤","007150" -"56729","99999999","금감원(테스트)","999980" -"56782","00574611","SDN","099220" -"56836","00144164","SK가스","018670" -"56885","00613318","와이지엔터테인먼트","122870" -"56887","00173102","모나리자","012690" -"56893","00110060","대백저축은행","026970" -"56903","01013311","민앤지","214180" -"56904","00977377","에이비온","203400" -"56922","00104999","윌비스","008600" -"56971","00117337","동양","001520" -"56975","00485177","일진파워","094820" -"57024","01267222","캐리소프트","317530" -"57103","00983934","아이스크림에듀","289010" -"57142","01027794","브이원텍","251630" -"57153","01393721","유진스팩5호","331380" -"57165","01124653","슈프리마","236200" -"57244","00166528","서암기계공업","100660" -"57290","01359736","유진스팩4호","321260" -"57320","00162911","한일사료","005860" -"57344","00521390","뉴파워프라즈마","144960" -"57350","00599285","서플러스글로벌","140070" -"57354","00155212","포스코 ICT","022100" -"57355","01251595","압타바이오","293780" -"57379","00161383","한미반도체","042700" -"57423","00165343","혜인","003010" -"57449","01014055","에스트래픽","234300" -"57462","00541862","판도라티비","202960" -"57468","01366000","신영스팩5호","323280" -"57547","00231105","동성화인텍","033500" -"57599","00137359","신풍제약","019170" -"57601","00141404","영풍제지","006740" -"57649","00149266","CNH","023460" -"57657","00135777","신대양제지","016590" -"57674","00164618","현대사료","016790" -"57709","00442631","태양기계","116100" -"57724","00651017","나우IB","293580" -"57739","00571298","덕우전자","263600" -"57793","00966168","디알젬","263690" -"57813","00144818","유유제약","000220" -"57946","00372253","연우","115960" -"57949","00971726","옵티팜","153710" -"57975","00101336","경남에너지","008020" -"57988","01419986","신영스팩6호","344050" -"58013","00612489","아이쓰리시스템","214430" -"58049","01046708","바디텍메드","206640" -"58161","00173069","아진카인텍","011400" -"58187","00158325","NICE평가정보","030190" -"58188","00159175","한국전기초자","009720" -"58211","01179617","한국비엔씨","256840" -"58300","00137289","이라이콤","041520" -"58312","00152862","코오롱","002020" -"58322","00229021","인성정보","033230" -"58329","00530556","예스티","122640" -"58345","00181934","포스코플랜텍","051310" -"58346","00498001","모베이스","101330" -"58380","01113718","올릭스","226950" -"58394","00112022","대원화성","024890" -"58440","00128111","포스코엠텍","009520" -"58578","00867973","서남","294630" -"58615","01046391","싸이토젠","217330" -"58638","00864338","웹스","196700" -"58676","00562245","태웅로직스","124560" -"58853","00429694","파버나인","177830" -"58875","00317104","라이온켐텍","171120" -"58888","01267684","쿠쿠홈시스","284740" -"58921","00961774","유티아이","179900" -"59000","00616962","린드먼아시아","277070" -"59010","00579139","한국맥널티","222980" -"59014","00363927","NE능률","053290" -"59119","00580065","월덱스","101160" -"59130","00915508","알엔투테크놀로지","148250" -"59190","00113128","대한약품","023910" -"59201","00108746","큐로","015590" -"59247","00632304","JW홀딩스","096760" -"59249","00414540","대주이엔티","114920" -"59293","01344336","신테카바이오","226330" -"59297","00108001","남화토건","091590" -"59328","00992871","종근당","185750" -"59348","01116380","씨앤에스링크","245450" -"59367","00681249","씨유메디칼","115480" -"59372","00450931","우양에이치씨","101970" -"59409","00544577","에이피티씨","089970" -"59432","00138190","GS글로벌","001250" -"59471","01089378","MP한강","219550" -"59481","01390876","상상인이안제2호스팩","329560" -"59600","01167056","샘표식품","248170" -"59605","00766106","트루윈","105550" -"59627","00828761","레이","228670" -"59660","00122472","백광산업","001340" -"59711","00408266","유니온커뮤니티","203450" -"59762","00474588","한중엔시에스","107640" -"59781","01326260","하나머스트제6호스팩","307160" -"59783","00631837","안트로젠","065660" -"59796","00160418","한국프랜지공업","010100" -"59836","01223219","에이에프더블류","312610" -"59852","00111838","대원","007680" -"59907","01405585","IBKS제12호스팩","335870" -"59988","01182550","EDGC","245620" -"60007","00117966","동원개발","013120" -"60093","00415868","펩트론","087010" -"60097","00578325","럭스피아","092590" -"60104","00365934","코세스","089890" -"60179","00130091","샘표","007540" -"60186","00228712","동우팜투테이블","088910" -"60193","00131197","서원","021050" -"60195","01188730","시그넷이브이","260870" -"60196","00353230","프리젠","060910" -"60232","00676122","지디","155960" -"60306","00359261","빛샘전자","072950" -"60316","01042711","글로벌텍스프리","204620" -"60341","00154329","태평양물산","007980" -"60437","01063963","유쎌","252370" -"60461","00303703","CS","065770" -"60468","00156354","지엠비코리아","013870" -"60489","00177199","디씨엠","024090" -"60492","00129235","삼화왕관","004450" -"60515","00415327","중앙백신","072020" -"60554","00659161","싸이맥스","160980" -"60558","00265421","인지디스플레","037330" -"60560","01047415","정다운","208140" -"60561","00162416","한양증권","001750" -"60572","00162063","한신공영","004960" -"60605","00161116","한라","014790" -"60645","01098792","본느","226340" -"60676","01190780","파워풀엑스","266870" -"60697","00111458","대영포장","014160" -"60713","00983916","제노포커스","187420" -"60769","00148920","조광페인트","004910" -"60791","00280688","SBS콘텐츠허브","046140" -"60819","01256864","에이비엘바이오","298380" -"60820","00116806","동아","012760" -"60824","00374020","이엘피","063760" -"60826","01089350","켐트로스","220260" -"60829","00413417","우리손에프앤지","073560" -"60830","00540340","코리아센터","290510" -"60843","00146454","일양약품","007570" -"60867","00564562","엠씨넥스","097520" -"60889","00112004","알바이오","003190" -"60912","00656340","소프트캠프","210610" -"60929","00989664","코아스템","166480" -"60931","00397058","엠게임","058630" -"60947","00888347","휴젤","145020" -"60986","00446479","신비앤텍","070480" -"61033","00377610","세아홀딩스","058650" -"61140","00239639","삼표시멘트","038500" -"61165","00874803","AP위성","211270" -"61166","00306870","손오공","066910" -"61167","00153278","서연탑메탈","019770" -"61179","00976615","틸론","217880" -"61181","00298377","아이씨디","040910" -"61189","01227039","브릿지바이오","288330" -"61277","01074862","메가스터디교육","215200" -"61330","00524421","테스","095610" -"61337","01325979","세아제강","306200" -"61349","00155355","풀무원","017810" -"61363","00536523","로보스타","090360" -"61406","01119387","이에스산업","241510" -"61512","00160010","한국큐빅","021650" -"61551","00122056","미창석유공업","003650" -"61652","01234297","미원에스씨","268280" -"61687","01058101","라파스","214260" -"61742","00438036","팅크웨어","084730" -"61776","00990262","피노텍","150440" -"61866","00490179","플랜티넷","075130" -"61894","00206659","이랜텍","054210" -"61923","00991225","더콘텐츠온","302920" -"61925","00172291","더존비즈온","012510" -"61963","00114686","동구바이오제약","006620" -"61965","00769158","케이엔제이","272110" -"62087","00133715","대주코레스","008340" -"62091","00166272","화승알앤에이","013520" -"62097","00139083","아진산업","013310" -"62155","00150633","지누스","013890" -"62188","00651901","에어부산","298690" -"62195","00486370","성창오토텍","080470" -"62201","01082348","오스테오닉","226400" -"62203","00163345","DB","012030" -"62211","01163050","다이오진","271850" -"62260","00653024","진에어","272450" -"62366","00239514","삼진엘앤디","054090" -"62404","00141626","오리엔트바이오","002630" -"62430","01169267","세화피앤씨","252500" -"62432","00397289","오텍","067170" -"62452","00807379","신흥에스이씨","243840" -"62469","01199189","이랜시스","264850" -"62499","00525882","청담러닝","096240" -"62533","00561866","락앤락","115390" -"62556","01267602","유안타제3호스팩","287410" -"62566","00148504","한국스탠다드차타드은행","000110" -"62622","00813389","베셀","177350" -"62624","00681373","디지캡","197140" -"62672","00109754","대림비앤코","005750" -"62677","00231442","디지틀조선","033130" -"62692","01194892","알로이스","271400" -"62725","00671978","그리티","204020" -"62821","00583442","노루페인트","090350" -"62881","00364740","머큐리","100590" -"62908","00165103","푸드웰","005670" -"62909","00362159","웰크론","065950" -"62970","01418543","케이비제20호스팩","342550" -"63002","00945457","아이진","185490" -"63006","01344363","다원넥스뷰","323350" -"63027","00535676","테크윙","089030" -"63031","00531014","유진테크","084370" -"63044","00103042","케이씨티시","009070" -"63133","00264945","나이스정보통신","036800" -"63137","01366365","이베스트이안스팩1호","323210" -"63181","00487546","웰크론한텍","076080" -"63232","00456218","모두투어","080160" -"63248","00411446","큐에스아이","066310" -"63265","00756163","볼빅","206950" -"63289","00122728","범양사","002480" -"63293","00763358","쎄미시스코","136510" -"63307","00273420","이스트소프트","047560" -"63317","00243757","인포뱅크","039290" -"63329","00170877","진로발효","018120" -"63334","00132637","동원시스템즈","014820" -"63379","01358463","엔에이치스팩14호","319400" -"63425","00890388","지성이씨에스","138290" -"63434","00857727","하림","136480" -"63535","00580056","스맥","099440" -"63536","00105244","알보젠코리아","002250" -"63570","00252269","아이에이","038880" -"63572","00154426","아이에이치큐","003560" -"63600","01051092","피씨엘","241820" -"63602","00532855","한국유니온제약","080720" -"63612","00156600","한주금속","198940" -"63613","00112651","대창","012800" -"63699","00358271","에스에프에이","056190" -"63705","00384887","에이테크솔루션","071670" -"63709","00602172","와이엠티","251370" -"63747","00124027","부산산업","011390" -"63755","00557933","진도","088790" -"63780","00106119","금양","001570" -"63797","00329093","코데즈컴바인","047770" -"63811","00876865","네오오토","212560" -"63844","00365989","용평리조트","070960" -"63970","00252001","해원에스티","058480" -"63989","00100957","건영","012720" -"64021","00162780","한일건설","006440" -"64048","00142591","셰프라인","012250" -"64161","00112721","대창스틸","140520" -"64169","00174208","진성티이씨","036890" -"64188","00454946","그린케미칼","083420" -"64211","00105165","극동전선","006250" -"64218","00173795","신흥","004080" -"64245","00374738","위세아이텍","065370" -"64246","00139764","에넥스","011090" -"64249","00423177","텔레칩스","054450" -"64255","01182444","셀리버리","268600" -"64257","00222532","LG헬로비전","037560" -"64285","00236863","희림","037440" -"64439","00125530","SPC삼립","005610" -"64491","00138701","아세아","002030" -"64515","01408999","유안타제5호스팩","336060" -"64524","00115931","디오","039840" -"64538","00327819","선익시스템","171090" -"64557","00138260","SIMPAC","009160" -"64605","00256043","비씨월드제약","200780" -"64616","00879020","유니트론텍","142210" -"64707","00263371","한국경제TV","039340" -"64708","00997812","코미코","183300" -"64716","00181299","상아프론테크","089980" -"64737","01238169","오리온","271560" -"64770","00118460","태림페이퍼","019300" -"64796","00770312","마니커에프앤지","195500" -"64814","00123648","심팩인더스트리","005350" -"64825","00488989","한양디지텍","078350" -"64876","00556907","에이스테크","088800" -"64877","00297934","피씨디렉트","051380" -"64906","00346966","에코솔루션","052510" -"64908","00357430","쎌바이오텍","049960" -"65028","00876908","모트렉스","118990" -"65067","01047840","미스터블루","207760" -"65098","01011395","레몬","294140" -"65122","00222806","에임하이글로벌","043580" -"65127","00183215","케이디지엠텍","032290" -"65145","00126779","삼아알미늄","006110" -"65164","01234525","한국제5호기업인수목적","271740" -"65165","00706715","미원홀딩스","107590" -"65175","00481454","금호타이어","073240" -"65221","00459871","농심홀딩스","072710" -"65229","00163318","모헨즈","006920" -"65342","00389679","해피드림","065180" -"65402","00256955","테이팩스","055490" -"65423","00577380","신진에스엠","138070" -"65457","00133858","세방전지","004490" -"65458","00454937","덕신하우징","090410" -"65560","00535481","에스와이","109610" -"65609","01099861","에이텍티앤","224110" -"65665","00134176","세우글로벌","013000" -"65671","00261735","위노바","039790" -"65690","00249247","YG PLUS","037270" -"65691","00526465","동북아13호선박투자회사","083380" -"65692","00296005","우리로","046970" -"65693","00867034","듀켐바이오","176750" -"65694","01117246","파인이엠텍","278990" -"65721","00318316","대아티아이","045390" -"65749","01186404","디앤씨미디어","263720" -"65821","00103662","광명전기","017040" -"65834","00442826","대봉엘에스","078140" -"65856","00124577","엠소닉","008120" -"65884","00407771","듀오백","073190" -"65900","00152385","에이프로젠 KIC","007460" -"65909","00366793","에스제이케이","080440" -"65910","01170865","네오셈","253590" -"65911","01259232","노바텍","285490" -"65937","00555740","툴코리아","110660" -"65943","00379229","에스에이티","060540" -"65947","00140955","한솔케미칼","014680" -"65999","00228536","에버다임","041440" -"66010","00610083","비아트론","141000" -"66030","00579999","고영","098460" -"66067","00302582","캐스텍코리아","071850" -"66114","00876643","엔피디","198080" -"66129","00132488","신세계톰보이","012580" -"66161","00230911","HRS","036640" -"66164","00347442","모보","051810" -"66188","00123143","보령제약","003850" -"66226","00117726","동양피스톤","092780" -"66229","01188749","지앤이헬스케어","299480" -"66285","00324104","디케이씨","047440" -"66362","01010110","더블유게임즈","192080" -"66394","00181712","SK","034730" -"66428","00624518","솔루에타","154040" -"66455","00526872","이수앱지스","086890" -"66466","00645089","NHN벅스","104200" -"66478","00611736","엑시콘","092870" -"66525","00153126","크라운해태홀딩스","005740" -"66541","00111999","대원제약","003220" -"66569","00913689","세경하이테크","148150" -"66592","01316236","효성화학","298000" -"66593","01021949","덱스터","206560" -"66716","00410678","다린","204690" -"66740","00126201","삼성공조","006660" -"66835","00210740","대화제약","067080" -"66839","01318933","지니틱스","303030" -"66840","01428249","케이프이에스제4호","347140" -"66902","00119672","두올","016740" -"66909","00137809","신화실업","001770" -"66965","00877819","데이타솔루션","263800" -"67037","00664181","인텔리안테크","189300" -"67131","00109514","티웨이홀딩스","004870" -"67201","00576798","디아이티","110990" -"67227","01109539","와이아이케이","232140" -"67240","00206084","세아메탈","033020" -"67264","00151447","천지산업","001490" -"67345","00141875","사조오양","006090" -"67397","00291152","디비엘","041500" -"67450","00188089","한섬","020000" -"67492","00983271","엔에이치엔","181710" -"67500","00874937","탑선","180060" -"67536","00556615","모바일어플라이언스","087260" -"67545","00363246","우원개발","046940" -"67560","00362238","휴비스","079980" -"67628","00634728","이크레더블","092130" -"67718","00305668","새론오토모티브","075180" -"67727","00612188","에스티오","098660" -"67773","01168383","일동제약","249420" -"67789","00158565","한국알콜","017890" -"67981","00131832","SK디스커버리","006120" -"68017","00268251","다산네트웍스","039560" -"68093","00480455","STX엔진","077970" -"68176","00413523","한미글로벌","053690" -"68177","00260408","팜스코","036580" -"68180","00350312","HDC아이콘트롤스","039570" -"68181","00306719","에스텍","069510" -"68182","00148364","하림지주","003380" -"68187","00491415","인포바인","115310" -"68217","00830447","수프로","185190" -"68230","00728638","한세실업","105630" -"68231","00161976","한세예스24홀딩스","016450" -"68258","00550994","아이센스","099190" -"68259","00112907","코원에너지서비스","026870" -"68303","00159874","한국컴퓨터지주","009760" -"68360","00442570","제우스","079370" -"68425","00484682","엘오티베큠","083310" -"68426","00117577","오리온홀딩스","001800" -"68427","00589127","KEC","092220" -"68445","00520887","비상교육","100220" -"68462","00102618","계양전기","012200" -"68464","00164308","이마트에브리데이","010090" -"68467","00111810","대웅","003090" -"68468","00427483","대웅제약","069620" -"68481","00398701","엘앤에프","066970" -"68494","00219097","BGF","027410" -"68539","01067516","골프존","215000" -"68557","01128622","줌인터넷","239340" -"68570","00162586","한올바이오파마","009420" -"68571","00223762","지니뮤직","043610" -"68606","00133991","세아특수강","019440" -"68621","00108913","대교","019680" -"68623","00599957","슈프리마에이치큐","094840" -"68625","01010642","녹십자랩셀","144510" -"68636","00414601","유니퀘스트","077500" -"68640","00148984","조선내화","000480" -"68667","00137368","신풍제지","002870" -"68675","00652007","메카로","241770" -"68689","00255433","라온시큐어","042510" -"68718","00661847","화인베스틸","133820" -"68771","00105156","극동유화","014530" -"68773","00268002","세종텔레콤","036630" -"68774","00530185","이엔에프테크놀로지","102710" -"68775","00115065","동남합성","023450" -"68776","00970453","파마리서치프로덕트","214450" -"68777","00123967","부산도시가스","015350" -"68778","00595243","엔스퍼트","098400" -"68837","00441447","케이씨티","089150" -"68839","00595191","후성","093370" -"68846","00357360","한세엠케이","069640" -"68848","00123718","부광약품","003000" -"68853","00156956","한국기업평가","034950" -"68890","00128032","삼일제약","000520" -"68894","00241005","코오롱플라스틱","138490" -"68916","00138729","아세아제지","002310" -"68943","00350738","제네시스디벨롭먼트홀딩스","053320" -"68982","00371485","켐트로닉스","089010" -"68986","01066058","파마리서치바이오","217950" -"68987","01118643","로보쓰리","238500" -"68996","01414936","SK6호스팩","340350" -"69070","00346610","유앤아이","056090" -"69099","01141942","액트로","290740" -"69109","00130684","서울도시가스","017390" -"69170","00248974","하나제약","293480" -"69247","01351114","케이비17호스팩","317030" -"69291","01062867","디알텍","214680" -"69358","00163761","한창제지","009460" -"69373","00862853","플레이디","237820" -"69379","01336373","메드팩토","235980" -"69387","00130763","서울반도체","046890" -"69408","00161693","한샘","009240" -"69466","00255044","현대통신","039010" -"69473","00113243","대한제분","001130" -"69474","00577016","수산아이앤티","050960" -"69475","00114552","도화엔지니어링","002150" -"69531","00118965","티에이치엔","019180" -"69532","00203209","에쎈테크","043340" -"69533","00269940","하나투어","039130" -"69534","00323868","웹케시","053580" -"69535","00442048","아바텍","149950" -"69536","00447760","미래컴퍼니","049950" -"69537","01015726","미래테크놀로지","213090" -"69556","00105280","현대그린푸드","005440" -"69557","00108490","엔피케이","048830" -"69558","00143262","우진아이엔에스","010400" -"69559","00146232","일성건설","013360" -"69560","00373571","룽투코리아","060240" -"69570","00111847","대원강업","000430" -"69571","00136226","신성델타테크","065350" -"69572","00116949","디티알오토모티브","007340" -"69573","00190756","와이엔텍","067900" -"69574","00259545","엠에스오토텍","123040" -"69575","00124780","사조씨푸드","014710" -"69576","00124799","사조산업","007160" -"69577","00261373","마크로젠","038290" -"69632","00106614","기신정기","092440" -"69633","01436336","이베스트스팩5호","349720" -"69656","00367844","자이에스앤디","317400" -"69695","01070149","올리패스","244460" -"69733","00450010","센트랄모텍","308170" -"69734","01037214","케이피에스","256940" -"69744","00149026","CS홀딩스","000590" -"69745","00153339","태경산업","015890" -"69746","00159254","한국전자홀딩스","006200" -"69807","00808086","애니젠","196300" -"69933","01236897","제일약품","271980" -"69953","00796994","조선선재","120030" -"69954","01032413","해성디에스","195870" -"69955","01144028","씨티케이코스메틱스","260930" -"69956","01365825","피에스케이","319660" -"69970","01337220","키움제5호스팩","311270" -"69991","01072518","디피코","163430" -"69992","00203582","한솔홈데코","025750" -"70150","01316245","효성중공업","298040" -"70168","00107987","남해화학","025860" -"70195","00378363","3S","060310" -"70209","00684732","풀무원식품","103160" -"70249","00930321","하이골드오션8호국제선박투자회사","159650" -"70250","01419135","이노진","344860" -"70294","00106313","금호산업","002990" -"70303","00216027","바이넥스","053030" -"70341","00268011","일레덱스홀딩스","033550" -"70354","01237540","이녹스첨단소재","272290" -"70523","00145464","이구산업","025820" -"70530","00104768","가온전선","000500" -"70580","00616290","이엠넷","123570" -"70606","00178754","동아지질","028100" -"70633","00599151","SV인베스트먼트","289080" -"70634","00136457","SH에너지화학","002360" -"70642","00104388","국도화학","007690" -"70644","00956930","동아에스티","170900" -"70648","00135999","신라섬유","001000" -"70662","00307037","더스텔라","065310" -"70692","00128175","원익큐브","014190" -"70701","00116824","동아쏘시오홀딩스","000640" -"70702","00135050","우리들제약","004720" -"70741","00361594","DMS","068790" -"70772","01336735","펨토바이오메드","327610" -"70804","00110583","대성엘텍","025440" -"70805","00119007","무림P&P","009580" -"70814","00145862","인천도시가스","034590" -"70841","00695464","차이나그레이트스타인터내셔널리미티드","900040" -"70842","00916826","드림씨아이에스","223250" -"70861","00430089","삼보오토","070080" -"70889","00129013","CJ씨푸드","011150" -"70890","00117179","디와이","013570" -"70904","00157104","대동전자","008110" -"70985","00990165","아세아시멘트","183190" -"70995","00340917","동원F&B","049770" -"71001","00326731","바이오니아","064550" -"71119","00117498","디피씨","026890" -"71187","00244747","파크시스템스","140860" -"71191","00493501","씨디네트웍스","073710" -"71292","00992543","삼양옵틱스","225190" -"71338","00389998","브레인콘텐츠","066980" -"71339","00939687","동일고무벨트","163560" -"71340","00442145","아바코","083930" -"71341","00866594","미애부","225850" -"71342","00664048","우리넷","115440" -"71343","00693554","티케이케미칼","104480" -"71344","01070857","켐온","217600" -"71345","01181807","까스텔바작","308100" -"71346","01245062","코오롱티슈진","950160" -"71347","01343920","유안타제4호스팩","313750" -"71370","00123152","보루네오가구","004740" -"71377","00927558","유바이오로직스","206650" -"71378","00993931","동양파일","228340" -"71382","00828497","한미약품","128940" -"71473","00157399","한솔테크닉스","004710" -"71477","01086812","케미메디","205290" -"71489","00673301","전우정밀","120780" -"71491","01388631","오션스톤","329020" -"71499","00151368","인터지스","129260" -"71502","01316254","효성첨단소재","298050" -"71554","01157235","아스타","246720" -"71681","00990819","래몽래인","200350" -"71683","00447575","제이앤티씨","204270" -"71691","00123772","부국증권","001270" -"71810","00165361","휴코드","036840" -"71812","01336391","엔에이치스팩13호","310840" -"71813","00158501","에스원","012750" -"71814","00161046","한독","002390" -"71920","00125576","체시스","033250" -"71990","00946030","로보티즈","108490" -"71991","01195828","대원모방","311840" -"71999","00359395","헬릭스미스","084990" -"72141","01148909","휴럼","284420" -"72143","00490090","이지케어텍","099750" -"72155","00171265","파라다이스","034230" -"72163","00857480","사람인에이치알","143240" -"72191","01060744","한솔제지","213500" -"72255","00255619","강원랜드","035250" -"72304","01412725","두산퓨얼셀","336260" -"72314","00124197","세아제강지주","003030" -"72448","01326792","상상인이안1호스팩","307870" -"72460","00118284","동일금속","109860" -"72514","00870481","에코캡","128540" -"72559","00963976","SG","255220" -"72595","01267967","마이크로디지탈","305090" -"72602","00154392","에스트라","016570" -"72668","00216434","체리부로","066360" -"72677","00241209","모아텍","033200" -"72678","00631518","SK이노베이션","096770" -"72702","00659815","금오하이텍","165270" -"72803","00112457","대주산업","003310" -"72814","00961136","씨티네트웍스","189540" -"72873","00659976","영화테크","265560" -"72914","00116356","동성화학","005190" -"72916","00893923","이푸른","185280" -"72975","00763473","코스메카코리아","241710" -"73007","00838500","엘브이엠씨","900140" -"73008","00359076","브리지텍","064480" -"73009","00938688","SBI핀테크솔루션즈","950110" -"73012","01309795","알로이스","297570" -"73024","00234412","신세계인터내셔날","031430" -"73087","00925189","신화콘텍","187270" -"73097","00965062","코셋","189350" -"73105","01003040","케이사인","192250" -"73163","00121039","명문제약","017180" -"73358","00115384","동방아그로","007590" -"73359","00160843","DB하이텍","000990" -"73400","01205329","크라운제과","264900" -"73453","00238199","로지시스","067730" -"73463","00120508","롯데푸드","002270" -"73511","00415594","동국S&C","100130" -"73535","00925295","에프엔씨엔터","173940" -"73654","00110750","리더스 기술투자","019570" -"73663","01371312","케이비제18호스팩","323940" -"73666","01393299","케이비제19호스팩","330990" -"73798","00530796","미성포리테크","094700" -"73805","00113207","대한전선","001440" -"73806","00489243","동아엘텍","088130" -"74018","00343127","자유투어","046840" -"74087","00776820","영원무역","111770" -"74116","00110875","대신정보통신","020180" -"74190","00875307","야스","255440" -"74205","00657002","에이디테크놀로지","200710" -"74209","01063954","수젠텍","253840" -"74278","00108241","농심","004370" -"74383","01059605","디와이파워","210540" -"74419","01182240","배럴","267790" -"74441","00121932","미원상사","002840" -"74463","00397243","티에스엠텍","066350" -"74486","00148276","제일기획","030000" -"74493","00602136","디와이피엔에프","104460" -"74528","00435312","하이스틸","071090" -"74644","00140061","정산애강","022220" -"74645","00150439","진양산업","003780" -"74691","00353762","키이스트","054780" -"74714","01255652","레이크머티리얼즈","281740" -"74723","01189438","노터스","278650" -"74724","00671437","파인넥스","123260" -"74729","00125488","삼륭물산","014970" -"74738","00274933","신세계푸드","031440" -"74739","00159564","한국주강","025890" -"74742","00362858","예스24","053280" -"74744","00155638","피에스텍","002230" -"74745","00441128","아리온","058220" -"74749","00116408","동신건설","025950" -"74760","00580199","메디톡스","086900" -"74766","00878915","DGB금융지주","139130" -"74788","00140858","영신금속","007530" -"74789","00185505","제일바이오","052670" -"74790","00133751","세명전기","017510" -"74792","00109268","대동스틸","048470" -"74793","00412348","엘비세미콘","061970" -"74803","00138695","뉴아세아조인트","013340" -"74817","01368354","네패스아크","330860" -"74819","00238001","블루콤","033560" -"74820","00422284","메가스터디","072870" -"74851","00627029","ITX-AI","099520" -"74856","00132354","쿠쿠홀딩스","192400" -"74857","00814786","스카이이앤엠","131100" -"74858","01257872","에스퓨얼셀","288620" -"74865","00133557","PN풍년","024940" -"74866","00122339","이매진아시아","036260" -"74867","00353878","다스코","058730" -"74868","01137383","카카오게임즈","293490" -"74869","00652159","코오롱머티리얼","144620" -"74870","00149239","조일알미늄","018470" -"74871","00107677","비비안","002070" -"74873","00126478","삼성중공업","010140" -"74874","00447928","네오티스","085910" -"74875","00127158","씨아이테크","004920" -"74876","00695969","동운아나텍","094170" -"74877","00109824","대림통상","006570" -"74879","00176914","다우기술","023590" -"74880","01063884","이디티","215090" -"74886","01042979","휴마시스","205470" -"74915","00258801","카카오","035720" -"74916","00295857","코다코","046070" -"74917","00369657","리노공업","058470" -"74918","00140566","한탑","002680" -"74922","00638487","파수","150900" -"74923","00186717","태양","053620" -"74925","00147772","정원엔시스","045510" -"74927","00101752","케이씨피드","025880" -"74960","00665630","테라텍","151750" -"74981","00157070","한국단자공업","025540" -"74984","00361488","텔코웨어","078000" -"74985","00828789","대성산업","128820" -"74986","00592653","유비벨록스","089850" -"74988","00540429","휴림로봇","090710" -"74991","00265041","KCI","036670" -"74992","00911229","라온테크","232680" -"74995","01399071","고바이오랩","348150" -"74997","00298687","한류타임즈","039670" -"74998","00601191","하나기술","299030" -"74999","00694942","이미지스","115610" -"75000","00121570","문배철강","008420" -"75002","00350048","오성첨단소재","052420" -"75003","00178851","동양에스텍","060380" -"75004","00152783","코메론","049430" -"75005","00495086","픽셀플러스","087600" -"75008","01042775","만도","204320" -"75010","00959229","세종메디칼","258830" -"75028","00159786","유니드","014830" -"75038","00103592","광동제약","009290" -"75051","01020843","엔에스","217820" -"75064","00204642","티비씨","033830" -"75065","00112165","디아이씨","092200" -"75067","00137012","에스앤더블류","103230" -"75068","00139719","와이지-원","019210" -"75069","00295370","에이아이비트","039230" -"75070","00493431","자안","221610" -"75071","00920379","티엘비","356860" -"75075","00146083","일동홀딩스","000230" -"75077","00759294","와이솔","122990" -"75079","01117918","셀젠텍","258250" -"75086","00411385","유비쿼스홀딩스","078070" -"75091","00670766","엠투아이","347890" -"75095","01497869","티와이홀딩스","363280" -"75098","00217947","신세계건설","034300" -"75102","00331016","에프앤가이드","064850" -"75105","00536329","디지탈옵틱","106520" -"75109","01136348","나무기술","242040" -"75110","01258710","이노메트리","302430" -"75114","00184667","유진기업","023410" -"75116","01135941","원익IPS","240810" -"75164","00872452","에이치엔티","176440" -"75166","00125965","삼본전자","111870" -"75167","01067808","넵튠","217270" -"75168","00113492","깨끗한나라","004540" -"75169","00185356","제룡전기","033100" -"75174","00144720","유양디앤유","011690" -"75175","01153293","제이엘케이","322510" -"75176","01263527","카이노스메드","284620" -"75177","01447244","에이치엠씨제5호스팩","353060" -"75179","01505450","DB금융스팩8호","367340" -"75210","00126788","삼아제약","009300" -"75244","00355548","한국테크놀로지","053590" -"75245","00155452","풍국주정","023900" -"75246","00242378","이지홀딩스","035810" -"75247","01480568","이지바이오","353810" -"75248","00192499","팜스토리","027710" -"75249","00204208","마니커","027740" -"75250","00623184","감마누","192410" -"75251","00418379","자연과환경","043910" -"75252","00351807","대원미디어","048910" -"75254","00110431","JW신약","067290" -"75256","00127699","에코마이스터","064510" -"75257","00152686","코리아써키트","007810" -"75258","00369170","인터플렉스","051370" -"75259","00206686","케이피엠테크","042040" -"75260","00954242","하이골드12호","172580" -"75262","00113526","대한항공","003490" -"75282","01061327","클래시스","214150" -"75290","01201590","에브리봇","270660" -"75348","00149770","중앙에너비스","000440" -"75351","00113225","대한제강","084010" -"75356","00367604","SM Life Design","063440" -"75357","00231372","롯데관광개발","032350" -"75360","01416642","유안타제6호스팩","340360" -"75364","01139497","에스제이켐","217910" -"75365","00124151","부산주공","005030" -"75381","00109310","대동기어","008830" -"75384","00127909","삼일","032280" -"75391","00110547","넥상스코리아","003050" -"75408","00173698","신일전자","002700" -"75419","00670340","씨에스윈드","112610" -"75438","00384948","대유","290380" -"75440","01025644","강스템바이오텍","217730" -"75451","00892526","디케이앤디","263020" -"75459","00523176","네추럴FNP","086220" -"75467","00587925","바이오리더스","142760" -"75498","00205687","글로스퍼랩스","032860" -"75503","00304915","코리아에셋투자증권","190650" -"75510","00861100","아이케이세미콘","149010" -"75530","00126955","삼양식품","003230" -"75531","00447007","알리코제약","260660" -"75536","00911955","잇츠한불","226320" -"75537","00660291","옵티시스","109080" -"75538","00267854","메디앙스","014100" -"75603","00113614","대현","016090" -"75604","00177320","대성미생물","036480" -"75614","01013694","인카금융서비스","211050" -"75623","00195229","휠라홀딩스","081660" -"75636","00191287","대동금속","020400" -"75643","00125150","SGC에너지","005090" -"75644","00652423","인바이오젠","101140" -"75659","00133706","세하","027970" -"75674","00886792","루켄테크놀러지스","162120" -"75688","01394669","대신밸런스제7호스팩","332290" -"75689","01407909","대신밸런스제8호스팩","336570" -"75697","00102681","고려개발","004200" -"75701","00604426","인터로조","119610" -"75706","01060814","삼양패키징","272550" -"75708","00220057","유비케어","032620" -"75713","01203376","유비쿼스","264450" -"75714","00264714","SG&G","040610" -"75728","01204056","빅히트","352820" -"75752","01337017","이앤에치","341310" -"75767","00136925","신원종합개발","017000" -"75769","00609315","멜파스","096640" -"75770","00124276","부스타","008470" -"75771","00136624","신영와코루","005800" -"75772","00127042","삼양통상","002170" -"75773","00604815","에스디시스템","121890" -"75774","00356389","프럼파스트","035200" -"75775","00609634","아이엠","101390" -"75776","00755739","파인테크닉스","106240" -"75777","00103130","엔케이물산","009810" -"75779","00145190","유화증권","003460" -"75780","00291860","조광ILI","044060" -"75781","00307028","경남제약","053950" -"75783","00301246","SFA반도체","036540" -"75795","00300548","현대리바트","079430" -"75797","01207761","디자인","227100" -"75802","01263022","BGF리테일","282330" -"75804","00264228","위즈코프","038620" -"75808","01310269","HDC현대산업개발","294870" -"75811","00148832","제주은행","006220" -"75812","01318261","더블유에스아이","299170" -"75813","00161408","케이비캐피탈","021960" -"75842","00603348","케이아이엔엑스","093320" -"75845","00445841","한컴MDS","086960" -"75846","00786331","에스앤씨엔진그룹","900080" -"75873","01290381","피엔케이피부임상연구센타","347740" -"75918","00357607","케이피티유","054410" -"75934","01065013","우정바이오","215380" -"75935","00128980","삼호개발","010960" -"75936","00357740","NHN한국사이버결제","060250" -"75938","00244455","케이티앤지","033780" -"75939","00624998","제닉","123330" -"75940","00442561","에프알텍","073540" -"75942","00129280","삼화전자공업","011230" -"75943","00129679","녹십자","006280" -"75944","00108135","녹십자홀딩스","005250" -"75945","00391197","메디프론","065650" -"75946","00125974","삼부토건","001470" -"75947","00403632","현대퓨처넷","126560" -"75948","00249894","테라젠이텍스","066700" -"75949","00199988","동아화성","041930" -"75951","00138242","쌍용자동차","003620" -"75958","00147994","새로닉스","042600" -"75965","00120526","롯데쇼핑","023530" -"75966","00963000","썬테크","217320" -"75977","00105138","파라텍","033540" -"75978","00105101","예스코홀딩스","015360" -"75979","00203023","시노펙스","025320" -"75980","01384477","엠에프엠코리아","323230" -"75998","00565154","이노션","214320" -"75999","01415892","제이알글로벌리츠","348950" -"76000","01476219","교보10호기업인수목적","355150" -"76020","00166500","화천기계","010660" -"76021","01243550","아이비케이에스제7호기업인수목적","276920" -"76024","00942131","앤디포스","238090" -"76073","00128971","대림건설","001880" -"76074","00136095","조은저축은행","031920" -"76106","00261887","티엘아이","062860" -"76108","00102113","경인양행","012610" -"76109","00266952","네오위즈홀딩스","042420" -"76110","00628860","네오위즈","095660" -"76114","00145552","이수화학","005950" -"76117","00406727","세진티에스","067770" -"76118","00173944","우진","105840" -"76119","00778235","TS인베스트먼트","246690" -"76120","00390055","종근당바이오","063160" -"76122","01267958","프로테옴텍","303360" -"76124","00389970","다날","064260" -"76125","00684714","풍산","103140" -"76127","00129411","삼환기업","000360" -"76141","00818472","이큐셀","160600" -"76142","00121941","대상","001680" -"76150","00656021","데일리블록체인","139050" -"76153","00205003","좋은사람들","033340" -"76193","00136721","신영증권","001720" -"76194","01440153","IBKS제14호스팩","351320" -"76198","00400060","이씨에스","067010" -"76215","00146649","일진홀딩스","015860" -"76228","01394377","이지스밸류플러스리츠","334890" -"76239","00155373","풍강","093380" -"76249","01165155","모비스","250060" -"76258","00148461","제일연마","001560" -"76273","00117744","메리츠화재","000060" -"76274","00123541","보해양조","000890" -"76275","00261443","엔씨소프트","036570" -"76276","00208444","피에스케이홀딩스","031980" -"76277","00390860","MP그룹","065150" -"76279","01117592","인터코스","240340" -"76282","00296324","큐로홀딩스","051780" -"76292","00105961","LG이노텍","011070" -"76293","00121507","무림SP","001810" -"76294","00382199","신한지주","055550" -"76307","00109693","대림산업","000210" -"76308","01055317","비엔디생활건강","215050" -"76314","00111014","애큐온저축은행","007640" -"76315","01032486","두산밥캣","241560" -"76317","00877059","삼성바이오로직스","207940" -"76321","00176792","오렌지라이프생명보험","079440" -"76322","00149293","신한은행","000010" -"76324","00148610","한화투자증권","003530" -"76351","00860730","에이리츠","140910" -"76353","00221728","하츠","066130" -"76354","00122898","벽산","007210" -"76362","00367385","효성오앤비","097870" -"76375","01152470","펄어비스","263750" -"76376","00532129","영림원소프트랩","060850" -"76395","00527491","코리아에스이","101670" -"76401","00158149","한국쉘석유","002960" -"76431","00243988","에스케이브로드밴드","033630" -"76442","00155735","피제이전자","006140" -"76444","01090471","씨아이에스","222080" -"76448","01363818","롯데리츠","330590" -"76449","00245472","티씨케이","064760" -"76450","00352718","소리바다","053110" -"76451","00785475","피앤이솔루션","131390" -"76452","01505186","엔에이치스팩18호","365590" -"76453","00164779","SK하이닉스","000660" -"76454","00137997","현대차증권","001500" -"76463","01102095","쿠첸","225650" -"76474","00106641","기아자동차","000270" -"76476","00147295","전북은행","006350" -"76482","00113058","한화생명","088350" -"76484","00138321","신한금융투자","008670" -"76489","00159616","두산중공업","034020" -"76491","01413371","단디바이오","343090" -"76493","00610490","비디아이","148140" -"76494","00814810","엠에프엠코리아","251960" -"76495","00763701","세틀뱅크","234340" -"76496","01186811","티에스트릴리온","284610" -"76497","01031502","디오스텍","196450" -"76498","01080252","넥스틴","348210" -"76499","01105621","엔투텍","227950" -"76500","01329957","국전약품","307750" -"76501","01353024","TS트릴리온","317240" -"76502","00141149","영진약품","003520" -"76507","00138792","아시아나항공","020560" -"76527","01032583","고려시멘트","198440" -"76549","00131850","SK증권","001510" -"76551","00266961","NAVER","035420" -"76571","00910202","대동고려삼","178600" -"76591","00242934","케이엠","083550" -"76622","00762377","씨앗","103660" -"76648","00605522","소룩스","290690" -"76653","00126292","삼성카드","029780" -"76654","00158909","하나은행","004940" -"76665","00877174","엠브레인","169330" -"76666","01236286","컬러레이","900310" -"76673","00547583","하나금융지주","086790" -"76688","00364795","레드로버","060300" -"76689","00105855","엘에스일렉트릭","010120" -"76692","01478712","대덕전자","353200" -"76695","00154055","태원물산","001420" -"76696","00226228","한프","066110" -"76697","00127954","CJ프레시웨이","051500" -"76705","01066030","드림티엔터테인먼트","220110" -"76711","00118275","디아이","003160" -"76724","01144569","명성티엔에스","257370" -"76755","00203847","국일신동","060480" -"76756","00540863","GST","083450" -"76759","01047169","지란지교시큐리티","208350" -"76760","01250374","에스에스알","275630" -"76765","00407814","크리스탈지노믹스","083790" -"76766","01487446","엔에이치스팩17호","359090" -"76773","00403793","바이브컴퍼니","301300" -"76869","00975290","에이스토리","241840" -"76873","00537221","바이오코아","216400" -"76874","01060735","아이피몬스터","223220" -"76891","00122694","범양건영","002410" -"76906","01416572","한화플러스제1호스팩","340440" -"76933","01336285","SK4호스팩","307070" -"76960","01259056","베스파","299910" -"76961","00425351","멀티캠퍼스","067280" -"76969","00977650","엔케이맥스","182400" -"76971","01071041","천랩","311690" -"76972","00357935","HDC현대EP","089470" -"76976","00122348","방림","003610" -"76977","00422895","세이브존I&C","067830" -"76998","00191588","삼원강재","023000" -"77017","01213586","아이디피","332370" -"77025","00135111","수산중공업","017550" -"77036","01328170","노드메이슨","317860" -"77065","01187494","덴티스","261200" -"77090","00363486","로체시스템즈","071280" -"77091","01265251","압타머사이언스","291650" -"77095","01447217","에이치엠씨제4호스팩","353070" -"77110","01350638","티티씨디펜스","309900" -"77124","00876209","미래엔에듀파트너","208890" -"77125","00670085","엘아이에스","138690" -"77132","00152127","심텍홀딩스","036710" -"77135","01095722","심텍","222800" -"77141","01418260","폭스소프트","354230" -"77152","01430475","코람코에너지리츠","357120" -"77168","00602279","그린플러스","186230" -"77179","00136271","신성이엔지","011930" -"77183","01237434","플리토","300080" -"77184","00411048","에스앤에스텍","101490" -"77185","00135467","승일","049830" -"77186","00537832","푸른기술","094940" -"77187","00815767","선데이토즈","123420" -"77227","01335851","박셀바이오","323990" -"77246","00120872","만호제강","001080" -"77266","00277736","한일네트웍스","046110" -"77287","01139035","티에스아이","277880" -"77288","01091054","쎄노텍","222420" -"77289","00760999","상신전자","263810" -"77290","00147152","유니크","011320" -"77305","00186452","릭스솔루션","029480" -"77306","00428729","대호에이엘","069460" -"77307","00187415","코미팜","041960" -"77308","00165060","하이록코리아","013030" -"77309","00296290","키움증권","039490" -"77314","00137234","모베이스전자","012860" -"77315","00356927","에이디칩스","054630" -"77316","00144650","유신","054930" -"77317","00568188","마이크로컨텍솔","098120" -"77319","00389387","라임","065160" -"77321","00531917","톱텍","108230" -"77323","00442835","메타랩스","090370" -"77324","00296078","APS홀딩스","054620" -"77326","01061497","퓨쳐스트림네트웍스","214270" -"77327","00453284","교촌에프앤비","339770" -"77330","01187458","에스알바이오텍","270210" -"77331","00202839","한창산업","079170" -"77332","00494218","엘엠에스","073110" -"77348","01089855","해마로푸드서비스","220630" -"77351","00989327","엘피케이로보틱스","183350" -"77384","00102432","계룡건설산업","013580" -"77440","00364847","이글루시큐리티","067920" -"77443","00396925","엠로","058970" -"77447","00235183","에이치케이","044780" -"77448","00100601","강원","114190" -"77449","00140052","에이스침대","003800" -"77458","00916516","흥국에프엔비","189980" -"77464","00168401","금비","008870" -"77473","00153621","참엔지니어링","009310" -"77475","01047451","셀바스헬스케어","208370" -"77476","00261957","한국정보공학","039740" -"77477","00642541","디엔에이링크","127120" -"77478","00674498","골프존뉴딘홀딩스","121440" -"77479","00557508","GKL","114090" -"77480","00299464","초록뱀","047820" -"77520","00158307","롯데하이마트","071840" -"77521","00120289","제이에스티나","026040" -"77529","00855093","선진","136490" -"77530","00164362","행남사","008800" -"77531","00151863","센트럴인사이트","012600" -"77532","00167280","폴루스바이오팜","007630" -"77533","00689418","지와이커머스","111820" -"77534","00199076","동일기연","032960" -"77535","00364306","성우전자","081580" -"77537","00132318","성광벤드","014620" -"77539","00861076","케이탑리츠","145270" -"77540","00488402","서울바이오시스","092190" -"77541","00109161","태경케미컬","006890" -"77542","00426086","휴켐스","069260" -"77543","00765851","위더스제약","330350" -"77557","00904672","넷마블","251270" -"77559","00858364","BNK금융지주","138930" -"77560","00110893","대신증권","003540" -"77561","00124540","대우건설","047040" -"77569","00178790","동양이엔피","079960" -"77570","00410739","필로시스헬스케어","057880" -"77571","00405719","아이크래프트","052460" -"77572","00258689","JYP Ent.","035900" -"77573","00868705","윈스","136540" -"77574","00370200","와이오엠","066430" -"77576","00103547","우진비앤지","018620" -"77579","00264671","세중","039310" -"77580","01008762","데브시스터즈","194480" -"77602","01436628","이지스레지던스리츠","350520" -"77604","00961570","신도기연","290520" -"77628","00599106","코닉글로리","094860" -"77647","01028164","광주은행","192530" -"77648","00157539","KB오토시스","024120" -"77650","00120906","에스아이리소스","065420" -"77651","00143527","경동인베스트","012320" -"77653","01049422","썸에이지","208640" -"77660","00128661","에스에이엠티","031330" -"77661","01208849","나인테크","267320" -"77662","00266934","파루","043200" -"77663","00373021","테라사이언스","073640" -"77664","00208134","KNN","058400" -"77665","00108038","엔피씨","004250" -"77666","00101549","경동제약","011040" -"77668","00132992","성우하이텍","015750" -"77669","00447502","바이오톡스텍","086040" -"77670","00174004","유성기업","002920" -"77671","00369833","유니온머티리얼","047400" -"77673","00288495","홈센타홀딩스","060560" -"77674","00365387","AJ네트웍스","095570" -"77675","00201733","TJ미디어","032540" -"77676","01311286","퀀타매트릭스","317690" -"77678","00304401","텔라움","047730" -"77688","00554024","셀트리온헬스케어","091990" -"77689","00230814","OQP","078590" -"77691","00162461","한화솔루션","009830" -"77693","00503668","LIG넥스원","079550" -"77695","00923792","내츄럴엔도텍","168330" -"77696","00878517","에스케이씨에스","224020" -"77697","00475718","이엠앤아이","083470" -"77699","01031229","지티지웰니스","219750" -"77701","00109806","삼일씨엔에스","004440" -"77702","00608839","에이루트","096690" -"77704","00313649","현대바이오","048410" -"77705","00270113","멕아이씨에스","058110" -"77707","00245898","아래스","050320" -"77708","00537337","앤씨앤","092600" -"77709","00136165","스페코","013810" -"77710","00372882","KTcs","058850" -"77711","00808068","에이씨티","138360" -"77712","00246620","케이엘넷","039420" -"77713","00134723","페이퍼코리아","001020" -"77714","00111218","KD","044180" -"77716","00361725","에스케이커뮤니케이션즈","066270" -"77724","00129989","코스모신소재","005070" -"77754","00258102","지엔코","065060" -"77760","00563147","에스에이티이엔지","158300" -"77762","00141389","영풍정밀","036560" -"77763","00124090","한국특수형강","007280" -"77764","00200275","YTN","040300" -"77765","00614478","휴메딕스","200670" -"77766","00525642","코오롱생명과학","102940" -"77768","00300557","위니아딤채","071460" -"77770","00480950","이아이디","093230" -"77771","00149804","대유에이텍","002880" -"77772","00656951","이지웰","090850" -"77774","00194947","한전산업","130660" -"77775","00180865","엔에스엔","031860" -"77776","00186939","특수건설","026150" -"77778","00476498","컴투스","078340" -"77786","00117382","디와이홀딩스","004510" -"77788","00150828","진흥기업","002780" -"77799","00491938","GH신소재","130500" -"77801","01337114","구스앤홈","329050" -"77805","00199252","에이치엘비","028300" -"77806","00260958","KTH","036030" -"77807","00113359","교보증권","030610" -"77809","00842585","네이블커뮤니케이션즈","153460" -"77812","01114559","씨엔티드림","286000" -"77822","00148896","OCI","010060" -"77831","00216762","한양이엔지","045100" -"77832","00197759","미래산업","025560" -"77835","00187725","코콤","015710" -"77837","00232089","바이온","032980" -"77839","00129554","갤럭시아에스엠","011420" -"77842","00155151","평화정공","043370" -"77845","00105475","금강철강","053260" -"77846","00352499","링네트","042500" -"77847","00820389","뉴지랩","214870" -"77848","00390903","우주일렉트로","065680" -"77874","00134316","세원정공","021820" -"77881","00117212","두산","000150" -"77886","00347877","이에스에이","052190" -"77888","00124726","빙그레","005180" -"77889","00141477","영흥","012160" -"77891","00116426","코센","009730" -"77892","00988364","SGA솔루션즈","184230" -"77893","00440712","WI","073570" -"77894","00617998","세원","234100" -"77898","00573579","평화산업","090080" -"77899","00605124","알파홀딩스","117670" -"77912","00171636","한솔홀딩스","004150" -"77917","00367871","인바이오","352940" -"77918","00412597","현대홈쇼핑","057050" -"77921","00513948","녹십자엠에스","142280" -"77922","00447487","제주반도체","080220" -"77923","00587457","갤럭시아머니트리","094480" -"77924","00101044","에이프로젠제약","003060" -"77926","00141608","오리엔탈정공","014940" -"77927","00146542","일정실업","008500" -"77928","00653194","에이프로젠 H&G","109960" -"77930","01084364","엔지스테크널러지","208860" -"77931","00122825","인스코비","006490" -"77935","00104102","우리종금","010050" -"77936","00136402","신송홀딩스","006880" -"77937","00563518","비나텍","126340" -"77942","00390514","이루온","065440" -"77943","01085187","씨알푸드","236030" -"77951","00552859","지트리비앤티","115450" -"77956","00533191","비보존 헬스케어","082800" -"77957","00180263","리더스코스메틱","016100" -"77963","00335076","오픈베이스","049480" -"77968","00351579","SGA","049470" -"77971","00186559","콤텍시스템","031820" -"77974","00248053","지에스엔텍","037640" -"77987","00596677","매커스","093520" -"77988","00678096","맥스로텍","141070" -"77990","00327396","옵트론텍","082210" -"78027","00145598","이연제약","102460" -"78028","00133812","세방","004360" -"78030","00155124","평화홀딩스","010770" -"78032","00156442","한국가구","004590" -"78033","00163673","한진중공업홀딩스","003480" -"78034","00197476","코엔텍","029960" -"78039","00526447","동북아12호선박투자회사","083370" -"78040","01063237","플럼라인생명과학","222670" -"78049","00149354","종근당홀딩스","001630" -"78050","00111874","대원산업","005710" -"78051","00453929","메디포스트","078160" -"78052","00176516","금화피에스시","036190" -"78053","01243161","인산가","277410" -"78054","00925967","이엠티","232530" -"78055","00166315","화신","010690" -"78058","00925587","위드텍","348350" -"78059","00112332","미래에셋생명","085620" -"78062","00249502","더블유에프엠","035290" -"78063","01437186","ESR켄달스퀘어리츠","365550" -"78064","00349811","W홀딩컴퍼니","052300" -"78065","00254045","우리은행","000030" -"78067","00167208","흥아해운","003280" -"78084","01029394","에코마케팅","230360" -"78086","00567222","우림기계","101170" -"78087","00366517","KC산업","112190" -"78091","00134963","송원산업","004430" -"78099","00855163","미원화학","134380" -"78127","00406392","엔에스쇼핑","138250" -"78128","00127167","삼영무역","002810" -"78129","00363769","한국전자금융","063570" -"78130","00389110","지어소프트","051160" -"78131","00985686","큐브엔터","182360" -"78132","00137915","에스엠코어","007820" -"78133","01158632","진코스텍","250030" -"78138","01016886","테크엔","308700" -"78140","00217743","화성밸브","039610" -"78146","00816696","큐엠씨","136660" -"78167","00455112","GV","045890" -"78168","00641393","엔시트론","101400" -"78169","01065679","이노인스트루먼트","215790" -"78170","00307189","에스케이씨솔믹스","057500" -"78171","01508855","대신밸런스제9호스팩","369370" -"78172","00921916","석경에이티","357550" -"78173","01276026","지놈앤컴퍼니","314130" -"78191","00120562","롯데지주","004990" -"78192","00164788","현대모비스","012330" -"78195","00425254","나우코스","257990" -"78211","00106623","현대위아","011210" -"78213","00140177","GS리테일","007070" -"78219","00303873","CJ CGV","079160" -"78221","01030132","경남은행","192520" -"78222","00149655","삼성물산","028260" -"78225","00311270","원텍","216280" -"78228","00104856","삼성증권","016360" -"78232","01214248","뉴트리","270870" -"78235","00121288","모나미","005360" -"78246","00207755","GS홈쇼핑","028150" -"78252","00207375","대한뉴팜","054670" -"78253","00260745","SCI평가정보","036120" -"78256","00938721","필옵틱스","161580" -"78258","00367695","대한그린파워","060900" -"78259","00306162","상상인","038540" -"78277","00435297","맥쿼리인프라","088980" -"78283","00107224","남선알미늄","008350" -"78299","00244419","디에스티","033430" -"78303","00480756","이트론","096040" -"78307","00219486","신세계I&C","035510" -"78308","01412822","솔루스첨단소재","336370" -"78309","00230036","드래곤플라이","030350" -"78310","00235147","에이팸","073070" -"78311","01262023","루트락","253610" -"78315","00164672","한일현대시멘트","006390" -"78317","00166175","대호피앤씨","021040" -"78319","00809517","아이엠텍","226350" -"78321","00159102","DB손해보험","005830" -"78324","00159023","SK텔레콤","017670" -"78327","00265005","옴니시스템","057540" -"78336","00360595","현대글로비스","086280" -"78357","00119195","동화약품","000020" -"78358","00127802","삼익THK","004380" -"78363","00260383","대한유화","006650" -"78364","00120030","GS건설","006360" -"78366","00166519","화천기공","000850" -"78367","00308896","아이씨케이","068940" -"78368","00138279","S-Oil","010950" -"78369","00255141","글로앤웰","035480" -"78370","00258421","기산텔레콤","035460" -"78371","00172228","엔케이","085310" -"78372","00113234","대한제당","001790" -"78373","00250997","대정화금","120240" -"78374","00861997","인포마크","175140" -"78376","00483735","해성옵틱스","076610" -"78377","00104810","대명소노시즌","007720" -"78378","00533003","디케이락","105740" -"78408","01062177","제놀루션","225220" -"78412","00583026","율호","072770" -"78413","00632793","전진바이오팜","110020" -"78414","01335453","오하임아이엔티","309930" -"78423","00164876","케이비증권","003450" -"78439","00161444","한국씨티은행","016830" -"78444","00441243","형지엘리트","093240" -"78447","00138303","쌍용정보통신","010280" -"78448","00598587","인터파크","108790" -"78449","00604268","에이프로","262260" -"78452","00184889","이건홀딩스","039020" -"78453","00145446","이건산업","008250" -"78457","00945208","THE MIDONG","161570" -"78458","00116268","동성제약","002210" -"78459","01026616","유투바이오","221800" -"78465","01043853","베노홀딩스","206400" -"78466","00585219","세원셀론텍","091090" -"78467","00458562","에이티세미콘","089530" -"78468","00134477","S&T중공업","003570" -"78501","00276083","스카이문스테크놀로지","033790" -"78519","01061558","덕산네오룩스","213420" -"78527","00362201","세운메디칼","100700" -"78529","00129350","삼화콘덴서공업","001820" -"78531","00112819","데코앤이","017680" -"78532","00355089","토탈소프트","045340" -"78534","00104786","미래아이앤지","007120" -"78535","00157681","롯데정밀화학","004000" -"78536","00572905","ISC","095340" -"78537","00103635","광림","014200" -"78538","00172185","남성","004270" -"78539","00353610","이니텍","053350" -"78551","00341916","오스템임플란트","048260" -"78552","01442115","소마젠","950200" -"78560","00163716","한창","005110" -"78575","00126186","삼성에스디에스","018260" -"78587","00164742","현대자동차","005380" -"78589","00115694","DB금융투자","016610" -"78590","00131780","SK네트웍스","001740" -"78594","00526599","덴티움","145720" -"78609","00173591","세원물산","024830" -"78610","00983040","한진칼","180640" -"78614","00161860","한성기업","003680" -"78615","00356802","큐렉소","060280" -"78616","00159342","한국정보통신","025770" -"78617","00423690","삼성출판사","068290" -"78618","00608699","디엠티","134580" -"78619","00114154","덕양산업","024900" -"78622","00273615","비케이탑스","030790" -"78630","00619640","이엔드디","101360" -"78640","00257732","한국정밀기계","101680" -"78641","00140779","영보화학","014440" -"78649","00384762","파커스","065690" -"78657","00146269","일신방직","003200" -"78667","00349097","케이티스카이라이프","053210" -"78675","00937324","한국타이어앤테크놀로지","161390" -"78681","00439965","나노신소재","121600" -"78691","00398792","S&T모티브","064960" -"78696","00128315","삼지전자","037460" -"78697","00172945","동일철강","023790" -"78709","00125725","포비스티앤씨","016670" -"78710","00136448","신신제약","002800" -"78711","01243772","신한제4호기업인수목적","277480" -"78719","00122481","태경비케이","014580" -"78721","00351092","삼보모터스","053700" -"78722","00140964","영원무역홀딩스","009970" -"78723","00535375","키네마스터","139670" -"78724","00148948","조비","001550" -"78726","00169048","넥스트사이언스","003580" -"78727","00126089","대유플러스","000300" -"78728","00172079","파나진","046210" -"78729","00171867","에스씨디","042110" -"78731","00301422","우수AMS","066590" -"78732","00624749","에스피시스템스","317830" -"78733","00133876","세보엠이씨","011560" -"78734","00174527","화성산업","002460" -"78737","00367455","현우산업","092300" -"78746","00793155","핸디소프트","220180" -"78759","00158015","한국선재","025550" -"78760","01101722","한송네오텍","226440" -"78762","00660121","디에이테크놀로지","196490" -"78763","00480367","삼강엠앤티","100090" -"78764","00105299","금강공업","014280" -"78765","00533508","제이엠티","094970" -"78766","00529815","오디텍","080520" -"78767","00347734","진양화학","051630" -"78768","00364111","다이노나","086080" -"78769","00350482","성우테크론","045300" -"78771","00878696","에스케이바이오팜","326030" -"78777","00143226","엠투엔","033310" -"78779","01187865","앙츠","267810" -"78782","00480783","상신이디피","091580" -"78786","00263654","오스코텍","039200" -"78787","00542074","나노캠텍","091970" -"78788","00103510","인지컨트롤스","023800" -"78790","00620558","에이비프로바이오","195990" -"78791","00295547","디지아이","043360" -"78792","00111421","휴니드테크놀러지스","005870" -"78793","00204262","한글과컴퓨터","030520" -"78794","00121543","무학","033920" -"78799","00351454","큐브앤컴퍼니","043090" -"78803","00155319","포스코","005490" -"78805","00629212","딜리","131180" -"78807","01406618","비올","335890" -"78808","00962393","포인트모바일","318020" -"78810","00444329","위메이드","112040" -"78817","00866062","엘티씨","170920" -"78818","00138367","플레이위드","023770" -"78819","00170026","시공테크","020710" -"78821","00156488","휴스틸","005010" -"78823","00765462","씨에스베어링","297090" -"78851","00399694","아프리카TV","067160" -"78860","00149345","조흥","002600" -"78864","00317210","성호전자","043260" -"78865","00102858","고려아연","010130" -"78870","00139889","SKC","011790" -"78877","00164609","현대미포조선","010620" -"78887","01359815","윈텍","320000" -"78888","01067242","티씨엠생명과학","228180" -"78889","00155498","풍림산업","001310" -"78905","01165739","잉글우드랩","950140" -"78906","01437292","미래에셋맵스리츠","357250" -"78936","00273439","메디아나","041920" -"78946","00874195","코썬바이오","204990" -"78953","00469799","이엔플러스","074610" -"78960","00299002","한라홀딩스","060980" -"78961","00120924","매일홀딩스","005990" -"78967","00111689","대우부품","009320" -"78969","00175623","라이브플렉스","050120" -"78977","00164812","현대종합상사","011760" -"78989","00273110","에스티큐브","052020" -"78990","01137295","프리시젼바이오","335810" -"78991","00173078","명신산업","009900" -"78992","00157991","한국석유공업","004090" -"79009","00159218","한전KPS","051600" -"79010","00108940","대성홀딩스","016710" -"79037","01166109","예선테크","250930" -"79038","00371740","디스플레이텍","066670" -"79039","00660750","하나머티리얼즈","166090" -"79040","01228515","지엔원에너지","270520" -"79074","00146560","일지테크","019540" -"79083","00146436","엔브이에이치코리아","067570" -"79084","00393469","녹원씨엔아이","065560" -"79086","00611912","아모그린텍","125210" -"79087","00346407","큐로컴","040350" -"79088","00297095","나라엠앤디","051490" -"79089","00122579","BYC","001460" -"79090","00407285","아이컴포넌트","059100" -"79091","00825223","화신정공","126640" -"79093","00206039","경남스틸","039240" -"79094","00475985","시너지이노베이션","048870" -"79095","00599595","코프라","126600" -"79097","00110608","DSR","155660" -"79098","01234507","매일유업","267980" -"79099","00173999","유성티엔에스","024800" -"79113","00971090","샘코","263540" -"79117","01113754","오션브릿지","241790" -"79122","00453488","세화아이엠씨","145210" -"79123","00114808","동국제약","086450" -"79128","00210980","원익","032940" -"79151","01264234","엘에이티","311060" -"79161","00102353","경창산업","024910" -"79183","01208885","경동도시가스","267290" -"79188","01107665","크리스탈신소재","900250" -"79190","00104120","광진실업","026910" -"79195","00158024","KBI메탈","024840" -"79198","00133335","성창기업지주","000180" -"79200","00761952","아나패스","123860" -"79234","01351822","한화에스비아이스팩","317320" -"79247","00231831","조아제약","034940" -"79248","00163691","유수홀딩스","000700" -"79250","00156691","한국공항","005430" -"79252","00468374","원익QnC","074600" -"79253","00361008","HSD엔진","082740" -"79254","01097906","이엑스티","226360" -"79255","00231707","비트컴퓨터","032850" -"79256","01183407","이십일스토어","270020" -"79259","00363592","한컴위드","054920" -"79260","00480048","모다이노칩","080420" -"79274","00663289","위월드","140660" -"79277","00111722","미래에셋대우","006800" -"79284","00351418","코리아오토글라스","152330" -"79289","00253985","국보디자인","066620" -"79308","00571483","오이솔루션","138080" -"79321","00133238","성지건설","005980" -"79327","00415646","드림텍","192650" -"79328","00228059","휴온스글로벌","084110" -"79342","00445799","서원인텍","093920" -"79344","00176835","농우바이오","054050" -"79347","00641171","모바일리더","100030" -"79350","00821607","알톤스포츠","123750" -"79351","00109587","대륙제관","004780" -"79352","00494476","이녹스","088390" -"79354","00173740","신한","005450" -"79355","01112700","에스엘에스바이오","246250" -"79356","00126414","삼성제약","001360" -"79366","00939942","포시에스","189690" -"79406","00215976","UCI","038340" -"79412","01022902","애드바이오텍","179530" -"79413","01396931","유엑스엔","337840" -"79427","00540447","유니테스트","086390" -"79438","01080687","아우딘퓨쳐스","227610" -"79442","00131054","유진증권","001200" -"79443","00620868","아이티센","124500" -"79444","00623661","원익머트리얼즈","104830" -"79446","00555874","제주항공","089590" -"79451","00832700","에이펙스인텍","207490" -"79452","00115676","KG동부제철","016380" -"79463","01219155","네오크레마","311390" -"79471","00103626","한솔피엔에스","010420" -"79473","00234227","유진로봇","056080" -"79475","00578130","아퓨어스","149300" -"79480","00152729","광전자","017900" -"79486","00113535","대한해운","005880" -"79504","00449379","도이치모터스","067990" -"79531","00141556","오로라","039830" -"79534","00310156","셀루메드","049180" -"79535","00243979","수성","084180" -"79536","01353848","이노벡스","279060" -"79537","00102760","두산건설","011160" -"79538","01338724","SNK","950180" -"79539","01262032","롯데정보통신","286940" -"79578","00360036","백금T&A","046310" -"79589","01036367","프로스테믹스","203690" -"79622","00462121","이노와이어리스","073490" -"79626","00264787","럭슬","033600" -"79637","00615723","아이에이네트웍스","123010" -"79639","00164973","현대해상","001450" -"79640","01136001","나노브릭","286750" -"79642","00136341","신성통상","005390" -"79663","01276594","신한알파리츠","293940" -"79665","00867478","농업회사법인아시아종묘","154030" -"79667","00200910","아이즈비전","031310" -"79669","00806972","매직마이크로","127160" -"79670","00492894","젬백스","082270" -"79674","00255275","HB테크놀러지","078150" -"79680","00843830","카이노스메드","220250" -"79681","00991298","씨이랩","189330" -"79702","00482426","인화정공","101930" -"79705","00673976","지스마트글로벌","114570" -"79708","00210856","코아스","071950" -"79709","00261656","잉크테크","049550" -"79710","00231664","우리기술투자","041190" -"79711","00107613","이수페타시스","007660" -"79712","00578538","티피씨글로벌","130740" -"79713","01190513","명진홀딩스","267060" -"79714","00128926","삼현철강","017480" -"79716","00261355","레드캡투어","038390" -"79719","00516246","알에프세미","096610" -"79760","00390408","에프앤리퍼블릭","064090" -"79761","00108977","대구백화점","006370" -"79762","00145437","키위미디어그룹","012170" -"79778","00522007","크리스에프앤씨","110790" -"79810","00249034","드림라인","035430" -"79816","00607797","엄지하우스","224810" -"79817","00304076","원방테크","053080" -"79818","00346911","아이톡시","052770" -"79819","00366942","미코","059090" -"79820","00397252","조이시티","067000" -"79821","00496225","이엠네트웍스","087730" -"79822","00656289","코스맥스엔비티","222040" -"79823","00657987","KMH","122450" -"79824","01091382","마이더스AI","222810" -"79825","01110678","유틸렉스","263050" -"79827","00154462","아모레퍼시픽그룹","002790" -"79828","00202060","구영테크","053270" -"79829","00216647","원익홀딩스","030530" -"79830","00238782","다우데이타","032190" -"79831","00303794","쇼박스","086980" -"79841","00803425","코리아에프티","123410" -"79842","00624509","디바이스이엔지","187870" -"79849","00231691","판타지오","032800" -"79850","00260611","크로바하이텍","043590" -"79852","00475976","인콘","083640" -"79855","00118804","동진쎄미켐","005290" -"79859","00160621","한국화장품제조","003350" -"79860","00408956","제넨바이오","072520" -"79862","00260356","산은캐피탈","008270" -"79863","00372226","티에스이","131290" -"79867","00117072","동양건설산업","005900" -"79875","00441304","가온미디어","078890" -"79878","00138598","아비코전자","036010" -"79890","00544452","이리츠코크렙","088260" -"79897","00385336","홈캐스트","064240" -"79899","00409788","비에이치아이","083650" -"79900","00264732","파인디지털","038950" -"79901","00244783","한네트","052600" -"79902","00543204","아이오케이","078860" -"79903","00477257","코렌","078650" -"79904","00152260","카스","016920" -"79905","00328191","케이에스피","073010" -"79907","00379016","이엘케이","094190" -"79908","00246417","이오테크닉스","039030" -"79910","00116301","NI스틸","008260" -"79911","01397903","엔젠바이오","354200" -"79913","00230425","나노엔텍","039860" -"79914","00541437","코렌텍","104540" -"79956","00141671","오리콤","010470" -"79958","00123781","부국철강","026940" -"79961","00220109","솔고바이오","043100" -"79962","00173032","동화기업","025900" -"79966","01021666","덕산테코피아","317330" -"79967","00107598","남양유업","003920" -"79968","00110051","SK머티리얼즈","036490" -"79987","00303192","디에이피","066900" -"80011","00455981","에스엔유","080000" -"80012","01112889","피엔에이치테크","239890" -"80015","00232007","상지카일룸","042940" -"80017","01114586","셀레믹스","331920" -"80018","00365590","에이치엘비생명과학","067630" -"80019","01405451","알체라","347860" -"80022","00593032","LF","093050" -"80041","00148470","제일전기공업","199820" -"80044","00132211","선창산업","002820" -"80045","00223513","제이씨현시스템","033320" -"80049","00146427","일야","058450" -"80050","00450339","에스에프씨","112240" -"80052","00244561","인터파크","035080" -"80060","00520610","씨큐브","101240" -"80093","00219574","코스맥스비티아이","044820" -"80094","00269922","인바디","041830" -"80095","00201742","유라테크","048430" -"80096","00110884","엠젠플러스","032790" -"80131","00161426","한미사이언스","008930" -"80132","00138747","아세아텍","050860" -"80134","00123107","보락","002760" -"80135","00142865","WISCOM","024070" -"80137","00526678","동방선기","099410" -"80140","00162993","한일홀딩스","003300" -"80142","00831428","디에이치피코리아","131030" -"80143","00122205","바른손","018700" -"80144","00400857","에이블씨엔씨","078520" -"80190","00162081","한국투자파트너스","019560" -"80191","00156859","KTB투자증권","030210" -"80194","00257149","지더블유바이텍","036180" -"80195","01110076","에이트원","230980" -"80212","00162832","한일단조","024740" -"80219","00159810","카프로","006380" -"80221","00145163","파미셀","005690" -"80222","00160302","코스모화학","005420" -"80225","00638539","뉴로스","126870" -"80226","00360142","아이마켓코리아","122900" -"80227","00526836","미래나노텍","095500" -"80228","00126371","삼성전기","009150" -"80229","00525934","실리콘웍스","108320" -"80230","00787376","대성에너지","117580" -"80231","00300405","오리엔트정공","065500" -"80232","00198192","한국팩키지","037230" -"80233","00334615","콜마파마","038710" -"80235","00256502","에이텍","045660" -"80236","00351375","뉴보텍","060260" -"80237","00145473","이글벳","044960" -"80238","00618401","크루셜텍","114120" -"80239","00576789","디젠스","113810" -"80272","00339072","팬스타엔터프라이즈","054300" -"80273","00106881","인디에프","014990" -"80274","01053540","퓨쳐켐","220100" -"80275","00142883","우신시스템","017370" -"80276","00252135","주성엔지니어링","036930" -"80278","01109690","도부마스크","227420" -"80279","00536888","다믈멀티미디어","093640" -"80280","00159500","한국종합기술","023350" -"80281","00560849","에스앤디","260970" -"80282","00347062","현대바이오랜드","052260" -"80284","00366137","KG ETS","151860" -"80285","00267906","베뉴지","019010" -"80286","00633835","한진중공업","097230" -"80287","01018617","메디쎄이","200580" -"80297","00647935","하나유비에스암바토비니켈해외자원개발2호","099350" -"80298","00647883","하나유비에스암바토비니켈해외자원개발1호","099340" -"80299","01110474","에스씨엠생명과학","298060" -"80300","01275665","리메드","302550" -"80308","01014657","비즈니스온","138580" -"80315","01137897","SGA클라우드서비스","224880" -"80322","01182408","소프트캠프","258790" -"80330","01395279","하나금융14호스팩","332710" -"80331","01034730","크로넥스","215570" -"80332","00292434","인트론바이오","048530" -"80333","00435969","인피니트헬스케어","071200" -"80334","00442905","대성파인텍","104040" -"80340","00130383","서부T&D","006730" -"80343","00402110","시큐브","131090" -"80344","00105271","케이씨씨","002380" -"80346","00428251","현대백화점","069960" -"80348","01064069","토박스코리아","215480" -"80356","00130879","서울식품공업","004410" -"80366","00109037","대성창투","027830" -"80367","00141413","영화금속","012280" -"80369","00402989","코스온","069110" -"80370","00501970","코드네이처","078940" -"80371","01009789","코스맥스","192820" -"80375","00470829","한솔인티큐브","070590" -"80376","00923899","스타모빌리티","158310" -"80377","00352064","프리엠스","053160" -"80378","00128546","삼천당제약","000250" -"80379","00151395","천일고속","000650" -"80381","00104698","LS네트웍스","000680" -"80383","01428203","케이씨씨글라스","344820" -"80393","00126362","삼성SDI","006400" -"80398","00260569","유니셈","036200" -"80399","00401731","LG전자","066570" -"80400","00113410","CJ대한통운","000120" -"80401","00442455","코스나인","082660" -"80402","00135917","한화손해보험","000370" -"80403","00585538","에스에너지","095910" -"80404","00120216","KB손해보험","002550" -"80406","00120182","NH투자증권","005940" -"80419","00204226","소프트센","032680" -"80420","00138224","쌍용양회공업","003410" -"80421","01185566","블러썸엠앤씨","263920" -"80423","00124805","푸른저축은행","007330" -"80431","00551920","네오팜","092730" -"80432","00159412","한국제지","002300" -"80433","00632845","노랑풍선","104620" -"80441","00896753","스킨앤스킨","159910" -"80442","00161462","에이티넘인베스트","021080" -"80443","00414850","효성 ITX","094280" -"80444","00626011","아이텍","119830" -"80447","00134565","이스타코","015020" -"80448","00659684","장원테크","174880" -"80449","00549891","케어젠","214370" -"80452","00153375","태광","023160" -"80453","00260930","에스엠","041510" -"80454","00185222","크린앤사이언스","045520" -"80457","00369569","휘닉스소재","050090" -"80461","00125822","삼보산업","009620" -"80463","00542898","하이소닉","106080" -"80469","00112378","KR모터스","000040" -"80500","00166333","이노와이즈","086250" -"80512","00170558","코웨이","021240" -"80513","01343665","메탈라이프","327260" -"80515","00240857","바이오스마트","038460" -"80517","01364297","코퍼스코리아","322780" -"80518","00565215","굿센","243870" -"80522","00103015","고려제약","014570" -"80525","01109937","티앤알바이오팹","246710" -"80527","01042650","큐리언트","115180" -"80528","00994994","나노","187790" -"80530","00486875","우양","103840" -"80546","00139685","양지사","030960" -"80558","00531865","유니테크노","241690" -"80566","00409964","하이텍팜","106190" -"80567","00105606","금호에이치티","214330" -"80571","00220969","오공","045060" -"80573","00146861","자화전자","033240" -"80576","00225742","KT서브마린","060370" -"80579","00111865","미래SCI","028040" -"80583","01128613","화승엔터프라이즈","241590" -"80584","00166227","화승인더스트리","006060" -"80585","01068348","러셀","217500" -"80586","00112059","상상인증권","001290" -"80590","00158316","NICE","034310" -"80591","01453670","엔에이치스팩16호","353190" -"80595","00155258","포스코강판","058430" -"80596","00978075","옐로페이","179720" -"80597","01047707","바이오로그디바이스","208710" -"80598","00377018","기가레인","049080" -"80599","01036446","서연이화","200880" -"80601","00608802","텔콘RF제약","200230" -"80602","00362292","씨티씨바이오","060590" -"80603","00406046","STX중공업","071970" -"80605","00222435","제이엠아이","033050" -"80608","00441650","케이씨에스","115500" -"80609","00271501","코아시아","045970" -"80611","00569691","디엔에프","092070" -"80612","00432102","한국금융지주","071050" -"80623","00635134","CJ제일제당","097950" -"80626","00759513","LG하우시스","108670" -"80627","00583424","아모레퍼시픽","090430" -"80632","00299358","해덕파워웨이","102210" -"80633","00458234","아시아나IDT","267850" -"80634","00364467","드림어스컴퍼니","060570" -"80635","00495554","아이엠이연이","090740" -"80636","00525679","차바이오텍","085660" -"80637","00596260","투비소프트","079970" -"80638","01277928","한국제7호기업인수목적","291210" -"80652","00100939","강남제비스코","000860" -"80653","00226547","오상자이엘","053980" -"80658","00142713","형지I&C","011080" -"80681","00693651","비덴트","121800" -"80708","00113997","일진머티리얼즈","020150" -"80713","00309503","한국항공우주","047810" -"80714","00413046","셀트리온","068270" -"80715","00150244","하이트진로","000080" -"80716","00148993","하이트진로홀딩스","000140" -"80718","00139214","삼성화재해상보험","000810" -"80721","00148595","제일테크노스","038010" -"80722","01205851","현대일렉트릭","267260" -"80724","00842619","레고켐바이오","141080" -"80726","01038693","드림시큐리티","203650" -"80733","00650629","SBS미디어홀딩스","101060" -"80740","00665676","아시아경제","127710" -"80744","00101257","경남기업","000800" -"80746","00527464","에이치시티","072990" -"80757","00233653","한국토지신탁","034830" -"80766","00155948","코웰패션","033290" -"80767","00325112","프로텍","053610" -"80768","00378220","버킷스튜디오","066410" -"80771","00143651","웅진","016880" -"80773","00406037","CSA 코스믹","083660" -"80774","00478900","빅솔론","093190" -"80777","00168331","국동","005320" -"80779","00150536","진양제약","007370" -"80780","00125521","에스엘","005850" -"80782","00146296","일신석재","007110" -"80786","00454399","이상네트웍스","080010" -"80799","01109715","DSC인베스트먼트","241520" -"80800","00661157","인트로메딕","150840" -"80802","00159971","KC그린홀딩스","009440" -"80803","01350869","우리금융지주","316140" -"80805","00228350","신화인터텍","056700" -"80806","00819073","인크로스","216050" -"80808","00563545","테스나","131970" -"80810","00140131","키다리스튜디오","020120" -"80811","00144252","유니온","000910" -"80813","00536541","에코프로","086520" -"80814","00148522","퍼스텍","010820" -"80818","00111704","대우조선해양","042660" -"80821","00164478","현대건설","000720" -"80824","00120076","LG상사","001120" -"80825","00683007","엑스큐어","070300" -"80827","00126566","한화에어로스페이스","012450" -"80833","00153861","태영건설","009410" -"80863","00455875","와토스코리아","079000" -"80870","00145914","인터엠","017250" -"80891","00145880","현대제철","004020" -"80894","00109019","대구은행","005270" -"80897","00115977","아이에스동서","010780" -"80900","00128555","삼천리","004690" -"80909","00124504","포스코인터내셔널","047050" -"80929","00667425","에이팩트","200470" -"80934","00799177","삼기","122350" -"80935","01049167","미코바이오메드","214610" -"80944","00266943","넥슨지티","041140" -"80945","01130885","바이오인프라생명과학","266470" -"80946","00545734","아미코젠","092040" -"80949","00223434","에프에스티","036810" -"80950","01051472","젠큐릭스","229000" -"80951","00193009","필룩스","033180" -"80953","00305297","코텍","052330" -"80954","00336297","고려신용정보","049720" -"80957","01158553","켄코아에어로스페이스","274090" -"80958","00130408","서산","079650" -"80959","00127936","삼일기업공사","002290" -"80960","00340272","에스폴리텍","050760" -"80962","00563837","제로투세븐","159580" -"80963","00261054","EG","037370" -"80964","00409371","인베니아","079950" -"80984","00104519","국보","001140" -"80986","00126937","삼양홀딩스","000070" -"80987","00896285","삼양사","145990" -"80988","00688996","KB금융","105560" -"80989","01160363","에코프로비엠","247540" -"80990","00264547","KG이니시스","035600" -"80991","00405278","KG모빌리언스","046440" -"80992","00201131","아즈텍WB","032080" -"80993","00153393","태광산업","003240" -"80994","00104573","국일제지","078130" -"80996","00133618","세기상사","002420" -"80997","00298270","안랩","053800" -"80998","00121534","MH에탄올","023150" -"80999","00161781","아난티","025980" -"81001","00138297","STX","011810" -"81002","00220561","광주신세계","037710" -"81003","00524786","비엠티","086670" -"81005","00194275","코너스톤네트웍스","033110" -"81006","00219361","해성산업","034810" -"81007","00232821","클라우드에어","036170" -"81015","00980122","JB금융지주","175330" -"81018","00103176","흥국화재","000540" -"81045","01073678","얼라인드","238120" -"81046","01335578","애니플러스","310200" -"81071","00500254","GS","078930" -"81072","00767628","나이벡","138610" -"81089","00980043","비플라이소프트","148780" -"81103","00267979","한일화학","007770" -"81104","00447609","비에이치","090460" -"81122","00523936","엘이티","297890" -"81124","00811372","노바렉스","194700" -"81132","00115612","동부건설","005960" -"81142","00479705","EMW","079190" -"81144","00138446","아가방컴퍼니","013990" -"81147","00258360","위지트","036090" -"81151","00800145","서진오토모티브","122690" -"81154","00268118","코맥스","036690" -"81156","00130587","서울전자통신","027040" -"81162","00125080","AK홀딩스","006840" -"81163","00139454","애경산업","018250" -"81164","00936787","애경유화","161000" -"81165","00547510","툴젠","199800" -"81169","00957568","세미콘라이트","214310" -"81170","00404288","아진엑스텍","059120" -"81173","00348034","액토즈소프트","052790" -"81174","00648721","S&TC","100840" -"81176","00264635","에스넷","038680" -"81177","00243067","부방","014470" -"81180","00361169","한국전자인증","041460" -"81181","00126487","F&F","007700" -"81184","00287788","정상제이엘에스","040420" -"81185","01414422","아이비김영","339950" -"81193","00149646","기업은행","024110" -"81196","00209443","아주캐피탈","033660" -"81197","00356361","LG화학","051910" -"81198","01258507","롯데제과","280360" -"81199","00160047","한국테크놀로지그룹","000240" -"81201","00795135","코오롱인더","120110" -"81202","00114792","동국제강","001230" -"81203","00105952","LS","006260" -"81206","00160588","한화","000880" -"81211","00191472","KPX케미칼","025000" -"81228","00466428","아미노로직스","074430" -"81230","00155276","포스코케미칼","003670" -"81254","00990396","뿌리깊은나무들","266170" -"81267","00164645","HMM","011200" -"81272","01440481","IBKS제13호스팩","351340" -"81298","00664853","제이씨케미칼","137950" -"81299","00226316","호전실업","111110" -"81317","00129420","까뮤이앤씨","013700" -"81325","00681142","쌍방울","102280" -"81326","00267881","보성파워텍","006910" -"81328","00125938","삼보판지","023600" -"81332","00128412","삼진제약","005500" -"81335","00152589","코리아나","027050" -"81341","00308559","코디","080530" -"81354","00918222","엘앤케이바이오","156100" -"81367","00261009","버추얼텍","036620" -"81368","00347716","센트럴바이오","051980" -"81369","00109189","대덕","008060" -"81370","00113562","롯데손해보험","000400" -"81372","00143907","원일특강","012620" -"81375","00683469","일진전기","103590" -"81377","00344287","두산인프라코어","042670" -"81378","00120571","롯데칠성음료","005300" -"81379","00117601","유안타증권","003470" -"81381","00126308","삼성엔지니어링","028050" -"81383","00164636","HDC","012630" -"81384","00122737","팬오션","028670" -"81385","00161125","한온시스템","018880" -"81393","00152880","코오롱글로벌","003070" -"81394","00159193","한국전력공사","015760" -"81396","00587466","KPX홀딩스","092230" -"81398","00166573","환인제약","016580" -"81400","00556712","힘스","238490" -"81401","00584362","넥스지","081970" -"81413","00153524","태림포장","011280" -"81415","00825959","하이비젼시스템","126700" -"81417","00309831","알에프텍","061040" -"81418","01023822","메디젠휴먼케어","236340" -"81419","00231363","LG유플러스","032640" -"81447","00137058","신일제약","012790" -"81461","01326224","교보8호스팩","307280" -"81463","00876166","한국바이오젠","318000" -"81468","00361266","네스엠","056000" -"81476","00190321","케이티","030200" -"81480","00330424","이베스트투자증권","078020" -"81481","00124106","부산은행","005280" -"81483","01205709","현대중공업지주","267250" -"81491","01179608","포인트엔지니어링","256630" -"81492","00185046","SM C&C","048550" -"81514","00136086","무림페이퍼","009200" -"81532","00189538","피델릭스","032580" -"81533","00151128","모토닉","009680" -"81534","00820352","파인텍","131760" -"81535","00118345","디아이동일","001530" -"81537","00227333","네패스","033640" -"81538","00392691","젬백스링크","064800" -"81540","00809429","한일진공","123840" -"81541","00177870","대창솔루션","096350" -"81542","00105040","뉴인텍","012340" -"81543","00225159","S&T홀딩스","036530" -"81544","00117629","동양철관","008970" -"81545","00349060","한스바이오메드","042520" -"81546","01075126","경남제약헬스케어","223310" -"81547","00148540","CJ","001040" -"81548","00545114","랩지노믹스","084650" -"81549","00255105","골드퍼시픽","038530" -"81550","00550082","빅텐츠","210120" -"81551","00131018","서울제약","018680" -"81552","00807397","코이즈","121850" -"81556","01186671","바이오시네틱스","281310" -"81568","00356839","메디콕스","054180" -"81569","00118521","진원생명과학","011000" -"81570","00188715","현진소재","053660" -"81571","00111227","대양제지","006580" -"81573","00151605","청보산업","013720" -"81574","00121969","에쓰씨엔지니어링","023960" -"81575","00302926","현대로템","064350" -"81576","00808022","메지온","140410" -"81577","00188797","포메탈","119500" -"81579","00147082","재영솔루텍","049630" -"81580","00977696","파티게임즈","194510" -"81587","00386937","국민은행","060000" -"81589","01205842","현대건설기계","267270" -"81591","00105873","LG디스플레이","034220" -"81593","00165680","호텔신라","008770" -"81595","00117267","동양생명","082640" -"81596","00136378","신세계","004170" -"81597","00222213","포트로닉스천안","039870" -"81603","00375931","인선이엔티","060150" -"81606","01391103","브랜드엑스코퍼레이션","337930" -"81611","00218052","국제엘렉트릭코리아","053740" -"81650","00136101","메이슨캐피탈","021880" -"81652","00380429","CMG제약","058820" -"81661","00307897","신한카드","032710" -"81662","01504804","유안타제7호스팩","367460" -"81666","00120021","LG","003550" -"81674","00156150","하이트론씨스템즈","019490" -"81702","00364403","쏠리드","050890" -"81703","00109718","사조대림","003960" -"81718","00336817","에이치엘비제약","047920" -"81719","01221947","비엔에프코퍼레이션","271780" -"81720","00175173","오스템","031510" -"81721","00372688","티사이언티픽","057680" -"81722","00445160","디이엔티","079810" -"81723","01405390","핌스","347770" -"81724","00788737","이퓨쳐","134060" -"81725","00138057","써니전자","004770" -"81726","00173449","경남바이오파마","044480" -"81729","00256715","국순당","043650" -"81730","00136642","엠벤처투자","019590" -"81731","00139205","안국약품","001540" -"81732","00660033","씨엔플러스","115530" -"81733","00287812","에코바이오","038870" -"81734","00114765","케이비아이동국실업","001620" -"81736","00180795","인팩","023810" -"81756","00160375","진양폴리우레탄","010640" -"81757","00145738","이화전기","024810" -"81759","00155531","풍산홀딩스","005810" -"81760","00910840","윈하이텍","192390" -"81762","01327092","라닉스","317120" -"81764","00163682","메리츠증권","008560" -"81765","00860332","메리츠금융지주","138040" -"81766","00359580","아이디스홀딩스","054800" -"81767","00159209","한전기술","052690" -"81770","00126256","삼성생명","032830" -"81796","00843900","육일씨엔에쓰","191410" -"81809","00370006","iMBC","052220" -"81810","01181515","엘앤씨바이오","290650" -"81814","00130772","SBS","034120" -"81829","00143794","원림","005820" -"81840","00239596","쎄니트","037760" -"81856","01343735","데이드림엔터","348840" -"81857","01416235","미투젠","950190" -"81871","00447982","에스피엠씨","074000" -"81873","00134510","세종공업","033530" -"81901","00298447","아모텍","052710" -"81902","01259311","푸드나무","290720" -"81903","00155586","피에스엠씨","024850" -"81904","00643656","조이맥스","101730" -"81907","00618410","상상인인더스트리","101000" -"81909","00654272","램테크놀러지","171010" -"81913","00250614","삼영엠텍","054540" -"81916","00535746","게임빌","063080" -"81917","00580667","S&K폴리텍","091340" -"81918","00351630","세코닉스","053450" -"81919","00463342","에스텍파마","041910" -"81920","00100258","에스마크","030270" -"81921","01113949","퓨전","195440" -"81922","00132804","성신양회","004980" -"81923","00144243","유니슨","018000" -"81924","00400121","유아이디","069330" -"81925","00103006","고려제강","002240" -"81938","00552992","진매트릭스","109820" -"81944","00141282","에이치디씨영창","001890" -"81947","00141246","SGC이테크건설","016250" -"81950","00117708","리드코프","012700" -"81954","00159731","글로본","019660" -"81955","01207716","앱코","129890" -"81959","00113191","코리안리","003690" -"81960","00136004","신라에스지","025870" -"81979","00104537","국영지앤엠","006050" -"81980","00415707","엔텔스","069410" -"81981","00523307","다원시스","068240" -"81982","00153755","태양금속공업","004100" -"81983","00108065","쎌마테라퓨틱스","015540" -"81985","00573269","에스코넥","096630" -"81987","00105466","KCC건설","021320" -"81995","00260392","대원전선","006340" -"81996","00149594","한국캐피탈","023760" -"81997","00305570","서울리거","043710" -"81998","00829380","피제이메탈","128660" -"81999","00318662","포스링크","056730" -"82016","00359623","우리산업홀딩스","072470" -"82021","00479787","인텍플러스","064290" -"82023","00995993","캔서롭","180400" -"82025","00141307","영풍","000670" -"82026","00390365","토비스","051360" -"82028","00626464","지엔씨에너지","119850" -"82029","00562360","에스맥","097780" -"82030","00259590","바른손이앤에이","035620" -"82034","00653103","나노스","151910" -"82035","00663669","우노앤컴퍼니","114630" -"82039","00117027","알루코","001780" -"82043","00131896","선광","003100" -"82045","00302120","피앤텔","054340" -"82052","01391033","NH프라임리츠","338100" -"82060","00307222","YBM넷","057030" -"82063","00173351","삼진","032750" -"82068","00569646","영우디에스피","143540" -"82070","00152039","SG충방","001380" -"82071","00540605","이월드","084680" -"82072","00145668","이화공영","001840" -"82090","01326279","아이엘사이언스","307180" -"82092","00117276","네이처셀","007390" -"82093","00152437","케이프","064820" -"82109","00139153","에코플라스틱","038110" -"82110","00158219","시그네틱스","033170" -"82111","00919966","신라젠","215600" -"82112","01293388","무진메디","322970" -"82116","00164830","한국조선해양","009540" -"82117","00108649","TPC","048770" -"82118","00144395","동서","026960" -"82121","00141273","웰바이오텍","010600" -"82122","00144012","원풍","008370" -"82123","00269241","주연테크","044380" -"82124","00159290","한국아트라스비엑스","023890" -"82127","00334624","팍스넷","038160" -"82135","00101220","KG케미칼","001390" -"82143","00753643","컨버즈","109070" -"82144","00117258","한화갤러리아타임월드","027390" -"82148","00923826","유테크","178780" -"82149","00135962","신라교역","004970" -"82150","00585608","다나와","119860" -"82151","00117300","TCC스틸","002710" -"82152","00117230","동양물산기업","002900" -"82153","00405959","스타플렉스","115570" -"82154","00238977","화진","134780" -"82156","00156895","아주IB투자","027360" -"82157","01110182","이노테라피","246960" -"82181","00265324","CJ ENM","035760" -"82191","00363510","이그잭스","060230" -"82192","00409681","아스트","067390" -"82193","00411905","테라셈","182690" -"82195","01096341","넷게임즈","225570" -"82198","00360674","코위버","056360" -"82199","00203315","제이콘텐트리","036420" -"82203","00362441","현대오토에버","307950" -"82204","00129642","상신브레이크","041650" -"82205","00110307","대선조선","031990" -"82216","00365624","광진윈텍","090150" -"82219","00591441","어보브반도체","102120" -"82220","00158112","한국수출포장공업","002200" -"82229","00595182","아톤","158430" -"82257","00532059","KPX생명과학","114450" -"82258","00852087","시디즈","134790" -"82259","00679314","동성코퍼레이션","102260" -"82260","00126380","삼성전자","005930" -"82262","00177816","대주전자재료","078600" -"82263","00247975","솔브레인홀딩스","036830" -"82264","00303396","한국컴퓨터","054040" -"82265","00147860","지코","010580" -"82266","00145686","이화산업","000760" -"82267","00133663","세동","053060" -"82269","01217829","엔에프씨","265740" -"82270","01119651","엘리비젼","276240" -"82272","00209780","케이씨","029460" -"82273","00612294","피엔티","137400" -"82274","00167192","넥센","005720" -"82275","00155692","우리조명","037400" -"82276","00141529","오뚜기","007310" -"82281","00118266","DRB동일","004840" -"82283","01108442","에치에프알","230240" -"82295","00905316","하이골드3호","153360" -"82296","00348292","한빛소프트","047080" -"82312","00986898","팜스빌","318010" -"82322","00159795","한국카본","017960" -"82350","00169215","에너토크","019990" -"82353","01190124","미디어젠","279600" -"82370","00369107","우리바이오","082850" -"82372","00109286","대동공업","000490" -"82373","00540641","동양고속","084670" -"82374","00198697","일진디스플","020760" -"82375","00160232","KSS해운","044450" -"82376","00127255","삼영화학공업","003720" -"82377","00791209","우리들휴브레인","118000" -"82378","00157636","케이피에프","024880" -"82380","00426068","아이에스이커머스","069920" -"82381","00173874","넥센타이어","002350" -"82382","00112970","대한방직","001070" -"82383","00269889","누리텔레콤","040160" -"82384","00129509","한익스프레스","014130" -"82386","00781202","뉴프라이드","900100" -"82387","01174038","자비스","254120" -"82388","01247918","한화에이스기업인수목적4호","279410" -"82423","00815095","우리이앤엘","153490" -"82440","01099667","에스디생명공학","217480" -"82452","00833064","엠플러스","259630" -"82458","00173661","에스지신성건설","001970" -"82483","01257526","한국제6호기업인수목적","281410" -"82484","01214743","더네이쳐홀딩스","298540" -"82534","00445054","하나마이크론","067310" -"82563","01251577","휴네시온","290270" -"82575","00118008","동원금속","018500" -"82578","00160861","한농화성","011500" -"82591","00606886","엔지켐생명과학","183490" -"82600","01042085","엑셈","205100" -"82606","00159005","생고뱅코리아홀딩스","002000" -"82611","00201432","비츠로시스","054220" -"82612","00519252","THE E&M","089230" -"82613","00961507","이더블유케이","258610" -"82614","01032404","콜마비앤에이치","200130" -"82615","01118281","피플바이오","304840" -"82641","00533924","액트","131400" -"82643","00201450","SK렌터카","068400" -"82662","00297989","오르비텍","046120" -"82663","00611286","티케이씨","191600" -"82675","01011872","엔에스엠","238170" -"82683","00867849","오파스넷","173130" -"82692","01023318","센코","347000" -"82701","00339391","한화시스템","272210" -"82705","00111111","대양금속","009190" -"82706","00359474","제이웨이","058420" -"82707","00115287","동방","004140" -"82708","01041828","JTC","950170" -"82709","00143314","에이엔피","015260" -"82710","00797364","KC코트렐","119650" -"82711","00939331","한국콜마","161890" -"82712","01412938","에이스캠퍼","322190" -"82716","00303217","우진플라임","049800" -"82717","00102140","경인전자","009140" -"82719","00159698","지역난방공사","071320" -"82733","00267942","큐캐피탈","016600" -"82734","01267550","나라소프트","288490" -"82736","00415628","세진중공업","075580" -"82746","00816544","토니모리","214420" -"82753","01115044","안지오랩","251280" -"82758","00872984","이마트","139480" -"82767","00155382","바이오빌","065940" -"82777","00545929","제넥신","095700" -"82789","00135795","신도리코","029530" -"82791","01160512","헝셩그룹","900270" -"82797","00812362","포티스","141020" -"82809","00364254","알티캐스트","085810" -"82814","00187770","한국파마","032300" -"82854","00296263","YW","051390" -"82865","00203290","한국콜마홀딩스","024720" -"82870","01267170","SK케미칼","285130" -"82879","01262458","동아타이어","282690" -"82880","01319808","한일시멘트","300720" -"82881","00799539","제노레이","122310" -"82891","00260657","오상헬스케어","036220" -"82894","00407036","선바이오","067370" -"82903","00373447","서린바이오","038070" -"82913","00508344","SK디앤디","210980" -"82937","00757816","모다","149940" -"82945","00367482","라이트론","069540" -"82946","01118139","이도바이오","336040" -"82950","00128607","삼천리자전거","024950" -"82957","00767460","PI첨단소재","178920" -"82960","01344752","케어룸의료산업","327970" -"82972","00337296","원포유","122830" -"82973","01236532","와이즈버즈","273060" -"82974","01259418","대보마그네틱","290670" -"82976","00539274","대상홀딩스","084690" -"82980","01188378","시스웍","269620" -"82981","00173731","씨앤에스자산관리","032040" -"83001","00384717","넥스트BT","065170" -"83010","00526951","이엠코리아","095190" -"83013","00606770","참좋은여행","094850" -"83014","00684802","에이플러스에셋","244920" -"83015","00608440","티앤엘","340570" -"83016","00406329","루멘스","038060" -"83021","00370255","바른전자","064520" -"83022","00144021","원풍물산","008290" -"83048","00317089","에스디","066930" -"83063","00599887","신텍","099660" -"83068","00688358","하이즈항공","221840" -"83073","00601641","리드","197210" -"83074","00160205","한국내화","010040" -"83080","00201788","제낙스","065620" -"83083","00385415","일신바이오","068330" -"83091","00372873","KTis","058860" -"83110","01489648","솔브레인","357780" -"83114","00164502","현대공업","170030" -"83128","00159573","한국주철관공업","000970" -"83135","00297448","젬백스지오","041590" -"83139","00269852","인프라웨어","041020" -"83140","00132868","성안","011300" -"83141","00560122","텔레필드","091440" -"83142","00148443","제일파마홀딩스","002620" -"83143","00369213","리노스","039980" -"83145","01042359","지엘팜텍","204840" -"83147","00129615","상보","027580" -"83150","00378628","KH바텍","060720" -"83151","01042429","액션스퀘어","205500" -"83152","00526696","웨이브일렉트로","095270" -"83155","00256380","유아이엘","049520" -"83156","00165413","롯데케미칼","011170" -"83157","00528515","한라IMS","092460" -"83160","00114093","덕성","004830" -"83167","00188973","화일약품","061250" -"83168","01168684","스튜디오드래곤","253450" -"83169","01153105","비비씨","318410" -"83180","01050738","솔트룩스","304100" -"83181","01419384","하나금융16호스팩","343510" -"83182","00164724","현대엘리베이터","017800" -"83186","00288343","삼영이엔씨","065570" -"83195","00136776","제이준코스메틱","025620" -"83197","00297961","케이맥","043290" -"83198","00317487","에이치엘비파워","043220" -"83199","01142400","이루다","164060" -"83201","00666064","셀바스AI","108860" -"83202","00666204","제너셈","217190" -"83206","01201970","클리노믹스","352770" -"83208","00131939","선도전기","007610" -"83209","00113544","대한화섬","003830" -"83210","00579971","칩스앤미디어","094360" -"83211","01015160","와이팜","332570" -"83222","00245694","남화산업","111710" -"83236","01414361","SK5호스팩","337450" -"83238","00249441","씨씨에스","066790" -"83243","00150165","브이티지엠피","018290" -"83248","01407158","신한제6호스팩","333050" -"83252","00306454","한국정보인증","053300" -"83255","00154718","비츠로셀","082920" -"83257","00103644","비츠로테크","042370" -"83259","01274310","이오플로우","294090" -"83265","01450105","미래에셋대우스팩 5호","353490" -"83272","00838962","알서포트","131370" -"83276","00259934","예림당","036000" -"83278","00226352","케이엠더블유","032500" -"83279","00549925","루트로닉","085370" -"83281","00568461","보광산업","225530" -"83285","00142661","우성사료","006980" -"83287","00531078","유비온","084440" -"83288","00163512","한진","002320" -"83289","00508274","일진다이아","081000" -"83291","00350020","파인디앤씨","049120" -"83292","00530413","코디엠","224060" -"83294","01046203","스튜디오산타클로스","204630" -"83295","00349732","코나아이","052400" -"83296","01085442","휴벡셀","212310" -"83297","00102751","고려산업","002140" -"83298","00974927","NEW","160550" -"83299","01222867","제테마","216080" -"83300","00609485","웅진에너지","103130" -"83301","00125743","현대비앤지스틸","004560" -"83302","00106395","금호전기","001210" -"83303","00302078","한국코퍼레이션","050540" -"83330","01035678","와이제이엠게임즈","193250" -"83331","00625942","아이원스","114810" -"83339","00220622","서호전기","065710" -"83341","00476036","에프엔에스테크","083500" -"83342","00899556","제룡산업","147830" -"83354","00172680","동국산업","005160" +"46416","00261285","한국가스공사","036460" +"46448","00503899","사조해표","079660" +"46626","00133317","에스티씨라이프","026220" +"46664","01018802","엔솔바이오사이언스","140610" +"46745","00311216","에이치엔에스하이텍","044990" +"46795","01201581","한화수성기업인수목적","265920" +"46823","00200956","현대정보기술","026180" +"46898","01111570","에스케이제3호기업인수목적","232330" +"47031","01113781","영현무역","242850" +"47038","01212383","엔케이맥스","262760" +"47053","00365758","아이앤씨","052860" +"47229","01063556","질경이","233990" +"47390","01194759","대신밸런스제4호기업인수목적","262830" +"47400","00165583","E1","017940" +"47408","00118451","동일제강","002690" +"47414","00125646","삼목에스폼","018310" +"47417","00119140","핸즈코퍼레이션","143210" +"47439","01261893","케이씨텍","281820" +"47447","00926522","테고사이언스","191420" +"47530","01396676","미래에셋대우스팩4호","333430" +"47610","00964595","엔바이오니아","317870" +"47657","00606293","나이스디앤비","130580" +"47670","00654175","마이크로프랜드","147760" +"47689","00107303","피엔에스커튼월","033220" +"47694","01159853","휴온스","243070" +"47777","00657756","지오씨","135160" +"47824","00138516","아남전자","008700" +"47889","00664288","스페이스솔루션","245030" +"47893","00231354","파워넷","037030" +"47963","00409672","티비에이치글로벌","084870" +"48026","00472890","동국알앤에스","075970" +"48057","00481223","넥스피안","079560" +"48105","01414370","하이제5호스팩","340120" +"48132","00175650","케이씨더블류","068060" +"48200","00131328","서주산업개발","016140" +"48287","00260879","카페24","042000" +"48289","00614089","에이원알폼","234070" +"48347","01069736","한독크린텍","256150" +"48356","01117422","나눔테크","244880" +"48379","00106368","금호석유화학","011780" +"48432","00145109","유한양행","000100" +"48510","00330044","캠시스","050110" +"48582","00101664","경보제약","214390" +"48672","00276834","KMH하이텍","052900" +"48693","00385363","빅텍","065450" +"48789","00491053","진바이오텍","086060" +"48936","00614593","넥스트아이","137940" +"48996","01363924","한국미라클피플사","331660" +"49040","01398151","교보9호스팩","331520" +"49108","00218575","황금에스티","032560" +"49157","00416654","유엔젤","072130" +"49208","00123523","창해에탄올","004650" +"49232","00541163","이엠텍","091120" +"49282","00361381","제이브이엠","054950" +"49299","00118044","동원수산","030720" +"49343","00220686","에스피지","058610" +"49395","01037542","바이옵트로","222160" +"49546","00972293","파멥신","208340" +"49578","00127200","삼영전자공업","005680" +"49607","00838005","서진시스템","178320" +"49708","00398668","휴비츠","065510" +"49710","00606664","케이엔더블유","105330" +"49718","00133511","SG세계물산","004060" +"49732","00167004","흥구석유","024060" +"49803","00493510","뉴프렉스","085670" +"49903","01011438","에이치엘사이언스","239610" +"49912","00599489","청광건설","140290" +"49932","01276901","삼성스팩2호","291230" +"49933","01309388","티라유텍","322180" +"49934","01251489","라온피플","300120" +"49935","00216498","성도이엔지","037350" +"49936","01416457","하나금융15호스팩","341160" +"49951","00497631","티플랙스","081150" +"49957","01113499","시큐센","232830" +"49964","00365457","제이티","089790" +"50073","00872850","비지스틸","179440" +"50257","00869272","서전기전","189860" +"50335","00493185","제이스텍","090470" +"50343","01246034","네오펙트","290660" +"50376","00115472","미주제강","002670" +"50391","00416414","넥스턴","089140" +"50408","00340096","미래에셋벤처투자","100790" +"50525","01222432","에스제이그룹","306040" +"50536","00156910","SBI인베스트먼트","019550" +"50565","01116344","이비테크","208850" +"50570","01035289","모두투어리츠","204210" +"50598","00910947","바다로19호","155900" +"50637","00646732","진양홀딩스","100250" +"50644","00761059","펌텍코리아","251970" +"50655","00198378","녹십자셀","031390" +"50677","01124680","보라티알","250000" +"50688","00138206","쌍용건설","012650" +"50698","00957735","클리오","237880" +"50705","00407975","엔터미디어","068420" +"50745","00579980","아이티엠반도체","084850" +"50787","00159740","KISCO홀딩스","001940" +"50798","01138993","원바이오젠","278380" +"50825","01137903","디케이티","290550" +"50832","00405320","웹젠","069080" +"50853","01199550","현대에너지솔루션","322000" +"50863","01068658","디딤","217620" +"50926","00608316","대한과학","131220" +"50930","00991191","앱클론","174900" +"50977","00249982","솔본","035610" +"50983","00107066","남광토건","001260" +"51031","00687711","한국철강","104700" +"51043","00870861","와이엠씨","155650" +"51054","00163114","서연","007860" +"51068","01108099","현대코퍼레이션홀딩스","227840" +"51092","00962223","지니언스","263860" +"51202","00148939","조광피혁","004700" +"51227","00158963","유나이티드","033270" +"51240","01108248","지노믹트리","228760" +"51273","00669450","바이오프로테크","199290" +"51315","00490151","파트론","091700" +"51394","00121154","엠에스씨","009780" +"51513","00445869","유니포인트","121060" +"51522","00521433","엔지브이아이","093510" +"51524","00398808","디지털대성","068930" +"51539","00137252","사조동아원","008040" +"51573","00140946","한솔로지스틱스","009180" +"51594","00101628","경방","000050" +"51596","00113508","노루홀딩스","000320" +"51642","00147222","전방","000950" +"51653","00113261","대한광통신","010170" +"51695","00129387","삼화페인트공업","000390" +"51762","00148531","제일제강","023440" +"51931","00104722","국제약품","002720" +"51991","00269612","파워로직스","047310" +"52013","01196313","유에스티","263770" +"52040","00133089","성원","015200" +"52058","01109070","비피도","238200" +"52096","01135057","녹십자웰빙","234690" +"52104","01182578","대유에이피","290120" +"52228","00317681","메타바이오메드","059210" +"52251","00540571","엘디티","096870" +"52323","00112679","대창단조","015230" +"52334","00225706","JW생명과학","234080" +"52338","00146214","일성신약","003120" +"52354","00929778","세기리텍","199870" +"52374","00164706","현대약품","004310" +"52439","01335930","티움바이오","321550" +"52448","00525864","피앤씨테크","237750" +"52460","00321204","관악산업","076340" +"52475","01020524","자이글","234920" +"52520","00788773","씨젠","096530" +"52559","00136864","신원","009270" +"52563","00132628","성문전자","014910" +"52610","01231786","슈프리마아이디","317770" +"52612","00531829","엠아이텍","179290" +"52621","00548519","에스엔피제네틱스","086460" +"52670","00442154","제이엔케이히터","126880" +"52742","01187148","케어랩스","263700" +"52791","00352000","하우리","049130" +"52799","00109781","대림제지","017650" +"52822","00506294","가비아","079940" +"52916","00867098","티로보틱스","117730" +"52922","00823429","한국화장품","123690" +"52943","00536286","윈팩","097800" +"52950","01390399","미래에셋대우스팩3호","328380" +"53021","00250137","전파기지국","065530" +"53033","00486097","나스미디어","089600" +"53036","01325429","네온테크","306620" +"53043","00554352","아이큐어","175250" +"53044","01035942","메디안디노스틱","233250" +"53109","00122551","백산","035150" +"53221","00109453","STX조선해양","067250" +"53349","00428321","해태제과식품","101530" +"53362","00560982","바이오솔루션","086820" +"53410","00914040","씨엠에스에듀","225330" +"53436","00332927","제이에스코퍼레이션","194370" +"53478","00989619","알테오젠","196170" +"53562","00140168","지투알","035000" +"53642","00609324","뷰웍스","100120" +"53687","00151298","DSR제강","069730" +"53764","00899459","레이언스","228850" +"53777","00917861","슈피겐코리아","192440" +"53781","00410915","서울옥션","063170" +"53788","01109955","링크제니시스","219420" +"53843","01316227","효성티앤씨","298020" +"53861","01211232","코윈테크","282880" +"53949","00541349","셀트리온제약","068760" +"53956","00264255","바텍","043150" +"53960","00617314","타이거일렉","219130" +"53968","00449254","쎄트렉아이","099320" +"53982","00598198","컴퍼니케이","307930" +"54026","00858124","알에스오토메이션","140670" +"54078","00172936","동일산업","004890" +"54109","00132725","성보화학","003080" +"54191","00675594","디티앤씨","187220" +"54200","01203808","AP시스템","265520" +"54247","00101488","경동나비엔","009450" +"54254","01076550","주노콜렉션","221670" +"54343","00527880","썬테크놀로지스","122800" +"54376","00977641","하이로닉","149980" +"54440","00161709","퍼시스","016800" +"54487","00231044","우리기술","032820" +"54542","00450995","대모","317850" +"54550","00962922","팬젠","222110" +"54579","01116274","엠앤씨생명과학","225860" +"54630","00219848","서희건설","035890" +"54646","00897752","천보","278280" +"54653","00291231","우전","052270" +"54695","00118026","동원산업","006040" +"54702","00360577","엑사이엔씨","054940" +"54734","00167031","흥국","010240" +"54758","00127857","삼익악기","002450" +"54830","00861720","이즈미디어","181340" +"54834","00219440","휴맥스홀딩스","028080" +"54857","00605328","씨앤지하이테크","264660" +"54864","00137207","유니켐","011330" +"54877","00351995","지에스이","053050" +"54919","00242712","엘컴텍","037950" +"54921","00163196","한일철강","002220" +"54944","00101433","경농","002100" +"54946","00301112","삼화네트웍스","046390" +"55001","00131692","서흥","008490" +"55002","00129271","삼화전기","009470" +"55029","00883980","아이디스","143160" +"55068","00106456","지앤엘","014590" +"55095","00162072","한신기계공업","011700" +"55099","00370918","케이디켐","221980" +"55100","00863038","윌링스","313760" +"55121","00145260","율촌화학","008730" +"55180","00671376","티웨이항공","091810" +"55186","01203507","에스엠비나","299670" +"55190","01071944","미래생명자원","218150" +"55215","01336443","한국제8호스팩","310870" +"55240","00131504","서한","011370" +"55492","00819374","나무가","190510" +"55535","00491336","잘만테크","090120" +"55539","00787057","휴맥스","115160" +"55555","00403766","피피아이","062970" +"55666","00111193","대양전기공업","108380" +"55742","00238153","파세코","037070" +"55753","01078178","RFHIC","218410" +"55765","00409140","이원컴포텍","088290" +"55845","00128227","삼정펄프","009770" +"55930","01276327","위지윅스튜디오","299900" +"55981","01093007","LS전선아시아","229640" +"56080","01095704","케이엠제약","225430" +"56095","00298340","에스티아이","039440" +"56144","01063990","로보로보","215100" +"56175","00807555","메가엠디","133750" +"56223","01101041","패션플랫폼","225590" +"56272","00130046","다함이텍","009280" +"56320","01235296","셀리드","299660" +"56346","00366845","탑엔지니어링","065130" +"56358","00471068","덕산하이메탈","077360" +"56384","00410997","누리플랜","069140" +"56425","00226866","인탑스","049070" +"56521","01011562","우성아이비","194610" +"56547","00397191","팬엔터테인먼트","068050" +"56549","00543107","오킨스전자","080580" +"56571","00404701","한국자산신탁","123890" +"56621","00871833","에스티팜","237690" +"56640","00965813","미투온","201490" +"56667","01077577","우리산업","215360" +"56692","00117638","동양텔레콤","007150" +"56709","99999999","금감원(테스트)","999980" +"56762","00574611","SDN","099220" +"56816","00144164","SK가스","018670" +"56865","00613318","와이지엔터테인먼트","122870" +"56867","00173102","모나리자","012690" +"56873","00110060","대백저축은행","026970" +"56883","01013311","민앤지","214180" +"56884","00977377","에이비온","203400" +"56902","00104999","윌비스","008600" +"56951","00117337","동양","001520" +"56955","00485177","일진파워","094820" +"57004","01267222","캐리소프트","317530" +"57083","00983934","아이스크림에듀","289010" +"57122","01027794","브이원텍","251630" +"57133","01393721","유진스팩5호","331380" +"57145","01124653","슈프리마","236200" +"57224","00166528","서암기계공업","100660" +"57270","01359736","유진스팩4호","321260" +"57300","00162911","한일사료","005860" +"57324","00521390","뉴파워프라즈마","144960" +"57330","00599285","서플러스글로벌","140070" +"57334","00155212","포스코 ICT","022100" +"57335","01251595","압타바이오","293780" +"57359","00161383","한미반도체","042700" +"57402","00165343","혜인","003010" +"57428","01014055","에스트래픽","234300" +"57441","00541862","판도라티비","202960" +"57447","01366000","신영스팩5호","323280" +"57526","00231105","동성화인텍","033500" +"57578","00137359","신풍제약","019170" +"57580","00141404","영풍제지","006740" +"57628","00149266","CNH","023460" +"57636","00135777","신대양제지","016590" +"57653","00164618","현대사료","016790" +"57688","00442631","태양기계","116100" +"57703","00651017","나우IB","293580" +"57718","00571298","덕우전자","263600" +"57772","00966168","디알젬","263690" +"57792","00144818","유유제약","000220" +"57925","00372253","연우","115960" +"57928","00971726","옵티팜","153710" +"57954","00101336","경남에너지","008020" +"57967","01419986","신영스팩6호","344050" +"57992","00612489","아이쓰리시스템","214430" +"58028","01046708","바디텍메드","206640" +"58140","00173069","아진카인텍","011400" +"58166","00158325","NICE평가정보","030190" +"58167","00159175","한국전기초자","009720" +"58190","01179617","한국비엔씨","256840" +"58279","00137289","이라이콤","041520" +"58291","00152862","코오롱","002020" +"58301","00229021","인성정보","033230" +"58308","00530556","예스티","122640" +"58324","00181934","포스코플랜텍","051310" +"58325","00498001","모베이스","101330" +"58359","01113718","올릭스","226950" +"58373","00112022","대원화성","024890" +"58419","00128111","포스코엠텍","009520" +"58557","00867973","서남","294630" +"58594","01046391","싸이토젠","217330" +"58617","00864338","웹스","196700" +"58655","00562245","태웅로직스","124560" +"58832","00429694","파버나인","177830" +"58866","01267684","쿠쿠홈시스","284740" +"58899","00961774","유티아이","179900" +"58978","00616962","린드먼아시아","277070" +"58988","00579139","한국맥널티","222980" +"58992","00363927","NE능률","053290" +"59096","00580065","월덱스","101160" +"59107","00915508","알엔투테크놀로지","148250" +"59167","00113128","대한약품","023910" +"59178","00108746","큐로","015590" +"59224","00632304","JW홀딩스","096760" +"59226","00414540","대주이엔티","114920" +"59270","01344336","신테카바이오","226330" +"59274","00108001","남화토건","091590" +"59305","00992871","종근당","185750" +"59325","01116380","씨앤에스링크","245450" +"59343","00681249","씨유메디칼","115480" +"59348","00450931","우양에이치씨","101970" +"59385","00544577","에이피티씨","089970" +"59408","00138190","GS글로벌","001250" +"59447","01089378","MP한강","219550" +"59457","01390876","상상인이안제2호스팩","329560" +"59576","01167056","샘표식품","248170" +"59581","00766106","트루윈","105550" +"59603","00828761","레이","228670" +"59636","00122472","백광산업","001340" +"59687","00408266","유니온커뮤니티","203450" +"59738","00474588","한중엔시에스","107640" +"59757","01326260","하나머스트제6호스팩","307160" +"59759","00631837","안트로젠","065660" +"59772","00160418","한국프랜지공업","010100" +"59812","01223219","에이에프더블류","312610" +"59828","00111838","대원","007680" +"59883","01405585","IBKS제12호스팩","335870" +"59964","01182550","EDGC","245620" +"59983","00117966","동원개발","013120" +"60069","00415868","펩트론","087010" +"60073","00578325","럭스피아","092590" +"60080","00365934","코세스","089890" +"60155","00130091","샘표","007540" +"60162","00228712","동우팜투테이블","088910" +"60169","00131197","서원","021050" +"60171","00353230","프리젠","060910" +"60207","00676122","지디","155960" +"60281","00359261","빛샘전자","072950" +"60291","01042711","글로벌텍스프리","204620" +"60316","00154329","태평양물산","007980" +"60412","01063963","유쎌","252370" +"60436","00303703","CS","065770" +"60443","00156354","지엠비코리아","013870" +"60464","00177199","디씨엠","024090" +"60467","00129235","삼화왕관","004450" +"60490","00415327","중앙백신","072020" +"60529","00659161","싸이맥스","160980" +"60533","00265421","인지디스플레","037330" +"60535","01047415","정다운","208140" +"60536","00162416","한양증권","001750" +"60547","00162063","한신공영","004960" +"60580","00161116","한라","014790" +"60620","01098792","본느","226340" +"60651","01190780","파워풀엑스","266870" +"60672","00111458","대영포장","014160" +"60688","00983916","제노포커스","187420" +"60744","00148920","조광페인트","004910" +"60766","00280688","SBS콘텐츠허브","046140" +"60794","01256864","에이비엘바이오","298380" +"60795","00116806","동아","012760" +"60799","00374020","이엘피","063760" +"60801","01089350","켐트로스","220260" +"60804","00413417","우리손에프앤지","073560" +"60805","00540340","코리아센터","290510" +"60818","00146454","일양약품","007570" +"60842","00564562","엠씨넥스","097520" +"60864","00112004","알바이오","003190" +"60887","00656340","소프트캠프","210610" +"60904","00989664","코아스템","166480" +"60906","00397058","엠게임","058630" +"60922","00888347","휴젤","145020" +"60961","00446479","신비앤텍","070480" +"61008","00377610","세아홀딩스","058650" +"61115","00239639","삼표시멘트","038500" +"61140","00874803","AP위성","211270" +"61141","00306870","손오공","066910" +"61142","00153278","서연탑메탈","019770" +"61154","00976615","틸론","217880" +"61156","00298377","아이씨디","040910" +"61164","01227039","브릿지바이오","288330" +"61252","01074862","메가스터디교육","215200" +"61305","00524421","테스","095610" +"61312","01325979","세아제강","306200" +"61324","00155355","풀무원","017810" +"61338","00536523","로보스타","090360" +"61381","01119387","이에스산업","241510" +"61525","00122056","미창석유공업","003650" +"61626","01234297","미원에스씨","268280" +"61661","01058101","라파스","214260" +"61716","00438036","팅크웨어","084730" +"61750","00990262","피노텍","150440" +"61840","00490179","플랜티넷","075130" +"61868","00206659","이랜텍","054210" +"61897","00991225","더콘텐츠온","302920" +"61899","00172291","더존비즈온","012510" +"61937","00114686","동구바이오제약","006620" +"61939","00769158","케이엔제이","272110" +"62061","00133715","대주코레스","008340" +"62065","00166272","화승알앤에이","013520" +"62071","00139083","아진산업","013310" +"62129","00150633","지누스","013890" +"62162","00651901","에어부산","298690" +"62169","00486370","성창오토텍","080470" +"62175","01082348","오스테오닉","226400" +"62177","00163345","DB","012030" +"62185","01163050","다이오진","271850" +"62234","00653024","진에어","272450" +"62340","00239514","삼진엘앤디","054090" +"62378","00141626","오리엔트바이오","002630" +"62404","01169267","세화피앤씨","252500" +"62406","00397289","오텍","067170" +"62426","00807379","신흥에스이씨","243840" +"62443","01199189","이랜시스","264850" +"62473","00525882","청담러닝","096240" +"62507","00561866","락앤락","115390" +"62530","01267602","유안타제3호스팩","287410" +"62540","00148504","한국스탠다드차타드은행","000110" +"62596","00813389","베셀","177350" +"62598","00681373","디지캡","197140" +"62646","00109754","대림비앤코","005750" +"62651","00231442","디지틀조선","033130" +"62666","01194892","알로이스","271400" +"62699","00671978","그리티","204020" +"62795","00583442","노루페인트","090350" +"62855","00364740","머큐리","100590" +"62882","00165103","푸드웰","005670" +"62883","00362159","웰크론","065950" +"62944","01418543","케이비제20호스팩","342550" +"62976","00945457","아이진","185490" +"62980","01344363","다원넥스뷰","323350" +"63001","00535676","테크윙","089030" +"63005","00531014","유진테크","084370" +"63018","00103042","케이씨티시","009070" +"63107","00264945","나이스정보통신","036800" +"63111","01366365","이베스트이안스팩1호","323210" +"63155","00487546","웰크론한텍","076080" +"63206","00456218","모두투어","080160" +"63222","00411446","큐에스아이","066310" +"63239","00756163","볼빅","206950" +"63263","00122728","범양사","002480" +"63267","00763358","쎄미시스코","136510" +"63281","00273420","이스트소프트","047560" +"63291","00243757","인포뱅크","039290" +"63303","00170877","진로발효","018120" +"63308","00132637","동원시스템즈","014820" +"63353","01358463","엔에이치스팩14호","319400" +"63399","00890388","지성이씨에스","138290" +"63408","00857727","하림","136480" +"63509","00580056","스맥","099440" +"63510","00105244","알보젠코리아","002250" +"63544","00252269","아이에이","038880" +"63546","00154426","아이에이치큐","003560" +"63574","01051092","피씨엘","241820" +"63576","00532855","한국유니온제약","080720" +"63586","00156600","한주금속","198940" +"63587","00112651","대창","012800" +"63673","00358271","에스에프에이","056190" +"63679","00384887","에이테크솔루션","071670" +"63683","00602172","와이엠티","251370" +"63721","00124027","부산산업","011390" +"63729","00557933","진도","088790" +"63754","00106119","금양","001570" +"63771","00329093","코데즈컴바인","047770" +"63785","00876865","네오오토","212560" +"63818","00365989","용평리조트","070960" +"63944","00252001","해원에스티","058480" +"63963","00100957","건영","012720" +"63995","00162780","한일건설","006440" +"64022","00142591","셰프라인","012250" +"64135","00112721","대창스틸","140520" +"64143","00174208","진성티이씨","036890" +"64162","00454946","그린케미칼","083420" +"64185","00105165","극동전선","006250" +"64192","00173795","신흥","004080" +"64219","00374738","위세아이텍","065370" +"64220","00139764","에넥스","011090" +"64223","00423177","텔레칩스","054450" +"64229","01182444","셀리버리","268600" +"64231","00222532","LG헬로비전","037560" +"64259","00236863","희림","037440" +"64413","00125530","SPC삼립","005610" +"64465","00138701","아세아","002030" +"64489","01408999","유안타제5호스팩","336060" +"64498","00115931","디오","039840" +"64512","00327819","선익시스템","171090" +"64531","00138260","SIMPAC","009160" +"64579","00256043","비씨월드제약","200780" +"64590","00879020","유니트론텍","142210" +"64681","00997812","코미코","183300" +"64689","00181299","상아프론테크","089980" +"64710","01238169","오리온","271560" +"64743","00118460","태림페이퍼","019300" +"64769","00770312","마니커에프앤지","195500" +"64787","00123648","심팩인더스트리","005350" +"64798","00488989","한양디지텍","078350" +"64849","00556907","에이스테크","088800" +"64850","00297934","피씨디렉트","051380" +"64879","00346966","에코솔루션","052510" +"64881","00357430","쎌바이오텍","049960" +"65001","00876908","모트렉스","118990" +"65040","01047840","미스터블루","207760" +"65071","01011395","레몬","294140" +"65095","00222806","에임하이글로벌","043580" +"65100","00183215","케이디지엠텍","032290" +"65118","00126779","삼아알미늄","006110" +"65137","01234525","한국제5호기업인수목적","271740" +"65138","00706715","미원홀딩스","107590" +"65148","00481454","금호타이어","073240" +"65194","00459871","농심홀딩스","072710" +"65202","00163318","모헨즈","006920" +"65314","00389679","해피드림","065180" +"65374","00256955","테이팩스","055490" +"65395","00577380","신진에스엠","138070" +"65429","00133858","세방전지","004490" +"65430","00454937","덕신하우징","090410" +"65532","00535481","에스와이","109610" +"65581","01099861","에이텍티앤","224110" +"65637","00134176","세우글로벌","013000" +"65643","00261735","위노바","039790" +"65662","00249247","YG PLUS","037270" +"65663","00526465","동북아13호선박투자회사","083380" +"65664","00296005","우리로","046970" +"65665","00867034","듀켐바이오","176750" +"65666","01117246","파인이엠텍","278990" +"65693","00318316","대아티아이","045390" +"65721","01186404","디앤씨미디어","263720" +"65793","00103662","광명전기","017040" +"65806","00442826","대봉엘에스","078140" +"65828","00124577","엠소닉","008120" +"65856","00407771","듀오백","073190" +"65872","00152385","에이프로젠 KIC","007460" +"65881","00366793","에스제이케이","080440" +"65882","01170865","네오셈","253590" +"65883","01259232","노바텍","285490" +"65909","00555740","툴코리아","110660" +"65915","00379229","에스에이티","060540" +"65919","00140955","한솔케미칼","014680" +"65971","00228536","에버다임","041440" +"65982","00610083","비아트론","141000" +"66002","00579999","고영","098460" +"66039","00302582","캐스텍코리아","071850" +"66086","00876643","엔피디","198080" +"66101","00132488","신세계톰보이","012580" +"66133","00230911","HRS","036640" +"66136","00347442","모보","051810" +"66160","00123143","보령제약","003850" +"66198","00117726","동양피스톤","092780" +"66201","01188749","지앤이헬스케어","299480" +"66257","00324104","디케이씨","047440" +"66334","01010110","더블유게임즈","192080" +"66366","00181712","SK","034730" +"66400","00624518","솔루에타","154040" +"66427","00526872","이수앱지스","086890" +"66438","00645089","NHN벅스","104200" +"66450","00611736","엑시콘","092870" +"66497","00153126","크라운해태홀딩스","005740" +"66513","00111999","대원제약","003220" +"66541","00913689","세경하이테크","148150" +"66564","01316236","효성화학","298000" +"66565","01021949","덱스터","206560" +"66688","00410678","다린","204690" +"66712","00126201","삼성공조","006660" +"66807","00210740","대화제약","067080" +"66811","01318933","지니틱스","303030" +"66812","01428249","케이프이에스제4호","347140" +"66874","00119672","두올","016740" +"66881","00137809","신화실업","001770" +"66937","00877819","데이타솔루션","263800" +"67009","00664181","인텔리안테크","189300" +"67103","00109514","티웨이홀딩스","004870" +"67173","00576798","디아이티","110990" +"67199","01109539","와이아이케이","232140" +"67212","00206084","세아메탈","033020" +"67236","00151447","천지산업","001490" +"67317","00141875","사조오양","006090" +"67369","00291152","디비엘","041500" +"67422","00188089","한섬","020000" +"67464","00983271","엔에이치엔","181710" +"67472","00874937","탑선","180060" +"67508","00556615","모바일어플라이언스","087260" +"67517","00363246","우원개발","046940" +"67532","00362238","휴비스","079980" +"67600","00634728","이크레더블","092130" +"67689","00305668","새론오토모티브","075180" +"67698","00612188","에스티오","098660" +"67744","01168383","일동제약","249420" +"67760","00158565","한국알콜","017890" +"67952","00131832","SK디스커버리","006120" +"67988","00268251","다산네트웍스","039560" +"68064","00480455","STX엔진","077970" +"68147","00413523","한미글로벌","053690" +"68148","00260408","팜스코","036580" +"68151","00350312","HDC아이콘트롤스","039570" +"68152","00306719","에스텍","069510" +"68153","00148364","하림지주","003380" +"68158","00491415","인포바인","115310" +"68188","00830447","수프로","185190" +"68201","00728638","한세실업","105630" +"68202","00161976","한세예스24홀딩스","016450" +"68229","00550994","아이센스","099190" +"68230","00112907","코원에너지서비스","026870" +"68274","00159874","한국컴퓨터지주","009760" +"68331","00442570","제우스","079370" +"68396","00484682","엘오티베큠","083310" +"68397","00117577","오리온홀딩스","001800" +"68398","00589127","KEC","092220" +"68416","00520887","비상교육","100220" +"68433","00102618","계양전기","012200" +"68435","00164308","이마트에브리데이","010090" +"68438","00111810","대웅","003090" +"68439","00427483","대웅제약","069620" +"68452","00398701","엘앤에프","066970" +"68465","00219097","BGF","027410" +"68510","01067516","골프존","215000" +"68528","01128622","줌인터넷","239340" +"68541","00162586","한올바이오파마","009420" +"68542","00223762","지니뮤직","043610" +"68577","00133991","세아특수강","019440" +"68592","00108913","대교","019680" +"68594","00599957","슈프리마에이치큐","094840" +"68596","01010642","녹십자랩셀","144510" +"68607","00414601","유니퀘스트","077500" +"68611","00148984","조선내화","000480" +"68638","00137368","신풍제지","002870" +"68646","00652007","메카로","241770" +"68660","00255433","라온시큐어","042510" +"68689","00661847","화인베스틸","133820" +"68742","00105156","극동유화","014530" +"68744","00268002","세종텔레콤","036630" +"68745","00530185","이엔에프테크놀로지","102710" +"68746","00115065","동남합성","023450" +"68747","00970453","파마리서치프로덕트","214450" +"68748","00123967","부산도시가스","015350" +"68749","00595243","엔스퍼트","098400" +"68807","00441447","케이씨티","089150" +"68809","00595191","후성","093370" +"68816","00357360","한세엠케이","069640" +"68818","00123718","부광약품","003000" +"68823","00156956","한국기업평가","034950" +"68860","00128032","삼일제약","000520" +"68864","00241005","코오롱플라스틱","138490" +"68886","00138729","아세아제지","002310" +"68913","00350738","제네시스디벨롭먼트홀딩스","053320" +"68952","00371485","켐트로닉스","089010" +"68956","01066058","파마리서치바이오","217950" +"68957","01118643","로보쓰리","238500" +"68966","01414936","SK6호스팩","340350" +"69040","00346610","유앤아이","056090" +"69069","01141942","액트로","290740" +"69079","00130684","서울도시가스","017390" +"69140","00248974","하나제약","293480" +"69217","01351114","케이비17호스팩","317030" +"69261","01062867","디알텍","214680" +"69328","00163761","한창제지","009460" +"69343","00862853","플레이디","237820" +"69349","01336373","메드팩토","235980" +"69357","00130763","서울반도체","046890" +"69378","00161693","한샘","009240" +"69436","00255044","현대통신","039010" +"69443","00113243","대한제분","001130" +"69444","00577016","수산아이앤티","050960" +"69445","00114552","도화엔지니어링","002150" +"69501","00118965","티에이치엔","019180" +"69502","00203209","에쎈테크","043340" +"69503","00269940","하나투어","039130" +"69504","00323868","웹케시","053580" +"69505","00442048","아바텍","149950" +"69506","00447760","미래컴퍼니","049950" +"69507","01015726","미래테크놀로지","213090" +"69526","00105280","현대그린푸드","005440" +"69527","00108490","엔피케이","048830" +"69528","00143262","우진아이엔에스","010400" +"69529","00146232","일성건설","013360" +"69530","00373571","룽투코리아","060240" +"69540","00111847","대원강업","000430" +"69541","00136226","신성델타테크","065350" +"69542","00116949","디티알오토모티브","007340" +"69543","00190756","와이엔텍","067900" +"69544","00259545","엠에스오토텍","123040" +"69545","00124780","사조씨푸드","014710" +"69546","00124799","사조산업","007160" +"69547","00261373","마크로젠","038290" +"69602","00106614","기신정기","092440" +"69603","01436336","이베스트스팩5호","349720" +"69626","00367844","자이에스앤디","317400" +"69665","01070149","올리패스","244460" +"69703","00450010","센트랄모텍","308170" +"69704","01037214","케이피에스","256940" +"69714","00149026","CS홀딩스","000590" +"69715","00153339","태경산업","015890" +"69716","00159254","한국전자홀딩스","006200" +"69777","00808086","애니젠","196300" +"69903","01236897","제일약품","271980" +"69923","00796994","조선선재","120030" +"69924","01032413","해성디에스","195870" +"69925","01144028","씨티케이코스메틱스","260930" +"69926","01365825","피에스케이","319660" +"69940","01337220","키움제5호스팩","311270" +"69961","01072518","디피코","163430" +"69962","00203582","한솔홈데코","025750" +"70120","01316245","효성중공업","298040" +"70138","00107987","남해화학","025860" +"70165","00378363","3S","060310" +"70179","00684732","풀무원식품","103160" +"70219","00930321","하이골드오션8호국제선박투자회사","159650" +"70220","01419135","이노진","344860" +"70264","00106313","금호산업","002990" +"70273","00216027","바이넥스","053030" +"70311","00268011","일레덱스홀딩스","033550" +"70324","01237540","이녹스첨단소재","272290" +"70492","00145464","이구산업","025820" +"70499","00104768","가온전선","000500" +"70549","00616290","이엠넷","123570" +"70575","00178754","동아지질","028100" +"70602","00599151","SV인베스트먼트","289080" +"70603","00136457","SH에너지화학","002360" +"70611","00104388","국도화학","007690" +"70613","00956930","동아에스티","170900" +"70617","00135999","신라섬유","001000" +"70631","00307037","더스텔라","065310" +"70661","00128175","원익큐브","014190" +"70670","00116824","동아쏘시오홀딩스","000640" +"70671","00135050","우리들제약","004720" +"70710","00361594","DMS","068790" +"70741","01336735","펨토바이오메드","327610" +"70773","00110583","대성엘텍","025440" +"70774","00119007","무림P&P","009580" +"70783","00145862","인천도시가스","034590" +"70810","00695464","차이나그레이트스타인터내셔널리미티드","900040" +"70811","00916826","드림씨아이에스","223250" +"70830","00430089","삼보오토","070080" +"70858","00129013","CJ씨푸드","011150" +"70859","00117179","디와이","013570" +"70873","00157104","대동전자","008110" +"70953","00990165","아세아시멘트","183190" +"70963","00340917","동원F&B","049770" +"70969","00326731","바이오니아","064550" +"71087","00117498","디피씨","026890" +"71155","00244747","파크시스템스","140860" +"71159","00493501","씨디네트웍스","073710" +"71260","00992543","삼양옵틱스","225190" +"71306","00389998","브레인콘텐츠","066980" +"71307","00939687","동일고무벨트","163560" +"71308","00442145","아바코","083930" +"71309","00866594","미애부","225850" +"71310","00664048","우리넷","115440" +"71311","00693554","티케이케미칼","104480" +"71312","01070857","켐온","217600" +"71313","01181807","까스텔바작","308100" +"71314","01245062","코오롱티슈진","950160" +"71315","01343920","유안타제4호스팩","313750" +"71337","00123152","보루네오가구","004740" +"71344","00927558","유바이오로직스","206650" +"71345","00993931","동양파일","228340" +"71349","00828497","한미약품","128940" +"71440","00157399","한솔테크닉스","004710" +"71444","01086812","케미메디","205290" +"71456","00673301","전우정밀","120780" +"71458","01388631","오션스톤","329020" +"71466","00151368","인터지스","129260" +"71469","01316254","효성첨단소재","298050" +"71521","01157235","아스타","246720" +"71648","00990819","래몽래인","200350" +"71650","00447575","제이앤티씨","204270" +"71658","00123772","부국증권","001270" +"71777","00165361","휴코드","036840" +"71779","01336391","엔에이치스팩13호","310840" +"71780","00158501","에스원","012750" +"71781","00161046","한독","002390" +"71887","00125576","체시스","033250" +"71957","00946030","로보티즈","108490" +"71958","01195828","대원모방","311840" +"71966","00359395","헬릭스미스","084990" +"72108","01148909","휴럼","284420" +"72110","00490090","이지케어텍","099750" +"72122","00171265","파라다이스","034230" +"72130","00857480","사람인에이치알","143240" +"72158","01060744","한솔제지","213500" +"72222","00255619","강원랜드","035250" +"72271","01412725","두산퓨얼셀","336260" +"72281","00124197","세아제강지주","003030" +"72415","01326792","상상인이안1호스팩","307870" +"72427","00118284","동일금속","109860" +"72481","00870481","에코캡","128540" +"72526","00963976","SG","255220" +"72562","01267967","마이크로디지탈","305090" +"72569","00154392","에스트라","016570" +"72635","00216434","체리부로","066360" +"72644","00241209","모아텍","033200" +"72645","00631518","SK이노베이션","096770" +"72669","00659815","금오하이텍","165270" +"72770","00112457","대주산업","003310" +"72781","00961136","씨티네트웍스","189540" +"72840","00659976","영화테크","265560" +"72881","00116356","동성화학","005190" +"72883","00893923","이푸른","185280" +"72942","00763473","코스메카코리아","241710" +"72974","00838500","엘브이엠씨","900140" +"72975","00359076","브리지텍","064480" +"72976","00938688","SBI핀테크솔루션즈","950110" +"72979","01309795","알로이스","297570" +"72991","00234412","신세계인터내셔날","031430" +"73054","00925189","신화콘텍","187270" +"73064","00965062","코셋","189350" +"73072","01003040","케이사인","192250" +"73130","00121039","명문제약","017180" +"73324","00115384","동방아그로","007590" +"73325","00160843","DB하이텍","000990" +"73366","01205329","크라운제과","264900" +"73419","00238199","로지시스","067730" +"73429","00120508","롯데푸드","002270" +"73477","00415594","동국S&C","100130" +"73501","00925295","에프엔씨엔터","173940" +"73620","00110750","리더스 기술투자","019570" +"73629","01371312","케이비제18호스팩","323940" +"73632","01393299","케이비제19호스팩","330990" +"73764","00530796","미성포리테크","094700" +"73771","00113207","대한전선","001440" +"73772","00489243","동아엘텍","088130" +"73984","00343127","자유투어","046840" +"74053","00776820","영원무역","111770" +"74082","00110875","대신정보통신","020180" +"74156","00875307","야스","255440" +"74171","00657002","에이디테크놀로지","200710" +"74175","01063954","수젠텍","253840" +"74243","00108241","농심","004370" +"74348","01059605","디와이파워","210540" +"74384","01182240","배럴","267790" +"74406","00121932","미원상사","002840" +"74428","00397243","티에스엠텍","066350" +"74451","00148276","제일기획","030000" +"74458","00602136","디와이피엔에프","104460" +"74493","00435312","하이스틸","071090" +"74609","00140061","정산애강","022220" +"74610","00150439","진양산업","003780" +"74656","00353762","키이스트","054780" +"74679","01255652","레이크머티리얼즈","281740" +"74688","01189438","노터스","278650" +"74689","00671437","파인넥스","123260" +"74694","00125488","삼륭물산","014970" +"74703","00274933","신세계푸드","031440" +"74704","00159564","한국주강","025890" +"74707","00362858","예스24","053280" +"74709","00155638","피에스텍","002230" +"74710","00441128","아리온","058220" +"74714","00116408","동신건설","025950" +"74725","00580199","메디톡스","086900" +"74731","00878915","DGB금융지주","139130" +"74753","00140858","영신금속","007530" +"74754","00185505","제일바이오","052670" +"74755","00133751","세명전기","017510" +"74757","00109268","대동스틸","048470" +"74758","00412348","엘비세미콘","061970" +"74768","00138695","뉴아세아조인트","013340" +"74782","01368354","네패스아크","330860" +"74784","00238001","블루콤","033560" +"74785","00422284","메가스터디","072870" +"74816","00627029","ITX-AI","099520" +"74821","00132354","쿠쿠홀딩스","192400" +"74822","00814786","스카이이앤엠","131100" +"74823","01257872","에스퓨얼셀","288620" +"74830","00133557","PN풍년","024940" +"74831","00122339","이매진아시아","036260" +"74832","00353878","다스코","058730" +"74833","01137383","카카오게임즈","293490" +"74834","00652159","코오롱머티리얼","144620" +"74835","00149239","조일알미늄","018470" +"74836","00107677","비비안","002070" +"74838","00126478","삼성중공업","010140" +"74839","00447928","네오티스","085910" +"74840","00127158","씨아이테크","004920" +"74841","00695969","동운아나텍","094170" +"74842","00109824","대림통상","006570" +"74844","00176914","다우기술","023590" +"74845","01063884","이디티","215090" +"74851","01042979","휴마시스","205470" +"74880","00258801","카카오","035720" +"74881","00295857","코다코","046070" +"74882","00369657","리노공업","058470" +"74883","00140566","한탑","002680" +"74887","00638487","파수","150900" +"74888","00186717","태양","053620" +"74890","00147772","정원엔시스","045510" +"74892","00101752","케이씨피드","025880" +"74925","00665630","테라텍","151750" +"74946","00157070","한국단자공업","025540" +"74949","00361488","텔코웨어","078000" +"74950","00828789","대성산업","128820" +"74951","00592653","유비벨록스","089850" +"74953","00540429","휴림로봇","090710" +"74956","00265041","KCI","036670" +"74957","00911229","라온테크","232680" +"74960","01399071","고바이오랩","348150" +"74962","00298687","한류타임즈","039670" +"74963","00601191","하나기술","299030" +"74964","00694942","이미지스","115610" +"74965","00121570","문배철강","008420" +"74967","00350048","오성첨단소재","052420" +"74968","00178851","동양에스텍","060380" +"74969","00152783","코메론","049430" +"74970","00495086","픽셀플러스","087600" +"74973","01042775","만도","204320" +"74975","00959229","세종메디칼","258830" +"74993","00159786","유니드","014830" +"75003","00103592","광동제약","009290" +"75016","01020843","엔에스","217820" +"75029","00204642","티비씨","033830" +"75030","00112165","디아이씨","092200" +"75032","00137012","에스앤더블류","103230" +"75033","00139719","와이지-원","019210" +"75034","00295370","에이아이비트","039230" +"75035","00493431","자안","221610" +"75036","00920379","티엘비","356860" +"75040","00146083","일동홀딩스","000230" +"75042","00759294","와이솔","122990" +"75044","01117918","셀젠텍","258250" +"75051","00411385","유비쿼스홀딩스","078070" +"75056","00670766","엠투아이","347890" +"75060","01497869","티와이홀딩스","363280" +"75063","00217947","신세계건설","034300" +"75067","00331016","에프앤가이드","064850" +"75070","00536329","디지탈옵틱","106520" +"75074","01136348","나무기술","242040" +"75075","01258710","이노메트리","302430" +"75079","00184667","유진기업","023410" +"75081","01135941","원익IPS","240810" +"75129","00872452","에이치엔티","176440" +"75131","00125965","삼본전자","111870" +"75132","01067808","넵튠","217270" +"75133","00113492","깨끗한나라","004540" +"75134","00185356","제룡전기","033100" +"75139","01153293","제이엘케이","322510" +"75140","01263527","카이노스메드","284620" +"75141","01447244","에이치엠씨제5호스팩","353060" +"75143","01505450","DB금융스팩8호","367340" +"75174","00126788","삼아제약","009300" +"75208","00355548","한국테크놀로지","053590" +"75209","00155452","풍국주정","023900" +"75210","00242378","이지홀딩스","035810" +"75211","01480568","이지바이오","353810" +"75212","00192499","팜스토리","027710" +"75213","00204208","마니커","027740" +"75214","00623184","감마누","192410" +"75215","00418379","자연과환경","043910" +"75216","00351807","대원미디어","048910" +"75218","00110431","JW신약","067290" +"75220","00127699","에코마이스터","064510" +"75221","00152686","코리아써키트","007810" +"75222","00369170","인터플렉스","051370" +"75223","00206686","케이피엠테크","042040" +"75224","00954242","하이골드12호","172580" +"75226","00113526","대한항공","003490" +"75246","01061327","클래시스","214150" +"75254","01201590","에브리봇","270660" +"75312","00149770","중앙에너비스","000440" +"75315","00113225","대한제강","084010" +"75320","00367604","SM Life Design","063440" +"75321","00231372","롯데관광개발","032350" +"75324","01416642","유안타제6호스팩","340360" +"75328","01139497","에스제이켐","217910" +"75329","00124151","부산주공","005030" +"75345","00109310","대동기어","008830" +"75348","00127909","삼일","032280" +"75355","00110547","넥상스코리아","003050" +"75372","00173698","신일전자","002700" +"75383","00670340","씨에스윈드","112610" +"75402","00384948","대유","290380" +"75404","01025644","강스템바이오텍","217730" +"75415","00892526","디케이앤디","263020" +"75423","00523176","네추럴FNP","086220" +"75431","00587925","바이오리더스","142760" +"75462","00205687","글로스퍼랩스","032860" +"75467","00304915","코리아에셋투자증권","190650" +"75474","00861100","아이케이세미콘","149010" +"75494","00126955","삼양식품","003230" +"75495","00447007","알리코제약","260660" +"75500","00911955","잇츠한불","226320" +"75501","00660291","옵티시스","109080" +"75502","00267854","메디앙스","014100" +"75567","00113614","대현","016090" +"75568","00177320","대성미생물","036480" +"75578","01013694","인카금융서비스","211050" +"75587","00195229","휠라홀딩스","081660" +"75600","00191287","대동금속","020400" +"75607","00125150","SGC에너지","005090" +"75608","00652423","인바이오젠","101140" +"75623","00133706","세하","027970" +"75638","00886792","루켄테크놀러지스","162120" +"75652","01394669","대신밸런스제7호스팩","332290" +"75653","01407909","대신밸런스제8호스팩","336570" +"75661","00102681","고려개발","004200" +"75665","00604426","인터로조","119610" +"75670","01060814","삼양패키징","272550" +"75676","01203376","유비쿼스","264450" +"75677","00264714","SG&G","040610" +"75691","01204056","빅히트","352820" +"75715","01337017","이앤에치","341310" +"75730","00136925","신원종합개발","017000" +"75732","00609315","멜파스","096640" +"75733","00124276","부스타","008470" +"75734","00136624","신영와코루","005800" +"75735","00127042","삼양통상","002170" +"75736","00604815","에스디시스템","121890" +"75737","00356389","프럼파스트","035200" +"75738","00609634","아이엠","101390" +"75739","00755739","파인테크닉스","106240" +"75740","00103130","엔케이물산","009810" +"75742","00145190","유화증권","003460" +"75743","00291860","조광ILI","044060" +"75744","00307028","경남제약","053950" +"75746","00301246","SFA반도체","036540" +"75758","00300548","현대리바트","079430" +"75760","01207761","디자인","227100" +"75765","01263022","BGF리테일","282330" +"75767","00264228","위즈코프","038620" +"75771","01310269","HDC현대산업개발","294870" +"75774","00148832","제주은행","006220" +"75775","01318261","더블유에스아이","299170" +"75776","00161408","케이비캐피탈","021960" +"75805","00603348","케이아이엔엑스","093320" +"75808","00445841","한컴MDS","086960" +"75809","00786331","에스앤씨엔진그룹","900080" +"75836","01290381","피엔케이피부임상연구센타","347740" +"75881","00357607","케이피티유","054410" +"75897","01065013","우정바이오","215380" +"75898","00128980","삼호개발","010960" +"75899","00357740","NHN한국사이버결제","060250" +"75901","00244455","케이티앤지","033780" +"75902","00624998","제닉","123330" +"75903","00442561","에프알텍","073540" +"75905","00129280","삼화전자공업","011230" +"75906","00129679","녹십자","006280" +"75907","00108135","녹십자홀딩스","005250" +"75908","00391197","메디프론","065650" +"75909","00125974","삼부토건","001470" +"75910","00403632","현대퓨처넷","126560" +"75911","00249894","테라젠이텍스","066700" +"75912","00199988","동아화성","041930" +"75914","00138242","쌍용자동차","003620" +"75921","00147994","새로닉스","042600" +"75928","00120526","롯데쇼핑","023530" +"75929","00963000","썬테크","217320" +"75940","00105138","파라텍","033540" +"75941","00105101","예스코홀딩스","015360" +"75942","00203023","시노펙스","025320" +"75960","00565154","이노션","214320" +"75961","01415892","제이알글로벌리츠","348950" +"75962","01476219","교보10호기업인수목적","355150" +"75982","00166500","화천기계","010660" +"75983","01243550","아이비케이에스제7호기업인수목적","276920" +"75986","00942131","앤디포스","238090" +"76035","00128971","대림건설","001880" +"76036","00136095","조은저축은행","031920" +"76068","00261887","티엘아이","062860" +"76070","00102113","경인양행","012610" +"76071","00266952","네오위즈홀딩스","042420" +"76072","00628860","네오위즈","095660" +"76076","00145552","이수화학","005950" +"76079","00406727","세진티에스","067770" +"76080","00173944","우진","105840" +"76081","00778235","TS인베스트먼트","246690" +"76082","00390055","종근당바이오","063160" +"76084","01267958","프로테옴텍","303360" +"76086","00389970","다날","064260" +"76087","00684714","풍산","103140" +"76089","00129411","삼환기업","000360" +"76103","00818472","이큐셀","160600" +"76104","00121941","대상","001680" +"76112","00656021","데일리블록체인","139050" +"76115","00205003","좋은사람들","033340" +"76155","00136721","신영증권","001720" +"76156","01440153","IBKS제14호스팩","351320" +"76160","00400060","이씨에스","067010" +"76177","00146649","일진홀딩스","015860" +"76190","01394377","이지스밸류플러스리츠","334890" +"76201","00155373","풍강","093380" +"76211","01165155","모비스","250060" +"76220","00148461","제일연마","001560" +"76235","00117744","메리츠화재","000060" +"76236","00123541","보해양조","000890" +"76237","00261443","엔씨소프트","036570" +"76238","00208444","피에스케이홀딩스","031980" +"76239","00390860","MP그룹","065150" +"76241","01117592","인터코스","240340" +"76244","00296324","큐로홀딩스","051780" +"76254","00105961","LG이노텍","011070" +"76255","00121507","무림SP","001810" +"76256","00382199","신한지주","055550" +"76269","00109693","대림산업","000210" +"76270","01055317","비엔디생활건강","215050" +"76276","00111014","애큐온저축은행","007640" +"76277","01032486","두산밥캣","241560" +"76279","00877059","삼성바이오로직스","207940" +"76283","00176792","오렌지라이프생명보험","079440" +"76284","00149293","신한은행","000010" +"76286","00148610","한화투자증권","003530" +"76313","00860730","에이리츠","140910" +"76315","00221728","하츠","066130" +"76316","00122898","벽산","007210" +"76324","00367385","효성오앤비","097870" +"76337","01152470","펄어비스","263750" +"76338","00532129","영림원소프트랩","060850" +"76357","00527491","코리아에스이","101670" +"76363","00158149","한국쉘석유","002960" +"76393","00243988","에스케이브로드밴드","033630" +"76404","00155735","피제이전자","006140" +"76406","01090471","씨아이에스","222080" +"76410","01363818","롯데리츠","330590" +"76411","00245472","티씨케이","064760" +"76412","00352718","소리바다","053110" +"76413","00785475","피앤이솔루션","131390" +"76414","01505186","엔에이치스팩18호","365590" +"76415","00164779","SK하이닉스","000660" +"76416","00137997","현대차증권","001500" +"76425","01102095","쿠첸","225650" +"76436","00106641","기아자동차","000270" +"76438","00147295","전북은행","006350" +"76444","00113058","한화생명","088350" +"76446","00138321","신한금융투자","008670" +"76451","00159616","두산중공업","034020" +"76453","01413371","단디바이오","343090" +"76455","00610490","비디아이","148140" +"76456","00814810","엠에프엠코리아","251960" +"76457","00763701","세틀뱅크","234340" +"76458","01186811","티에스트릴리온","284610" +"76459","01031502","디오스텍","196450" +"76460","01080252","넥스틴","348210" +"76461","01105621","엔투텍","227950" +"76462","01329957","국전약품","307750" +"76463","01353024","TS트릴리온","317240" +"76464","00141149","영진약품","003520" +"76469","00138792","아시아나항공","020560" +"76489","01032583","고려시멘트","198440" +"76511","00131850","SK증권","001510" +"76513","00266961","NAVER","035420" +"76533","00910202","대동고려삼","178600" +"76553","00242934","케이엠","083550" +"76584","00762377","씨앗","103660" +"76610","00605522","소룩스","290690" +"76615","00126292","삼성카드","029780" +"76616","00158909","하나은행","004940" +"76627","00877174","엠브레인","169330" +"76628","01236286","컬러레이","900310" +"76635","00547583","하나금융지주","086790" +"76650","00364795","레드로버","060300" +"76651","00105855","엘에스일렉트릭","010120" +"76653","01478712","대덕전자","353200" +"76656","00154055","태원물산","001420" +"76657","00226228","한프","066110" +"76658","00127954","CJ프레시웨이","051500" +"76666","01066030","드림티엔터테인먼트","220110" +"76671","00118275","디아이","003160" +"76684","01144569","명성티엔에스","257370" +"76715","00203847","국일신동","060480" +"76716","00540863","GST","083450" +"76719","01047169","지란지교시큐리티","208350" +"76720","01250374","에스에스알","275630" +"76725","00407814","크리스탈지노믹스","083790" +"76726","01487446","엔에이치스팩17호","359090" +"76733","00403793","바이브컴퍼니","301300" +"76828","00975290","에이스토리","241840" +"76832","00537221","바이오코아","216400" +"76833","01060735","아이피몬스터","223220" +"76850","00122694","범양건영","002410" +"76865","01416572","한화플러스제1호스팩","340440" +"76892","01336285","SK4호스팩","307070" +"76919","01259056","베스파","299910" +"76920","00425351","멀티캠퍼스","067280" +"76928","00977650","엔케이맥스","182400" +"76930","01071041","천랩","311690" +"76931","00357935","HDC현대EP","089470" +"76935","00122348","방림","003610" +"76936","00422895","세이브존I&C","067830" +"76956","00191588","삼원강재","023000" +"76975","01213586","아이디피","332370" +"76983","00135111","수산중공업","017550" +"76994","01328170","노드메이슨","317860" +"77023","01187494","덴티스","261200" +"77048","00363486","로체시스템즈","071280" +"77049","01265251","압타머사이언스","291650" +"77053","01447217","에이치엠씨제4호스팩","353070" +"77068","01350638","티티씨디펜스","309900" +"77082","00876209","미래엔에듀파트너","208890" +"77083","00670085","엘아이에스","138690" +"77090","00152127","심텍홀딩스","036710" +"77093","01095722","심텍","222800" +"77099","01418260","폭스소프트","354230" +"77110","01430475","코람코에너지리츠","357120" +"77126","00602279","그린플러스","186230" +"77137","00136271","신성이엔지","011930" +"77141","01237434","플리토","300080" +"77142","00411048","에스앤에스텍","101490" +"77143","00135467","승일","049830" +"77144","00537832","푸른기술","094940" +"77145","00815767","선데이토즈","123420" +"77185","01335851","박셀바이오","323990" +"77204","00120872","만호제강","001080" +"77224","00277736","한일네트웍스","046110" +"77245","01139035","티에스아이","277880" +"77246","01091054","쎄노텍","222420" +"77247","00760999","상신전자","263810" +"77248","00147152","유니크","011320" +"77263","00186452","릭스솔루션","029480" +"77264","00428729","대호에이엘","069460" +"77265","00187415","코미팜","041960" +"77266","00165060","하이록코리아","013030" +"77267","00296290","키움증권","039490" +"77272","00137234","모베이스전자","012860" +"77273","00356927","에이디칩스","054630" +"77274","00144650","유신","054930" +"77275","00568188","마이크로컨텍솔","098120" +"77277","00389387","라임","065160" +"77279","00531917","톱텍","108230" +"77281","00442835","메타랩스","090370" +"77282","00296078","APS홀딩스","054620" +"77284","01061497","퓨쳐스트림네트웍스","214270" +"77285","00453284","교촌에프앤비","339770" +"77288","01187458","에스알바이오텍","270210" +"77289","00202839","한창산업","079170" +"77290","00494218","엘엠에스","073110" +"77306","01089855","해마로푸드서비스","220630" +"77309","00989327","엘피케이로보틱스","183350" +"77342","00102432","계룡건설산업","013580" +"77398","00364847","이글루시큐리티","067920" +"77401","00396925","엠로","058970" +"77405","00235183","에이치케이","044780" +"77406","00100601","강원","114190" +"77407","00140052","에이스침대","003800" +"77415","00916516","흥국에프엔비","189980" +"77421","00168401","금비","008870" +"77430","00153621","참엔지니어링","009310" +"77432","01047451","셀바스헬스케어","208370" +"77433","00261957","한국정보공학","039740" +"77434","00642541","디엔에이링크","127120" +"77435","00674498","골프존뉴딘홀딩스","121440" +"77436","00557508","GKL","114090" +"77437","00299464","초록뱀","047820" +"77477","00158307","롯데하이마트","071840" +"77478","00120289","제이에스티나","026040" +"77486","00855093","선진","136490" +"77487","00164362","행남사","008800" +"77488","00151863","센트럴인사이트","012600" +"77489","00167280","폴루스바이오팜","007630" +"77490","00689418","지와이커머스","111820" +"77491","00199076","동일기연","032960" +"77492","00364306","성우전자","081580" +"77494","00132318","성광벤드","014620" +"77496","00861076","케이탑리츠","145270" +"77497","00488402","서울바이오시스","092190" +"77498","00109161","태경케미컬","006890" +"77499","00426086","휴켐스","069260" +"77500","00765851","위더스제약","330350" +"77514","00904672","넷마블","251270" +"77516","00858364","BNK금융지주","138930" +"77517","00110893","대신증권","003540" +"77518","00124540","대우건설","047040" +"77526","00178790","동양이엔피","079960" +"77527","00410739","필로시스헬스케어","057880" +"77528","00405719","아이크래프트","052460" +"77529","00258689","JYP Ent.","035900" +"77530","00868705","윈스","136540" +"77531","00370200","와이오엠","066430" +"77533","00103547","우진비앤지","018620" +"77536","00264671","세중","039310" +"77537","01008762","데브시스터즈","194480" +"77559","01436628","이지스레지던스리츠","350520" +"77561","00961570","신도기연","290520" +"77585","00599106","코닉글로리","094860" +"77604","01028164","광주은행","192530" +"77605","00157539","KB오토시스","024120" +"77607","00120906","에스아이리소스","065420" +"77608","00143527","경동인베스트","012320" +"77610","01049422","썸에이지","208640" +"77616","00128661","에스에이엠티","031330" +"77617","01208849","나인테크","267320" +"77618","00266934","파루","043200" +"77619","00373021","테라사이언스","073640" +"77620","00208134","KNN","058400" +"77621","00108038","엔피씨","004250" +"77622","00101549","경동제약","011040" +"77624","00132992","성우하이텍","015750" +"77625","00447502","바이오톡스텍","086040" +"77626","00174004","유성기업","002920" +"77627","00369833","유니온머티리얼","047400" +"77629","00288495","홈센타홀딩스","060560" +"77630","00365387","AJ네트웍스","095570" +"77631","00201733","TJ미디어","032540" +"77632","01311286","퀀타매트릭스","317690" +"77634","00304401","텔라움","047730" +"77644","00554024","셀트리온헬스케어","091990" +"77645","00230814","OQP","078590" +"77647","00162461","한화솔루션","009830" +"77649","00503668","LIG넥스원","079550" +"77651","00923792","내츄럴엔도텍","168330" +"77652","00878517","에스케이씨에스","224020" +"77653","00475718","이엠앤아이","083470" +"77655","01031229","지티지웰니스","219750" +"77657","00109806","삼일씨엔에스","004440" +"77658","00608839","에이루트","096690" +"77660","00313649","현대바이오","048410" +"77661","00270113","멕아이씨에스","058110" +"77663","00245898","아래스","050320" +"77664","00537337","앤씨앤","092600" +"77665","00136165","스페코","013810" +"77666","00372882","KTcs","058850" +"77667","00808068","에이씨티","138360" +"77668","00246620","케이엘넷","039420" +"77669","00134723","페이퍼코리아","001020" +"77670","00111218","KD","044180" +"77672","00361725","에스케이커뮤니케이션즈","066270" +"77680","00129989","코스모신소재","005070" +"77710","00258102","지엔코","065060" +"77716","00563147","에스에이티이엔지","158300" +"77718","00141389","영풍정밀","036560" +"77719","00124090","한국특수형강","007280" +"77720","00200275","YTN","040300" +"77721","00614478","휴메딕스","200670" +"77722","00525642","코오롱생명과학","102940" +"77724","00300557","위니아딤채","071460" +"77726","00480950","이아이디","093230" +"77727","00149804","대유에이텍","002880" +"77728","00656951","이지웰","090850" +"77730","00194947","한전산업","130660" +"77731","00180865","엔에스엔","031860" +"77732","00186939","특수건설","026150" +"77734","00476498","컴투스","078340" +"77742","00117382","디와이홀딩스","004510" +"77744","00150828","진흥기업","002780" +"77755","00491938","GH신소재","130500" +"77757","01337114","구스앤홈","329050" +"77761","00199252","에이치엘비","028300" +"77762","00260958","KTH","036030" +"77763","00113359","교보증권","030610" +"77765","00842585","네이블커뮤니케이션즈","153460" +"77768","01114559","씨엔티드림","286000" +"77778","00148896","OCI","010060" +"77787","00216762","한양이엔지","045100" +"77788","00197759","미래산업","025560" +"77791","00187725","코콤","015710" +"77793","00232089","바이온","032980" +"77795","00129554","갤럭시아에스엠","011420" +"77798","00155151","평화정공","043370" +"77801","00105475","금강철강","053260" +"77802","00352499","링네트","042500" +"77803","00820389","뉴지랩","214870" +"77804","00390903","우주일렉트로","065680" +"77830","00134316","세원정공","021820" +"77837","00117212","두산","000150" +"77842","00347877","이에스에이","052190" +"77844","00124726","빙그레","005180" +"77845","00141477","영흥","012160" +"77847","00116426","코센","009730" +"77848","00988364","SGA솔루션즈","184230" +"77849","00440712","WI","073570" +"77850","00617998","세원","234100" +"77854","00573579","평화산업","090080" +"77855","00605124","알파홀딩스","117670" +"77868","00171636","한솔홀딩스","004150" +"77873","00367871","인바이오","352940" +"77874","00412597","현대홈쇼핑","057050" +"77877","00513948","녹십자엠에스","142280" +"77878","00447487","제주반도체","080220" +"77879","00587457","갤럭시아머니트리","094480" +"77880","00101044","에이프로젠제약","003060" +"77882","00141608","오리엔탈정공","014940" +"77883","00146542","일정실업","008500" +"77884","00653194","에이프로젠 H&G","109960" +"77886","01084364","엔지스테크널러지","208860" +"77887","00122825","인스코비","006490" +"77891","00104102","우리종금","010050" +"77892","00136402","신송홀딩스","006880" +"77893","00563518","비나텍","126340" +"77898","00390514","이루온","065440" +"77899","01085187","씨알푸드","236030" +"77907","00552859","지트리비앤티","115450" +"77912","00533191","비보존 헬스케어","082800" +"77913","00180263","리더스코스메틱","016100" +"77919","00335076","오픈베이스","049480" +"77924","00351579","SGA","049470" +"77927","00186559","콤텍시스템","031820" +"77930","00248053","지에스엔텍","037640" +"77943","00596677","매커스","093520" +"77944","00678096","맥스로텍","141070" +"77946","00327396","옵트론텍","082210" +"77983","00145598","이연제약","102460" +"77984","00133812","세방","004360" +"77986","00155124","평화홀딩스","010770" +"77988","00156442","한국가구","004590" +"77989","00163673","한진중공업홀딩스","003480" +"77990","00197476","코엔텍","029960" +"77995","00526447","동북아12호선박투자회사","083370" +"77996","01063237","플럼라인생명과학","222670" +"78005","00149354","종근당홀딩스","001630" +"78006","00111874","대원산업","005710" +"78007","00453929","메디포스트","078160" +"78008","00176516","금화피에스시","036190" +"78009","01243161","인산가","277410" +"78010","00925967","이엠티","232530" +"78011","00166315","화신","010690" +"78014","00925587","위드텍","348350" +"78015","00112332","미래에셋생명","085620" +"78018","00249502","더블유에프엠","035290" +"78019","01437186","ESR켄달스퀘어리츠","365550" +"78020","00349811","W홀딩컴퍼니","052300" +"78021","00254045","우리은행","000030" +"78023","00167208","흥아해운","003280" +"78040","01029394","에코마케팅","230360" +"78042","00567222","우림기계","101170" +"78043","00366517","KC산업","112190" +"78047","00134963","송원산업","004430" +"78055","00855163","미원화학","134380" +"78083","00406392","엔에스쇼핑","138250" +"78084","00127167","삼영무역","002810" +"78085","00363769","한국전자금융","063570" +"78086","00389110","지어소프트","051160" +"78087","00137915","에스엠코어","007820" +"78088","01158632","진코스텍","250030" +"78093","01016886","테크엔","308700" +"78095","00217743","화성밸브","039610" +"78101","00816696","큐엠씨","136660" +"78122","00455112","GV","045890" +"78123","00641393","엔시트론","101400" +"78124","01065679","이노인스트루먼트","215790" +"78125","00307189","에스케이씨솔믹스","057500" +"78126","01508855","대신밸런스제9호스팩","369370" +"78127","00921916","석경에이티","357550" +"78128","01276026","지놈앤컴퍼니","314130" +"78146","00120562","롯데지주","004990" +"78147","00164788","현대모비스","012330" +"78150","00425254","나우코스","257990" +"78166","00106623","현대위아","011210" +"78168","00140177","GS리테일","007070" +"78174","00303873","CJ CGV","079160" +"78176","01030132","경남은행","192520" +"78177","00149655","삼성물산","028260" +"78180","00311270","원텍","216280" +"78183","00104856","삼성증권","016360" +"78187","01214248","뉴트리","270870" +"78190","00121288","모나미","005360" +"78201","00207755","GS홈쇼핑","028150" +"78207","00207375","대한뉴팜","054670" +"78208","00260745","SCI평가정보","036120" +"78211","00938721","필옵틱스","161580" +"78213","00367695","대한그린파워","060900" +"78214","00306162","상상인","038540" +"78232","00435297","맥쿼리인프라","088980" +"78238","00107224","남선알미늄","008350" +"78254","00244419","디에스티","033430" +"78258","00480756","이트론","096040" +"78262","00219486","신세계I&C","035510" +"78263","01412822","솔루스첨단소재","336370" +"78264","00230036","드래곤플라이","030350" +"78265","00235147","에이팸","073070" +"78266","01262023","루트락","253610" +"78270","00164672","한일현대시멘트","006390" +"78272","00166175","대호피앤씨","021040" +"78273","00263371","한국경제TV","039340" +"78276","00317104","라이온켐텍","171120" +"78285","00809517","아이엠텍","226350" +"78287","00159102","DB손해보험","005830" +"78290","00159023","SK텔레콤","017670" +"78293","00265005","옴니시스템","057540" +"78302","00360595","현대글로비스","086280" +"78323","00119195","동화약품","000020" +"78324","00127802","삼익THK","004380" +"78329","00260383","대한유화","006650" +"78330","00120030","GS건설","006360" +"78332","00166519","화천기공","000850" +"78333","00308896","아이씨케이","068940" +"78334","00138279","S-Oil","010950" +"78335","00255141","글로앤웰","035480" +"78336","00258421","기산텔레콤","035460" +"78337","00172228","엔케이","085310" +"78338","00113234","대한제당","001790" +"78339","00250997","대정화금","120240" +"78340","00861997","인포마크","175140" +"78342","00483735","해성옵틱스","076610" +"78343","00104810","대명소노시즌","007720" +"78344","00533003","디케이락","105740" +"78374","01062177","제놀루션","225220" +"78378","00583026","율호","072770" +"78379","00632793","전진바이오팜","110020" +"78380","01335453","오하임아이엔티","309930" +"78389","00164876","케이비증권","003450" +"78405","00161444","한국씨티은행","016830" +"78410","00441243","형지엘리트","093240" +"78413","00138303","쌍용정보통신","010280" +"78414","00598587","인터파크","108790" +"78415","00604268","에이프로","262260" +"78418","00184889","이건홀딩스","039020" +"78419","00145446","이건산업","008250" +"78423","00945208","THE MIDONG","161570" +"78424","00116268","동성제약","002210" +"78425","01026616","유투바이오","221800" +"78431","01043853","베노홀딩스","206400" +"78432","00585219","세원셀론텍","091090" +"78433","00458562","에이티세미콘","089530" +"78434","00134477","S&T중공업","003570" +"78467","00276083","스카이문스테크놀로지","033790" +"78485","01061558","덕산네오룩스","213420" +"78493","00362201","세운메디칼","100700" +"78495","00129350","삼화콘덴서공업","001820" +"78497","00112819","데코앤이","017680" +"78498","00355089","토탈소프트","045340" +"78500","00104786","미래아이앤지","007120" +"78501","00157681","롯데정밀화학","004000" +"78502","00572905","ISC","095340" +"78503","00103635","광림","014200" +"78504","00172185","남성","004270" +"78505","00353610","이니텍","053350" +"78517","00341916","오스템임플란트","048260" +"78518","01442115","소마젠","950200" +"78526","00163716","한창","005110" +"78541","00126186","삼성에스디에스","018260" +"78553","00164742","현대자동차","005380" +"78555","00115694","DB금융투자","016610" +"78556","00131780","SK네트웍스","001740" +"78560","00526599","덴티움","145720" +"78575","00173591","세원물산","024830" +"78576","00983040","한진칼","180640" +"78580","00161860","한성기업","003680" +"78581","00356802","큐렉소","060280" +"78582","00159342","한국정보통신","025770" +"78583","00423690","삼성출판사","068290" +"78584","00608699","디엠티","134580" +"78585","00114154","덕양산업","024900" +"78588","00273615","비케이탑스","030790" +"78596","00619640","이엔드디","101360" +"78606","00257732","한국정밀기계","101680" +"78607","00140779","영보화학","014440" +"78615","00384762","파커스","065690" +"78623","00146269","일신방직","003200" +"78633","00349097","케이티스카이라이프","053210" +"78641","00937324","한국타이어앤테크놀로지","161390" +"78653","00439965","나노신소재","121600" +"78663","00398792","S&T모티브","064960" +"78668","00128315","삼지전자","037460" +"78669","00172945","동일철강","023790" +"78681","00125725","포비스티앤씨","016670" +"78682","00136448","신신제약","002800" +"78683","01243772","신한제4호기업인수목적","277480" +"78691","00122481","태경비케이","014580" +"78693","00351092","삼보모터스","053700" +"78694","00140964","영원무역홀딩스","009970" +"78695","00535375","키네마스터","139670" +"78696","00148948","조비","001550" +"78698","00169048","넥스트사이언스","003580" +"78699","00126089","대유플러스","000300" +"78700","00172079","파나진","046210" +"78701","00171867","에스씨디","042110" +"78703","00301422","우수AMS","066590" +"78704","00624749","에스피시스템스","317830" +"78705","00133876","세보엠이씨","011560" +"78706","00174527","화성산업","002460" +"78709","00367455","현우산업","092300" +"78718","00793155","핸디소프트","220180" +"78733","01384477","엠에프엠코리아","323230" +"78734","00797364","KC코트렐","119650" +"78736","00159971","KC그린홀딩스","009440" +"78741","01188730","시그넷이브이","260870" +"78742","00158015","한국선재","025550" +"78743","01101722","한송네오텍","226440" +"78745","00660121","디에이테크놀로지","196490" +"78746","00480367","삼강엠앤티","100090" +"78747","00105299","금강공업","014280" +"78748","00533508","제이엠티","094970" +"78749","00529815","오디텍","080520" +"78750","00347734","진양화학","051630" +"78751","00364111","다이노나","086080" +"78752","00350482","성우테크론","045300" +"78754","00878696","에스케이바이오팜","326030" +"78760","00143226","엠투엔","033310" +"78762","01187865","앙츠","267810" +"78765","00480783","상신이디피","091580" +"78769","00263654","오스코텍","039200" +"78770","00542074","나노캠텍","091970" +"78771","00103510","인지컨트롤스","023800" +"78773","00620558","에이비프로바이오","195990" +"78774","00295547","디지아이","043360" +"78775","00111421","휴니드테크놀러지스","005870" +"78776","00204262","한글과컴퓨터","030520" +"78777","00121543","무학","033920" +"78782","00351454","큐브앤컴퍼니","043090" +"78786","00155319","포스코","005490" +"78788","00629212","딜리","131180" +"78790","01406618","비올","335890" +"78791","00962393","포인트모바일","318020" +"78793","00444329","위메이드","112040" +"78800","00866062","엘티씨","170920" +"78801","00138367","플레이위드","023770" +"78802","00170026","시공테크","020710" +"78804","00156488","휴스틸","005010" +"78806","00765462","씨에스베어링","297090" +"78834","00399694","아프리카TV","067160" +"78843","00149345","조흥","002600" +"78847","00317210","성호전자","043260" +"78848","00102858","고려아연","010130" +"78853","00139889","SKC","011790" +"78860","00164609","현대미포조선","010620" +"78870","01359815","윈텍","320000" +"78871","01067242","티씨엠생명과학","228180" +"78872","00155498","풍림산업","001310" +"78888","01165739","잉글우드랩","950140" +"78889","01437292","미래에셋맵스리츠","357250" +"78919","00273439","메디아나","041920" +"78929","00874195","코썬바이오","204990" +"78936","00469799","이엔플러스","074610" +"78943","00299002","한라홀딩스","060980" +"78944","00120924","매일홀딩스","005990" +"78950","00111689","대우부품","009320" +"78952","00175623","라이브플렉스","050120" +"78956","00106669","세아베스틸","001430" +"78965","00164812","현대종합상사","011760" +"78977","00273110","에스티큐브","052020" +"78978","01137295","프리시젼바이오","335810" +"78979","00173078","명신산업","009900" +"78980","00157991","한국석유공업","004090" +"78997","00159218","한전KPS","051600" +"78998","00108940","대성홀딩스","016710" +"79025","01166109","예선테크","250930" +"79026","00371740","디스플레이텍","066670" +"79027","00660750","하나머티리얼즈","166090" +"79028","01228515","지엔원에너지","270520" +"79061","00146560","일지테크","019540" +"79070","00146436","엔브이에이치코리아","067570" +"79071","00393469","녹원씨엔아이","065560" +"79073","00611912","아모그린텍","125210" +"79074","00346407","큐로컴","040350" +"79075","00297095","나라엠앤디","051490" +"79076","00122579","BYC","001460" +"79077","00407285","아이컴포넌트","059100" +"79078","00825223","화신정공","126640" +"79080","00206039","경남스틸","039240" +"79081","00475985","시너지이노베이션","048870" +"79082","00599595","코프라","126600" +"79084","00110608","DSR","155660" +"79085","01234507","매일유업","267980" +"79086","00173999","유성티엔에스","024800" +"79100","00971090","샘코","263540" +"79104","01113754","오션브릿지","241790" +"79109","00453488","세화아이엠씨","145210" +"79110","00114808","동국제약","086450" +"79115","00210980","원익","032940" +"79138","01264234","엘에이티","311060" +"79148","00102353","경창산업","024910" +"79170","01208885","경동도시가스","267290" +"79175","01107665","크리스탈신소재","900250" +"79177","00104120","광진실업","026910" +"79182","00158024","KBI메탈","024840" +"79185","00133335","성창기업지주","000180" +"79187","00761952","아나패스","123860" +"79221","01351822","한화에스비아이스팩","317320" +"79234","00231831","조아제약","034940" +"79235","00163691","유수홀딩스","000700" +"79237","00156691","한국공항","005430" +"79239","00468374","원익QnC","074600" +"79240","00361008","HSD엔진","082740" +"79241","01097906","이엑스티","226360" +"79242","00231707","비트컴퓨터","032850" +"79243","01183407","이십일스토어","270020" +"79246","00363592","한컴위드","054920" +"79247","00480048","모다이노칩","080420" +"79261","00663289","위월드","140660" +"79264","00111722","미래에셋대우","006800" +"79271","00351418","코리아오토글라스","152330" +"79276","00253985","국보디자인","066620" +"79295","00571483","오이솔루션","138080" +"79308","00133238","성지건설","005980" +"79314","00415646","드림텍","192650" +"79315","00228059","휴온스글로벌","084110" +"79329","00445799","서원인텍","093920" +"79331","00176835","농우바이오","054050" +"79334","00641171","모바일리더","100030" +"79337","00821607","알톤스포츠","123750" +"79338","00109587","대륙제관","004780" +"79339","00494476","이녹스","088390" +"79341","00173740","신한","005450" +"79342","01112700","에스엘에스바이오","246250" +"79343","00126414","삼성제약","001360" +"79353","00939942","포시에스","189690" +"79393","00215976","UCI","038340" +"79399","01022902","애드바이오텍","179530" +"79400","01396931","유엑스엔","337840" +"79414","00540447","유니테스트","086390" +"79425","01080687","아우딘퓨쳐스","227610" +"79429","00131054","유진증권","001200" +"79430","00620868","아이티센","124500" +"79431","00623661","원익머트리얼즈","104830" +"79433","00555874","제주항공","089590" +"79438","00832700","에이펙스인텍","207490" +"79439","00115676","KG동부제철","016380" +"79450","01219155","네오크레마","311390" +"79458","00103626","한솔피엔에스","010420" +"79460","00234227","유진로봇","056080" +"79462","00578130","아퓨어스","149300" +"79467","00152729","광전자","017900" +"79473","00113535","대한해운","005880" +"79491","00449379","도이치모터스","067990" +"79518","00141556","오로라","039830" +"79521","00310156","셀루메드","049180" +"79522","00243979","수성","084180" +"79523","01353848","이노벡스","279060" +"79524","00102760","두산건설","011160" +"79525","01338724","SNK","950180" +"79526","01262032","롯데정보통신","286940" +"79565","00360036","백금T&A","046310" +"79576","01036367","프로스테믹스","203690" +"79609","00462121","이노와이어리스","073490" +"79613","00264787","럭슬","033600" +"79624","00615723","아이에이네트웍스","123010" +"79625","00164973","현대해상","001450" +"79626","01136001","나노브릭","286750" +"79628","00136341","신성통상","005390" +"79649","01276594","신한알파리츠","293940" +"79651","00867478","농업회사법인아시아종묘","154030" +"79653","00200910","아이즈비전","031310" +"79655","00806972","매직마이크로","127160" +"79656","00492894","젬백스","082270" +"79660","00255275","HB테크놀러지","078150" +"79666","00843830","카이노스메드","220250" +"79667","00991298","씨이랩","189330" +"79688","00482426","인화정공","101930" +"79691","00673976","지스마트글로벌","114570" +"79694","00210856","코아스","071950" +"79695","00261656","잉크테크","049550" +"79696","00231664","우리기술투자","041190" +"79697","00107613","이수페타시스","007660" +"79698","00578538","티피씨글로벌","130740" +"79699","01190513","명진홀딩스","267060" +"79700","00128926","삼현철강","017480" +"79702","00261355","레드캡투어","038390" +"79705","00516246","알에프세미","096610" +"79746","00390408","에프앤리퍼블릭","064090" +"79747","00108977","대구백화점","006370" +"79748","00145437","키위미디어그룹","012170" +"79764","00522007","크리스에프앤씨","110790" +"79796","00249034","드림라인","035430" +"79802","00607797","엄지하우스","224810" +"79803","00304076","원방테크","053080" +"79804","00346911","아이톡시","052770" +"79805","00366942","미코","059090" +"79806","00397252","조이시티","067000" +"79807","00656289","코스맥스엔비티","222040" +"79808","00657987","KMH","122450" +"79809","01091382","마이더스AI","222810" +"79810","01110678","유틸렉스","263050" +"79812","00154462","아모레퍼시픽그룹","002790" +"79813","00202060","구영테크","053270" +"79814","00216647","원익홀딩스","030530" +"79815","00238782","다우데이타","032190" +"79816","00303794","쇼박스","086980" +"79826","00803425","코리아에프티","123410" +"79827","00624509","디바이스이엔지","187870" +"79834","00231691","판타지오","032800" +"79835","00260611","크로바하이텍","043590" +"79837","00475976","인콘","083640" +"79840","00118804","동진쎄미켐","005290" +"79844","00160621","한국화장품제조","003350" +"79845","00408956","제넨바이오","072520" +"79847","00260356","산은캐피탈","008270" +"79848","00372226","티에스이","131290" +"79852","00117072","동양건설산업","005900" +"79860","00441304","가온미디어","078890" +"79863","00138598","아비코전자","036010" +"79875","00544452","이리츠코크렙","088260" +"79882","00385336","홈캐스트","064240" +"79884","00409788","비에이치아이","083650" +"79885","00264732","파인디지털","038950" +"79886","00244783","한네트","052600" +"79887","00543204","아이오케이","078860" +"79888","00477257","코렌","078650" +"79889","00152260","카스","016920" +"79890","00328191","케이에스피","073010" +"79892","00379016","이엘케이","094190" +"79893","00246417","이오테크닉스","039030" +"79895","00116301","NI스틸","008260" +"79896","01397903","엔젠바이오","354200" +"79898","00230425","나노엔텍","039860" +"79899","00541437","코렌텍","104540" +"79941","00141671","오리콤","010470" +"79943","00123781","부국철강","026940" +"79946","00220109","솔고바이오","043100" +"79947","00173032","동화기업","025900" +"79951","01021666","덕산테코피아","317330" +"79952","00107598","남양유업","003920" +"79953","00110051","SK머티리얼즈","036490" +"79972","00303192","디에이피","066900" +"79996","00455981","에스엔유","080000" +"79997","01112889","피엔에이치테크","239890" +"80000","00232007","상지카일룸","042940" +"80002","01114586","셀레믹스","331920" +"80003","00365590","에이치엘비생명과학","067630" +"80004","01405451","알체라","347860" +"80007","00593032","LF","093050" +"80026","00148470","제일전기공업","199820" +"80029","00132211","선창산업","002820" +"80030","00223513","제이씨현시스템","033320" +"80034","00146427","일야","058450" +"80035","00450339","에스에프씨","112240" +"80037","00244561","인터파크","035080" +"80045","00520610","씨큐브","101240" +"80078","00219574","코스맥스비티아이","044820" +"80079","00269922","인바디","041830" +"80080","00201742","유라테크","048430" +"80081","00110884","엠젠플러스","032790" +"80116","00161426","한미사이언스","008930" +"80117","00138747","아세아텍","050860" +"80119","00123107","보락","002760" +"80120","00142865","WISCOM","024070" +"80122","00526678","동방선기","099410" +"80125","00162993","한일홀딩스","003300" +"80127","00831428","디에이치피코리아","131030" +"80128","00122205","바른손","018700" +"80129","00400857","에이블씨엔씨","078520" +"80175","00162081","한국투자파트너스","019560" +"80176","00156859","KTB투자증권","030210" +"80179","00257149","지더블유바이텍","036180" +"80180","01110076","에이트원","230980" +"80197","00162832","한일단조","024740" +"80204","00159810","카프로","006380" +"80206","00145163","파미셀","005690" +"80207","00160302","코스모화학","005420" +"80210","00638539","뉴로스","126870" +"80211","00360142","아이마켓코리아","122900" +"80212","00526836","미래나노텍","095500" +"80213","00126371","삼성전기","009150" +"80214","00525934","실리콘웍스","108320" +"80215","00787376","대성에너지","117580" +"80216","00300405","오리엔트정공","065500" +"80217","00198192","한국팩키지","037230" +"80218","00334615","콜마파마","038710" +"80220","00256502","에이텍","045660" +"80221","00351375","뉴보텍","060260" +"80222","00145473","이글벳","044960" +"80223","00618401","크루셜텍","114120" +"80224","00576789","디젠스","113810" +"80257","00339072","팬스타엔터프라이즈","054300" +"80258","00106881","인디에프","014990" +"80259","01053540","퓨쳐켐","220100" +"80260","00142883","우신시스템","017370" +"80261","00252135","주성엔지니어링","036930" +"80263","01109690","도부마스크","227420" +"80264","00536888","다믈멀티미디어","093640" +"80265","00159500","한국종합기술","023350" +"80266","00560849","에스앤디","260970" +"80267","00347062","현대바이오랜드","052260" +"80269","00366137","KG ETS","151860" +"80270","00267906","베뉴지","019010" +"80271","00633835","한진중공업","097230" +"80272","01018617","메디쎄이","200580" +"80282","00647935","하나유비에스암바토비니켈해외자원개발2호","099350" +"80283","00647883","하나유비에스암바토비니켈해외자원개발1호","099340" +"80284","01110474","에스씨엠생명과학","298060" +"80285","01275665","리메드","302550" +"80293","01014657","비즈니스온","138580" +"80300","01137897","SGA클라우드서비스","224880" +"80307","01182408","소프트캠프","258790" +"80315","01395279","하나금융14호스팩","332710" +"80316","01034730","크로넥스","215570" +"80317","00292434","인트론바이오","048530" +"80318","00435969","인피니트헬스케어","071200" +"80319","00442905","대성파인텍","104040" +"80325","00130383","서부T&D","006730" +"80328","00402110","시큐브","131090" +"80329","00105271","케이씨씨","002380" +"80331","00428251","현대백화점","069960" +"80333","01064069","토박스코리아","215480" +"80341","00130879","서울식품공업","004410" +"80351","00109037","대성창투","027830" +"80352","00141413","영화금속","012280" +"80354","00402989","코스온","069110" +"80355","00501970","코드네이처","078940" +"80356","01009789","코스맥스","192820" +"80360","00470829","한솔인티큐브","070590" +"80361","00923899","스타모빌리티","158310" +"80362","00352064","프리엠스","053160" +"80363","00128546","삼천당제약","000250" +"80364","00151395","천일고속","000650" +"80366","00104698","LS네트웍스","000680" +"80368","01428203","케이씨씨글라스","344820" +"80378","00126362","삼성SDI","006400" +"80383","00260569","유니셈","036200" +"80384","00401731","LG전자","066570" +"80385","00113410","CJ대한통운","000120" +"80386","00442455","코스나인","082660" +"80387","00135917","한화손해보험","000370" +"80388","00585538","에스에너지","095910" +"80389","00120216","KB손해보험","002550" +"80391","00120182","NH투자증권","005940" +"80404","00204226","소프트센","032680" +"80405","00138224","쌍용양회공업","003410" +"80406","01185566","블러썸엠앤씨","263920" +"80408","00124805","푸른저축은행","007330" +"80416","00551920","네오팜","092730" +"80417","00159412","한국제지","002300" +"80418","00632845","노랑풍선","104620" +"80426","00896753","스킨앤스킨","159910" +"80427","00161462","에이티넘인베스트","021080" +"80428","00414850","효성 ITX","094280" +"80429","00626011","아이텍","119830" +"80432","00134565","이스타코","015020" +"80433","00659684","장원테크","174880" +"80434","00549891","케어젠","214370" +"80437","00153375","태광","023160" +"80438","00260930","에스엠","041510" +"80439","00185222","크린앤사이언스","045520" +"80442","00369569","휘닉스소재","050090" +"80446","00125822","삼보산업","009620" +"80448","00542898","하이소닉","106080" +"80454","00112378","KR모터스","000040" +"80485","00166333","이노와이즈","086250" +"80497","00170558","코웨이","021240" +"80498","01343665","메탈라이프","327260" +"80500","00240857","바이오스마트","038460" +"80502","01364297","코퍼스코리아","322780" +"80503","00565215","굿센","243870" +"80507","00103015","고려제약","014570" +"80510","01109937","티앤알바이오팹","246710" +"80512","01042650","큐리언트","115180" +"80513","00994994","나노","187790" +"80515","00486875","우양","103840" +"80531","00139685","양지사","030960" +"80543","00531865","유니테크노","241690" +"80551","00409964","하이텍팜","106190" +"80552","00105606","금호에이치티","214330" +"80556","00220969","오공","045060" +"80558","00146861","자화전자","033240" +"80561","00225742","KT서브마린","060370" +"80564","00111865","미래SCI","028040" +"80568","01128613","화승엔터프라이즈","241590" +"80569","00166227","화승인더스트리","006060" +"80570","01068348","러셀","217500" +"80571","00112059","상상인증권","001290" +"80575","00158316","NICE","034310" +"80576","01453670","엔에이치스팩16호","353190" +"80580","00155258","포스코강판","058430" +"80581","00978075","옐로페이","179720" +"80582","01047707","바이오로그디바이스","208710" +"80583","00377018","기가레인","049080" +"80584","01036446","서연이화","200880" +"80586","00608802","텔콘RF제약","200230" +"80587","00362292","씨티씨바이오","060590" +"80588","00406046","STX중공업","071970" +"80590","00222435","제이엠아이","033050" +"80593","00441650","케이씨에스","115500" +"80594","00271501","코아시아","045970" +"80596","00569691","디엔에프","092070" +"80597","00432102","한국금융지주","071050" +"80608","00635134","CJ제일제당","097950" +"80611","00759513","LG하우시스","108670" +"80612","00583424","아모레퍼시픽","090430" +"80617","00299358","해덕파워웨이","102210" +"80618","00458234","아시아나IDT","267850" +"80619","00364467","드림어스컴퍼니","060570" +"80620","00495554","아이엠이연이","090740" +"80621","00525679","차바이오텍","085660" +"80622","00596260","투비소프트","079970" +"80623","01277928","한국제7호기업인수목적","291210" +"80637","00100939","강남제비스코","000860" +"80638","00226547","오상자이엘","053980" +"80643","00142713","형지I&C","011080" +"80666","00693651","비덴트","121800" +"80693","00113997","일진머티리얼즈","020150" +"80698","00309503","한국항공우주","047810" +"80699","00413046","셀트리온","068270" +"80700","00150244","하이트진로","000080" +"80701","00148993","하이트진로홀딩스","000140" +"80703","00139214","삼성화재해상보험","000810" +"80706","00148595","제일테크노스","038010" +"80707","01205851","현대일렉트릭","267260" +"80709","00842619","레고켐바이오","141080" +"80711","01038693","드림시큐리티","203650" +"80718","00650629","SBS미디어홀딩스","101060" +"80725","00665676","아시아경제","127710" +"80729","00101257","경남기업","000800" +"80731","00527464","에이치시티","072990" +"80742","00233653","한국토지신탁","034830" +"80751","00155948","코웰패션","033290" +"80752","00325112","프로텍","053610" +"80753","00378220","버킷스튜디오","066410" +"80756","00143651","웅진","016880" +"80758","00406037","CSA 코스믹","083660" +"80759","00478900","빅솔론","093190" +"80762","00168331","국동","005320" +"80764","00150536","진양제약","007370" +"80765","00125521","에스엘","005850" +"80767","00146296","일신석재","007110" +"80771","00454399","이상네트웍스","080010" +"80784","01109715","DSC인베스트먼트","241520" +"80785","00661157","인트로메딕","150840" +"80787","01350869","우리금융지주","316140" +"80789","00228350","신화인터텍","056700" +"80790","00819073","인크로스","216050" +"80792","00563545","테스나","131970" +"80794","00140131","키다리스튜디오","020120" +"80795","00144252","유니온","000910" +"80797","00536541","에코프로","086520" +"80798","00148522","퍼스텍","010820" +"80802","00111704","대우조선해양","042660" +"80805","00164478","현대건설","000720" +"80808","00120076","LG상사","001120" +"80809","00683007","엑스큐어","070300" +"80811","00126566","한화에어로스페이스","012450" +"80817","00153861","태영건설","009410" +"80847","00455875","와토스코리아","079000" +"80854","00145914","인터엠","017250" +"80875","00145880","현대제철","004020" +"80878","00109019","대구은행","005270" +"80881","00115977","아이에스동서","010780" +"80884","00128555","삼천리","004690" +"80893","00124504","포스코인터내셔널","047050" +"80913","00667425","에이팩트","200470" +"80918","00799177","삼기","122350" +"80919","01049167","미코바이오메드","214610" +"80928","00266943","넥슨지티","041140" +"80929","01130885","바이오인프라생명과학","266470" +"80930","00545734","아미코젠","092040" +"80933","00223434","에프에스티","036810" +"80934","01051472","젠큐릭스","229000" +"80935","00193009","필룩스","033180" +"80937","00305297","코텍","052330" +"80938","00336297","고려신용정보","049720" +"80941","01158553","켄코아에어로스페이스","274090" +"80942","00130408","서산","079650" +"80943","00127936","삼일기업공사","002290" +"80944","00340272","에스폴리텍","050760" +"80946","00563837","제로투세븐","159580" +"80947","00261054","EG","037370" +"80948","00409371","인베니아","079950" +"80968","00104519","국보","001140" +"80970","00126937","삼양홀딩스","000070" +"80971","00896285","삼양사","145990" +"80972","00688996","KB금융","105560" +"80973","01160363","에코프로비엠","247540" +"80974","00264547","KG이니시스","035600" +"80975","00405278","KG모빌리언스","046440" +"80976","00201131","아즈텍WB","032080" +"80977","00153393","태광산업","003240" +"80978","00104573","국일제지","078130" +"80980","00133618","세기상사","002420" +"80981","00298270","안랩","053800" +"80982","00121534","MH에탄올","023150" +"80983","00161781","아난티","025980" +"80985","00138297","STX","011810" +"80986","00220561","광주신세계","037710" +"80987","00524786","비엠티","086670" +"80989","00194275","코너스톤네트웍스","033110" +"80990","00219361","해성산업","034810" +"80991","00232821","클라우드에어","036170" +"80999","00980122","JB금융지주","175330" +"81002","00103176","흥국화재","000540" +"81029","01073678","얼라인드","238120" +"81030","01335578","애니플러스","310200" +"81055","00500254","GS","078930" +"81056","00767628","나이벡","138610" +"81073","00980043","비플라이소프트","148780" +"81087","00267979","한일화학","007770" +"81088","00447609","비에이치","090460" +"81106","00523936","엘이티","297890" +"81108","00811372","노바렉스","194700" +"81116","00115612","동부건설","005960" +"81126","00479705","EMW","079190" +"81128","00138446","아가방컴퍼니","013990" +"81131","00258360","위지트","036090" +"81135","00800145","서진오토모티브","122690" +"81138","00268118","코맥스","036690" +"81140","00130587","서울전자통신","027040" +"81146","00125080","AK홀딩스","006840" +"81147","00139454","애경산업","018250" +"81148","00936787","애경유화","161000" +"81149","00547510","툴젠","199800" +"81153","00957568","세미콘라이트","214310" +"81154","00404288","아진엑스텍","059120" +"81157","00348034","액토즈소프트","052790" +"81158","00648721","S&TC","100840" +"81160","00264635","에스넷","038680" +"81161","00243067","부방","014470" +"81164","00361169","한국전자인증","041460" +"81165","00126487","F&F","007700" +"81168","00287788","정상제이엘에스","040420" +"81169","01414422","아이비김영","339950" +"81177","00149646","기업은행","024110" +"81180","00209443","아주캐피탈","033660" +"81181","00356361","LG화학","051910" +"81182","01258507","롯데제과","280360" +"81184","00795135","코오롱인더","120110" +"81185","00114792","동국제강","001230" +"81186","00105952","LS","006260" +"81189","00160588","한화","000880" +"81194","00191472","KPX케미칼","025000" +"81211","00466428","아미노로직스","074430" +"81213","00155276","포스코케미칼","003670" +"81237","00990396","뿌리깊은나무들","266170" +"81249","00164645","HMM","011200" +"81254","01440481","IBKS제13호스팩","351340" +"81280","00664853","제이씨케미칼","137950" +"81281","00226316","호전실업","111110" +"81298","00129420","까뮤이앤씨","013700" +"81306","00681142","쌍방울","102280" +"81307","00267881","보성파워텍","006910" +"81309","00125938","삼보판지","023600" +"81313","00128412","삼진제약","005500" +"81316","00152589","코리아나","027050" +"81322","00308559","코디","080530" +"81335","00918222","엘앤케이바이오","156100" +"81348","00261009","버추얼텍","036620" +"81349","00347716","센트럴바이오","051980" +"81350","00109189","대덕","008060" +"81351","00113562","롯데손해보험","000400" +"81353","00143907","원일특강","012620" +"81356","00683469","일진전기","103590" +"81358","00344287","두산인프라코어","042670" +"81359","00120571","롯데칠성음료","005300" +"81360","00117601","유안타증권","003470" +"81362","00126308","삼성엔지니어링","028050" +"81364","00164636","HDC","012630" +"81365","00122737","팬오션","028670" +"81366","00161125","한온시스템","018880" +"81374","00152880","코오롱글로벌","003070" +"81375","00159193","한국전력공사","015760" +"81377","00587466","KPX홀딩스","092230" +"81379","00166573","환인제약","016580" +"81381","00556712","힘스","238490" +"81382","00584362","넥스지","081970" +"81394","00153524","태림포장","011280" +"81396","00825959","하이비젼시스템","126700" +"81398","00309831","알에프텍","061040" +"81399","01023822","메디젠휴먼케어","236340" +"81400","00231363","LG유플러스","032640" +"81428","00137058","신일제약","012790" +"81442","01326224","교보8호스팩","307280" +"81444","00876166","한국바이오젠","318000" +"81449","00361266","네스엠","056000" +"81457","00190321","케이티","030200" +"81461","00330424","이베스트투자증권","078020" +"81462","00124106","부산은행","005280" +"81464","01205709","현대중공업지주","267250" +"81472","01179608","포인트엔지니어링","256630" +"81473","00185046","SM C&C","048550" +"81495","00136086","무림페이퍼","009200" +"81513","00189538","피델릭스","032580" +"81514","00151128","모토닉","009680" +"81515","00820352","파인텍","131760" +"81516","00118345","디아이동일","001530" +"81518","00227333","네패스","033640" +"81519","00392691","젬백스링크","064800" +"81521","00809429","한일진공","123840" +"81522","00177870","대창솔루션","096350" +"81523","00105040","뉴인텍","012340" +"81524","00225159","S&T홀딩스","036530" +"81525","00117629","동양철관","008970" +"81526","00349060","한스바이오메드","042520" +"81527","01075126","경남제약헬스케어","223310" +"81528","00148540","CJ","001040" +"81529","00545114","랩지노믹스","084650" +"81530","00255105","골드퍼시픽","038530" +"81531","00550082","빅텐츠","210120" +"81532","00131018","서울제약","018680" +"81533","00807397","코이즈","121850" +"81537","01186671","바이오시네틱스","281310" +"81549","00356839","메디콕스","054180" +"81550","00118521","진원생명과학","011000" +"81551","00188715","현진소재","053660" +"81552","00111227","대양제지","006580" +"81554","00151605","청보산업","013720" +"81555","00121969","에쓰씨엔지니어링","023960" +"81556","00302926","현대로템","064350" +"81557","00808022","메지온","140410" +"81558","00188797","포메탈","119500" +"81560","00147082","재영솔루텍","049630" +"81561","00977696","파티게임즈","194510" +"81568","00386937","국민은행","060000" +"81570","01205842","현대건설기계","267270" +"81572","00105873","LG디스플레이","034220" +"81574","00165680","호텔신라","008770" +"81576","00117267","동양생명","082640" +"81577","00136378","신세계","004170" +"81578","00222213","포트로닉스천안","039870" +"81584","00375931","인선이엔티","060150" +"81587","01391103","브랜드엑스코퍼레이션","337930" +"81592","00218052","국제엘렉트릭코리아","053740" +"81631","00136101","메이슨캐피탈","021880" +"81633","00380429","CMG제약","058820" +"81642","00307897","신한카드","032710" +"81643","01504804","유안타제7호스팩","367460" +"81647","00120021","LG","003550" +"81655","00156150","하이트론씨스템즈","019490" +"81683","00364403","쏠리드","050890" +"81684","00109718","사조대림","003960" +"81699","00336817","에이치엘비제약","047920" +"81700","01221947","비엔에프코퍼레이션","271780" +"81701","00175173","오스템","031510" +"81702","00372688","티사이언티픽","057680" +"81703","00445160","디이엔티","079810" +"81704","01405390","핌스","347770" +"81705","00788737","이퓨쳐","134060" +"81706","00138057","써니전자","004770" +"81707","00173449","경남바이오파마","044480" +"81710","00256715","국순당","043650" +"81711","00136642","엠벤처투자","019590" +"81712","00139205","안국약품","001540" +"81713","00660033","씨엔플러스","115530" +"81714","00287812","에코바이오","038870" +"81715","00114765","케이비아이동국실업","001620" +"81717","00180795","인팩","023810" +"81737","00160375","진양폴리우레탄","010640" +"81738","00145738","이화전기","024810" +"81740","00155531","풍산홀딩스","005810" +"81741","00910840","윈하이텍","192390" +"81743","01327092","라닉스","317120" +"81745","00163682","메리츠증권","008560" +"81746","00860332","메리츠금융지주","138040" +"81747","00359580","아이디스홀딩스","054800" +"81748","00159209","한전기술","052690" +"81751","00126256","삼성생명","032830" +"81761","00144720","유양디앤유","011690" +"81762","00220057","유비케어","032620" +"81763","00496225","이엠네트웍스","087730" +"81764","00160047","한국앤컴퍼니","000240" +"81765","00985686","큐브엔터","182360" +"81790","00843900","육일씨엔에쓰","191410" +"81803","00370006","iMBC","052220" +"81804","01181515","엘앤씨바이오","290650" +"81808","00130772","SBS","034120" +"81823","00143794","원림","005820" +"81834","00239596","쎄니트","037760" +"81850","01343735","데이드림엔터","348840" +"81851","01416235","미투젠","950190" +"81865","00447982","에스피엠씨","074000" +"81867","00134510","세종공업","033530" +"81895","00298447","아모텍","052710" +"81896","01259311","푸드나무","290720" +"81897","00155586","피에스엠씨","024850" +"81898","00643656","조이맥스","101730" +"81901","00618410","상상인인더스트리","101000" +"81903","00654272","램테크놀러지","171010" +"81907","00250614","삼영엠텍","054540" +"81910","00535746","게임빌","063080" +"81911","00580667","S&K폴리텍","091340" +"81912","00351630","세코닉스","053450" +"81913","00463342","에스텍파마","041910" +"81914","00100258","에스마크","030270" +"81915","01113949","퓨전","195440" +"81916","00132804","성신양회","004980" +"81917","00144243","유니슨","018000" +"81918","00400121","유아이디","069330" +"81919","00103006","고려제강","002240" +"81932","00552992","진매트릭스","109820" +"81938","00141282","에이치디씨영창","001890" +"81941","00141246","SGC이테크건설","016250" +"81944","00117708","리드코프","012700" +"81948","00159731","글로본","019660" +"81949","01207716","앱코","129890" +"81953","00113191","코리안리","003690" +"81954","00136004","신라에스지","025870" +"81972","00104537","국영지앤엠","006050" +"81973","00415707","엔텔스","069410" +"81974","00523307","다원시스","068240" +"81975","00153755","태양금속공업","004100" +"81976","00108065","쎌마테라퓨틱스","015540" +"81978","00573269","에스코넥","096630" +"81980","00105466","KCC건설","021320" +"81988","00260392","대원전선","006340" +"81989","00149594","한국캐피탈","023760" +"81990","00305570","서울리거","043710" +"81991","00829380","피제이메탈","128660" +"81992","00318662","포스링크","056730" +"82009","00359623","우리산업홀딩스","072470" +"82014","00479787","인텍플러스","064290" +"82016","00995993","캔서롭","180400" +"82018","00141307","영풍","000670" +"82019","00390365","토비스","051360" +"82021","00626464","지엔씨에너지","119850" +"82022","00562360","에스맥","097780" +"82023","00259590","바른손이앤에이","035620" +"82027","00653103","나노스","151910" +"82028","00663669","우노앤컴퍼니","114630" +"82032","00117027","알루코","001780" +"82036","00131896","선광","003100" +"82038","00302120","피앤텔","054340" +"82045","01391033","NH프라임리츠","338100" +"82053","00307222","YBM넷","057030" +"82056","00173351","삼진","032750" +"82061","00569646","영우디에스피","143540" +"82063","00152039","SG충방","001380" +"82064","00540605","이월드","084680" +"82065","00145668","이화공영","001840" +"82082","01326279","아이엘사이언스","307180" +"82084","00117276","네이처셀","007390" +"82085","00152437","케이프","064820" +"82101","00139153","에코플라스틱","038110" +"82102","00158219","시그네틱스","033170" +"82103","00919966","신라젠","215600" +"82104","01293388","무진메디","322970" +"82108","00164830","한국조선해양","009540" +"82109","00108649","TPC","048770" +"82110","00144395","동서","026960" +"82113","00141273","웰바이오텍","010600" +"82114","00144012","원풍","008370" +"82115","00269241","주연테크","044380" +"82116","00159290","한국아트라스비엑스","023890" +"82119","00334624","팍스넷","038160" +"82127","00101220","KG케미칼","001390" +"82135","00753643","컨버즈","109070" +"82136","00117258","한화갤러리아타임월드","027390" +"82140","00923826","유테크","178780" +"82141","00135962","신라교역","004970" +"82142","00585608","다나와","119860" +"82143","00117300","TCC스틸","002710" +"82144","00117230","동양물산기업","002900" +"82145","00405959","스타플렉스","115570" +"82146","00238977","화진","134780" +"82148","00156895","아주IB투자","027360" +"82149","01110182","이노테라피","246960" +"82182","00363510","이그잭스","060230" +"82183","00409681","아스트","067390" +"82184","00411905","테라셈","182690" +"82186","01096341","넷게임즈","225570" +"82189","00360674","코위버","056360" +"82190","00203315","제이콘텐트리","036420" +"82194","00362441","현대오토에버","307950" +"82195","00129642","상신브레이크","041650" +"82196","00110307","대선조선","031990" +"82207","00365624","광진윈텍","090150" +"82210","00591441","어보브반도체","102120" +"82211","00158112","한국수출포장공업","002200" +"82220","00595182","아톤","158430" +"82248","00532059","KPX생명과학","114450" +"82249","00852087","시디즈","134790" +"82250","00679314","동성코퍼레이션","102260" +"82251","00126380","삼성전자","005930" +"82253","00177816","대주전자재료","078600" +"82254","00247975","솔브레인홀딩스","036830" +"82255","00303396","한국컴퓨터","054040" +"82256","00147860","지코","010580" +"82257","00145686","이화산업","000760" +"82258","00133663","세동","053060" +"82260","01217829","엔에프씨","265740" +"82261","01119651","엘리비젼","276240" +"82263","00209780","케이씨","029460" +"82264","00612294","피엔티","137400" +"82265","00167192","넥센","005720" +"82266","00155692","우리조명","037400" +"82267","00141529","오뚜기","007310" +"82272","00118266","DRB동일","004840" +"82274","01108442","에치에프알","230240" +"82286","00905316","하이골드3호","153360" +"82287","00348292","한빛소프트","047080" +"82303","00986898","팜스빌","318010" +"82313","00159795","한국카본","017960" +"82341","00169215","에너토크","019990" +"82344","01190124","미디어젠","279600" +"82361","00369107","우리바이오","082850" +"82363","00109286","대동공업","000490" +"82364","00540641","동양고속","084670" +"82365","00198697","일진디스플","020760" +"82366","00160232","KSS해운","044450" +"82367","00127255","삼영화학공업","003720" +"82368","00791209","우리들휴브레인","118000" +"82369","00157636","케이피에프","024880" +"82371","00426068","아이에스이커머스","069920" +"82372","00173874","넥센타이어","002350" +"82373","00112970","대한방직","001070" +"82374","00269889","누리텔레콤","040160" +"82375","00129509","한익스프레스","014130" +"82377","00781202","뉴프라이드","900100" +"82378","01174038","자비스","254120" +"82379","01247918","한화에이스기업인수목적4호","279410" +"82414","00815095","우리이앤엘","153490" +"82431","01099667","에스디생명공학","217480" +"82443","00833064","엠플러스","259630" +"82449","00173661","에스지신성건설","001970" +"82474","01257526","한국제6호기업인수목적","281410" +"82475","01214743","더네이쳐홀딩스","298540" +"82525","00445054","하나마이크론","067310" +"82554","01251577","휴네시온","290270" +"82566","00118008","동원금속","018500" +"82569","00160861","한농화성","011500" +"82582","00606886","엔지켐생명과학","183490" +"82590","01042085","엑셈","205100" +"82596","00159005","생고뱅코리아홀딩스","002000" +"82601","00201432","비츠로시스","054220" +"82602","00519252","THE E&M","089230" +"82603","00961507","이더블유케이","258610" +"82604","01032404","콜마비앤에이치","200130" +"82605","01118281","피플바이오","304840" +"82630","00533924","액트","131400" +"82632","00201450","SK렌터카","068400" +"82650","00297989","오르비텍","046120" +"82651","00611286","티케이씨","191600" +"82663","01011872","엔에스엠","238170" +"82671","00867849","오파스넷","173130" +"82680","01023318","센코","347000" +"82689","00339391","한화시스템","272210" +"82693","00111111","대양금속","009190" +"82694","00359474","제이웨이","058420" +"82695","00115287","동방","004140" +"82696","01041828","JTC","950170" +"82697","00143314","에이엔피","015260" +"82698","00939331","한국콜마","161890" +"82699","01412938","에이스캠퍼","322190" +"82703","00303217","우진플라임","049800" +"82704","00102140","경인전자","009140" +"82706","00159698","지역난방공사","071320" +"82720","00267942","큐캐피탈","016600" +"82721","01267550","나라소프트","288490" +"82723","00415628","세진중공업","075580" +"82733","00816544","토니모리","214420" +"82740","01115044","안지오랩","251280" +"82745","00872984","이마트","139480" +"82754","00155382","바이오빌","065940" +"82756","00265324","CJ ENM","035760" +"82768","00545929","제넥신","095700" +"82780","00135795","신도리코","029530" +"82798","01160512","헝셩그룹","900270" +"82803","00812362","포티스","141020" +"82815","00364254","알티캐스트","085810" +"82821","00160010","한국큐빅","021650" +"82822","00187770","한국파마","032300" +"82862","00296263","YW","051390" +"82873","00203290","한국콜마홀딩스","024720" +"82878","01267170","SK케미칼","285130" +"82887","01262458","동아타이어","282690" +"82888","01319808","한일시멘트","300720" +"82889","00799539","제노레이","122310" +"82899","00260657","오상헬스케어","036220" +"82902","00407036","선바이오","067370" +"82911","00373447","서린바이오","038070" +"82921","00508344","SK디앤디","210980" +"82945","00757816","모다","149940" +"82953","00367482","라이트론","069540" +"82954","01118139","이도바이오","336040" +"82958","00128607","삼천리자전거","024950" +"82965","00767460","PI첨단소재","178920" +"82968","01344752","케어룸의료산업","327970" +"82980","00337296","원포유","122830" +"82981","01236532","와이즈버즈","273060" +"82982","01259418","대보마그네틱","290670" +"82984","00539274","대상홀딩스","084690" +"82988","01188378","시스웍","269620" +"82989","00173731","씨앤에스자산관리","032040" +"83009","00384717","넥스트BT","065170" +"83018","00526951","이엠코리아","095190" +"83020","00606770","참좋은여행","094850" +"83021","00684802","에이플러스에셋","244920" +"83022","00608440","티앤엘","340570" +"83023","00406329","루멘스","038060" +"83028","00370255","바른전자","064520" +"83029","00144021","원풍물산","008290" +"83055","00317089","에스디","066930" +"83070","00599887","신텍","099660" +"83075","00688358","하이즈항공","221840" +"83080","00601641","리드","197210" +"83081","00160205","한국내화","010040" +"83087","00201788","제낙스","065620" +"83090","00385415","일신바이오","068330" +"83098","00372873","KTis","058860" +"83117","01489648","솔브레인","357780" +"83121","00164502","현대공업","170030" +"83135","00159573","한국주철관공업","000970" +"83142","00297448","젬백스지오","041590" +"83146","00269852","인프라웨어","041020" +"83147","00132868","성안","011300" +"83148","00560122","텔레필드","091440" +"83149","00148443","제일파마홀딩스","002620" +"83150","00369213","리노스","039980" +"83152","01042359","지엘팜텍","204840" +"83154","00129615","상보","027580" +"83157","00378628","KH바텍","060720" +"83158","01042429","액션스퀘어","205500" +"83159","00526696","웨이브일렉트로","095270" +"83162","00256380","유아이엘","049520" +"83163","00165413","롯데케미칼","011170" +"83164","00528515","한라IMS","092460" +"83167","00114093","덕성","004830" +"83174","00188973","화일약품","061250" +"83175","01168684","스튜디오드래곤","253450" +"83176","01153105","비비씨","318410" +"83186","01050738","솔트룩스","304100" +"83187","01419384","하나금융16호스팩","343510" +"83188","00164724","현대엘리베이터","017800" +"83192","00288343","삼영이엔씨","065570" +"83201","00136776","제이준코스메틱","025620" +"83203","00297961","케이맥","043290" +"83204","00317487","에이치엘비파워","043220" +"83205","01142400","이루다","164060" +"83207","00666064","셀바스AI","108860" +"83208","00666204","제너셈","217190" +"83212","01201970","클리노믹스","352770" +"83214","00131939","선도전기","007610" +"83215","00113544","대한화섬","003830" +"83216","00579971","칩스앤미디어","094360" +"83217","01015160","와이팜","332570" +"83228","00245694","남화산업","111710" +"83242","01414361","SK5호스팩","337450" +"83244","00249441","씨씨에스","066790" +"83249","00150165","브이티지엠피","018290" +"83254","01407158","신한제6호스팩","333050" +"83258","00306454","한국정보인증","053300" +"83261","00154718","비츠로셀","082920" +"83263","00103644","비츠로테크","042370" +"83265","01274310","이오플로우","294090" +"83271","01450105","미래에셋대우스팩 5호","353490" +"83278","00838962","알서포트","131370" +"83282","00259934","예림당","036000" +"83284","00226352","케이엠더블유","032500" +"83285","00549925","루트로닉","085370" +"83287","00568461","보광산업","225530" +"83291","00142661","우성사료","006980" +"83293","00531078","유비온","084440" +"83294","00163512","한진","002320" +"83295","00508274","일진다이아","081000" +"83297","00350020","파인디앤씨","049120" +"83298","00530413","코디엠","224060" +"83300","01046203","스튜디오산타클로스","204630" +"83301","00349732","코나아이","052400" +"83302","01085442","휴벡셀","212310" +"83303","00102751","고려산업","002140" +"83304","00974927","NEW","160550" +"83305","01222867","제테마","216080" +"83306","00609485","웅진에너지","103130" +"83307","00125743","현대비앤지스틸","004560" +"83308","00106395","금호전기","001210" +"83309","00302078","한국코퍼레이션","050540" +"83336","01035678","와이제이엠게임즈","193250" +"83337","00625942","아이원스","114810" +"83345","00220622","서호전기","065710" +"83347","00476036","에프엔에스테크","083500" +"83348","00899556","제룡산업","147830" +"83360","00172680","동국산업","005160" diff --git a/data/reg_table.html b/data/reg_table.html index 5721931d..031b3227 100644 --- a/data/reg_table.html +++ b/data/reg_table.html @@ -5,22 +5,22 @@ Mkt_excess-0.262*** (0.016) -SMB-0.349*** +SMB-0.346*** (0.036) HML-0.098*** (0.033) UMD0.081*** -(0.027) +(0.026) Constant0.003*** (0.001) -Observations377 +Observations378 R20.638 Adjusted R20.634 -Residual Std. Error0.013 (df = 372) -F Statistic164.000*** (df = 4; 372) +Residual Std. Error0.013 (df = 373) +F Statistic164.300*** (df = 4; 373) Note:*p<0.1; **p<0.05; ***p<0.01 diff --git a/docs/04-crawling.md b/docs/04-crawling.md index 32e33547..5082f8d9 100644 --- a/docs/04-crawling.md +++ b/docs/04-crawling.md @@ -126,26 +126,26 @@ print(data_title) ``` -## [1] "거래소, 시장정보포털 'KRX 정보데" -## [2] "거래소, 상장사 ESG 정보공개 가이" -## [3] "美 바이든 정부 공식 출범…韓 조정 " -## [4] "금감원, 삼성증권 종합검사 이르면 1" -## [5] "포모증후군?, 증시 변동성 높아져도." -## [6] "3년간 공매도 수익 9170억…개미 " -## [7] "엠씨넥스에 대한 기대감이 솔솔 높아지" -## [8] "바이든 정부 공식 출범…지수 상승 부" -## [9] "대작 줄줄이 개봉...CGV 웃을 수" -## [10] "[fn마켓워치] 변동성 커진 두산인프" -## [11] "거래소, `KRX 정보데이터시스템` " -## [12] "거래소, `ESG 정보공개 가이던스`" -## [13] "한국거래소, 시장정보포털 'KRX정보" -## [14] "컴투스, 신작 줄줄이 출시…‘백년전쟁" -## [15] "'ESG 정보공개 가이던스' 제정…온" -## [16] "한국거래소, ESG 투자 활성화 속도" -## [17] "증권·파생상품시장 정보 한 눈에..." -## [18] "뜨거운 증시만큼 달궈진 IPO 시장 " -## [19] "한국거래소 'ESG 정보공개 가이던스" -## [20] "주식 정보 여기서 다 본다…거래소, " +## [1] "[주식 초고수는 지금] 씨아이에스·현" +## [2] "코스피, 장중 3060선 회복…삼성전" +## [3] "아시아증시 혼조세…상해종합↓·니케이2" +## [4] "해덕파워웨이, 신규 사외이사 3명 선" +## [5] "저평가 손보주, 날개 펼치나" +## [6] "中 경제 턱밑 추격, 바이든의 선택은" +## [7] "[특징주]에이스토리, 글로벌 성장 기" +## [8] "[특징주] 실적 고고행진…빅히트 장중" +## [9] "조정장은 기회…경기민감·실적개선株 주" +## [10] "달러-엔 103.880엔(11시09분" +## [11] "[공시] 푸본현대생명보험, 4580억" +## [12] "에이피티씨, 작년 영업익 83.7% " +## [13] "에이디테크놀로지, 374.6억원 규모" +## [14] "韓, 무디스 ESG 평가서 中, 日 " +## [15] "윈텍, 22억원 규모 전자부품 마운터" +## [16] "예탁원 \"기관간 레포 거래금액 2.2" +## [17] "[표]유럽 주요기업 주가(1/18)" +## [18] "[특징주]삼진엘앤디, 삼성SDI 롤스" +## [19] "대신증권, 오픈뱅킹 서비스 이용시 최" +## [20] "[올댓차이나] 홍콩 증시, 中 경제회" ``` ### 기업공시채널에서 오늘의 공시 불러오기 @@ -415,27 +415,20 @@ print(head(table)) ``` ``` -## N 종목명 현재가 전일비 등락률 액면가 -## 1 NA -## 2 1 삼성전자 88,000 1,700 -1.90% 100 -## 3 2 SK하이닉스 127,500 3,000 -2.30% 5,000 -## 4 3 LG화학 979,000 31,000 -3.07% 5,000 -## 5 4 삼성전자우 77,600 1,200 -1.52% 100 -## 6 5 삼성바이오로직스 804,000 12,000 -1.47% 2,500 -## 시가총액 상장주식수 외국인비율 거래량 PER -## 1 NA -## 2 5,253,409 5,969,783 55.57 33,117,980 24.03 -## 3 928,203 728,002 50.20 4,763,450 32.34 -## 4 691,099 70,592 43.59 442,295 91.47 -## 5 638,560 822,887 80.52 3,116,433 21.19 -## 6 531,967 66,165 10.40 136,954 149.69 -## ROE 토론실 -## 1 NA -## 2 8.69 NA -## 3 4.25 NA -## 4 1.84 NA -## 5 N/A NA -## 6 4.77 NA +## N 종목명 현재가 전일비 등락률 액면가 시가총액 +## 1 NA +## 2 1 삼성전자 86,900 1,900 +2.24% 100 5,187,741 +## 3 2 SK하이닉스 130,500 500 +0.38% 5,000 950,043 +## 4 3 LG화학 992,000 28,000 +2.90% 5,000 700,276 +## 5 4 삼성전자우 75,800 1,200 +1.61% 100 623,748 +## 6 5 현대차 257,500 16,500 +6.85% 5,000 550,196 +## 상장주식수 외국인비율 거래량 PER ROE 토론실 +## 1 NA NA +## 2 5,969,783 55.51 23,522,814 23.73 8.69 NA +## 3 728,002 50.21 1,943,415 33.11 4.25 NA +## 4 70,592 43.52 169,494 92.68 1.84 NA +## 5 822,887 80.40 4,706,589 20.70 N/A NA +## 6 213,668 31.01 3,096,888 61.53 4.32 NA ``` 이 중 마지막 열인 토론실은 필요 없는 열이며, 첫 번째 행과 같이 아무런 정보가 없는 행도 있습니다. 이를 다음과 같이 정리해줍니다. @@ -449,26 +442,26 @@ print(head(table)) ``` ## N 종목명 현재가 전일비 등락률 액면가 -## 2 1 삼성전자 88,000 1,700 -1.90% 100 -## 3 2 SK하이닉스 127,500 3,000 -2.30% 5,000 -## 4 3 LG화학 979,000 31,000 -3.07% 5,000 -## 5 4 삼성전자우 77,600 1,200 -1.52% 100 -## 6 5 삼성바이오로직스 804,000 12,000 -1.47% 2,500 -## 10 6 현대차 240,000 10,500 -4.19% 5,000 +## 2 1 삼성전자 86,900 1,900 +2.24% 100 +## 3 2 SK하이닉스 130,500 500 +0.38% 5,000 +## 4 3 LG화학 992,000 28,000 +2.90% 5,000 +## 5 4 삼성전자우 75,800 1,200 +1.61% 100 +## 6 5 현대차 257,500 16,500 +6.85% 5,000 +## 10 6 삼성바이오로직스 794,000 6,000 +0.76% 2,500 ## 시가총액 상장주식수 외국인비율 거래량 PER -## 2 5,253,409 5,969,783 55.57 33,117,980 24.03 -## 3 928,203 728,002 50.20 4,763,450 32.34 -## 4 691,099 70,592 43.59 442,295 91.47 -## 5 638,560 822,887 80.52 3,116,433 21.19 -## 6 531,967 66,165 10.40 136,954 149.69 -## 10 512,804 213,668 30.95 3,863,728 57.35 +## 2 5,187,741 5,969,783 55.51 23,522,814 23.73 +## 3 950,043 728,002 50.21 1,943,415 33.11 +## 4 700,276 70,592 43.52 169,494 92.68 +## 5 623,748 822,887 80.40 4,706,589 20.70 +## 6 550,196 213,668 31.01 3,096,888 61.53 +## 10 525,350 66,165 10.37 76,151 147.83 ## ROE ## 2 8.69 ## 3 4.25 ## 4 1.84 ## 5 N/A -## 6 4.77 -## 10 4.32 +## 6 4.32 +## 10 4.77 ``` 이제 필요한 정보는 6자리 티커입니다. 티커 역시 개발자 도구 화면을 통해 tbody → td → a 태그의 href 속성에 위치하고 있음을 알고 있습니다. 티커를 추출하는 코드는 다음과 같습니다. @@ -493,8 +486,8 @@ print(head(symbol, 10)) ## [6] "/item/board.nhn?code=051910" ## [7] "/item/main.nhn?code=005935" ## [8] "/item/board.nhn?code=005935" -## [9] "/item/main.nhn?code=207940" -## [10] "/item/board.nhn?code=207940" +## [9] "/item/main.nhn?code=005380" +## [10] "/item/board.nhn?code=005380" ``` 1. `read_html()` 함수를 통해 HTML 정보를 읽어오며, 인코딩은 EUC-KR로 설정합니다. @@ -524,8 +517,8 @@ print(head(symbol, 10)) ## "051910" "051910" ## /item/main.nhn?code=005935 /item/board.nhn?code=005935 ## "005935" "005935" -## /item/main.nhn?code=207940 /item/board.nhn?code=207940 -## "207940" "207940" +## /item/main.nhn?code=005380 /item/board.nhn?code=005380 +## "005380" "005380" ``` `sapply()` 함수를 통해 symbol 변수의 내용들에 `function()`을 적용하며, `stringr` 패키지의 `str_sub()` 함수를 이용해 마지막6자리 글자만 추출합니다. @@ -539,8 +532,8 @@ print(head(symbol, 10)) ``` ``` -## [1] "005930" "000660" "051910" "005935" "207940" -## [6] "005380" "006400" "035420" "068270" "035720" +## [1] "005930" "000660" "051910" "005935" "005380" +## [6] "207940" "035420" "006400" "068270" "035720" ``` `unique()` 함수를 이용해 중복되는 티커를 제거하면 우리가 원하는 티커 부분만 깔끔하게 정리됩니다. 해당 내용을 위에서 구한 테이블에 입력한 후 데이터를 다듬는 과정은 다음과 같습니다. diff --git a/docs/05-crawling_practice.md b/docs/05-crawling_practice.md index f096abba..97e10d4c 100644 --- a/docs/05-crawling_practice.md +++ b/docs/05-crawling_practice.md @@ -272,7 +272,7 @@ print(biz_day) ``` ``` -## [1] "20210113" +## [1] "20210115" ``` 1. 페이지의 url을 저장합니다. diff --git a/docs/06-crawling_actual.md b/docs/06-crawling_actual.md index c43d607c..e42c3cd0 100644 --- a/docs/06-crawling_actual.md +++ b/docs/06-crawling_actual.md @@ -71,7 +71,7 @@ print(price) ``` ## [,1] -## 2021-01-17 NA +## 2021-01-19 NA ``` 1. data 폴더 내에 KOR_price 폴더를 생성합니다. @@ -96,12 +96,12 @@ print(head(data_html)) ``` ``` -## [1] "20190108|38000|39200|37950|38100|12756554" -## [2] "20190109|38650|39600|38300|39600|17452708" -## [3] "20190110|40000|40150|39600|39800|14731699" -## [4] "20190111|40350|40550|39950|40500|11661063" -## [5] "20190114|40450|40700|39850|40050|11984996" -## [6] "20190115|40050|41100|39850|41100|11492756" +## [1] "20190110|40000|40150|39600|39800|14731699" +## [2] "20190111|40350|40550|39950|40500|11661063" +## [3] "20190114|40450|40700|39850|40050|11984996" +## [4] "20190115|40050|41100|39850|41100|11492756" +## [5] "20190116|41150|41450|40700|41450|8491595" +## [6] "20190117|41700|42100|41450|41950|11736903" ``` 1. `paste0()` 함수를 이용해 원하는 종목의 url을 생성합니다. url 중 티커에 해당하는 6자리 부분만 위에서 입력한 name으로 설정해주면 됩니다. @@ -121,14 +121,14 @@ print(head(price)) ``` ## # A tibble: 6 x 6 -## `20190108` `38000` `39200` `37950` `38100` `12756554` +## `20190110` `40000` `40150` `39600` `39800` `14731699` ## -## 1 20190109 38650 39600 38300 39600 17452708 -## 2 20190110 40000 40150 39600 39800 14731699 -## 3 20190111 40350 40550 39950 40500 11661063 -## 4 20190114 40450 40700 39850 40050 11984996 -## 5 20190115 40050 41100 39850 41100 11492756 -## 6 20190116 41150 41450 40700 41450 8491595 +## 1 20190111 40350 40550 39950 40500 11661063 +## 2 20190114 40450 40700 39850 40050 11984996 +## 3 20190115 40050 41100 39850 41100 11492756 +## 4 20190116 41150 41450 40700 41450 8491595 +## 5 20190117 41700 42100 41450 41950 11736903 +## 6 20190118 42000 42400 41950 42300 11029256 ``` readr 패키지의 `read_delim()` 함수를 쓰면 구분자로 이루어진 데이터를 테이블로 쉽게 변경할 수 있습니다. 데이터를 확인해보면 테이블 형태로 변경되었으며 각 열은 날짜, 시가, 고가, 저가, 종가, 거래량을 의미합니다. 이 중 우리가 필요한 날짜와 종가를 선택한 후 데이터 클렌징을 해줍니다. @@ -149,12 +149,12 @@ print(tail(price)) ``` ## Price -## 2021-01-08 88800 -## 2021-01-11 91000 ## 2021-01-12 90600 ## 2021-01-13 89700 ## 2021-01-14 89700 ## 2021-01-15 88000 +## 2021-01-18 85000 +## 2021-01-19 86900 ``` 1. 날짜에 해당하는 첫 번째 열과, 종가에 해당하는 다섯 번째 열만 선택해 저장합니다. @@ -589,7 +589,7 @@ print(price) ``` ``` -## [1] 88000 +## [1] 85000 ``` 1. url을 입력한 후, `GET()` 함수를 이용해 데이터를 불러오며, 역시나 user_agent를 추가해 줍니다. @@ -664,7 +664,7 @@ print(data_value) ``` ## PER PBR PCR PSR -## 24.429 1.998 11.576 2.280 +## 23.596 1.930 11.181 2.202 ``` 분자에는 현재 주가를 입력하며, 분모에는 재무 데이터를 보통주 발행주식수로 나눈 값을 입력합니다. 단, 주가는 원 단위, 재무 데이터는 억 원 단위이므로, 둘 사이에 단위를 동일하게 맞춰주기 위해 분모에 억을 곱합니다. 또한 가치지표가 음수인 경우는 NA로 변경해줍니다. @@ -882,7 +882,7 @@ print(codezip_data) ``` ## Response [https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key=b1a630e527b0e5ff5bd58ed81b49825017fa80b8] -## Date: 2021-01-17 05:38 +## Date: 2021-01-19 02:23 ## Status: 200 ## Content-Type: application/x-msdownload ## Size: 1.4 MB @@ -924,7 +924,7 @@ print(nm) ``` ## Name Length Date -## 1 CORPCODE.xml 16073037 +## 1 CORPCODE.xml 16074227 ``` 1. `tempfile()` 함수 통해 빈 .zip 파일을 만듭니다. @@ -996,7 +996,7 @@ nrow(corp_list) ``` ``` -## [1] 83370 +## [1] 83376 ``` ```r @@ -1013,7 +1013,7 @@ head(corp_list) ## 6 00179984 연방건설산업 ``` -종목수를 확인해보면 83370 개가 확인되며, 이 중 stock 열이 빈 종목은 거래소에 상장되지 않은 종목입니다. 따라서 해당 데이터는 삭제하여 거래소 상장 종목만을 남긴 후, csv 파일로 저장하도록 합니다. +종목수를 확인해보면 83376 개가 확인되며, 이 중 stock 열이 빈 종목은 거래소에 상장되지 않은 종목입니다. 따라서 해당 데이터는 삭제하여 거래소 상장 종목만을 남긴 후, csv 파일로 저장하도록 합니다. ```r @@ -1069,27 +1069,27 @@ head(notice_data) ``` ``` -## corp_code corp_name stock_code corp_cls -## 1 00186452 릭스솔루션 029480 K -## 2 00231372 롯데관광개발 032350 Y -## 3 00105138 파라텍 033540 K -## 4 01437186 ESR켄달스퀘어리츠 365550 Y -## 5 00249502 더블유에프엠 035290 K -## 6 00188380 유진자산운용 E -## report_nm -## 1 최대주주변경을수반하는주식담보제공계약체결 -## 2 유상증자또는주식관련사채등의발행결과(자율공시) -## 3 최대주주변경을수반하는주식담보제공계약해제ㆍ취소등 -## 4 자본잠식50%이상또는매출액50억원미만사실발생 -## 5 기타시장안내(최대주주의의무보유관련) -## 6 [기재정정]증권신고서(집합투자증권-신탁형)(유진지수연계증권투자신탁76호(온라인전용)[ELS-파생형]) -## rcept_no flr_nm rcept_dt rm -## 1 20210115900736 릭스솔루션 20210115 코 -## 2 20210115800737 롯데관광개발 20210115 유 -## 3 20210115900735 파라텍 20210115 코 -## 4 20210115800731 ESR켄달스퀘어리츠 20210115 유 -## 5 20210115900726 코스닥시장본부 20210115 코 -## 6 20210113000532 유진자산운용 20210113 +## corp_code corp_name stock_code corp_cls +## 1 00159795 한국카본 017960 Y +## 2 00542898 하이소닉 106080 K +## 3 00160588 한화 000880 Y +## 4 01142075 핑거 E +## 5 00138321 신한금융투자 008670 E +## 6 01142075 핑거 E +## report_nm rcept_no +## 1 주식등의대량보유상황보고서(일반) 20210119000046 +## 2 주식등의대량보유상황보고서(약식) 20210119000044 +## 3 타법인주식및출자증권취득결정(자율공시) 20210119800139 +## 4 [기재정정]투자설명서 20210119000043 +## 5 증권발행실적보고서 20210119000042 +## 6 [발행조건확정]증권신고서(지분증권) 20210119000041 +## flr_nm rcept_dt rm +## 1 조문수 20210119 +## 2 최영호 20210119 +## 3 한화 20210119 유 +## 4 핑거 20210119 +## 5 신한금융투자 20210119 +## 6 핑거 20210119 ``` `fromJSON()` 함수를 통해 JSON 데이터를 받은 후 list를 확인해보면 우리가 원하는 공시정보, 즉 일주일 전부터 100건의 공시 정보가 다운로드 되어 있습니다. diff --git a/docs/08-data_analysis.md b/docs/08-data_analysis.md index d04f7069..cb75f54b 100644 --- a/docs/08-data_analysis.md +++ b/docs/08-data_analysis.md @@ -1032,7 +1032,7 @@ ggplotly(p) ```
- + plotly 패키지는 R뿐만 아니라 Python, MATLAB, Julia 등 여러 프로그래밍 언어에 사용될 수 있는 그래픽 패키지로서 최근에 많은 사랑을 받고 있습니다. R에서는 단순히 `ggplot()`을 이용해 나타낸 그림에 `ggplotly()` 함수를 추가하는 것만으로 인터랙티브한 그래프를 만들어줍니다. diff --git a/docs/13-evaluation.md b/docs/13-evaluation.md index f93c1de3..9b76eb27 100644 --- a/docs/13-evaluation.md +++ b/docs/13-evaluation.md @@ -101,7 +101,7 @@ chart.CumReturns(df$QMJ) -먼저 `chart.CumReturns()` 함수를 이용해 QMJ 팩터의 누적수익률을 그래프로 나타내봅니다. 1989-07-31부터 2020-11-30까지 장기간동안 우상향하는 모습을 보이고 있습니다. +먼저 `chart.CumReturns()` 함수를 이용해 QMJ 팩터의 누적수익률을 그래프로 나타내봅니다. 1989-07-31부터 2020-12-31까지 장기간동안 우상향하는 모습을 보이고 있습니다. ```r @@ -109,7 +109,7 @@ prod((1+df$QMJ)) - 1 # 누적수익률 ``` ``` -## [1] 3.829 +## [1] 3.772 ``` ```r @@ -117,7 +117,7 @@ mean(df$QMJ) * 12 # 연율화 수익률(산술) ``` ``` -## [1] 0.05294 +## [1] 0.05242 ``` ```r @@ -125,7 +125,7 @@ mean(df$QMJ) * 12 # 연율화 수익률(산술) ``` ``` -## [1] 0.0514 +## [1] 0.05086 ``` 수익률 중 가장 많이보는 지표는 누적 수익률, 연율화 수익률(산술), 연율화 수익률(기하)입니다. 각 수익률을 구하는 법은 다음과 같습니다. @@ -143,7 +143,7 @@ Return.cumulative(df$QMJ) # 누적수익률 ``` ## QMJ -## Cumulative Return 3.829 +## Cumulative Return 3.772 ``` ```r @@ -152,7 +152,7 @@ Return.annualized(df$QMJ, geometric = FALSE) # 연율화 수익률(산술) ``` ## QMJ -## Annualized Return 0.05294 +## Annualized Return 0.05242 ``` ```r @@ -160,8 +160,8 @@ Return.annualized(df$QMJ) # 연율화 수익률(기하) ``` ``` -## QMJ -## Annualized Return 0.0514 +## QMJ +## Annualized Return 0.05086 ``` 수식에 맞게 값을 입력해 계산할 수도 있지만, 함수를 이용하면 더욱 손쉽게 계산이 가능하며 실수할 가능성도 줄어듭니다. 누적수익률은 `Return.cumulative()` 함수를 통해, 연율화 수익률(산술)은 `Return.annualized()` 함수 내 geometric 인자를 FALSE로 선택해줌으로써, 연율화 수익률(기하)는 `Return.annualized()` 함수를 통해 계산이 가능합니다. 수식으로 계산한 값과 함수를 통해 계산한 값을 비교하면 동일함이 확인됩니다. @@ -172,7 +172,7 @@ sd(df$QMJ) * sqrt(12) # 연율화 변동성 ``` ``` -## [1] 0.07389 +## [1] 0.07385 ``` ```r @@ -181,7 +181,7 @@ StdDev.annualized(df$QMJ) # 연율화 변동성 ``` ## QMJ -## Annualized Standard Deviation 0.07389 +## Annualized Standard Deviation 0.07385 ``` ```r @@ -190,7 +190,7 @@ SharpeRatio.annualized(df$QMJ, Rf = df$RF, geometric = TRUE) ``` ## QMJ -## Annualized Sharpe Ratio (Rf=2.7%) 0.3136 +## Annualized Sharpe Ratio (Rf=2.7%) 0.3076 ``` 위험으로 가장 많이 사용되는 지표는 변동성입니다. 연율화 변동성은 `sd()` 함수를 통해 변동성을 계산한 후 조정값을 곱해 계산합니다. 그러나 `StdDev.annualized()` 함수를 사용해 더욱 쉽게 계산할 수도 있습니다. @@ -218,13 +218,13 @@ table.Drawdowns(df$QMJ) ## 1 2002-10-31 2004-01-31 2008-08-31 -0.2134 71 ## 2 2009-03-31 2009-09-30 2011-12-31 -0.2002 34 ## 3 1992-11-30 1993-08-31 1997-01-31 -0.1408 51 -## 4 2020-04-30 2020-11-30 -0.1009 9 +## 4 2020-04-30 2020-12-31 -0.1110 10 ## 5 1998-10-31 1999-04-30 2000-05-31 -0.0869 20 ## To Trough Recovery ## 1 16 55 ## 2 7 27 ## 3 10 41 -## 4 8 NA +## 4 9 NA ## 5 7 13 ``` @@ -254,7 +254,7 @@ CalmarRatio(df$QMJ) ``` ## QMJ -## Calmar Ratio 0.2409 +## Calmar Ratio 0.2384 ``` 위험 조정 수익률 중 사용되는 지표 중 칼마 지수(Calmar Ratio)도 있습니다. 칼마 지수는 연율화 수익률을 최대낙폭으로 나눈 값으로서, 특히나 안정적인 절대 수익률을 추구하는 헤지펀드에서 많이 참조하는 지표입니다. @@ -269,7 +269,7 @@ apply.yearly(df$QMJ, Return.cumulative) %>% head() ``` ## QMJ ## 1989-12-31 0.07695 -## 1990-12-31 0.21971 +## 1990-12-31 0.21991 ## 1991-12-31 0.01086 ## 1992-12-31 0.02983 ## 1993-12-31 -0.09306 @@ -332,10 +332,10 @@ UpsideFrequency(df$QMJ, MAR = 0) ``` ``` -## [1] 0.5942 +## [1] 0.5926 ``` -`UpsideFrequency()` 함수는 벤치마크 대비 승률을 계산해줍니다. MAR 인자는 0이 기본값으로 설정되어 있으며, 원하는 벤치마크가 있을 시 이를 입력해주면 됩니다. QMJ 팩터는 월간 기준 수익률이 플러스를 기록했던 비율이 59.42%입니다. +`UpsideFrequency()` 함수는 벤치마크 대비 승률을 계산해줍니다. MAR 인자는 0이 기본값으로 설정되어 있으며, 원하는 벤치마크가 있을 시 이를 입력해주면 됩니다. QMJ 팩터는 월간 기준 수익률이 플러스를 기록했던 비율이 59.26%입니다. 위에서 구한 각종 지표들은 투자자가 포트폴리오의 시작부터 현재까지 투자를 했다는 전제 하에 계산됩니다. 그러나 투자를 시작하는 시점은 사람마다 다르기에, 무작위 시점에 투자했을 때 향후 n개월 후 승률 혹은 연율화 수익률 등을 계산할 필요도 있습니다. 이러한 기법을 **롤링 윈도우**라고 합니다. @@ -359,7 +359,7 @@ print(roll_win) ``` ## roll_12 roll_24 roll_36 -## [1,] 0.7623 0.8023 0.8772 +## [1,] 0.7602 0.8 0.8776 ``` 롤링 윈도우 승률은 무작위 시점에 투자했을 시 미래 n개월 동안의 연율화 수익률을 구하고, 해당 값이 벤치마크 대비 수익이 높았던 비율을 계산합니다. 만일 12개월 롤링 윈도우 승률이 100%라면, 어떠한 시점에 투자해도 12개월 후에는 언제나 벤치마크를 이겼음을 의미합니다. 반면 아무리 연율화 수익률이 높은 전략도 이러한 롤링 윈도우 승률이 지나치게 낮다면, 단순히 한 번의 운으로 인해 수익률이 높은 것처럼 보일수 있습니다. @@ -371,7 +371,7 @@ print(roll_win) 3. 계산에 필요한 n개월 동안은 수익률이 없으므로 `na.omit()`을 통해 삭제해줍니다. 4. `UpsideFrequency()` 함수를 통해 승률을 계산합니다. -해당 과정을 통해 계산된 12개월, 24개월, 36개월 롤링 승률은 각각 76.23%, 80.23%, 87.72%이며, 투자 기간이 길어질수록 승률이 높아집니다. +해당 과정을 통해 계산된 12개월, 24개월, 36개월 롤링 승률은 각각 76.02%, 80%, 87.76%이며, 투자 기간이 길어질수록 승률이 높아집니다. ```r @@ -404,11 +404,11 @@ summary(reg)$coefficient ``` ## Estimate Std. Error t value Pr(>|t|) -## (Intercept) 0.002554 0.0007049 3.623 3.316e-04 -## Mkt_excess -0.262459 0.0162821 -16.119 1.038e-44 -## SMB -0.348949 0.0362560 -9.625 1.001e-19 -## HML -0.097755 0.0326155 -2.997 2.908e-03 -## UMD 0.081063 0.0265079 3.058 2.389e-03 +## (Intercept) 0.002572 0.0007039 3.654 2.949e-04 +## Mkt_excess -0.262077 0.0162675 -16.110 1.065e-44 +## SMB -0.346379 0.0361225 -9.589 1.305e-19 +## HML -0.097724 0.0326096 -2.997 2.911e-03 +## UMD 0.081402 0.0264952 3.072 2.280e-03 ``` 먼저 우리가 구한 데이터를 통해 다음과 같은 회귀분석을 실시합니다. 즉 QMJ 팩터의 초과수익률을 시장위험 프리미엄, 사이즈 팩터, 밸류 팩터, 모멘텀 팩터에 회귀분석을 수행합니다. @@ -417,11 +417,11 @@ $$QMJ - R_f= \beta_m \times \ [R_m - R_f] + \beta_{SMB} \times R_{SMB} + \beta_{ `lm()` 함수 내에서 R_excess는 $QMJ - R_f$와 동일하며, Mkt_excess는 $R_m - R_f$와 동일합니다. 베타의 절댓값이 크다는 의미는 QMJ 팩터의 수익률이 해당 팩터와의 관계가 높다는 의미이며, 양수일 경우에는 양의 관계가, 음수일 경우에는 음의 관계가 높다는 의미입니다. 또한 t값 혹은 P값을 통해 관계가 얼마나 유의한지도 확인할 수 있습니다. -1. 시장 베타에 해당하는 $\beta_m$은 -0.262로 음숫값을 보이며, 퀄리티 팩터의 경우 시장과 역의 관계에 있다고 볼 수 있습니다. 또한 t값이 -16.119로 충분히 유의합니다. -2. 사이즈 베타에 해당하는 $\beta_{SMB}$는 -0.349이며 역시나 음숫값을 보입니다. 즉 퀄리티 팩터는 소형주보다는 대형주 수익률과 관계가 있으며, t값 역시 -9.625로 충분히 유의합니다. +1. 시장 베타에 해당하는 $\beta_m$은 -0.262로 음숫값을 보이며, 퀄리티 팩터의 경우 시장과 역의 관계에 있다고 볼 수 있습니다. 또한 t값이 -16.11로 충분히 유의합니다. +2. 사이즈 베타에 해당하는 $\beta_{SMB}$는 -0.346이며 역시나 음숫값을 보입니다. 즉 퀄리티 팩터는 소형주보다는 대형주 수익률과 관계가 있으며, t값 역시 -9.589로 충분히 유의합니다. 3. 밸류 베타에 해당하는 $\beta_{HML}$은 -0.098이며 이 역시 음숫값을 보입니다. 즉 퀄리티와 밸류 간의 관계에서 살펴본 것처럼, 두 팩터는 서로 역의 관계가 있습니다. t값 역시 -2.997로 유의합니다. -4. 모멘텀 베타에 해당하는 $\beta_{UMD}$는 0.081로 양의 관계가 있으며, 모멘텀 팩터가 좋은 시기에 퀄리티 팩터도 좋을 수 있습니다. t값은 3.058로 유의하다고 볼 수 있습니다. -5. 이러한 설명변수를 제외하고도 월간 초과수익률에 해당하는 계숫값이 0.003이며, t값은 3.623로 유의합니다. 즉, 퀄리티 팩터는 기존의 여러 팩터들로 설명되지 않는 새로운 팩터라고도 볼 수 있습니다. +4. 모멘텀 베타에 해당하는 $\beta_{UMD}$는 0.081로 양의 관계가 있으며, 모멘텀 팩터가 좋은 시기에 퀄리티 팩터도 좋을 수 있습니다. t값은 3.072로 유의하다고 볼 수 있습니다. +5. 이러한 설명변수를 제외하고도 월간 초과수익률에 해당하는 계숫값이 0.003이며, t값은 3.654로 유의합니다. 즉, 퀄리티 팩터는 기존의 여러 팩터들로 설명되지 않는 새로운 팩터라고도 볼 수 있습니다. ```r @@ -433,11 +433,11 @@ tidy(reg) ## # A tibble: 5 x 5 ## term estimate std.error statistic p.value ## -## 1 (Intercept) 0.00255 0.000705 3.62 3.32e- 4 -## 2 Mkt_excess -0.262 0.0163 -16.1 1.04e-44 -## 3 SMB -0.349 0.0363 -9.62 1.00e-19 -## 4 HML -0.0978 0.0326 -3.00 2.91e- 3 -## 5 UMD 0.0811 0.0265 3.06 2.39e- 3 +## 1 (Intercept) 0.00257 0.000704 3.65 2.95e- 4 +## 2 Mkt_excess -0.262 0.0163 -16.1 1.07e-44 +## 3 SMB -0.346 0.0361 -9.59 1.31e-19 +## 4 HML -0.0977 0.0326 -3.00 2.91e- 3 +## 5 UMD 0.0814 0.0265 3.07 2.28e- 3 ``` broom 패키지의 `tidy()` 함수를 사용하면 분석 결과 중 계수에 해당하는 값만을 요약해서 볼 수 있습니다. @@ -459,24 +459,24 @@ stargazer(reg, type = 'text', out = 'data/reg_table.html') ## Mkt_excess -0.262*** ## (0.016) ## -## SMB -0.349*** +## SMB -0.346*** ## (0.036) ## ## HML -0.098*** ## (0.033) ## ## UMD 0.081*** -## (0.027) +## (0.026) ## ## Constant 0.003*** ## (0.001) ## ## ----------------------------------------------- -## Observations 377 +## Observations 378 ## R2 0.638 ## Adjusted R2 0.634 -## Residual Std. Error 0.013 (df = 372) -## F Statistic 164.000*** (df = 4; 372) +## Residual Std. Error 0.013 (df = 373) +## F Statistic 164.300*** (df = 4; 373) ## =============================================== ## Note: *p<0.1; **p<0.05; ***p<0.01 ``` diff --git a/docs/index.html b/docs/index.html index 1a354090..53fd8e34 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,7 +24,7 @@ - + @@ -112,70 +112,6 @@ - @@ -424,11 +360,14 @@

Welcome

+
+

현재 개정판이 작업중에 있습니다. 2021년 2월 즈음 만나보실 수 있습니다.

+

R을 이용한 퀀트 투자 포트폴리오 만들기 구매 링크

본 페이지는 R을 이용한 퀀트 투자 포트폴리오 만들기의 웹사이트 입니다. 책의 수정 사항이 있을시 즉시 반영할 예정이며, 책에서 다루지 못했던 추가적인 내용도 지속적으로 업데이트 할 예정입니다.

@@ -438,7 +377,7 @@

Welcome

  • 2021년 1월 17일: 한국거래소가 사이트를 개편함에 따라 5장 [한국거래소의 산업별 현황 및 개별지표 크롤링] 부분을 새롭게 작성했습니다.

  • 2020년 1월 17일: 9장 [퀀트 전략을 이용한 종목선정 (기본)]과 10장 [퀀트 전략을 이용한 종목선정 (심화)]에서 재무제표를 이용한 전략의 경우, 1~4월에는 최근년도 데이터가 일부 종목에 대해서만 들어옵니다. 따라서 해당 기간에는 전전년도 데이터를 사용해야 하며, 이를 고려하도록 코드를 변경하였습니다.

  • 2020년 9월 26일: 5장 [한국거래소의 산업별 현황 및 개별지표 크롤링] 부분이 R 4.0 이상 버젼에서 잘 작동하지 않는 문제가 발생하고 있습니다. R 3.6 버젼에서 실행해주시길 바랍니다. (https://rstudio.cloud/ 에서는 원하는 R 버젼으로 프로그램 실행이 가능합니다.) 해당 버젼에서도 안될 경우 session을 재시작하면 제대로 실행이 됩니다.

  • -
  • 2020년 9월 26일: 4장 [기업공시채널에서 오늘의 공시 불러오기]에서 POST 부분의 url이 기존 htt://kind.krx.co.kr/disclosure/todaydisclosure.do* 에서 https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do 로 변경되었습니다.

  • +
  • 2020년 9월 26일: 4장 [기업공시채널에서 오늘의 공시 불러오기]에서 POST 부분의 url이 기존 http://kind.krx.co.kr/disclosure/todaydisclosure.do 에서 https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do 로 변경되었습니다.

  • 2020년 4월 27일: 9장 [금융 데이터 수집하기 (심화)]에 [DART의 Open API를 이용한 데이터 수집하기] 챕터를 추가하였습니다. 이를 통해 더욱 다양한 데이터를 수집할 수 있습니다.

  • 2020년 4월 7일: 각 페이지 하단에 질문/답변 기능을 추가하였습니다. 이제 블로그나 이메일, SNS 보다는 웹북에 질문을 남겨주시기 바랍니다.

  • 2020년 3월 22일: 11장 [포트폴리오 구성]에 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 추가하였습니다.

  • @@ -457,9 +396,18 @@

    지은이 소개

    머리말

    퀀트 투자 중 팩터에 관한 이론적 내용을 다룬 《SMART BETA(스마트 베타): 감정을 이기는 퀀트 투자》(김병규, 이현열, 워터베어프레스, 2017) 출간 이후 강의와 세미나를 통해많은 분들을 만났고, 공통적인 어려움을 느낄 수 있었습니다. 기관 투자자들이 손쉽게 데이터를 구할 수 있는 것과는 다르게, 일반 투자자들은 퀀트 투자를 하기 위한 데이터를 구하는 시작점부터 어려움을 겪는다는 것이었습니다.

    그러나 프로그래밍을 이용하면 일반 투자자들도 얼마든지 금융 데이터 수집 및 처리, 퀀트 모델 개발, 포트폴리오 분석 및 자동화 등이 가능합니다. 이 책을 읽는 독자분들이 스스로 이러한 퀀트 투자 프로세스를 만들 수 있기를 바라는 마음으로 책을 구성했습니다. 또한 실제 전문 투자자들이 사용하는 기술들도 포함했으니 책의 내용을 넘어 더욱 훌륭한 모델을 만드는 데 도움이 되시리라 생각합니다.

    -

    이 책에서 데이터 수집을 위해 주로 다루는 크롤링은 웹페이지의 데이터를 가져오는 것입니다. 따라서 페이지의 형태가 바뀌면 코드도 다시 작성해야 합니다. 책 발간 이후 참고자료로 사용된 페이지 중 형태가 바뀐곳이 많아 개정판을 작성하게 되었습니다. 또한 많은 문의가 있었던 DART 크롤링에 대한 내용도 추가했습니다.

    -

    앞으로도 페이지 변경 등 코드를 수정해야 하거나 추가된 내용이 있을 경우 책의 공식 페이지인 https://hyunyulhenry.github.io/quant_cookbook/ 에 즉각적으로 업데이트 할 예정입니다. -어느때보다 주식과 투자에 대한 관심이 뜨거워진 지금, 유행이라는 파도에 휩쓸리는 투자보다는 데이터를 이용한 객관적이고 장기적인 투자로 성공하시길 기원합니다.

    +

    이 책에서 데이터 수집을 위해 주로 다루는 크롤링은 웹페이지의 데이터를 가져오는 것입니다. 기존에 책 발간 이후 참고자료로 사용된 페이지 중 형태가 바뀐곳이 많아 개정판을 작성하게 되었습니다. 수정된 내역은 다음과 같습니다.

    +
      +
    1. 야후 파이낸스의 웹페이지 구조가 바뀌어 크롤링이 사실상 어렵게 되었고, 책 내용에서 제외하였습니다.
    2. +
    3. 한국거래소 사이트가 개편되어 해당 부분은 바뀐 페이지에 맞게 새로 작성하였습니다.
    4. +
    5. 일부 페이지가 크롤러의 접근을 막음에 따라 user_agent() 함수를 사용해 크롤링이 가능해지게 하였습니다.
    6. +
    7. 많은 문의가 있었던 DART 크롤링에 대한 내용을 추가했습니다.
    8. +
    9. 포트폴리오 구성 부분에 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 추가하였습니다.
    10. +
    11. ggplot2 패키지의 기본적인 사용법을 추가하였습니다.
    12. +
    13. 일부 코드를 수정하여 더욱 데이터 처리를 쉽게 하거나, 종목 선택을 강건하게 하였습니다.
    14. +
    +

    앞으로도 페이지 변경 등 코드를 수정해야 하거나 추가된 내용이 있을 경우 책의 공식 페이지인 https://hyunyulhenry.github.io/quant_cookbook/ 에 즉각적으로 업데이트 할 예정이며, 질문사항이 있을 경우 페이지에 남겨주시면 답변드리고 있습니다.

    +

    어느때보다 주식과 투자에 대한 관심이 뜨거워진 지금, 유행이라는 파도에 휩쓸리는 투자보다는 데이터를 이용한 객관적이고 장기적인 투자로 성공하시길 기원합니다.

    2021년 1월

    이현열()

    @@ -629,20 +577,6 @@

    세션정보

    }); - - diff --git a/docs/index.md b/docs/index.md index de05c8b5..58f5add4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,7 @@ --- title: "R을 이용한 퀀트 투자 포트폴리오 만들기" author: "이현열" -date: "2021-01-17" +date: "2021-01-19" output: bookdown::gitbook: includes: @@ -18,6 +18,11 @@ github-repo: hyunyulhenry/quant_cookbook +
    +현재 개정판이 작업중에 있습니다. 2021년 2월 즈음 만나보실 수 있습니다. +
    + + [R을 이용한 퀀트 투자 포트폴리오 만들기 구매 링크](https://book.naver.com/bookdb/book_detail.nhn?bid=15369836) 본 페이지는 **R을 이용한 퀀트 투자 포트폴리오 만들기**의 웹사이트 입니다. 책의 수정 사항이 있을시 즉시 반영할 예정이며, 책에서 다루지 못했던 추가적인 내용도 지속적으로 업데이트 할 예정입니다. @@ -34,7 +39,7 @@ github-repo: hyunyulhenry/quant_cookbook - 2020년 9월 26일: 5장 [한국거래소의 산업별 현황 및 개별지표 크롤링] 부분이 R 4.0 이상 버젼에서 잘 작동하지 않는 문제가 발생하고 있습니다. R 3.6 버젼에서 실행해주시길 바랍니다. (https://rstudio.cloud/ 에서는 원하는 R 버젼으로 프로그램 실행이 가능합니다.) 해당 버젼에서도 안될 경우 session을 재시작하면 제대로 실행이 됩니다. -- 2020년 9월 26일: 4장 [기업공시채널에서 오늘의 공시 불러오기]에서 POST 부분의 url이 기존 *htt*://kind.krx.co.kr/disclosure/todaydisclosure.do* 에서 *https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do* 로 변경되었습니다. +- 2020년 9월 26일: 4장 [기업공시채널에서 오늘의 공시 불러오기]에서 POST 부분의 url이 기존 *http://kind.krx.co.kr/disclosure/todaydisclosure.do* 에서 *https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do* 로 변경되었습니다. - 2020년 4월 27일: 9장 [금융 데이터 수집하기 (심화)]에 [DART의 Open API를 이용한 데이터 수집하기] 챕터를 추가하였습니다. 이를 통해 더욱 다양한 데이터를 수집할 수 있습니다. @@ -64,11 +69,20 @@ github-repo: hyunyulhenry/quant_cookbook 그러나 프로그래밍을 이용하면 일반 투자자들도 얼마든지 금융 데이터 수집 및 처리, 퀀트 모델 개발, 포트폴리오 분석 및 자동화 등이 가능합니다. 이 책을 읽는 독자분들이 스스로 이러한 퀀트 투자 프로세스를 만들 수 있기를 바라는 마음으로 책을 구성했습니다. 또한 실제 전문 투자자들이 사용하는 기술들도 포함했으니 책의 내용을 넘어 더욱 훌륭한 모델을 만드는 데 도움이 되시리라 생각합니다. -이 책에서 데이터 수집을 위해 주로 다루는 크롤링은 웹페이지의 데이터를 가져오는 것입니다. 따라서 페이지의 형태가 바뀌면 코드도 다시 작성해야 합니다. 책 발간 이후 참고자료로 사용된 페이지 중 형태가 바뀐곳이 많아 개정판을 작성하게 되었습니다. 또한 많은 문의가 있었던 DART 크롤링에 대한 내용도 추가했습니다. +이 책에서 데이터 수집을 위해 주로 다루는 크롤링은 웹페이지의 데이터를 가져오는 것입니다. 기존에 책 발간 이후 참고자료로 사용된 페이지 중 형태가 바뀐곳이 많아 개정판을 작성하게 되었습니다. 수정된 내역은 다음과 같습니다. + +1. 야후 파이낸스의 웹페이지 구조가 바뀌어 크롤링이 사실상 어렵게 되었고, 책 내용에서 제외하였습니다. +2. 한국거래소 사이트가 개편되어 해당 부분은 바뀐 페이지에 맞게 새로 작성하였습니다. +3. 일부 페이지가 크롤러의 접근을 막음에 따라 user_agent() 함수를 사용해 크롤링이 가능해지게 하였습니다. +4. 많은 문의가 있었던 DART 크롤링에 대한 내용을 추가했습니다. +5. 포트폴리오 구성 부분에 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 추가하였습니다. +6. ggplot2 패키지의 기본적인 사용법을 추가하였습니다. +7. 일부 코드를 수정하여 더욱 데이터 처리를 쉽게 하거나, 종목 선택을 강건하게 하였습니다. + +앞으로도 페이지 변경 등 코드를 수정해야 하거나 추가된 내용이 있을 경우 책의 공식 페이지인 https://hyunyulhenry.github.io/quant_cookbook/ 에 즉각적으로 업데이트 할 예정이며, 질문사항이 있을 경우 페이지에 남겨주시면 답변드리고 있습니다. -앞으로도 페이지 변경 등 코드를 수정해야 하거나 추가된 내용이 있을 경우 책의 공식 페이지인 https://hyunyulhenry.github.io/quant_cookbook/ 에 즉각적으로 업데이트 할 예정입니다. 어느때보다 주식과 투자에 대한 관심이 뜨거워진 지금, 유행이라는 파도에 휩쓸리는 투자보다는 데이터를 이용한 객관적이고 장기적인 투자로 성공하시길 기원합니다. - + 2021년 1월 이현열(leebisu@gmail.com) diff --git a/docs/search_index.json b/docs/search_index.json index 3000adeb..885a5d87 100644 --- a/docs/search_index.json +++ b/docs/search_index.json @@ -1,17 +1,3 @@ [ -["index.html", "R을 이용한 퀀트 투자 포트폴리오 만들기 Welcome 지은이 소개 머리말 이 책의 구성 이 책에서 다루지 않은 주제 도움이 될 만한 자료들 이 책의 지원 페이지 종목과 관련된 유의사항 세션정보", " R을 이용한 퀀트 투자 포트폴리오 만들기 이현열 2021-01-17 Welcome R을 이용한 퀀트 투자 포트폴리오 만들기 구매 링크 본 페이지는 R을 이용한 퀀트 투자 포트폴리오 만들기의 웹사이트 입니다. 책의 수정 사항이 있을시 즉시 반영할 예정이며, 책에서 다루지 못했던 추가적인 내용도 지속적으로 업데이트 할 예정입니다. 패스트캠퍼스에서 본 책의 내용을 바탕으로 강의가 진행중이니, 수강을 원하시는 분은 참조하시기 바랍니다. 강의 링크 책 발간 이후 업데이트 내용은 다음과 같습니다. 2021년 1월 17일: 한국거래소가 사이트를 개편함에 따라 5장 [한국거래소의 산업별 현황 및 개별지표 크롤링] 부분을 새롭게 작성했습니다. 2020년 1월 17일: 9장 [퀀트 전략을 이용한 종목선정 (기본)]과 10장 [퀀트 전략을 이용한 종목선정 (심화)]에서 재무제표를 이용한 전략의 경우, 1~4월에는 최근년도 데이터가 일부 종목에 대해서만 들어옵니다. 따라서 해당 기간에는 전전년도 데이터를 사용해야 하며, 이를 고려하도록 코드를 변경하였습니다. 2020년 9월 26일: 5장 [한국거래소의 산업별 현황 및 개별지표 크롤링] 부분이 R 4.0 이상 버젼에서 잘 작동하지 않는 문제가 발생하고 있습니다. R 3.6 버젼에서 실행해주시길 바랍니다. (https://rstudio.cloud/ 에서는 원하는 R 버젼으로 프로그램 실행이 가능합니다.) 해당 버젼에서도 안될 경우 session을 재시작하면 제대로 실행이 됩니다. 2020년 9월 26일: 4장 [기업공시채널에서 오늘의 공시 불러오기]에서 POST 부분의 url이 기존 htt://kind.krx.co.kr/disclosure/todaydisclosure.do* 에서 https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do 로 변경되었습니다. 2020년 4월 27일: 9장 [금융 데이터 수집하기 (심화)]에 [DART의 Open API를 이용한 데이터 수집하기] 챕터를 추가하였습니다. 이를 통해 더욱 다양한 데이터를 수집할 수 있습니다. 2020년 4월 7일: 각 페이지 하단에 질문/답변 기능을 추가하였습니다. 이제 블로그나 이메일, SNS 보다는 웹북에 질문을 남겨주시기 바랍니다. 2020년 3월 22일: 11장 [포트폴리오 구성]에 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 추가하였습니다. 2020년 3월 22일: 8장 [데이터 분석 및 시각화하기]에서 종목정보 시각화 이전에 ggplot() 기초 챕터를 추가하였습니다. 이로써 기존에 해당 패키지를 모르던 분도 쉽게 배울수 있도록 하였습니다. 2020년 3월 15일: 6장 [금융 데이터 수집하기 (심화)]에 [재무제표 및 가치지표 크롤링]에서 사용하는 페이지가 크롤러의 접근을 막음에 따라, user_agent() 를 이용하여 웹브라우저 인자를 추가해 주었습니다. 2020년 1월 19일: 5장의 [거래소 데이터 정리하기] 부분에서 substr() 함수 대신 stringr 패키지의 str_sub() 함수를 사용하여 코드를 훨씬 간결하게 표현했습니다. 또한 종목코드 끝이 0이 아닐 경우 우선주인 점을 이용하여 더욱 쉽게 클렌징 처리를 하였습니다. 2020년 1월 18일: 야후 파이낸스 웹페이지의 구조가 바뀌어 동적 크롤링을 통해서만 데이터 수집이 가능하게 되었습니다. 이는 본 책에서는 다루지 않으므로, 6장 [금융 데이터 수집하기 (심화)]에서 해당 부분을 삭제하였습니다. 지은이 소개 이현열 한양대학교에서 경영학을 전공하고, 카이스트 대학원에서 금융공학 석사 학위를 받았다. 졸업 후 증권사에서 주식운용, 자산운용사에서 퀀트 포트폴리오 매니저, 보험사에서 데이터 분석 업무를 거쳐, 현재 핀테크 스타트업에서 퀀트 및 자산배분 리서치 업무를 하고 있다. 평소 꾸준한 SNS와 블로그 활동으로 퀀트 아이디어 및 백테스트 결과 등을 공유하면서 퀀트 투자의 대중화를 위해 노력하고 있다. 한양대학교 재무금융 박사 과정을 수료했으며, 패스트캠퍼스에서 R과 퀀트 투자 강의를 맡고 있다. 지은 책으로는 《스마트베타》(2017)가 있다. 머리말 퀀트 투자 중 팩터에 관한 이론적 내용을 다룬 《SMART BETA(스마트 베타): 감정을 이기는 퀀트 투자》(김병규, 이현열, 워터베어프레스, 2017) 출간 이후 강의와 세미나를 통해많은 분들을 만났고, 공통적인 어려움을 느낄 수 있었습니다. 기관 투자자들이 손쉽게 데이터를 구할 수 있는 것과는 다르게, 일반 투자자들은 퀀트 투자를 하기 위한 데이터를 구하는 시작점부터 어려움을 겪는다는 것이었습니다. 그러나 프로그래밍을 이용하면 일반 투자자들도 얼마든지 금융 데이터 수집 및 처리, 퀀트 모델 개발, 포트폴리오 분석 및 자동화 등이 가능합니다. 이 책을 읽는 독자분들이 스스로 이러한 퀀트 투자 프로세스를 만들 수 있기를 바라는 마음으로 책을 구성했습니다. 또한 실제 전문 투자자들이 사용하는 기술들도 포함했으니 책의 내용을 넘어 더욱 훌륭한 모델을 만드는 데 도움이 되시리라 생각합니다. 이 책에서 데이터 수집을 위해 주로 다루는 크롤링은 웹페이지의 데이터를 가져오는 것입니다. 따라서 페이지의 형태가 바뀌면 코드도 다시 작성해야 합니다. 책 발간 이후 참고자료로 사용된 페이지 중 형태가 바뀐곳이 많아 개정판을 작성하게 되었습니다. 또한 많은 문의가 있었던 DART 크롤링에 대한 내용도 추가했습니다. 앞으로도 페이지 변경 등 코드를 수정해야 하거나 추가된 내용이 있을 경우 책의 공식 페이지인 https://hyunyulhenry.github.io/quant_cookbook/ 에 즉각적으로 업데이트 할 예정입니다. 어느때보다 주식과 투자에 대한 관심이 뜨거워진 지금, 유행이라는 파도에 휩쓸리는 투자보다는 데이터를 이용한 객관적이고 장기적인 투자로 성공하시길 기원합니다. 2021년 1월 이현열(leebisu@gmail.com) 이 책의 구성 이 책은 API와 크롤링을 통한 금융 데이터 수집, 투자 종목 선택 및 포트폴리오 구성, 백테스트와 성과 분석으로 이루어져 있습니다. CHAPTER 1 퀀트 투자의 심장: 데이터와 프로그래밍 퀀트 투자란 무엇인지, 왜 프로그래밍이 필요한지, 여러 언어 중 R을 사용해야 하는 이유에 대해 살펴봅니다. CHAPTER 2 크롤링을 위한 기본 지식 크롤링을 통한 데이터 수집에 앞서 인코딩, 웹의 동작 방식, HTML에 대한 기본 정보와 데이터 처리에 편리한 R 코드를 살펴봅니다. CHAPTER 3 API를 이용한 데이터 수집 API를 통한 데이터 수집과 getSymbols() 함수의 사용 방법에 대해 살펴봅니다. CHAPTER 4 크롤링 이해하기 크롤링이 무엇인가에 대해 살펴보며, GET과 POST 방식을 이용한 간단한 예제를 살펴봅니다. CHAPTER 5 금융 데이터 수집하기 기본 한국거래소에서 제공하는 데이터를 크롤링하는 방법, 섹터의 구성종목을 수집하는 방법에 대해 살펴봅니다. CHAPTER 6 금융 데이터 수집하기 심화 퀀트 투자의 핵심 자료인 수정주가, 재무제표 및 가치지표를 크롤링하는 방법을 살펴봅니다. CHAPTER 7 데이터 정리하기 앞에서 수집한 주가, 재무제표, 가치지표를 하나의 파일로 정리하는 방법을 살펴봅니다. CHAPTER 8 데이터 분석 및 시각화하기 수집한 데이터를 바탕으로 dplyr 패키지를 이용한 데이터 분석 및 ggplot2 패키지를 이용한 데이터 시각화, 인터랙티브 그래프를 나타내는 방법을 살펴봅니다. CHAPTER 9 퀀트 전략을 이용한 종목 선정 기본 베타에 대한 이해 및 기본적 팩터인 저변동성, 모멘텀, 밸류, 퀄리티를 이용한 종목 선정에 대해 살펴봅니다. CHAPTER 10 퀀트 전략을 이용한 종목 선정 심화 단순 종목 선정을 넘어 실무에서 사용되는 섹터 중립 포트폴리오 및 이상치 제거와 팩터 결합 방법, 마법공식 및 멀티팩터에 대해 살펴봅니다. CHAPTER 11 포트폴리오 구성 최적화 패키지를 이용한 포트폴리오 구성에서 가장 대중적으로 사용되는 최소분산 포트폴리오, 최대분산효과 포트폴리오, 위험균형 포트폴리오를 구현합니다. 또한 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 살펴봅니다. CHAPTER 12 포트폴리오 백테스트 Return.portfolio() 함수를 이용한 백테스트 방법에 대해 살펴보겠습니다. CHAPTER 13 성과 및 위험 평가 포트폴리오의 수익률을 바탕으로 성과 및 위험 평가에 사용되는 각종 지표에 대해 알아보며, 4팩터 회귀분석을 통한 요인 분석을 실행합니다. 이 책에서 다루지 않은 주제 이 책은 R을 기본적으로 사용할 줄 아는 독자를 대상으로 작성되었습니다. 따라서 내용의 효율적 전달을 위해 R과 R Studio 설치, 기초적인 프로그래밍 등의 내용은 생략했습니다. 따라서 프로그래밍을 처음 접하는 독자라면 프로그래밍 기초를 먼저 익히신후 본 책을 읽으시길 추천드립니다. 또한 이 책에서는 프로그램 언어로 R을 이용했기 때문에 Python 혹은 다른 언어를 사용하는 분들에게는 직접적으로 도움이 되지 않을 수 있다고 생각할 수 있습니다. 그러나 투자에 필요한 금융 데이터 수집을 어디서 어떻게 하는지, 종목 선택을 어떻게 하고 포트폴리오를 어떻게 구성하는지에 대한 이론적 내용을 이해한 후 본인들이 사용하는 언어로 구현해보는 것도 좋은 도전이 될 것입니다. 도움이 될 만한 자료들 먼저 팩터 투자와 관련하여 심화된 내용을 알고 싶은 분은 저의 이전 책 및 책에서 인용된 논문을 읽어볼 것을 권합니다. R 프로그래밍과 관련하여 기초부터 tidyverse 패키지까지 이해하는 데 도움이 될만한 책 목록은 다음과 같습니다. 《SMART BETA(스마트 베타): 감정을 이기는 퀀트 투자》(김병규, 이현열, 워터베어프레스, 2017) 《손에 잡히는 R 프로그래밍》(가렛 그롤먼드, 한빛미디어, 2015) 《R Cookbook》(폴 티터, 인사이트, 2012) 《R을 활용한 데이터 과학》(해들리 위컴, 개럿 그롤문드, 인사이트, 2019) 《Do it! 쉽게 배우는 R 데이터 분석》(김영우, 이지스퍼블리싱, 2017) 《ggplot2: R로 분석한 데이터를 멋진 그래픽으로》(해들리 위컴, 프리렉, 2017) 이 책의 지원 페이지 이 책은 R의 bookdown 패키지로 작성되어 웹페이지 및 GitHub 저장소에 공유되어 있습니다. 따라서 책에 포함되어 있는 각종 코드를 웹페이지에 방문하여 얻으실 수 있습니다. 웹페이지: https://hyunyulhenry.github.io/quant_cookbook GitHub 저장소: https://github.com/hyunyulhenry/quant_cookbook 크롤링 대상 웹페이지의 구조가 바뀌어 코드의 수정이 필요할 경우 즉각적으로 반영할 것이며, 인쇄본에서 다루지 않은 내용도 추가적으로 업데이트될 예정입니다. 또한 bookdown 패키지를 이용하여 책을 집필하고자 하는 분들에게도 많은 도움이 될 것입 니다. 이 외에도 퀀트 투자 혹은 R을 이용한 투자 활용법 등의 내용은 저자의 블로그에 많은글들이 있으니 참조하기 바랍니다. Henry’s Quantopia: http://henryquant.blogspot.com 종목과 관련된 유의사항 팩터 모델을 이용한 종목 선택과 관련된 CHAPTER에서는 해당 조건으로 선택된 종목들이 나열되어 있습니다. 그러나 이는 해당 종목에 대한 매수 추천이 아님을 밝히며, 데이터를 받은 시점의 종목이기에 독자 여러분이 책을 읽는 시점에서 선택된 종목과는 상당한 차이가 있습니다. 또한 이 책에서 다루는 모델을 이용하여 투자를 할 경우, 이로 인한 이익과 손해는 본인에게 귀속됨을 알립니다. 세션정보 본 책에서 사용된 R 버젼 및 각종 정보는 다음과 같습니다. ## R version 3.6.3 (2020-02-29) ## Platform: x86_64-pc-linux-gnu (64-bit) ## Running under: Ubuntu 16.04.7 LTS ## ## Matrix products: default ## BLAS: /usr/lib/atlas-base/atlas/libblas.so.3.0 ## LAPACK: /usr/lib/atlas-base/atlas/liblapack.so.3.0 ## ## locale: ## [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C ## [3] LC_TIME=C.UTF-8 LC_COLLATE=C.UTF-8 ## [5] LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8 ## [7] LC_PAPER=C.UTF-8 LC_NAME=C ## [9] LC_ADDRESS=C LC_TELEPHONE=C ## [11] LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C ## ## attached base packages: ## [1] stats graphics grDevices utils datasets ## [6] methods base ## ## other attached packages: ## [1] showtext_0.9 showtextdb_3.0 sysfonts_0.8.1 ## ## loaded via a namespace (and not attached): ## [1] compiler_3.6.3 magrittr_1.5 bookdown_0.20 ## [4] htmltools_0.5.0 tools_3.6.3 yaml_2.2.1 ## [7] stringi_1.5.3 rmarkdown_2.3 knitr_1.30 ## [10] stringr_1.4.0 digest_0.6.25 xfun_0.17 ## [13] rlang_0.4.7 evaluate_0.14 "], -["퀀트-투자의-심장-데이터와-프로그래밍.html", "Chapter 1 퀀트 투자의 심장: 데이터와 프로그래밍 1.1 데이터 구하기 1.2 퀀트 투자와 프로그래밍 1.3 R 프로그램 1.4 퀀트 투자에 유용한 R 패키지", " Chapter 1 퀀트 투자의 심장: 데이터와 프로그래밍 몇 년 전까지만 하더라도 퀀트 투자는 일반 투자자들에게 매우 낯선 영역이었지만, 최근에는 각종 커뮤니티와 매체를 통해 많은 사람들에게 익숙한 단어가 되었습니다. 퀀트 투자에서 ‘퀀트’란 모형을 기반으로 금융상품의 가격을 산정하거나, 이를 바탕으로투자를 하는 사람을 말합니다. 퀀트(Quant)라는 단어가 ‘계량적’을 의미하는 퀀티터티브(Quantitative)의 앞 글자를 따왔음을 생각하면 쉽게 이해가 될 것입니다. 일반적으로 투자자들이 산업과 기업을 분석해 가치를 매기는 정성적인 투자법과는 달리, 퀀트 투자는 수학과 통계를 기반으로 전략을 만들고 이를 바탕으로 투자하는 정량적인 투자법을 의미합니다. 이처럼 데이터를 수집·가공한 후 이를 바탕으로 모델을 만들고 실행하는 단계는 데이터 과학의 업무 흐름도와 매우 유사합니다. 해들리 위컴 (Hadley Wickham)(Grolemund and Wickham 2018)에 따르면 데이터 과학의 업무 과정은 그림 1.1과 같습니다. 그림 1.1: 데이터 과학 업무 과정 데이터 과학자들은 프로그래밍을 통해 데이터를 불러온 후 이를 정리하고, 원하는 결과를 찾기 위해 데이터를 변형하거나 시각화하고 모델링합니다. 이러한 결과를 바탕으로 타인과 소통하는 일련의 과정을 거칩니다. 퀀트 투자의 단계 역시 이와 매우 유사합니다. 투자에 필요한 주가, 재무제표 등의 데이터를 수집해 정리한 후 필요한 지표를 얻기 위해 가공합니다. 그 후 각종 모형을 이용해 투자 종목을 선택하거나 백테스트를 수행하며, 이를 바탕으로 실제로 투자하고 성과를 평가합니다. 따라서 퀀트 투자는 데이터 과학이 금융에 응용된 사례라고도 볼수 있으며, 퀀트 투자의 중심에는 데이터와 프로그래밍이 있습니다. 이 책에서도 데이터 과학의 업무 단계와 동일하게 데이터 불러오기, 데이터별로 정리하고 가공하기, 시각화를 통해 데이터의 특징 파악하기, 퀀트 모델을 이용해 종목 선택하기, 백테스트를 실시한 후 성과 및 위험 평가하기에 대해 알아보겠습니다. 이에 앞서 이 CHAPTER에서는 퀀트 투자의 심장이라고 할 수 있는 데이터를 어떻게 얻을 수 있 는지, 왜 프로그래밍을 해야 하는지, 그중에서도 R이 무엇인지에 대해 간략히 살펴보겠습니다. 1.1 데이터 구하기 퀀트 투자에 필요한 데이터는 여러 데이터 제공업체의 서비스를 이용해서 매우 쉽게 구할 수 있습니다. 해외 데이터 수집에는 블룸버그 혹은 Factset, 국내 데이터 수집에는 DataGuide가 흔히 사용됩니다. 물론 비용을 더 지불한다면 단순 데이터 수집뿐만 아니라 즉석에서 백테스트 및 성과 평가까지 가능합니다. Factset에서 판매하는 Alpha Testing 혹은 S&P Global에서 판매하는 ClariFI(그림 1.2)를 사용한다면, 전 세계 주식을 대상으로 원하는 전략의 백테스트 결과를 마우스 몇 번 클릭해서 얻을 수 있습니다. 그림 1.2: ClariFI®의 백테스트 기능 데이터 제공업체를 이용하는 방법의 최대 단점은 바로 비용입니다. 블룸버그 단말기는 1년 사용료가 대리 한 명의 연봉과 비슷해, 흔히 ‘블대리’라고 부르기도 합니다. 국내 데이터 업체의 사용료는 이보다 저렴하기는 하지만, 역시 1년 사용료가 수백만 원 정도로, 일반 개인 투자자가 감당하기에는 부담이 됩니다. 해외데이터는 Quandl1이나 tiingo2등의 업체가 제공하는 서비스를 이용하면 상대적으로 저렴한 가격에 데이터를 구할 수 있습니다. 물론 대형 데이터 제공업체에 비해 데 이터의 종류가 적고 기간은 짧은 편이지만, 대부분의 일반 투자자가 사용하기에는 충분한 데이터를 얻을 수 있습니다. tiingo에서는 전 세계 64,386개 주식의 30년 이상 가격 정보, 21,352개 주식의 12년 이상 재무정보를 월 $10에 받을 수 있으며, 한정된 종목과 용량에 대해서는 무료로 데이터를 받을 수도 있습니다. 더군다나 API를 통해 프로그램 내에서 직접 데이터를 받을 수 있어 편리합니다. 그러나 아쉽게도 이러한 데이터에서 한국 시장의 정보는 소외되어 있습니다. 따라서 돈을 들이지 않고 국내 데이터를 얻기 위해서는 직접 발품을 파는 수밖에 없습니다.야후 파이낸스3 혹은 국내 금융 웹사이트에서 제공하는 정보를 크롤링해 데이터를 수집할 수 있습니다. 그림 1.3: NAVER 금융 제공 재무정보 이러한 정보를 잘만 활용한다면 장기간의 주가 및 재무정보를 무료로 수집할 수 있습니다. 물론 데이터 제공업체가 제공하는 깔끔한 형태의 데이터가 아니므로 클렌징 작업이 필요하고 상장폐지된 기업의 데이터를 구하기 힘들다는 단점이 있습니다. 그러나 비용이 들지 않는 데다 현재 시점에서 투자 종목을 선택할 때는 상장폐지된 기업의 정 보가 필요하지 않는다는 점을 고려하면 이는 큰 문제가 되지 않습니다. 1.2 퀀트 투자와 프로그래밍 우리가 구한 데이터는 연구나 투자에 바로 사용할 수 있는 형태로 주어지는 경우가 거의 없습니다. 따라서 데이터를 목적에 맞게 처리하는 과정을 거쳐야 하며, 이를 흔히 데이터 클렌징 작업이라고 합니다. 또한 정제된 데이터를 활용한 투자 전략의 백테스트나 종목 선정을 위해서 프로그래밍은 필수입니다. 물론 모든 퀀트 투자에서 프로그래 밍이 필수인 것은 아닙니다. 엑셀을 이용해도 간단한 형태의 백테스트 및 종목 선정은 얼마든지 가능합니다. 그러나 응용성 및 효율성의 측면에서 엑셀은 매우 비효율적입니다. 데이터를 수집하고 클렌징 작업을 할 때 대상이 몇 종목 되지 않는다면 엑셀을 이용해도 충분히 가능합니다. 그러나 종목 수가 수천 종목을 넘어간다면 데이터를 손으로 일일이 처리하기가 사실상 불가능에 가깝습니다. 이러한 단순 반복 작업은 프로그래밍을 이용한다면 훨씬 효율적으로 수행할 수 있습니다. 백테스트에서도 프로그래밍이 훨씬 효율적입니다. 과거 12개월 누적수익률이 높은 종목에 투자하는 모멘텀 전략의 백테스트를 한다고 가정합시다. 처음에는 엑셀로 백테스트를 하는 것이 편하다고 생각할 수 있습니다. 그러나 만일 12개월이 아닌 6개월 누적 수익률로 백테스트를 하고자 한다면 어떨까요? 엑셀에서 다시 6개월 누적수익률을 구하기 위해 명령어를 바꾸고 드래그하는 작업을 반복해야 할 것입니다. 그러나 프로그래밍을 이용한다면 n = 12였던 부분을 n = 6으로 변경한 후 단지 클릭 한 번만으로 새로운 백테스트가 완료됩니다. 전체 데이터가 100MB 정도라고 가정할 때, 투자 전략이 계속해서 늘어날 경우는 어떨까요? 엑셀에서 A라는 전략을 백테스트하기 위해서는 해당 데이터로 작업한 후 저장할 것입니다. 그 후 B라는 전략을 새롭게 백테스트하려면 해당 데이터를 새로운 엑셀 파일에 복사해 작업한 후 다시 저장해야 합니다. 결과적으로 10개의 전략만 백테스트 하더라도 100MB짜리 엑셀 파일이 10개, 즉 1GB 정도의 엑셀 파일이 쌓이게 됩니다. 만일 데이터가 바뀔 경우 다시 10개 엑셀 시트의 데이터를 일일이 바꿔야 하는 귀찮음도 감수해야 합니다. 물론 하나의 엑셀 파일 내에서 모든 전략을 수행할 수도 있지만, 이러한 경우 속도가 상당히 저하되는 문제가 있습니다. 프로그래밍을 이용한다면 어떨까요? 백테스트를 수행하는 프로그래밍 스크립트는 불과 몇 KB에 불과하므로, 10개의 전략에 대한 스크립트 파일을 합해도 1MB가 되지 않습니다. 데이터가 바뀌더라도 원본 데이터 파일 하나만 수정해주면 됩니다. 물론 대부분의 사람들에게 프로그래밍은 낯선 도구입니다. 그러나 퀀트 투자에 필요한 프로그래밍은 매우 한정적이고 몇 가지 기능을 반복적으로 쓰기 때문에 몇 개의 단어와 구문만 익숙해지면 사용하는 데 큰 어려움이 없습니다. 또한 전문 개발자들의 프로그래밍에 비하면 상당히 쉬운 수준이므로, 비교적 빠른 시간 내에 원하는 전략을 테스트하고 수행하는 정도의 능력을 갖출 수도 있습니다. 1.3 R 프로그램 인간이 사용하는 언어의 종류가 다양하듯이, 프로그래밍 언어의 종류 역시 다양합니다. 대략 700여 개 이상의 프로그래밍 언어 중4 대중적으로 사용하는 언어는 그리 많지 않으므로, 대중성과 효율성을 위해 사용량이 많은 언어를 이용하는 것이 좋습니다. 그림 1.5는 프로그래밍 언어의 사용 순위5입니다. 이 중 R과 Python은 매우 대중적인 언어입니다. 해당 언어가 많이 사용되는 가장 큰 이유는 무료인 데다 일반인들이 사용하기에도 매우 편한 형태로 구성되어 있기 때문입니다. 그림 1.4: 2017년 기준 프로그래밍 언어 사용 통계 순위 이러한 프로그래밍 언어 중 이 책에서는 R을 이용합니다. R의 장점은 무료라는 점 이외에도 타 언어와 비교할 수 없이 다양한 패키지가 있다는 점입니다. R은 두터운 사용자층을 기반으로 두고 있어 상상할 수 없을 정도로 패키지가 많으며, 특히 통계나 계량분석과 관련된 패키지는 독보적이라고 할 수 있습니다. 그림 1.5: CRAN 등록 패키지 수 1.4 퀀트 투자에 유용한 R 패키지 R에는 여러 연구자와 실무자의 헌신적인 노력 덕분에 금융 연구와 퀀트 투자를 위한 다양한 패키지가 만들어져 있으며, 누구나 무료로 이용할 수 있습니다. 이 책에서 사용되는 패키지 중 중요한 것은 다음과 같습니다. 각 패키지에 대한 자세한 설명은 구글에서 패키지명을 검색한 후 PDF 파일을 통해 확인할 수 있습니다. quantmod: 이름에서 알 수 있듯이 퀀트 투자에 매우 유용한 패키지입니다. API를 이용해 데이터를 다운로드하는 getSymbols() 함수는 대단히 많이 사용됩니다. 이 외에도 볼린저밴드, 이동평균선, 상대강도지수(RSI) 등 여러 기술적 지표를 주가 차트에 나타낼 수도 있습니다. PerformanceAnalytics: 포트폴리오의 성과와 위험을 측정하는 데 매우 유용한 패키지입니다. Return.portfolio() 함수는 포트폴리오 백테스트에 필수적인 함수입니다. xts: 기본적으로 금융 데이터는 시계열 형태이며, xts 패키지는 여러 데이터를 시계열 형태(eXtensible TimeSeries)로 변형해줍니다. 일별 수익률을 월별 수익률 혹은 연도별 수익률로 변환하는 apply.monthly()와 apply.yearly() 함수, 데이터들의 특정 시점을 찾아주는 endpoints() 함수 역시 백테스트에 필수적으로 사용되는 함수입니다. 이 패키지는 PerformanceAnalytics 패키지 설치 시 자동으로 설치됩니다. zoo: zoo 패키지 역시 시계열 데이터를 다루는 데 유용한 함수가 있습니다. rollapply() 함수는 apply() 함수를 전체 데이터가 아닌 롤링 윈도우 기법으로 활용할 수 있게 해주며, NA 데이터를 채워주는 na.locf() 함수는 시계열 데이터의 결측치를 보정할 때 매우 유용합니다. httr & rvest: 데이터를 웹에서 수집하기 위해서는 크롤링이 필수이며, httr과 rvest는 크롤링에 사용되는 패키지입니다. httr은 http의 표준 요청을 수행하는 패키지로서 단순히 데이터를 받는 GET() 함수와 사용자가 필요한 값을 선택해 요청하는 POST() 함수가 대표적으로 사용됩니다. rvest는 HTML 문서의 데이터를 가져오는 패키지이며, 웹페이지에서 데이터를 크롤링한 후 원하는 데이터만 뽑는데 필요한 여러 함수가 포함되어 있습니다. dplyr: 데이터 처리에 특화되어 R을 이용한 데이터 과학 분야에서 많이 사용되는 패키지입니다. C++로 작성되어 매우 빠른 처리 속도를 보이며, API나 크롤링을 통해 수집한 데이터들을 정리할 때도 매우 유용합니다. ggplot2: 데이터를 시각화할 때 가장 많이 사용되는 패키지입니다. 물론 R에서 기본적으로 내장된 plot() 함수를 이용해도 시각화가 가능하지만, 해당 패키지를 이용하면 훨씬 다양하고 깔끔하게 데이터를 그림으로 표현할 수 있습니다. 이 외에도 이 책에서는 다양한 패키지를 사용했으며, 아래의 코드를 실행하면 설치되지 않은 패키지를 설치할 수 있습니다. pkg = c('magrittr', 'quantmod', 'rvest', 'httr', 'jsonlite', 'readr', 'readxl', 'stringr', 'lubridate', 'dplyr', 'tidyr', 'ggplot2', 'corrplot', 'dygraphs', 'highcharter', 'plotly', 'PerformanceAnalytics', 'nloptr', 'quadprog', 'RiskPortfolios', 'cccp', 'timetk', 'broom', 'stargazer', 'timeSeries') new.pkg = pkg[!(pkg %in% installed.packages()[, "Package"])] if (length(new.pkg)) { install.packages(new.pkg, dependencies = TRUE)} References "], -["크롤링을-위한-기본-지식.html", "Chapter 2 크롤링을 위한 기본 지식 2.1 인코딩의 이해와 R에서 UTF-8 설정하기 2.2 웹의 동작 방식 2.3 HTML과 CSS This is page heading Page heading: size 1 Page heading: size 2 Unordered List Ordered List Major Stock Indices and US ETF a tag & href attribute img tag & src attribute S&P 500 Dow Jones Industrial Average NASDAQ Composite My Header 2.4 파이프 오퍼레이터(%>%) 2.5 오류에 대한 예외처리", " Chapter 2 크롤링을 위한 기본 지식 프로그래밍에 익숙한 분들도 크롤링은 생소한 경우가 많습니다. 기본적인 프로그래밍에 관한 책과 강의가 굉장히 많지만 크롤링을 다루는 자료는 접하기 힘들기 때문입니다. 크롤링은 기계적인 단계가 많기 때문에 조금만 연습해도 활용할 수 있는 기술입니다. 그러나 복잡한 웹페이지나 데이터 내용을 수집하려면 인코딩, 통신 구조에 대한 지 식이 필요할 때가 있습니다. 이 CHAPTER에서는 크롤링을 하기 위해 사전에 알고 있으면 도움이 되는 인코딩, 웹의 동작 방식, HTML과 CSS에 대해 알아보겠습니다. 그리고 실제 크롤링 시 유용한 파이프 오퍼레이터와 오류에 대한 예외처리도 알아보겠습니다. 2.1 인코딩의 이해와 R에서 UTF-8 설정하기 2.1.1 인간과 컴퓨터 간 번역의 시작, ASCII R에서 스크립트를 한글로 작성해 저장한 후 이를 다시 불러올 때, 혹은 한글로 된 데이터를 크롤링하면 오류가 뜨거나 읽을 수 없는 문자로 나타나는 경우가 종종 있습니다. 이는 한글 인코딩 때문에 발생하는 문제이며, 이러한 현상을 흔히 ‘인코딩이 깨졌다’라고 표현합니다. 인코딩이란 사람이 사용하는 언어를 컴퓨터가 사용하는 0과 1로 변환하는 과정을 말하며, 이와 반대의 과정을 디코딩이라고 합니다. 이렇듯 사람과 컴퓨터 간의 언어를 번역하기 위해 최초로 사용된 방식이 아스키(ASCII: American Standard Code for Information Interchange)입니다. 0부터 127까지 총 128개 바이트에 알파벳과 숫자, 자주 사용되는 특수문자 값을 부여하고, 문자가 입력되면 이에 대응되는 바이트가 저장됩니다. 그러나 아스키의 ‘American’이라는 이름에서 알 수 있듯이 이는 영어의 알파벳이 아닌 다른 문자를 표현하는 데 한계가 있으며, 이를 보완하기 위한 여러 방법이 나오게 되었습니다. 그림 1.3: 아스키 코드 표 2.1.2 한글 인코딩 방식의 종류 인코딩에 대한 전문적인 내용은 이 책의 범위를 넘어가며, 크롤링을 위해서는 한글을 인코딩하는 데 쓰이는 EUC-KR과 CP949, UTF-8 정도만 이해해도 충분합니다. 만일 ‘알’이라는 단어를 인코딩한다면 어떤 방법이 있을까요? 먼저 ‘알’이라는 문자 자체에 해당하는 코드를 부여해 나타내는 방법이 있습니다. 아니면 이를 구성하는 모음과 자음을 나누어 ㅇ, ㅏ, ㄹ 각각에 해당하는 코드를 부여하고 이를 조합할 수도 있습니다. 전자와 같이 완성된 문자 자체로 나타내는 방법을 완성형, 후자와 같이 각 자모로 나타내는 방법을 조합형이라고 합니다. 한글 인코딩 중 완성형으로 가장 대표적인 방법은 EUC-KR입니다. EUC-KR은 현대 한글에서 많이 쓰이는 문자 2,350개에 번호를 붙인 방법입니다. 그러나 2,350개 문자로 모든 한글 자모의 조합을 표현하기 부족해, 이를 보완하고자 마이크로소프트가 도입한 방법이 CP949입니다. CP949는 11,720개 한글 문자에 번호를 붙인 방법으로 기존 EUC-KR보다 나타낼 수 있는 한글의 개수가 훨씬 많아졌습니다. 윈도우의 경우 기본 인코딩이 CP949로 되어 있습니다. 조합형의 대표적 방법인 UTF-8은 모음과 자음 각각에 코드를 부여한 후 조합해 한글을 나타냅니다. 조합형은 한글뿐만 아니라 다양한 언어에 적용할 수 있다는 장점이 있어 전 세계 웹페이지의 대부분이 UTF-8로 만들어지고 있습니다. 그림 2.1: 웹페이지에서 사용되는 인코딩 비율 2.1.3 R에서 UTF-8 설정하기 윈도우에서는 기본 인코딩이 CP949로 이루어져 있으며, 일부 국내 웹사이트는 EUC-KR로 인코딩이 된 경우도 있습니다. 반면 R의 여러 함수는 인코딩이 UTF-8로 이루어져 있어, 인코딩 방식의 차이로 인해 스크립트 작성 및 크롤링 과정에서 오류가 발생하는 경우가 종종 있습니다. 만일 CP949 인코딩을 그대로 사용하면 미리 저장되었던 한글 스크립트가 깨져 나오는 일이 발생할 수 있습니다. 이를 방지하기 위해 그림 2.2과 같이 기본 인코딩을 UTF-8로 변경해주는 것이 좋습니다. R Studio의 [Tools → Global Options] 메뉴에서 [Code → Saving] 항목 중 [Default text encodings] 항목을 통해 기본 인코딩을 UTF-8로 변경합니다. 그림 2.2: 인코딩 변경 해당 방법으로도 해결되지 않는다면 그림 2.3와 같이 [File → Reopen with Encoding] 메뉴에서 [UTF-8] 항목을 선택하고 [Set as default encoding for source files] 항목을 선택한 후 [OK]를 클릭합니다. UTF-8로 인코딩이 설정된 후 파일을 다시 엽니다. 그림 2.3: 인코딩 변경 후 재시작 2.2 웹의 동작 방식 크롤링은 웹사이트의 정보를 수집하는 과정입니다. 따라서 웹이 어떻게 동작하는지 이해할 필요가 있습니다. 그림 2.4: 웹 환경 구조 먼저 클라이언트란 여러분의 데스크톱이나 휴대폰과 같은 장치와 크롬이나 파이어폭스와 같은 소프트웨어를 의미합니다. 서버는 웹사이트와 앱을 저장하는 컴퓨터를 의미합니다. 클라이언트가 특정 정보를 요구하는 과정을 ‘요청’이라고 하며, 서버가 해당 정보를 제공하는 과정을 ‘응답’이라고 합니다. 그러나 클라이언트와 서버가 연결되어 있지 않다면 둘 사이에 정보를 주고받을 수 없으며, 이를 연결하는 공간이 바로 인터넷입니다. 또한 건물에도 고유의 주소가 있는 것처럼, 각 서버에도 고유의 주소가 있는데 이것이 인터넷 주소 혹은 URL입니다. 여러분이 네이버에서 경제 기사를 클릭하는 경우를 생각해봅시다. 클라이언트는 사용자인 여러분이고, 서버는 네이버이며, URL은 www.naver.com이 됩니다. 경제 기사를 클릭하는 과정이 요청이며, 클릭 후 해당 페이지를 보여주는 과정이 응답입니다. 2.2.1 HTTP 클라이언트가 각기 다른 방법으로 데이터를 요청한다면, 서버는 해당 요청을 알아듣지 못할 것입니다. 이를 방지하기 위해 규정된 약속이나 표준에 맞추어 데이터를 요청해야 합니다. 이러한 약속을 HTTP(HyperText Transfer Protocol)라고 합니다. 클라이언트가 서버에게 요청의 목적이나 종류를 알리는 방법을 HTTP 요청 방식(HTTP Request Method)이라고 합니다. HTTP 요청 방식은 크게 표 2.1와 같이 GET, POST, PUT, DELETE라는 네 가지로 나눌 수 있지만 크롤링에는 GET과 POST 방식이 대부분 사용되므로 이 두 가지만 알아도 충분합니다. GET 방식과 POST 방식의 차이 및 크롤링 방법은 CHAPTER 4에서 자세하게 다루겠습니다. 표 2.1: HTTP 요청 방식과 설명 요청방식 주소 GET 특정 정보 조회 POST 새로운 정보 등록 PUT 기존 특정 정보 갱신 DELETE 기존 특정 정보 삭제 인터넷을 사용하다 보면 한 번쯤 ‘이 페이지를 볼 수 있는 권한이 없음(HTTP 오류 403 - 사용할 수 없음)’ 혹은 ‘페이지를 찾을 수 없음(HTTP 오류 404 - 파일을 찾을 수 없음)’이라는 오류를 본 적이 있을 겁니다. 여기서 403과 404라는 숫자는 클라이언트의 요청에 대한 서버의 응답 상태를 나타내는 HTTP 상태 코드입니다. HTTP 상태 코드는 100번대부터 500번대까지 있으며, 성공적으로 응답을 받을 시 200번 코드를 받게 됩니다. 각 코드에 대한 내용은 HTTP 상태 코드를 검색하면 확인할 수 있으며, 크롤링 과정에서 오류가 발생할 시 해당 코드를 통해 어떤 부분에서 오류가 발생했는지 확인이 가능합니다. 표 2.2: HTTP 상태 코드 그룹 별 내용 코드 주소 내용 1xx Informational (조건부 응답) 리퀘스트를 받고, 처리 중에 있음 2xx Success (성공) 리퀘스트를 정상적으로 처리함 3xx Redirection (리디렉션) 리퀘스트 완료를 위해 추가 동작이 필요함 4xx Client Error (클라이언트 오류) 클라이언트 요청을 처리할 수 없어 오류 발생 5xx Server Error (서버 오류) 서버에서 처리를 하지 못하여 오류 발생 2.3 HTML과 CSS 클라이언트와 서버가 데이터를 주고받을 때는 디자인이라는 개념이 필요하지 않습니다. 그러나 응답받은 정보를 사람이 확인하려면 보기 편한 방식으로 바꾸어줄 필요가 있는데 웹페이지가 그러한 역할을 합니다. 웹페이지의 제목, 단락, 목록 등 레이아웃을 잡아주는 데 쓰이는 대표적인 마크업 언어가 HTML(HyperText Markup Language)입니다. HTML을 통해 잡혀진 뼈대에 글자의 색상이나 폰트, 배경색, 배치 등 화면을 꾸며주는 역할을 하는 것이 CSS(Cascading Style Sheets)입니다. 우리의 목적은 웹페이지를 만드는 것이 아니므로 HTML과 CSS에 대해 자세히 알 필요는 없습니다. 그러나 크롤링하고자 하는 데이터가 웹페이지의 어떤 태그 내에 위치하고 있는지, 어떻게 크롤링하면 될지 파악하기 위해서는 HTML과 CSS에 대한 기본적인 지식은 알아야 합니다. 메모장에서 HTML 코드를 입력한 후 ‘파일명.html’로 저장하면 해당 코드가 웹페이지에서 어떻게 나타나는지 확인할 수 있습니다. 2.3.1 HTML 기본 구조 HTML은 크게 메타 데이터를 나타내는 head와 본문을 나타내는 body로 나누어집니다. head에서 title은 웹페이지에서 나타나는 제목을 나타내며 body 내에는 본문에 들어갈 각종 내용들이 포함되어 있습니다. <html> <head> <title>Page Title</title> </head> <body> <h2> This is page heading </h2> <p> THis is first paragraph text </p> </body> </html> Page Title This is page heading THis is first paragraph text 그림 2.5: HTML 기본 구조 2.3.2 태그와 속성 HTML 코드는 태그와 속성, 내용으로 이루어져 있습니다. 크롤링한 데이터에서 특정 태그의 데이터만을 찾는 방법, 특정 속성의 데이터만을 찾는 방법, 뽑은 자료에서 내용만을 찾는 방법 등 내용을 찾는 방법이 모두 다르기 때문에 태그와 속성에 대해 좀 더 자세히 살펴보겠습니다. 그림 2.6: HTML 구성 요소 분석 꺾쇠(<>)로 감싸져 있는 부분을 태그라고 하며, 여는 태그 <>가 있으면 반드시 이를 닫는 태그인 </>가 쌍으로 있어야 합니다. 속성은 해당 태그에 대한 추가적인 정보를 제공해주는 것으로, 뒤에 속성값이 따라와야 합니다. 내용은 우리가 눈으로 보는 텍스트 부분을 의미합니다. 앞의 HTML 코드는 문단을 나타내는 <p> 그, 정렬을 나타내는 align 속성과 center를 통해 가운데 정렬을 지정하며, 내용에는 ‘퀀트 투자 Cookbook’을 나타내고, </p> 태그를 통해 태그를 마쳤습니다. 2.3.3 h 태그와 p 태그 h 태그는 폰트의 크기를 나타내는 태그이며, p 태그는 문단을 나타내는 태그입니다. 이를 사용한 간단한 예제는 다음과 같습니다. h 태그의 숫자가 작을수록 텍스트 크기는 커지는 것이 확인되며, 숫자는 1에서 6까지 지원됩니다. p 태그를 사용하면 각각의 문단이 만들어지는 것이 확인됩니다. <html> <body> <h1>Page heading: size 1</h1> <h2>Page heading: size 2</h2> <h3>Page heading: size 3</h3> <p>Quant Cookbook</p> <p>By Henry</p> </body> </html> Page heading: size 1 Page heading: size 2 Page heading: size 3 Quant Cookbook By Henry 그림 2.7: h 태그와 p 태그 예제 2.3.4 리스트를 나타내는 ul 태그와 ol 태그 ul과 ol 태그는 리스트(글머리 기호)를 만들 때 사용됩니다. ul은 순서가 없는 리스트(unordered list), ol은 순서가 있는 리스트(ordered list)를 만듭니다. <html> <body> <h2> Unordered List</h2> <ul> <li>Price</li> <li>Financial Statement</li> <li>Sentiment</li> </ul> <h2> Ordered List</h2> <ol> <li>Import</li> <li>Tidy</li> <li>Understand</li> <li>Communicate</li> </ol> </body> </html> Unordered List Price Financial Statement Sentiment Ordered List Import Tidy Understand Communicate 그림 2.8: 리스트 관련 태그 예제 ul 태그로 감싼 부분은 글머리 기호가 순서가 없는 •으로 표현되며, ol 태그로 감싼 부분은 숫자가 순서대로 표현됩니다. 각각의 리스트는 li를 통해 생성됩니다. 2.3.5 table 태그 table 태그는 표를 만드는 태그입니다. <html> <body> <h2>Major Stock Indices and US ETF</h2> <table> <tr> <th>Country</th> <th>Index</th> <th>ETF</th> </tr> <tr> <td>US</td> <td>S&P 500</td> <td>IVV</td> </tr> <tr> <td>Europe</td> <td>Euro Stoxx 50</td> <td>IEV</td> </tr> <tr> <td>Japan</td> <td>Nikkei 225</td> <td>EWJ</td> </tr> <tr> <td>Korea</td> <td>KOSPI 200</td> <td>EWY</td> </tr> </table> </body> </html> Major Stock Indices and US ETF Country Index ETF US S&P 500 IVV Europe Euro Stoxx 50 IEV Japan Nikkei 225 EWJ Korea KOSPI 200 EWY 그림 2.9: table 태그 예제 table 태그 내의 tr 태그는 각 행을 의미합니다. 각 셀의 구분은 th 혹은 td 태그를 통해 구분할 수 있습니다. th 태그는 진하게 표현되므로 주로 테이블의 제목에 사용되고, td 태그는 테이블의 내용에 사용됩니다. 2.3.6 a 태그와 src 태그 및 속성 a 태그와 src 태그는 다른 태그와는 다르게, 혼자 쓰이기보다는 속성과 결합해 사용됩니다. a 태그는 href 속성과 결합해 다른 페이지의 링크를 걸 수 있습니다. src 태그는 img 속성과 결합해 이미지를 불러옵니다. <html> <body> <h2>a tag & href attribute</h2> <p>HTML links are defined with the a tag. The link address is specified in the href attribute:</p> <a href="https://henryquant.blogspot.com/">Henry's Quantopia</a> <h2>img tag & src attribute</h2> <p>HTML images are defined with the img tag, and the filename of the image source is specified in the src attribute:</p> <img src="https://cran.r-project.org/Rlogo.svg", width="180",height="140"> </body> </html> a tag & href attribute HTML links are defined with the a tag. The link address is specified in the href attribute: Henry's Quantopia img tag & src attribute HTML images are defined with the img tag, and the filename of the image source is specified in the src attribute: 그림 2.10: a 태그와 src 태그 예제 a 태그 뒤 href 속성의 속성값으로 연결하려는 웹페이지 주소를 입력한 후 내용을 입력하면, 내용 텍스트에 웹페이지의 링크가 추가됩니다. img 태그 뒤 src 속성의 속성값에는 불러오려는 이미지 주소를 입력하며, width 속성과 height 속성을 통해 이미지의 가로세로 길이를 조절할 수도 있습니다. 페이지 내에서 링크된 주소를 모두 찾거나, 모든 이미지를 저장하려고 할 때 속성값을 찾으면 손쉽게 원하는 작업을 할 수 있습니다. 2.3.7 div 태그 div 태그는 화면의 전체적인 틀(레이아웃)을 만들 때 주로 사용하는 태그입니다. 단독으로도 사용될 수 있으며, 꾸밈을 담당하는 style 속성과 결합되어 사용되기도 합니다. <html> <body> <div style="background-color:black;color:white"> <h5>First Div</h5> <p>Black backgrond, White Color</p> </div> <div style="background-color:yellow;color:red"> <h5>Second Div</h5> <p>Yellow backgrond, Red Color</p> </div> <div style="background-color:blue;color:grey"> <h5>Second Div</h5> <p>Blue backgrond, Grey Color</p> </div> </body> </html> First Div Black backgrond, White Color Second Div Yellow backgrond, Red Color Second Div Blue backgrond, Grey Color 그림 2.11: div 태그 예제 div 태그를 통해 총 세 개의 레이아웃으로 나누어진 것을 알 수 있습니다. style 속성 중 background-color는 배경 색상을, color는 글자 색상을 의미하며, 각 레이아웃마다 다른 스타일이 적용되었습니다. 2.3.8 CSS CSS는 앞서 설명했듯이 웹페이지를 꾸며주는 역할을 합니다. head에서 각 태그에 CSS 효과를 입력하면 본문의 모든 해당 태그에 CSS 효과가 적용됩니다. 이처럼 웹페이지를 꾸미기 위해 특정 요소에 접근하는 것을 셀렉터(Selector)라고 합니다. <html> <head> <style> body {background-color: powderblue;} h4 {color: blue;} </style> </head> <body> <h4>This is a heading</h4> <p>This is a first paragraph.</p> <p>This is a second paragraph.</p> </body> </html> body {background-color: powderblue;} h4 {color: blue;} This is a heading This is a first paragraph. This is a second paragraph. 그림 2.12: css 예제 head 태그 사이에 여러 태그에 대한 CSS 효과가 정의되었습니다. 먼저 body의 전체 배경 색상을 powderblue로 설정했으며, h4 태그의 글자 색상은 파란색(blue)으로 설정했습니다. body 태그 내에서 style에 태그를 주지 않더라도, CSS 효과가 모두 적용되었음이 확인됩니다. 2.3.9 클래스와 id CSS를 이용하면 본문의 모든 태그에 효과가 적용되므로, 특정한 요소(Element)에만 동일한 효과를 적용할 수 없습니다. 클래스 속성을 이용하면 동일한 이름을 가진 클래스에는 동일한 효과가 적용됩니다. <html> <style> .index { background-color: tomato; color: white; padding: 10px; } .desc { background-color: moccasin; color: black; padding: 10px; } </style> <div> <h2 class="index">S&P 500</h2> <p class="desc"> Market capitalizations of 500 large companies having common stock listed on the NYSE, NASDAQ, or the Cboe BZX Exchange</p> </div> <div> <h2>Dow Jones Industrial Average</h2> <p>Value of 30 large, publicly owned companies based in the United States</p> </div> <div> <h2 class="index">NASDAQ Composite</h2> <p class="desc">The composition of the NASDAQ Composite is heavily weighted towards information technology companies</p> <div> </html> .index { background-color: tomato; color: white; padding: 10px; } .desc { background-color: moccasin; color: black; padding: 10px; } S&P 500 Market capitalizations of 500 large companies having common stock listed on the NYSE, NASDAQ, or the Cboe BZX Exchange Dow Jones Industrial Average Value of 30 large, publicly owned companies based in the United States NASDAQ Composite The composition of the NASDAQ Composite is heavily weighted towards information technology companies 그림 2.13: class 예제 셀렉터를 클래스에 적용할 때는 클래스명 앞에 마침표(.)를 붙여 표현합니다. 위 예제에서 index 클래스는 배경 색상이 tomato, 글자 색상은 흰색, 여백은 10px로 정의되었습니다. desc 클래스는 배경 색상이 moccasin, 글자 색상은 검은색, 여백은 10px로 정의되었습니다. 본문의 첫 번째(S&P 500)와 세 번째(NASDAQ Composite) 레이아웃의 h2 태그 뒤에는 index 클래스를, p 태그 뒤에는 desc 클래스를 속성으로 입력했습니다. 따라서 해당 레이아웃에만 CSS 효과가 적용되며, 클래스 값이 없는 두 번째 레이아웃에는 효과가 적용되지 않습니다. id 또한 이와 비슷한 역할을 하며, HTML 내에서 여러 개의 클래스가 정의될 수 있는 반면, id는 단 하나만 사용하기를 권장합니다. <html> <head> <style> /* Style the element with the id "myHeader" */ #myHeader { background-color: lightblue; color: black; padding: 15px; text-align: center; } </style> </head> <body> <!-- A unique element --> <h1 id="myHeader">My Header</h1> </body> </html> /* Style the element with the id \"myHeader\" */ #myHeader { background-color: lightblue; color: black; padding: 15px; text-align: center; } My Header 그림 2.14: id 예제 셀렉터를 id에 적용할 때는 클래스명 앞에 샵(#)를 붙여 표현하며, 페이지에서 한 번만 사용된다는 점을 제외하면 클래스와 사용 방법이 거의 동일합니다. 클래스나 id 값을 통해 원하는 내용을 크롤링하는 경우도 많으므로, 각각의 이름 앞에 마침표(.)와 샵(#) 을 붙여야 한다는 점을 꼭 기억하기 바랍니다. HTML과 관련해 추가적인 정보가 필요하거나 내용이 궁금하다면 아래 웹사이트를 참고하기 바랍니다. w3schools: https://www.w3schools.in/html-tutorial/ 웨버 스터디: http://webberstudy.com/ 2.4 파이프 오퍼레이터(%>%) 파이프 오퍼레이터는 R에서 동일한 데이터를 대상으로 연속으로 작업하게 해주는 오퍼레이터(연산자)입니다. 크롤링에 필수적인 rvest 패키지를 설치하면 자동으로 magrittr 패키지가 설치되어 파이프 오퍼레이터를 사용할 수 있습니다. 흔히 프로그래밍에서 x라는 데이터를 F()라는 함수에 넣어 결괏값을 확인하고 싶으면 F(x)의 방법을 사용합니다. 예를 들어 3과 5라는 데이터 중 큰 값을 찾으려면 max(3,5)를 통해 확인합니다. 이를 통해 나온 결괏값을 또 다시 G()라는 함수에 넣어 결괏값을 확인하려면 비슷한 과정을 거칩니다. max(3,5)를 통해 나온 값의 제곱근을 구하려면 result = max(3,5)를 통해 첫 번째 결괏값을 저장하고, sqrt(result)를 통해 두 번째 결괏값을 계산합니다. 물론 sqrt(max(3,5))와 같은 표현법으로 한 번에 표현할 수 있습니다. 이러한 표현의 단점은 계산하는 함수가 많아질수록 저장하는 변수가 늘어나거나 괄호가 지나치게 길어진다는 것입니다. 그러나 파이프 오퍼레이터인 %>%를 사용하면 함수 간의 관계를 매우 직관적으로 표현하고 이해할 수 있습니다. 이를 정리하면 아래 표 2.3와 같습니다. 표 2.3: 파이프 오퍼레이터의 표현과 내용 비교 내용 표현.방법 F(x) x %>% F G(F(x)) x %>% F %>% G 간단한 예제를 통해 파이프 오퍼레이터의 사용법을 살펴보겠습니다. 먼저 다음과 같은 10개의 숫자가 있다고 가정합니다. x = c(0.3078, 0.2577, 0.5523, 0.0564, 0.4685, 0.4838, 0.8124, 0.3703, 0.5466, 0.1703) 우리가 원하는 과정은 다음과 같습니다. 각 값들의 로그값을 구할 것 로그값들의 계차를 구할 것 구해진 계차의 지수값을 구할 것 소수 둘째 자리까지 반올림할 것 입니다. 즉 log(), diff(), exp(), round()에 대한 값을 순차적으로 구하고자 합니다. x1 = log(x) x2 = diff(x1) x3 = exp(x2) round(x3, 2) ## [1] 0.84 2.14 0.10 8.31 1.03 1.68 0.46 1.48 0.31 첫 번째 방법은 단계별 함수의 결괏값을 변수에 저장하고 저장된 변수를 다시 불러와 함수에 넣고 계산하는 방법입니다. 전반적인 계산 과정을 확인하기에는 좋지만 매번 변수에 저장하고 불러오는 과정이 매우 비효율적이며 코드도 불필요하게 길어집니다. round(exp(diff(log(x))), 2) ## [1] 0.84 2.14 0.10 8.31 1.03 1.68 0.46 1.48 0.31 두 번째는 괄호를 통해 감싸는 방법입니다. 앞선 방법에 비해 코드는 짧아졌지만, 계산 과정을 알아보기에는 매우 불편한 방법으로 코드가 짜여 있습니다. library(magrittr) x %>% log() %>% diff() %>% exp() %>% round(., 2) ## [1] 0.84 2.14 0.10 8.31 1.03 1.68 0.46 1.48 0.31 마지막으로 파이프 오퍼레이터를 사용하는 방법입니다. 코드도 짧으며 계산 과정을 한눈에 파악하기도 좋습니다. 맨 왼쪽에는 원하는 변수를 입력하며, %>% 뒤에는 차례대로 계산하고자 하는 함수를 입력합니다. 변수의 입력값을 ()로 비워둘 경우, 오퍼레이터의 왼쪽에 있는 값이 입력 변수가 됩니다. 반면 round()와 같이 입력값이 두 개 이상 필요하면 마침표(.)가 오퍼레이터의 왼쪽 값으로 입력됩니다. 파이프 오퍼레이터는 크롤링뿐만 아니라 모든 코드에 사용할 수 있습니다. 이를 통해 훨씬 깔끔하면서도 데이터 처리 과정을 직관적으로 이해할 수 있습니다. 2.5 오류에 대한 예외처리 크롤링을 이용해 데이터를 수집할 때 일반적으로 for loop 구문을 통해 수천 종목에 해당하는 웹페이지에 접속해 해당 데이터를 읽어옵니다. 그러나 특정 종목에 해당하는 페이지가 없거나, 단기적으로 접속이 불안정할 경우 오류가 발생해 루프를 처음부터 다시 실행해야 하는 번거로움이 있습니다. tryCatch() 함수를 이용하면 예외처리, 즉 오류가 발생할 경우 이를 무시하고 넘어갈 수 있습니다. tryCatch() 함수의 구조는 다음과 같습니다. result = tryCatch({ expr }, warning = function(w) { warning-handler-code }, error = function(e) { error-handler-code }, finally = { cleanup-code }) 먼저 expr는 실행하고자 하는 코드를 의미합니다. warning은 경고를 나타내며, warning-handler-code는 경고 발생 시 실행할 구문을 의미합니다. 이와 비슷하게 error와 error-handler-code는 각각 오류와 오류 발생 시 실행할 구문을 의미합니다. finally는 오류의 여부와 관계 없이 무조건 수행할 구문을 의미하며, 생략할 수도 있습니다. number = data.frame(1,2,3,"4",5, stringsAsFactors = FALSE) str(number) ## 'data.frame': 1 obs. of 5 variables: ## $ X1 : num 1 ## $ X2 : num 2 ## $ X3 : num 3 ## $ X.4.: chr "4" ## $ X5 : num 5 먼저 number 변수에는 1에서 5까지 값이 입력되어 있으며, 다른 값들은 형태가 숫자인 반면 4는 문자 형태입니다. for (i in number) { print(i^2) } ## [1] 1 ## [1] 4 ## [1] 9 ## Error in i^2: non-numeric argument to binary operator for loop 구문을 통해 순서대로 값들의 제곱을 출력하는 명령어를 실행하면 문자 4는 제곱을 할 수 없어 오류가 발생하게 됩니다. tryCatch() 함수를 사용하면 이처럼 오류가 발생하는 루프를 무시하고 다음 루프로 넘어갈 수 있게 됩니다. for (i in number) { tryCatch({ print(i^2) }, error = function(e) { print(paste('Error:', i)) }) } ## [1] 1 ## [1] 4 ## [1] 9 ## [1] "Error: 4" ## [1] 25 expr 부분은 print(i^2)이며, error-handler-code 부분은 오류가 발생한 i를 출력합니다. 해당 코드를 실행하면 문자 4에서 오류가 발생함을 알려준 후 루프가 멈추지 않고 다음으로 진행됩니다. "], -["api를-이용한-데이터-수집.html", "Chapter 3 API를 이용한 데이터 수집 3.1 API를 이용한 Quandl 데이터 다운로드 3.2 getSymbols() 함수를 이용한 API 다운로드", " Chapter 3 API를 이용한 데이터 수집 이 CHAPTER와 다음 CHAPTER에서는 본격적으로 데이터를 수집하는 방법을 배우겠습니다. 먼저 API를 이용해 데이터를 수집하는 방법을 살펴봅니다. API 제공자는 본인이 가진 데이터베이스를 다른 누군가가 쉽게 사용할 수 있는 형태로 가지고 있으며, 해당 데이터베이스에 접근할 수 있는 열쇠인 API 주소를 가진 사람은 이를 언제든지 사용할 수 있습니다. 그림 1.3: API 개념 API는 API 주소만 가지고 있다면 데이터를 언제, 어디서, 누구나 쉽게 이용할 수 있다는 장점이 있습니다. 또한 대부분의 경우 사용자가 필요한 데이터만을 가지고 있으므로 접속 속도가 빠르며, 데이터를 가공하는 번거로움도 줄어듭니다. 해외에는 금융 데이터를 API의 형태로 제공하는 업체가 많으므로, 이를 잘만 활용한다면 매우 손쉽게 퀀트 투자에 필요한 데이터를 수집할 수 있습니다. 3.1 API를 이용한 Quandl 데이터 다운로드 데이터 제공업체 Quandl은 일부 데이터를 무료로 제공하며 API를 통해서 다운로드할 수 있습니다.6 이 책에서는 예제로 애플(AAPL)의 주가를 다운로드해보겠습니다. csv 형식의 API 주소는 다음과 같습니다. https://www.quandl.com/api/v3/datasets/WIKI/AAPL/data.csv?api_key=xw3NU3xLUZ7vZgrz5QnG 위 주소를 웹 브라우저 주소 창에 직접 입력하면 csv 형식의 파일이 다운로드되며, 파일을 열어보면 애플의 주가 데이터가 있습니다. 그림 2.1: API 주소를 이용한 데이터 다운로드 그러나 웹 브라우저에 해당 주소를 입력해 csv 파일을 다운로드하고 csv 파일을 다시 R에서 불러오는 작업은 무척이나 비효율적입니다. R에서 API 주소를 이용해 직접 데이터를 다운로드할 수 있습니다. url.aapl = "https://www.quandl.com/api/v3/datasets/WIKI/AAPL/data.csv?api_key=xw3NU3xLUZ7vZgrz5QnG" data.aapl = read.csv(url.aapl) head(data.aapl) ## Date Open High Low Close Volume ## 1 2018-03-27 173.7 175.2 166.9 168.3 38962839 ## 2 2018-03-26 168.1 173.1 166.4 172.8 36272617 ## 3 2018-03-23 168.4 169.9 164.9 164.9 40248954 ## 4 2018-03-22 170.0 172.7 168.6 168.8 41051076 ## 5 2018-03-21 175.0 175.1 171.3 171.3 35247358 ## 6 2018-03-20 175.2 176.8 174.9 175.2 19314039 ## Ex.Dividend Split.Ratio Adj..Open Adj..High Adj..Low ## 1 0 1 173.7 175.2 166.9 ## 2 0 1 168.1 173.1 166.4 ## 3 0 1 168.4 169.9 164.9 ## 4 0 1 170.0 172.7 168.6 ## 5 0 1 175.0 175.1 171.3 ## 6 0 1 175.2 176.8 174.9 ## Adj..Close Adj..Volume ## 1 168.3 38962839 ## 2 172.8 36272617 ## 3 164.9 40248954 ## 4 168.8 41051076 ## 5 171.3 35247358 ## 6 175.2 19314039 url에 해당 주소를 입력한 후 read.csv() 함수를 이용해 간단하게 csv 파일을 불러올 수 있습니다. 3.2 getSymbols() 함수를 이용한 API 다운로드 이전 예에서 API 주소를 이용하면 매우 간단하게 데이터를 수집할 수 있음을 살펴보았습니다. 그러나 이 방법에는 단점도 있습니다. 먼저 원하는 항목에 대한 API를 일일이 얻기가 힘듭니다. 또한 Quandl의 경우 무료로 얻을 수 있는 정보에 제한이 있으며, 다운로드 양에도 제한이 있습니다. 이 방법으로 한두 종목의 데이터를 수집할 수 있지만, 전 종목의 데이터를 구하기는 사실상 불가능합니다. 다행히 야후 파이낸스 역시 주가 데이터를 무료로 제공하며, quantmod 패키지의 getSymbols() 함수는 해당 API에 접속해 데이터를 다운로드합니다. 3.2.1 주가 다운로드 getSymbols() 함수의 기본적인 사용법은 매우 간단합니다. 괄호 안에 다운로드하려는 종목의 티커를 입력하면 됩니다. library(quantmod) getSymbols('AAPL') ## [1] "AAPL" head(AAPL) ## AAPL.Open AAPL.High AAPL.Low AAPL.Close ## 2007-01-03 3.082 3.092 2.925 2.993 ## 2007-01-04 3.002 3.070 2.994 3.059 ## 2007-01-05 3.063 3.079 3.014 3.038 ## 2007-01-08 3.070 3.090 3.046 3.053 ## 2007-01-09 3.087 3.321 3.041 3.306 ## 2007-01-10 3.384 3.493 3.337 3.464 ## AAPL.Volume AAPL.Adjusted ## 2007-01-03 1238319600 2.582 ## 2007-01-04 847260400 2.639 ## 2007-01-05 834741600 2.620 ## 2007-01-08 797106800 2.633 ## 2007-01-09 3349298400 2.852 ## 2007-01-10 2952880000 2.988 먼저 getSymbols() 함수 내에 애플의 티커인 AAPL을 입력합니다. 티커와 동일한 변수인 AAPL이 생성되며, 주가 데이터가 다운로드된 후 xts 형태로 입력됩니다. 다운로드 결과로 총 6개의 열이 생성됩니다. Open은 시가, High는 고가, Low는 저가, Close는 종가를 의미합니다. 또한 Volume은 거래량을 의미하며, Adjusted는 배당이 반영된 수정주가를 의미합니다. 이 중 가장 많이 사용되는 데이터는 Adjusted, 즉 배당이 반영된 수정주가입니다. chart_Series(Ad(AAPL)) Ad() 함수를 통해 다운로드한 데이터에서 수정주가만을 선택한 후 chart_Series() 함수를 이용해 시계열 그래프를 그릴 수도 있습니다. 시계열 기간을 입력하지 않으면 2007년 1월부터 현재까지의 데이터가 다운로드되며, 입력 변수를 추가해서 원하는 기간의 데이터를 다운로드할 수도 있습니다. data = getSymbols('AAPL', from = '2000-01-01', to = '2018-12-31', auto.assign = FALSE) head(data) ## AAPL.Open AAPL.High AAPL.Low AAPL.Close ## 2000-01-03 0.9364 1.0045 0.9079 0.9994 ## 2000-01-04 0.9665 0.9877 0.9035 0.9152 ## 2000-01-05 0.9263 0.9872 0.9196 0.9286 ## 2000-01-06 0.9475 0.9554 0.8482 0.8482 ## 2000-01-07 0.8616 0.9018 0.8527 0.8884 ## 2000-01-10 0.9107 0.9129 0.8460 0.8728 ## AAPL.Volume AAPL.Adjusted ## 2000-01-03 535796800 0.8622 ## 2000-01-04 512377600 0.7895 ## 2000-01-05 778321600 0.8010 ## 2000-01-06 767972800 0.7317 ## 2000-01-07 460734400 0.7664 ## 2000-01-10 505064000 0.7529 from에는 시작시점을 입력하고 to에는 종료시점을 입력하면 해당 기간의 데이터가 다운로드됩니다. getSymbols() 함수를 통해 다운로드한 데이터는 자동으로 티커와 동일한 변수명에 저장됩니다. 만일 티커명이 아닌 원하는 변수명에 데이터를 저장하려면 auto.assign 인자를 FALSE로 설정해주면 다운로드한 데이터가 원하는 변수에 저장됩니다. ticker = c('FB', 'NVDA') getSymbols(ticker) ## [1] "FB" "NVDA" head(FB) ## FB.Open FB.High FB.Low FB.Close FB.Volume ## 2012-05-18 42.05 45.00 38.00 38.23 573576400 ## 2012-05-21 36.53 36.66 33.00 34.03 168192700 ## 2012-05-22 32.61 33.59 30.94 31.00 101786600 ## 2012-05-23 31.37 32.50 31.36 32.00 73600000 ## 2012-05-24 32.95 33.21 31.77 33.03 50237200 ## 2012-05-25 32.90 32.95 31.11 31.91 37149800 ## FB.Adjusted ## 2012-05-18 38.23 ## 2012-05-21 34.03 ## 2012-05-22 31.00 ## 2012-05-23 32.00 ## 2012-05-24 33.03 ## 2012-05-25 31.91 head(NVDA) ## NVDA.Open NVDA.High NVDA.Low NVDA.Close ## 2007-01-03 24.71 25.01 23.19 24.05 ## 2007-01-04 23.97 24.05 23.35 23.94 ## 2007-01-05 23.37 23.47 22.28 22.44 ## 2007-01-08 22.52 23.04 22.13 22.61 ## 2007-01-09 22.64 22.79 22.14 22.17 ## 2007-01-10 21.93 23.47 21.60 23.26 ## NVDA.Volume NVDA.Adjusted ## 2007-01-03 28870500 22.11 ## 2007-01-04 19932400 22.01 ## 2007-01-05 31083600 20.63 ## 2007-01-08 16431700 20.78 ## 2007-01-09 19104100 20.38 ## 2007-01-10 27718600 21.39 한 번에 여러 종목의 주가를 다운로드할 수도 있습니다. 위 예제와 같이 페이스북과 엔비디아의 티커인 FB와 NVDA를 ticker 변수에 입력하고 getSymbols() 함수에 티커를 입력한 변수를 넣으면 두 종목의 주가가 순차적으로 다운로드됩니다. 3.2.2 국내 종목 주가 다운로드 getSymbols() 함수를 이용하면 미국뿐 아니라 국내 종목의 주가도 다운로드할 수 있습니다. 국내 종목의 티커는 총 6자리로 구성되어 있으며, 해당 함수에 입력되는 티커는 코스피 상장 종목의 경우 티커.KS, 코스닥 상장 종목의 경우 티커.KQ의 형태로 입력해야 합니다. 다음은 코스피 상장 종목인 삼성전자 데이터의 다운로드 예시입니다. getSymbols('005930.KS', from = '2000-01-01', to = '2018-12-31') ## [1] "005930.KS" tail(Ad(`005930.KS`)) ## 005930.KS.Adjusted ## 2018-12-20 38293 ## 2018-12-21 38293 ## 2018-12-24 38442 ## 2018-12-26 37996 ## 2018-12-27 38250 ## 2018-12-28 38700 삼성전자의 티커인 005930에 .KS를 붙여 getSymbols() 함수에 입력하면 티커명에 해당하는 005930.KS 변수명에 데이터가 저장됩니다. 변수명에 마침표(.)가 있으므로 Ad() 함수를 통해 수정주가를 확인하려면 변수명 앞뒤에 억음 부호(`)를 붙여야 합니다. 국내 종목은 종종 수정주가에 오류가 발생하는 경우가 많아서 배당이 반영된 값보다는 단순 종가(Close) 데이터를 사용하기를 권장합니다. tail(Cl(`005930.KS`)) ## 005930.KS.Close ## 2018-12-20 38650 ## 2018-12-21 38650 ## 2018-12-24 38800 ## 2018-12-26 38350 ## 2018-12-27 38250 ## 2018-12-28 38700 Cl() 함수는 Close, 즉 종가만을 선택하며, 사용 방법은 Ad() 함수와 동일합니다. 비록 배당을 고려할 수는 없지만, 전반적으로 오류가 없는 데이터를 사용할 수 있습니다. 다음은 코스닥 상장종목인 셀트리온제약의 예시이며, 티커인 068670에 .KQ를 붙여 함수에 입력합니다. 역시나 데이터가 다운로드되어 티커명의 변수에 저장됩니다. getSymbols("068760.KQ", from = '2000-01-01', to = '2018-12-31') ## [1] "068760.KQ" tail(Cl(`068760.KQ`)) ## 068760.KQ.Close ## 2018-12-20 NA ## 2018-12-21 NA ## 2018-12-24 NA ## 2018-12-26 NA ## 2018-12-27 NA ## 2018-12-28 NA 3.2.3 FRED 데이터 다운로드 미국 연방준비은행에서 관리하는 Federal Reserve Economic Data(FRED)는 미국 및 각국의 중요 경제지표 데이터를 살펴볼 때 가장 많이 참조되는 곳 중 하나입니다. getSymbols() 함수를 통해 FRED 데이터를 다운로드할 수 있습니다. 먼저 미 국채 10년물 금리를 다운로드하는 예제를 살펴보겠습니다. getSymbols('DGS10', src='FRED') ## [1] "DGS10" chart_Series(DGS10) 미 국채 10년물 금리에 해당하는 티커인 DGS10을 입력해주며, 데이터 출처에 해당하는 src에 FRED를 입력해줍니다. FRED에서 제공하는 API를 이용해 데이터가 다운로드되며, chart_Series() 함수를 통해 금리 추이를 살펴볼 수 있습니다. 각 항목별 티커를 찾는 방법은 매우 간단합니다. 먼저 FRED의 웹사이트7원 하는 데이터를 검색합니다. 만일 원/달러 환율에 해당하는 티커를 찾고자 한다면 그림 3.1와 같이 이에 해당하는 South Korea / U.S. Foreign Exchange Rate를 검색해 원하는 페이지에 접속합니다. 이 중 페이지 주소에서 /series/ 다음에 위치하는 DEXKOUS가 해당 항목의 티커입니다. 그림 3.1: FRED 사이트 내 원/달러 환율의 티커 확인 getSymbols('DEXKOUS', src='FRED') ## [1] "DEXKOUS" tail(DEXKOUS) ## DEXKOUS ## 2021-01-01 NA ## 2021-01-04 1082 ## 2021-01-05 1087 ## 2021-01-06 1086 ## 2021-01-07 1087 ## 2021-01-08 1089 해당 티커를 입력하면, FRED 웹사이트와 동일한 데이터가 다운로드됩니다. 이 외에도 509,000여 개의 방대한 FRED 데이터를 해당 함수를 통해 손쉽게 R에서 다운로드할 수 있습니다. 자세한 내용은 https://docs.quandl.com/ 에서 확인할 수 있습니다.↩︎ https://fred.stlouisfed.org/↩︎ "], -["크롤링-이해하기.html", "Chapter 4 크롤링 이해하기 4.1 GET과 POST 방식 이해하기 4.2 크롤링 예제", " Chapter 4 크롤링 이해하기 API를 이용하면 데이터를 매우 쉽게 수집할 수 있지만, 국내 주식 데이터를 다운로드 하기에는 한계가 있으며, 원하는 데이터가 API의 형태로 제공된다는 보장도 없습니다. 따라서 우리는 필요한 데이터를 얻기 위해 직접 찾아 나서야 합니다. 각종 금융 웹사이트에는 주가, 재무정보 등 우리가 원하는 대부분의 주식 정보가 제공되고 있으며, API를 활용할 수 없는 경우에도 크롤링을 통해 이러한 데이터를 수집할 수 있습니다. 크롤링 혹은 스크래핑이란 웹사이트에서 원하는 정보를 수집하는 기술입니다. 대부분의 금융 웹사이트는 간단한 형태로 작성되어 있어, 몇 가지 기술만 익히면 어렵지 않게 데이터를 크롤링할 수 있습니다. 이 CHAPTER에서는 크롤링에 대한 간단한 설명과 예제를 살펴보겠습니다. 크롤링을 할 때 주의해야 할 점이 있습니다. 특정 웹사이트의 페이지를 쉬지 않고 크롤링하는 행위를 무한 크롤링이라고 합니다. 무한 크롤링은 해당 웹사이트의 자원을 독점하게 되어 타인의 사용을 막게 되며 웹사이트에 부하를 주게 됩니다. 일부 웹사이트에서는 동일한 IP로 쉬지 않고 크롤링을 할 경우 접속을 막아버리는 경우도 있습니다. 따라서 하나의 페이지를 크롤링한 후 1~2초 가량 정지하고 다시 다음 페이지를 크롤링하는 것이 좋습니다. 4.1 GET과 POST 방식 이해하기 우리가 인터넷에 접속해 서버에 파일을 요청하면, 서버는 이에 해당하는 파일을 우리에게 보내줍니다. 크롬과 같은 웹 브라우저는 이러한 과정을 사람이 수행하기 편하고 시각적으로 보기 편하도록 만들어진 것이며, 인터넷 주소는 서버의 주소를 기억하기 쉽게 만든 것입니다. 우리가 서버에 데이터를 요청하는 형태는 다양하지만 크롤링에서는 주로 GET과 POST 방식을 사용합니다. 그림 1.3: 클라이언트와 서버 간의 요청/응답 과정 4.1.1 GET 방식 GET 방식은 인터넷 주소를 기준으로 이에 해당하는 데이터나 파일을 요청하는 것입니다. 주로 클라이언트가 요청하는 쿼리를 앰퍼샌드(&) 혹은 물음표(?) 형식으로 결합해 서버에 전달합니다. 한경컨센서스8에 접속한 후 상단 탭에서 [기업] 탭을 선택하면, 주소 끝부분에 ?skinType=business가 추가되며 이에 해당하는 페이지의 내용을 보여줍니다. 즉, 해당 페이지는 GET 방식을 사용하고 있으며 입력 종류는 skinType, 이에 해당하는 [기업] 탭의 입력값은 business임을 알 수 있습니다. 그림 2.1: 한경 컨센서스 기업 REPORT 페이지 이번에는 [파생] 탭을 선택해봅니다. 역시나 웹사이트 주소 끝부분이 ?skinType=derivative로 변경되며, 해당 주소에 맞는 내용이 나타납니다. 여러 다른 탭들을 클릭해보면 ?skinType= 뒷부분의 입력값이 변함에 따라 이에 해당하는 페이지로 내용 이 변경되는 것을 알 수 있습니다. 다시 [기업] 탭을 선택한 후 다음 페이지를 확인하기 위해 하단의 [2]를 클릭합니다. 기존 주소인 ?skinType=business 뒤에 추가로 sdate와 edate, now_page 쿼리가 추가됩니다. sdate는 검색 기간의 시작시점, edate는 검색 기간의 종료시점, now_ page는 현재 페이지를 의미하며, 원하는 데이터를 수기로 입력해도 이에 해당하는 페이지의 데이터를 보여줍니다. 이처럼 GET 방식으로 데이터를 요청하면 웹페이지 주소를 수정해 원하는 종류의 데이터를 받아올 수 있습니다. 그림 2.4: 쿼리 추가로 인한 url의 변경 4.1.2 POST 방식 POST 방식은 사용자가 필요한 값을 추가해서 요청하는 방법입니다. GET 방식과 달리 클라이언트가 요청하는 쿼리를 body에 넣어서 전송하므로 요청 내역을 직접 볼 수 없습니다. 한국거래소 상장공시시스템9에 접속해 [전체메뉴보기]를 클릭하고 [상장법인상세정보] 중 [상장종목현황]을 선택합니다. 웹페이지 주소가 바뀌며, 상장종목현황이 나타납니다. 그림 4.1: 상장공시시스템의 상장종목현황 메뉴 이번엔 조회일자를 [2017-12-28]로 선택한 후 [검색]을 클릭합니다. 페이지의 내용은 선택일 기준으로 변경되었지만, 주소는 변경되지 않고 그대로 남아 있습니다. GET 방식에서는 선택 항목에 따라 웹페이지 주소가 변경되었지만, POST 방식을 사용해 서버에 데이터를 요청하는 해당 웹사이트는 그렇지 않은 것을 알 수 있습니다. POST 방식의 데이터 요청 과정을 살펴보려면 개발자 도구를 이용해야 하며, 크롬에서는 [F12]키를 눌러 개발자 도구 화면을 열 수 있습니다. 개발자 도구 화면에서 다시 한번 [검색]을 클릭해봅니다. [Network] 탭을 클릭하면, [검색]을 클릭함과 동시에 브라우저와 서버 간의 통신 과정을 살펴볼 수 있습니다. 이 중 listedIssueStatus.do라는 항목이 POST 형태임을 알 수 있습니다. 그림 4.2: 크롬 개발자도구의 Network 화면 해당 메뉴를 클릭하면 통신 과정을 좀 더 자세히 알 수 있습니다. 가장 하단의 Form Data에는 서버에 데이터를 요청하는 내역이 있습니다. method에는 readListIssueStatus, selDate에는 2017-12-28이라는 값이 있습니다. 그림 2.5: POST 방식의 서버 요청 내역 이처럼 POST 방식은 요청하는 데이터에 대한 쿼리가 GET 방식처럼 URL을 통해 전송되는 것이 아닌 body를 통해 전송되므로, 이에 대한 정보는 웹 브라우저를 통해 확인할 수 없습니다. 4.2 크롤링 예제 일반적인 크롤링은 httr 패키지의 GET() 혹은 POST() 함수를 이용해 데이터를 다운로드한 후 rvest 패키지의 함수들을 이용해 원하는 데이터를 찾는 과정으로 이루어집니다. 이 CHAPTER에서는 GET 방식으로 금융 실시간 속보의 제목을 추출하는 예제, POST 방식으로 기업공시채널에서 오늘의 공시를 추출하는 예제, 태그와 속성, 페이지 내비게이션 값을 결합해 국내 상장주식의 종목명 및 티커를 추출하는 예제를 학습해 보겠습니다. 4.2.1 금융 속보 크롤링 크롤링의 간단한 예제로 금융 속보의 제목을 추출해보겠습니다. 먼저 네이버 금융에 접속한 후 [뉴스 → 실시간 속보]10를 선택합니다. 이 중 뉴스의 제목에 해당하는 텍스트만 추출하고자 합니다. 뉴스 제목 부분에 마우스 커서를 올려둔 후 마우스 오른쪽 버튼을 클릭하고 [검사]를 선택하면 개발자 도구 화면이 나타납니다. 여기서 해당 글자가 HTML 내에서 어떤 부분에 위치하는지 확인할 수 있습니다. 해당 제목은 dl 태그 → dd 태그의 articleSubject 클래스 → a 태그 중 title 속성에 위치하고 있습니다. 태그와 속성의 차이가 이해되지 않은 분은 CHAPTER 2를 다시 살펴보기 바랍니다. 그림 2.6: 실시간 속보의 제목 부분 html 먼저 해당 페이지의 내용을 R로 불러옵니다. library(rvest) library(httr) url = 'https://finance.naver.com/news/news_list.nhn?mode=LSS2D&section_id=101&section_id2=258' data = GET(url) print(data) 먼저 url 변수에 해당 주소를 입력한 후 GET() 함수를 이용해 해당 페이지의 내용을 받아 data 변수에 저장합니다. data 변수를 확인해보면 Status가 200, 즉 데이터가 이상 없이 받아졌으며, 인코딩(charset)은 EUC-KR 타입으로 되어 있습니다. 우리는 개발자 도구 화면을 통해 제목에 해당하는 부분이 dl 태그 → dd 태그의 articleSubject 클래스 → a 태그 중 title 속성에 위치하고 있음을 살펴보았습니다. 이를 활용해 제목 부분만을 추출하는 방법은 다음과 같습니다. data_title = data %>% read_html(encoding = 'EUC-KR') %>% html_nodes('dl') %>% html_nodes('.articleSubject') %>% html_nodes('a') %>% html_attr('title') read_html() 함수를 이용해 해당 페이지의 HTML 내용을 읽어오며, 인코딩은 EUC-KR로 설정합니다. html_nodes() 함수는 해당 태그를 추출하는 함수이며 dl 태그에 해당하는 부분을 추출합니다. html_nodes() 함수를 이용해 articleSubject 클래스에 해당하는 부분을 추출할 수 있으며, 클래스 속성의 경우 이름 앞에 마침표(.)를 붙여주어야 합니다. html_nodes() 함수를 이용해 a 태그를 추출합니다. html_attr() 함수는 속성을 추출하는 함수이며 title에 해당하는 부분만을 추출합니다. 위 과정을 거쳐 data_title에는 실시간 속보의 제목만이 저장됩니다. 이처럼 개발자 도구 화면을 통해 내가 추출하고자 하는 데이터가 HTML 중 어디에 위치하고 있는지 먼저 확인하면 어렵지 않게 해당 데이터를 읽어올 수 있습니다. print(data_title) ## [1] "거래소, 시장정보포털 'KRX 정보데" ## [2] "거래소, 상장사 ESG 정보공개 가이" ## [3] "美 바이든 정부 공식 출범…韓 조정 " ## [4] "금감원, 삼성증권 종합검사 이르면 1" ## [5] "포모증후군?, 증시 변동성 높아져도." ## [6] "3년간 공매도 수익 9170억…개미 " ## [7] "엠씨넥스에 대한 기대감이 솔솔 높아지" ## [8] "바이든 정부 공식 출범…지수 상승 부" ## [9] "대작 줄줄이 개봉...CGV 웃을 수" ## [10] "[fn마켓워치] 변동성 커진 두산인프" ## [11] "거래소, `KRX 정보데이터시스템` " ## [12] "거래소, `ESG 정보공개 가이던스`" ## [13] "한국거래소, 시장정보포털 'KRX정보" ## [14] "컴투스, 신작 줄줄이 출시…‘백년전쟁" ## [15] "'ESG 정보공개 가이던스' 제정…온" ## [16] "한국거래소, ESG 투자 활성화 속도" ## [17] "증권·파생상품시장 정보 한 눈에..." ## [18] "뜨거운 증시만큼 달궈진 IPO 시장 " ## [19] "한국거래소 'ESG 정보공개 가이던스" ## [20] "주식 정보 여기서 다 본다…거래소, " 4.2.2 기업공시채널에서 오늘의 공시 불러오기 한국거래소 상장공시시스템에 접속한 후 [오늘의 공시 → 전체 → 더보기]를 선택해 전체 공시내용을 확인할 수 있습니다. 그림 4.3: 오늘의공시 확인하기 해당 페이지에서 날짜를 변경하면 페이지의 내용은 해당일의 공시로 변경되지만 URL은 변경되지 않습니다. 이처럼 POST 방식은 요청하는 데이터에 대한 쿼리가 body의 형태를 통해 전송되므로, 개발자 도구 화면을 통해 해당 쿼리에 대한 내용을 확인해야 합니다. 개발자 도구 화면을 연 상태에서 조회일자를 [2018-12-28]로 선택한 후 [검색]을 클릭하고 [Network] 탭의 todaydisclosure.do 항목을 살펴보면 Form Data를 통해 서버에 데이터를 요청하는 내역을 확인할 수 있습니다. 여러 항목 중 selDate 부분이 우리가 선택한 일자로 설정되어 있습니다. 그림 2.9: POST 방식의 데이터 요청 POST 방식으로 쿼리를 요청하는 방법을 코드로 나타내면 다음과 같습니다. library(httr) library(rvest) Sys.setlocale("LC_ALL", "English") url = 'https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do' data = POST(url, body = list( method = 'searchTodayDisclosureSub', currentPageSize = '15', pageIndex = '1', orderMode = '0', orderStat = 'D', forward = 'todaydisclosure_sub', chose = 'S', todayFlag = 'Y', selDate = '2018-12-28' )) data = read_html(data) %>% html_table(fill = TRUE) %>% .[[1]] Sys.setlocale("LC_ALL", "Korean") 한글(korean)로 작성된 페이지를 크롤링하면 오류가 발생하는 경우가 종종 있으므로 Sys.setlocale() 함수를 통해 로케일 언어를 영어(English)로 설정합니다. POST() 함수를 통해 해당 url에 원하는 쿼리를 요청하며, 쿼리는 body 내에 리스트 형태로 입력해줍니다. 해당 값은 개발자 도구 화면의 Form Data와 동일하게 입력해주며, marketType과 같이 값이 없는 항목은 입력하지 않아도 됩니다. read_html() 함수를 이용해 해당 페이지의 HTML 내용을 읽어옵니다. html_table() 함수는 테이블 형태의 데이터를 읽어오는 함수입니다. 셀 병합이 된 열이 있으므로 fill=TRUE를 추가합니다. .[[1]]를 통해 첫 번째 리스트를 선택합니다. 한글을 읽기 위해 Sys.setlocale() 함수를 통해 로케일 언어를 다시 Korean으로 변경합니다. 저장된 데이터를 확인하면 화면과 동일한 내용이 출력됩니다. print(head(data)) ## NA NA ## 1 18:32 이노와이즈 ## 2 18:26 에스제이케이 ## 3 18:11 아이엠텍 ## 4 18:10 시그넷이브이 ## 5 18:09 ## 6 18:09 ## NA ## 1 최대주주변경 ## 2 증권 발행결과(자율공시)(제3자배정 유상증자) ## 3 [정정]유상증자결정(제3자배정) ## 4 유형자산 양수 결정 ## 5 자기주식매매신청내역(코스닥시장) ## 6 대량매매내역(코스닥시장) ## NA NA ## 1 화신테크 공시차트\\r\\n\\t\\t\\t\\t\\t주가차트 ## 2 에스제이케이 공시차트\\r\\n\\t\\t\\t\\t\\t주가차트 ## 3 아이엠텍 공시차트\\r\\n\\t\\t\\t\\t\\t주가차트 ## 4 시그넷이브이 공시차트\\r\\n\\t\\t\\t\\t\\t주가차트 ## 5 코스닥시장본부 ## 6 코스닥시장본부 POST 형식의 경우 body에 들어가는 쿼리 내용을 바꾸어 원하는 데이터를 받을 수 있습니다. 만일 2020년 9월 18일 공시를 확인하고자 한다면 위의 코드에서 selDate만 2020-09-18로 변경해주면 됩니다. 아래 코드의 출력 결과물을 2019년 9월 18일 공시와 비교하면 동일한 결과임을 확인할 수 있습니다. Sys.setlocale("LC_ALL", "English") url = 'https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do' data = POST(url, body = list( method = 'searchTodayDisclosureSub', currentPageSize = '15', pageIndex = '1', orderMode = '0', orderStat = 'D', forward = 'todaydisclosure_sub', chose = 'S', todayFlag = 'Y', selDate = '2020-09-18' )) data = read_html(data) %>% html_table(fill = TRUE) %>% .[[1]] Sys.setlocale("LC_ALL", "Korean") print(head(data)) ## NA NA ## 1 16:22 케이티스카이라이프 ## NA ## 1 [정정]기업설명회(IR) 개최(안내공시) ## NA NA ## 1 케이티스카이라이프 공시차트\\r\\n\\t\\t\\t\\t\\t주가차트 4.2.3 네이버 금융에서 주식티커 크롤링 태그와 속성, 페이지 내비게이션 값을 결합해 국내 상장주식의 종목명 및 티커를 추출하는 방법을 알아보겠습니다. 네이버 금융에서 [국내증시 → 시가총액] 페이지에는 코스피와 코스닥의 시가총액별 정보가 나타나 있습니다. 코스피: https://finance.naver.com/sise/sise_market_sum.nhn?sosok=0&page=1 코스닥: https://finance.naver.com/sise/sise_market_sum.nhn?sosok=1&page=1 또한 종목명을 클릭해 이동하는 페이지의 URL을 확인해보면, 끝 6자리가 각 종목의 거래소 티커임도 확인이 됩니다. 티커 정리를 위해 HTML에서 확인해야 할 부분은 총 두 가지입니다. 먼저 하단의 페이지 내비게이션을 통해 코스피와 코스닥 시가총액에 해당하는 페이지가 각각 몇 번째 페이지까지 있는지 알아야 합니다. 아래와 같은 항목 중 [맨뒤]에 해당하는 페이지가 가장 마지막 페이지입니다. 그림 4.4: 페이지 네비게이션 [맨뒤]에 마우스 커서를 올려두고 마우스 오른쪽 버튼을 클릭한 후 [검사]를 선택하면 개발자 도구 화면이 열립니다. 여기서 해당 글자가 HTML 내에서 어떤 부분에 위치하는지 확인할 수 있습니다. 해당 링크는 pgRR 클래스 → a 태그 중 href 속성에 위치하며, page= 뒷부분의 숫자에 위치하는 페이지로 링크가 걸려 있습니다. 그림 2.12: HTML 내 페이지 네비게이션 부분 종목명 링크에 해당하는 주소 중 끝 6자리는 티커에 해당합니다. 따라서 각 링크들의 주소를 알아야 할 필요도 있습니다. 그림 4.5: 네이버 금융 시가총액 페이지 삼성전자에 마우스 커서를 올려둔 후 마우스 오른쪽 버튼을 클릭하고 [검사]를 선택합니다. 개발자 도구 화면을 살펴보면 해당 링크가 tbody → td → a 태그의 href 속성에 위치하고 있음을 알 수 있습니다. 위 정보들을 이용해 데이터를 다운로드하겠습니다. 아래 코드에서 i = 0일 경우 코스피에 해당하는 URL이 생성되고, i = 1일 경우 코스닥에 해당하는 URL이 생성됩니다. 먼저 코스피에 해당하는 데이터를 다운로드하겠습니다. library(httr) library(rvest) i = 0 ticker = list() url = paste0('https://finance.naver.com/sise/', 'sise_market_sum.nhn?sosok=',i,'&page=1') down_table = GET(url) 빈 리스트인 ticker 변수를 만들어줍니다. paste0() 함수를 이용해 코스피 시가총액 페이지의 url을 만듭니다. GET() 함수를 통해 해당 페이지 내용을 받아 down_table 변수에 저장합니다. 가장 먼저 해야 할 작업은 마지막 페이지가 몇 번째 페이지인지 찾아내는 작업입니다. 우리는 이미 개발자 도구 화면을 통해 해당 정보가 pgRR 클래스의 a 태그 중 href 속성에 위치하고 있음을 알고 있습니다. navi.final = read_html(down_table, encoding = 'EUC-KR') %>% html_nodes(., '.pgRR') %>% html_nodes(., 'a') %>% html_attr(., 'href') read_html() 함수를 이용해 해당 페이지의 HTML 내용을 읽어오며, 인코딩은 EUC-KR로 설정합니다. html_nodes() 함수를 이용해 pgRR 클래스 정보만 불러오며, 클래스 속성이므로 앞에 마침표(.)를 붙입니다. html_nodes() 함수를 통해 a 태그 정보만 불러옵니다. html_attr() 함수를 통해 href 속성을 불러옵니다. 이를 통해 navi.final에는 해당 부분에 해당하는 내용이 저장됩니다. print(navi.final) ## [1] "/sise/sise_market_sum.nhn?sosok=0&page=32" 이 중 우리가 알고 싶은 내용은 page= 뒤에 있는 숫자입니다. 해당 내용을 추출하는 코드는 다음과 같습니다. navi.final = navi.final %>% strsplit(., '=') %>% unlist() %>% tail(., 1) %>% as.numeric() strsplit() 함수는 전체 문장을 특정 글자 기준으로 나눕니다. page= 뒷부분 의 데이터만 필요하므로 =를 기준으로 문장을 나눠줍니다. unlist() 함수를 통해 결과를 벡터 형태로 변환합니다. tail() 함수를 통해 뒤에서 첫 번째 데이터만 선택합니다. as.numeric() 함수를 통해 해당 값을 숫자 형태로 바꾸어줍니다. print(navi.final) ## [1] 32 코스피 시가총액 페이지는 32번째 페이지까지 있으며, for loop 구문을 이용하면 1페이지부터 navi.final, 즉 32 페이지까지 모든 내용을 읽어올 수 있습니다. 먼저 코스피의 첫 번째 페이지에서 우리가 원하는 데이터를 추출하는 방법을 살펴보겠습니다. i = 0 # 코스피 j = 1 # 첫번째 페이지 url = paste0('https://finance.naver.com/sise/', 'sise_market_sum.nhn?sosok=',i,"&page=",j) down_table = GET(url) i와 j에 각각 0과 1을 입력해 코스피 첫 번째 페이지에 해당하는 url을 생성합니다. GET() 함수를 이용해 해당 페이지의 데이터를 다운로드합니다. Sys.setlocale("LC_ALL", "English") table = read_html(down_table, encoding = "EUC-KR") %>% html_table(fill = TRUE) table = table[[2]] Sys.setlocale("LC_ALL", "Korean") Sys.setlocale() 함수를 통해 로케일 언어를 English로 설정합니다. read_html() 함수를 통해 HTML 정보를 읽어옵니다. html_table() 함수를 통해 테이블 정보를 읽어오며, fill=TRUE를 추가해줍니다. table 변수에는 리스트 형태로 총 세 가지 테이블이 저장되어 있습니다. 첫 번째 리스트에는 거래량, 시가, 고가 등 적용 항목이 저장되어 있고 세 번째 리스트에는 페이지 내비게이션 테이블이 저장되어 있으므로, 우리에게 필요한 두 번째 리스트만을 table 변수에 다시 저장합니다. 한글을 읽기 위해 Sys.setlocale() 함수를 통해 로케일 언어를 다시 Korean으로 변경합니다. 저장된 테이블 내용을 확인하면 다음과 같습니다. print(head(table)) ## N 종목명 현재가 전일비 등락률 액면가 ## 1 NA ## 2 1 삼성전자 88,000 1,700 -1.90% 100 ## 3 2 SK하이닉스 127,500 3,000 -2.30% 5,000 ## 4 3 LG화학 979,000 31,000 -3.07% 5,000 ## 5 4 삼성전자우 77,600 1,200 -1.52% 100 ## 6 5 삼성바이오로직스 804,000 12,000 -1.47% 2,500 ## 시가총액 상장주식수 외국인비율 거래량 PER ## 1 NA <NA> ## 2 5,253,409 5,969,783 55.57 33,117,980 24.03 ## 3 928,203 728,002 50.20 4,763,450 32.34 ## 4 691,099 70,592 43.59 442,295 91.47 ## 5 638,560 822,887 80.52 3,116,433 21.19 ## 6 531,967 66,165 10.40 136,954 149.69 ## ROE 토론실 ## 1 <NA> NA ## 2 8.69 NA ## 3 4.25 NA ## 4 1.84 NA ## 5 N/A NA ## 6 4.77 NA 이 중 마지막 열인 토론실은 필요 없는 열이며, 첫 번째 행과 같이 아무런 정보가 없는 행도 있습니다. 이를 다음과 같이 정리해줍니다. table[, ncol(table)] = NULL table = na.omit(table) print(head(table)) ## N 종목명 현재가 전일비 등락률 액면가 ## 2 1 삼성전자 88,000 1,700 -1.90% 100 ## 3 2 SK하이닉스 127,500 3,000 -2.30% 5,000 ## 4 3 LG화학 979,000 31,000 -3.07% 5,000 ## 5 4 삼성전자우 77,600 1,200 -1.52% 100 ## 6 5 삼성바이오로직스 804,000 12,000 -1.47% 2,500 ## 10 6 현대차 240,000 10,500 -4.19% 5,000 ## 시가총액 상장주식수 외국인비율 거래량 PER ## 2 5,253,409 5,969,783 55.57 33,117,980 24.03 ## 3 928,203 728,002 50.20 4,763,450 32.34 ## 4 691,099 70,592 43.59 442,295 91.47 ## 5 638,560 822,887 80.52 3,116,433 21.19 ## 6 531,967 66,165 10.40 136,954 149.69 ## 10 512,804 213,668 30.95 3,863,728 57.35 ## ROE ## 2 8.69 ## 3 4.25 ## 4 1.84 ## 5 N/A ## 6 4.77 ## 10 4.32 이제 필요한 정보는 6자리 티커입니다. 티커 역시 개발자 도구 화면을 통해 tbody → td → a 태그의 href 속성에 위치하고 있음을 알고 있습니다. 티커를 추출하는 코드는 다음과 같습니다. symbol = read_html(down_table, encoding = 'EUC-KR') %>% html_nodes(., 'tbody') %>% html_nodes(., 'td') %>% html_nodes(., 'a') %>% html_attr(., 'href') print(head(symbol, 10)) ## [1] "/item/main.nhn?code=005930" ## [2] "/item/board.nhn?code=005930" ## [3] "/item/main.nhn?code=000660" ## [4] "/item/board.nhn?code=000660" ## [5] "/item/main.nhn?code=051910" ## [6] "/item/board.nhn?code=051910" ## [7] "/item/main.nhn?code=005935" ## [8] "/item/board.nhn?code=005935" ## [9] "/item/main.nhn?code=207940" ## [10] "/item/board.nhn?code=207940" read_html() 함수를 통해 HTML 정보를 읽어오며, 인코딩은 EUC-KR로 설정합니다. html_nodes() 함수를 통해 tbody 태그 정보를 불러옵니다. 다시 html_nodes() 함수를 통해 td와 a 태그 정보를 불러옵니다. html_attr() 함수를 이용해 href 속성을 불러옵니다. 이를 통해 symbol에는 href 속성에 해당하는 링크 주소들이 저장됩니다. 이 중 마지막 6자리 글자만 추출하는 코드는 다음과 같습니다. library(stringr) symbol = sapply(symbol, function(x) { str_sub(x, -6, -1) }) print(head(symbol, 10)) ## /item/main.nhn?code=005930 /item/board.nhn?code=005930 ## "005930" "005930" ## /item/main.nhn?code=000660 /item/board.nhn?code=000660 ## "000660" "000660" ## /item/main.nhn?code=051910 /item/board.nhn?code=051910 ## "051910" "051910" ## /item/main.nhn?code=005935 /item/board.nhn?code=005935 ## "005935" "005935" ## /item/main.nhn?code=207940 /item/board.nhn?code=207940 ## "207940" "207940" sapply() 함수를 통해 symbol 변수의 내용들에 function()을 적용하며, stringr 패키지의 str_sub() 함수를 이용해 마지막6자리 글자만 추출합니다. 결과를 살펴보면 티커에 해당하는 마지막 6글자만 추출되지만 동일한 내용이 두 번 연속해 추출됩니다. 이는 main.nhn?code=에 해당하는 부분은 종목명에 설정된 링크, board.nhn?code=에 해당하는 부분은 토론실에 설정된 링크이기 때문입니다. symbol = unique(symbol) print(head(symbol, 10)) ## [1] "005930" "000660" "051910" "005935" "207940" ## [6] "005380" "006400" "035420" "068270" "035720" unique() 함수를 이용해 중복되는 티커를 제거하면 우리가 원하는 티커 부분만 깔끔하게 정리됩니다. 해당 내용을 위에서 구한 테이블에 입력한 후 데이터를 다듬는 과정은 다음과 같습니다. table$N = symbol colnames(table)[1] = '종목코드' rownames(table) = NULL ticker[[j]] = table 위에서 구한 티커를 N열에 입력합니다. 해당 열 이름을 종목코드로 변경합니다. na.omit() 함수를 통해 특정 행을 삭제했으므로, 행 이름을 초기화해줍니다. ticker의 j번째 리스트에 정리된 데이터를 입력합니다. 위의 코드에서 i와 j 값을 for loop 구문에 이용하면 코스피와 코스닥 전 종목의 티커가 정리된 테이블을 만들 수 있습니다. 이를 전체 코드로 나타내면 다음과 같습니다. data = list() # i = 0 은 코스피, i = 1 은 코스닥 종목 for (i in 0:1) { ticker = list() url = paste0('https://finance.naver.com/sise/', 'sise_market_sum.nhn?sosok=',i,'&page=1') down_table = GET(url) # 최종 페이지 번호 찾아주기 navi.final = read_html(down_table, encoding = "EUC-KR") %>% html_nodes(., ".pgRR") %>% html_nodes(., "a") %>% html_attr(.,"href") %>% strsplit(., "=") %>% unlist() %>% tail(., 1) %>% as.numeric() # 첫번째 부터 마지막 페이지까지 for loop를 이용하여 테이블 추출하기 for (j in 1:navi.final) { # 각 페이지에 해당하는 url 생성 url = paste0( 'https://finance.naver.com/sise/', 'sise_market_sum.nhn?sosok=',i,"&page=",j) down_table = GET(url) Sys.setlocale("LC_ALL", "English") # 한글 오류 방지를 위해 영어로 로케일 언어 변경 table = read_html(down_table, encoding = "EUC-KR") %>% html_table(fill = TRUE) table = table[[2]] # 원하는 테이블 추출 Sys.setlocale("LC_ALL", "Korean") # 한글을 읽기위해 로케일 언어 재변경 table[, ncol(table)] = NULL # 토론식 부분 삭제 table = na.omit(table) # 빈 행 삭제 # 6자리 티커만 추출 symbol = read_html(down_table, encoding = "EUC-KR") %>% html_nodes(., "tbody") %>% html_nodes(., "td") %>% html_nodes(., "a") %>% html_attr(., "href") symbol = sapply(symbol, function(x) { str_sub(x, -6, -1) }) symbol = unique(symbol) # 테이블에 티커 넣어준 후, 테이블 정리 table$N = symbol colnames(table)[1] = "종목코드" rownames(table) = NULL ticker[[j]] = table Sys.sleep(0.5) # 페이지 당 0.5초의 슬립 적용 } # do.call을 통해 리스트를 데이터 프레임으로 묶기 ticker = do.call(rbind, ticker) data[[i + 1]] = ticker } # 코스피와 코스닥 테이블 묶기 data = do.call(rbind, data) http://hkconsensus.hankyung.com/↩︎ http://kind.krx.co.kr/↩︎ https://finance.naver.com/news/news_list.nhn?mode=LSS2D&section_id=101&section_id2=258↩︎ "], -["금융-데이터-수집하기-기본.html", "Chapter 5 금융 데이터 수집하기 (기본) 5.1 한국거래소의 산업별 현황 및 개별지표 크롤링 5.2 WICS 기준 섹터정보 크롤링", " Chapter 5 금융 데이터 수집하기 (기본) API와 크롤링을 이용한다면 비용을 지불하지 않고 얼마든지 금융 데이터를 수집할 수있습니다. 이 CHAPTER에서는 금융 데이터를 받기 위해 필요한 주식티커를 구하는 방법과 섹터별 구성종목을 크롤링하는 방법을 알아보겠습니다. 5.1 한국거래소의 산업별 현황 및 개별지표 크롤링 앞 CHAPTER의 예제를 통해 네이버 금융에서 주식티커를 크롤링하는 방법을 살펴보았습니다. 그러나 이 방법은 지나치게 복잡하고 시간이 오래 걸립니다. 반면 한국거래소에서 제공하는 업종분류 현황과 개별종목 지표 데이터를 이용하면 훨씬 간단하게 주식티커 데이터를 수집할 수 있습니다. KRX 정보데이터시스템 http://data.krx.co.kr/ 에서 [기본통계 → 주식 → 세부안내] 부분 [12025] 업종분류 현황 [12021] 개별종목 해당 데이터들을 크롤링이 아닌 [Excel] 버튼을 클릭해 엑셀 파일로 받을 수도 있습니다. 그러나 매번 엑셀 파일을 다운로드하고 이를 R로 불러오는 작업은 상당히 비효율적이며, 크롤링을 이용한다면 해당 데이터를 R로 직접 불러올 수 있습니다. 5.1.1 업종분류 현황 크롤링 먼저 업종분류 현황에 해당하는 페이지에 접속한 후 개발자 도구 화면을 열고 [다운로드] 버튼을 클릭한 후 [CSV]를 누릅니다. [Network] 탭에는 generate.cmd와 download.cmd 두 가지 항목이 있습니다. 거래소에서 엑셀 데이터를 받는 과정은 다음과 같습니다. http://data.krx.co.kr/comm/fileDn/download_excel/download.cmd 에 원하는 항목을 쿼리로 발송하면 해당 쿼리에 해당하는 OTP(generate.cmd)를 받게 됩니다. 부여받은 OTP를 http://data.krx.co.kr/에 제출하면 이에 해당하는 데이터(download.cmd)를 다운로드하게 됩니다. 먼저 1번 단계를 살펴보겠습니다. 그림 1.3: OTP 생성 부분 General 항목의 Request URL의 앞부분이 원하는 항목을 제출할 주소입니다. Form Data에는 우리가 원하는 항목들이 적혀 있습니다. 이를 통해 POST 방식으로 데이터를 요청함을 알 수 있습니다. 다음으로 2번 단계를 살펴보겠습니다. 그림 2.1: OTP 제출 부분 General 항목의 Request URL은 OTP를 제출할 주소입니다. Form Data의 OTP는 1번 단계에서 부여받은 OTP에 해당합니다. 이 역시 POST 방식으로 데이터를 요청합니다. 위 과정을 코드로 나타내면 다음과 같습니다. library(httr) library(rvest) library(readr) gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd' gen_otp_data = list( mktId = 'STK', trdDd = '20210108', money = '1', csvxls_isNo = 'false', name = 'fileDown', url = 'dbms/MDC/STAT/standard/MDCSTAT03901' ) otp = POST(gen_otp_url, query = gen_otp_data) %>% read_html() %>% html_text() gen_otp_url에 원하는 항목을 제출할 URL을 입력합니다. 개발자 도구 화면에 나타는 쿼리 내용들을 리스트 형태로 입력합니다. 이 중 mktId의 STK는 코스피에 해당하는 내용이며, 코스닥 데이터를 받고자 할 경우 KSQ를 입력해야 합니다. POST() 함수를 통해 해당 URL에 쿼리를 전송하면 이에 해당하는 데이터를 받게 됩니다. read_html()함수를 통해 HTML 내용을 읽어옵니다. html_text() 함수는 HTML 내에서 텍스트에 해당하는 부분만을 추출합니다. 이를 통해 OTP 값만 추출하게 됩니다. 위의 과정을 거쳐 생성된 OTP를 제출하면, 우리가 원하는 데이터를 다운로드할 수 있습니다. down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd' down_sector_KS = POST(down_url, query = list(code = otp), add_headers(referer = gen_otp_url)) %>% read_html(encoding = 'EUC-KR') %>% html_text() %>% read_csv() OTP를 제출할 URL을 down_url에 입력합니다. POST() 함수를 통해 위에서 부여받은 OTP 코드를 해당 URL에 제출합니다. add_headers() 구문을 통해 리퍼러(referer)를 추가해야 합니다. 리퍼러란 링크를 통해서 각각의 웹사이트로 방문할 때 남는 흔적입니다. 거래소 데이터를 다운로드하는 과정을 살펴보면 첫 번째 URL에서 OTP를 부여받고, 이를 다시 두번째 URL에 제출했습니다. 그런데 이러한 과정의 흔적이 없이 OTP를 바로 두번째 URL에 제출하면 서버는 이를 로봇으로 인식해 데이터를 반환하지 않습니다. 따라서 add_headers() 함수를 통해 우리가 거쳐온 과정을 흔적으로 남겨 야 데이터를 반환하게 되며 첫 번째 URL을 리퍼러로 지정해줍니다. read_html()과 html_text() 함수를 통해 텍스트 데이터만 추출합니다. EUC-KR로 인코딩이 되어 있으므로 read_html() 내에 이를 입력해줍니다. read_csv() 함수는 csv 형태의 데이터를 불러옵니다. print(down_sector_KS) ## # A tibble: 917 x 8 ## 종목코드 종목명 시장구분 업종명 종가 대비 등락률 ## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> ## 1 095570 AJ네트웍… KOSPI 서비스업… 4540 -155 -3.3 ## 2 006840 AK홀딩스… KOSPI 기타금융… 25350 150 0.6 ## 3 027410 BGF KOSPI 기타금융… 4905 -25 -0.51 ## 4 282330 BGF리테… KOSPI 유통업 141000 4500 3.3 ## 5 138930 BNK금융… KOSPI 기타금융… 5780 0 0 ## 6 001460 BYC KOSPI 섬유의복… 324500 10500 3.34 ## 7 001465 BYC우 KOSPI 섬유의복… 157500 10000 6.78 ## 8 001040 CJ KOSPI 기타금융… 102500 7600 8.01 ## 9 079160 CJ CGV KOSPI 서비스업… 26150 300 1.16 ## 10 00104K CJ4우(… KOSPI 기타금융… 81400 5300 6.96 ## # … with 907 more rows, and 1 more variable: ## # 시가총액 <dbl> 위 과정을 통해 down_sector 변수에는 산업별 현황 데이터가 저장되었습니다. 코스닥 시장의 데이터도 다운로드 받도록 하겠습니다. gen_otp_data = list( mktId = 'KSQ', # 코스닥으로 변경 trdDd = '20210108', money = '1', csvxls_isNo = 'false', name = 'fileDown', url = 'dbms/MDC/STAT/standard/MDCSTAT03901' ) otp = POST(gen_otp_url, query = gen_otp_data) %>% read_html() %>% html_text() down_sector_KQ = POST(down_url, query = list(code = otp), add_headers(referer = gen_otp_url)) %>% read_html(encoding = 'EUC-KR') %>% html_text() %>% read_csv() 코스피 데이터와 코스닥 데이터를 하나로 합치도록 합니다. down_sector = rbind(down_sector_KS, down_sector_KQ) 이를 csv 파일로 저장하겠습니다. ifelse(dir.exists('data'), FALSE, dir.create('data')) write.csv(down_sector, 'data/krx_sector.csv') 먼저 ifelse() 함수를 통해 data라는 이름의 폴더가 있으면 FALSE를 반환하고, 없으면 해당 이름으로 폴더를 생성해줍니다. 그 후 앞서 다운로드한 데이터를 data 폴더 안에 krx_sector.csv 이름으로 저장합니다. 해당 폴더를 확인해보면 데이터가 csv 형태로 저장되어 있습니다. 5.1.2 개별종목 지표 크롤링 개별종목 데이터를 크롤링하는 방법은 위와 매우 유사하며, 요청하는 쿼리 값에만 차이가 있습니다. 개발자 도구 화면을 열고 [CSV] 버튼을 클릭해 어떠한 쿼리를 요청하는지 확인합니다. 그림 2.7: 개별지표 OTP 생성 부분 이 중 tboxisuCd_finder_stkisu0_6, isu_Cd, isu_Cd2 등의 항목은 조회 구분의 개별추이 탭에 해당하는 부분이므로 우리가 원하는 전체 데이터를 받을 때는 필요하지 않은 요청값입니다. 이를 제외한 요청값을 산업별 현황 예제에 적용하면 해당 데이터 역시 손쉽게 다운로드할 수 있습니다. library(httr) library(rvest) library(readr) gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd' gen_otp_data = list( searchType = '1', mktId = 'ALL', trdDd = '20210108', csvxls_isNo = 'false', name = 'fileDown', url = 'dbms/MDC/STAT/standard/MDCSTAT03501' ) otp = POST(gen_otp_url, query = gen_otp_data) %>% read_html() %>% html_text() down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd' down_ind = POST(down_url, query = list(code = otp), add_headers(referer = gen_otp_url)) %>% read_html(encoding = 'EUC-KR') %>% html_text() %>% read_csv() print(down_ind) ## # A tibble: 2,345 x 11 ## 종목코드 종목명 종가 대비 등락률 EPS PER ## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 060310 3S 2245 -45 -1.97 NA NA ## 2 095570 AJ네트웍… 4540 -155 -3.3 982 4.62 ## 3 006840 AK홀딩스… 25350 150 0.6 2168 11.7 ## 4 054620 APS홀딩… 7500 -150 -1.96 NA NA ## 5 265520 AP시스템… 26000 -100 -0.38 671 38.8 ## 6 211270 AP위성 8100 -250 -2.99 51 159. ## 7 027410 BGF 4905 -25 -0.51 281 17.5 ## 8 282330 BGF리테… 141000 4500 3.3 8763 16.1 ## 9 138930 BNK금융… 5780 0 0 1647 3.51 ## 10 001460 BYC 324500 10500 3.34 33265 9.75 ## # … with 2,335 more rows, and 4 more variables: ## # BPS <dbl>, PBR <dbl>, 주당배당금 <dbl>, ## # 배당수익률 <dbl> 위 과정을 통해 down_ind 변수에는 개별종목 지표 데이터가 저장되었습니다. 해당 데이터 역시 csv 파일로 저장하겠습니다. write.csv(down_ind, 'data/krx_ind.csv') 5.1.3 최근 영업일 기준 데이터 받기 위 예제의 쿼리 항목 중 date와 schdate 부분을 원하는 일자로 입력하면(예: 20190104) 해당일의 데이터를 다운로드할 수 있으며, 전 영업일 날짜를 입력하면 가장 최근의 데이터를 받을 수 있습니다. 그러나 매번 해당 항목을 입력하기는 번거로우므로 자동으로 반영되게 할 필요가 있습니다. 네이버 금융의 [국내증시 → 증시자금동향]에는 이전 2영업일에 해당하는 날짜가 있으며, 자동으로 날짜가 업데이트되어 편리합니다. 따라서 해당 부분을 크롤링해 쿼리 항목에 사용할 수 있습니다. 그림 2.9: 최근 영업일 부분 크롤링하고자 하는 데이터가 하나거나 소수일때는 HTML 구조를 모두 분해한 후 데이터를 추출하는 것보다 Xpath를 이용하는 것이 훨씬 효율적입니다. Xpath란 XML 중 특정 값의 태그나 속성을 찾기 쉽게 만든 주소라 생각하면 됩니다. 예를 들어 R 프로그램이 저장된 곳을 윈도우 탐색기를 이용해 이용하면 C:\\Program Files\\R\\R-3.4.2 형태의 주소를 보이는데 이것은 윈도우의 path 문법입니다. XML 역시 이와 동일한 개념의 Xpath가 있습니다. 웹페이지에서 Xpath를 찾는 법은 다음과 같습니다. 그림 5.1: Xpath 복사하기 먼저 크롤링하고자 하는 내용에 마우스 커서를 올린 채 마우스 오른쪽 버튼을 클릭한 후 [검사]를 선택합니다. 그러면 개발자 도구 화면이 열리며 해당 지점의 HTML 부분이 선택됩니다. 그 후 HTML 화면에서 마우스 오른쪽 버튼을 클릭하고 [Copy → Copy Xpath]를 선택하면 해당 지점의 Xpath가 복사됩니다. //*[@id="type_0"]/div/ul[2]/li/span //*[@id=\"type_0\"]/div/ul[2]/li/span 위에서 구한 날짜의 Xpath를 이용해 해당 데이터를 크롤링하겠습니다. library(httr) library(rvest) library(stringr) url = 'https://finance.naver.com/sise/sise_deposit.nhn' biz_day = GET(url) %>% read_html(encoding = 'EUC-KR') %>% html_nodes(xpath = '//*[@id="type_1"]/div/ul[2]/li/span') %>% html_text() %>% str_match(('[0-9]+.[0-9]+.[0-9]+') ) %>% str_replace_all('\\\\.', '') print(biz_day) ## [1] "20210113" 페이지의 url을 저장합니다. GET() 함수를 통해 해당 페이지 내용을 받습니다. read_html() 함수를 이용해 해당 페이지의 HTML 내용을 읽어오며, 인코딩은 EUC-KR로 설정합니다. html_node() 함수 내에 위에서 구한 Xpath를 입력해서 해당 지점의 데이터를 추출합니다. html_text() 함수를 통해 텍스트 데이터만을 추출합니다. str_match() 함수 내에서 정규표현식11을 이용해 숫자.숫자.숫자 형식의 데이터를 추출합니다. str_replace_all() 함수를 이용해 마침표(.)를 모두 없애줍니다. 이처럼 Xpath를 이용하면 태그나 속성을 분해하지 않고도 원하는 지점의 데이터를 크롤링할 수 있습니다. 위 과정을 통해 yyyymmdd 형태의 날짜만 남게 되었습니다. 이를 위의 date와 schdate에 입력하면 산업별 현황과 개별종목 지표를 최근일자 기준으로 다운로드하게 됩니다. 전체 코드는 다음과 같습니다. library(httr) library(rvest) library(stringr) library(readr) # 최근 영업일 구하기 url = 'https://finance.naver.com/sise/sise_deposit.nhn' biz_day = GET(url) %>% read_html(encoding = 'EUC-KR') %>% html_nodes(xpath = '//*[@id="type_1"]/div/ul[2]/li/span') %>% html_text() %>% str_match(('[0-9]+.[0-9]+.[0-9]+') ) %>% str_replace_all('\\\\.', '') # 코스피 업종분류 OTP 발급 gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd' gen_otp_data = list( mktId = 'STK', trdDd = biz_day, # 최근영업일로 변경 money = '1', csvxls_isNo = 'false', name = 'fileDown', url = 'dbms/MDC/STAT/standard/MDCSTAT03901' ) otp = POST(gen_otp_url, query = gen_otp_data) %>% read_html() %>% html_text() # 코스피 업종분류 데이터 다운로드 down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd' down_sector_KS = POST(down_url, query = list(code = otp), add_headers(referer = gen_otp_url)) %>% read_html(encoding = 'EUC-KR') %>% html_text() %>% read_csv() # 코스닥 업종분류 OTP 발급 gen_otp_data = list( mktId = 'KSQ', trdDd = biz_day, # 최근영업일로 변경 money = '1', csvxls_isNo = 'false', name = 'fileDown', url = 'dbms/MDC/STAT/standard/MDCSTAT03901' ) otp = POST(gen_otp_url, query = gen_otp_data) %>% read_html() %>% html_text() # 코스닥 업종분류 데이터 다운로드 down_sector_KQ = POST(down_url, query = list(code = otp), add_headers(referer = gen_otp_url)) %>% read_html(encoding = 'EUC-KR') %>% html_text() %>% read_csv() down_sector = rbind(down_sector_KS, down_sector_KQ) ifelse(dir.exists('data'), FALSE, dir.create('data')) write.csv(down_sector, 'data/krx_sector.csv') # 개별종목 지표 OTP 발급 gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd' gen_otp_data = list( searchType = '1', mktId = 'ALL', trdDd = biz_day, # 최근영업일로 변경 csvxls_isNo = 'false', name = 'fileDown', url = 'dbms/MDC/STAT/standard/MDCSTAT03501' ) otp = POST(gen_otp_url, query = gen_otp_data) %>% read_html() %>% html_text() # 개별종목 지표 데이터 다운로드 down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd' down_ind = POST(down_url, query = list(code = otp), add_headers(referer = gen_otp_url)) %>% read_html(encoding = 'EUC-KR') %>% html_text() %>% read_csv() write.csv(down_ind, 'data/krx_ind.csv') 5.1.4 거래소 데이터 정리하기 위에서 다운로드한 데이터는 중복된 열이 있으며, 불필요한 데이터 역시 있습니다. 따라서 하나의 테이블로 합친 후 정리할 필요가 있습니다. 먼저 다운로드한 csv 파일을 읽어옵니다. down_sector = read.csv('data/krx_sector.csv', row.names = 1, stringsAsFactors = FALSE) down_ind = read.csv('data/krx_ind.csv', row.names = 1, stringsAsFactors = FALSE) read.csv() 함수를 이용해 csv 파일을 불러옵니다. row.names = 1을 통해 첫 번째 열을 행 이름으로 지정하고, stringsAsFactors = FALSE를 통해 문자열 데이터가 팩터 형태로 변형되지 않게 합니다. intersect(names(down_sector), names(down_ind)) ## [1] "종목코드" "종목명" "종가" "대비" ## [5] "등락률" 먼저 intersect() 함수를 통해 두 데이터 간 중복되는 열 이름을 살펴보면 종목코드와 종목명 등이 동일한 위치에 있습니다. setdiff(down_sector[, '종목명'], down_ind[ ,'종목명']) ## [1] "ESR켄달스퀘어리츠" "NH프라임리츠" ## [3] "롯데리츠" "맥쿼리인프라" ## [5] "맵스리얼티1" "모두투어리츠" ## [7] "미래에셋맵스리츠" "바다로19호" ## [9] "베트남개발1" "신한알파리츠" ## [11] "에이리츠" "엘브이엠씨홀딩스" ## [13] "이리츠코크렙" "이지스레지던스리츠" ## [15] "이지스밸류리츠" "제이알글로벌리츠" ## [17] "케이탑리츠" "코람코에너지리츠" ## [19] "하이골드12호" "하이골드3호" ## [21] "한국ANKOR유전" "한국패러랠" ## [23] "GRT" "JTC" ## [25] "SBI핀테크솔루션즈" "SNK" ## [27] "골든센츄리" "글로벌에스엠" ## [29] "넥스틴" "뉴프라이드" ## [31] "로스웰" "미투젠" ## [33] "소마젠(Reg.S)" "씨케이에이치" ## [35] "에스앤씨엔진그룹" "엑세스바이오" ## [37] "오가닉티코스메틱" "윙입푸드" ## [39] "이스트아시아홀딩스" "잉글우드랩" ## [41] "컬러레이" "코오롱티슈진" ## [43] "크리스탈신소재" "헝셩그룹" setdiff() 함수를 통해 두 데이터에 공통적으로 없는 종목명, 즉 하나의 데이터에만 있는 종목을 살펴보면 위와 같습니다. 해당 종목들은 선박펀드, 광물펀드, 해외종목 등 일반적이지 않은 종목들이므로 제외하는 것이 좋습니다. 따라서 둘 사이에 공통적으로 존재하는 종목을 기준으로 데이터를 합쳐주겠습니다. KOR_ticker = merge(down_sector, down_ind, by = intersect(names(down_sector), names(down_ind)), all = FALSE ) merge() 함수는 by를 기준으로 두 데이터를 하나로 합치며, 공통으로 존재하는 종목코드, 종목명, 종가, 대비, 등락률을 기준으로 입력해줍니다. 또한 all 값을 TRUE로 설정하면 합집합을 반환하고, FALSE로 설정하면 교집합을 반환합니다. 공통으로 존재하는 항목을 원하므로 여기서는 FALSE를 입력합니다. KOR_ticker = KOR_ticker[order(-KOR_ticker['시가총액']), ] print(head(KOR_ticker)) ## 종목코드 종목명 종가 대비 등락률 ## 327 005930 삼성전자 89700 -900 -0.99 ## 45 000660 SK하이닉스 133000 4000 3.10 ## 1076 051910 LG화학 1000000 38000 3.95 ## 328 005935 삼성전자우 78600 -1400 -1.75 ## 299 005380 현대차 259000 -2000 -0.77 ## 1928 207940 삼성바이오로직스 830000 12000 1.47 ## 시장구분 업종명 시가총액 EPS PER BPS ## 327 KOSPI 전기전자 5.355e+14 3166 28.33 37528 ## 45 KOSPI 전기전자 9.682e+13 2943 45.19 65836 ## 1076 KOSPI 화학 7.059e+13 4085 244.80 217230 ## 328 KOSPI 전기전자 6.468e+13 NA NA NA ## 299 KOSPI 운수장비 5.534e+13 11310 22.90 253001 ## 1928 KOSPI 의약품 5.492e+13 3067 270.62 65812 ## PBR 주당배당금 배당수익률 ## 327 2.39 1416 1.58 ## 45 2.02 1000 0.75 ## 1076 4.60 2000 0.20 ## 328 NA 1417 1.80 ## 299 1.02 4000 1.54 ## 1928 12.61 0 0.00 데이터를 시가총액 기준으로 내림차순 정렬할 필요도 있습니다. order() 함수를 통해 상대적인 순서를 구할 수 있습니다. R은 기본적으로 오름차순으로 순서를 구하므로 앞에 마이너스(-)를 붙여 내림차순 형태로 바꿉니다. 결과적으로 시가총액 기준 내림차 순으로 해당 데이터가 정렬됩니다. 마지막으로 스팩, 우선주 종목 역시 제외해야 합니다. library(stringr) KOR_ticker[grepl('스팩', KOR_ticker[, '종목명']), '종목명'] ## [1] "엔에이치스팩14호" "유안타제3호스팩" ## [3] "케이비제18호스팩" "삼성스팩2호" ## [5] "교보8호스팩" "엔에이치스팩17호" ## [7] "유안타제6호스팩" "미래에셋대우스팩3호" ## [9] "케이비제20호스팩" "DB금융스팩8호" ## [11] "유안타제5호스팩" "SK6호스팩" ## [13] "대신밸런스제8호스팩" "케이비17호스팩" ## [15] "유안타제7호스팩" "교보10호스팩" ## [17] "IBKS제13호스팩" "대신밸런스제7호스팩" ## [19] "한화에스비아이스팩" "미래에셋대우스팩 5호" ## [21] "하이제5호스팩" "SK4호스팩" ## [23] "신한제6호스팩" "에이치엠씨제5호스팩" ## [25] "한국제7호스팩" "하나금융15호스팩" ## [27] "유안타제4호스팩" "한화플러스제1호스팩" ## [29] "하나머스트제6호스팩" "상상인이안1호스팩" ## [31] "엔에이치스팩18호" "IBKS제14호스팩" ## [33] "하나금융14호스팩" "엔에이치스팩16호" ## [35] "미래에셋대우스팩4호" "SK5호스팩" ## [37] "케이비제19호스팩" "신영스팩6호" ## [39] "에이치엠씨제4호스팩" "대신밸런스제9호스팩" ## [41] "엔에이치스팩13호" "하나금융16호스팩" ## [43] "유진스팩5호" "교보9호스팩" ## [45] "상상인이안제2호스팩" "키움제5호스팩" ## [47] "이베스트스팩5호" "신영스팩5호" ## [49] "유진스팩4호" "한국제8호스팩" ## [51] "IBKS제12호스팩" "이베스트이안스팩1호" KOR_ticker[str_sub(KOR_ticker[, '종목코드'], -1, -1) != 0, '종목명'] ## [1] "삼성전자우" "현대차2우B" ## [3] "LG화학우" "현대차우" ## [5] "LG생활건강우" "LG전자우" ## [7] "삼성SDI우" "아모레퍼시픽우" ## [9] "미래에셋대우2우B" "삼성화재우" ## [11] "한국금융지주우" "신영증권우" ## [13] "CJ4우(전환)" "삼성전기우" ## [15] "한화3우B" "아모레G3우(전환)" ## [17] "현대차3우B" "신풍제약우" ## [19] "대신증권우" "SK케미칼우" ## [21] "CJ제일제당 우" "SK이노베이션우" ## [23] "LG우" "삼성물산우B" ## [25] "대림산업우" "두산퓨얼셀1우" ## [27] "금호석유우" "S-Oil우" ## [29] "NH투자증권우" "두산우" ## [31] "SK우" "CJ우" ## [33] "아모레G우" "솔루스첨단소재1우" ## [35] "녹십자홀딩스2우" "대신증권2우B" ## [37] "유한양행우" "한화솔루션우" ## [39] "SK디스커버리우" "미래에셋대우우" ## [41] "호텔신라우" "코오롱인더우" ## [43] "롯데지주우" "두산퓨얼셀2우B" ## [45] "부국증권우" "두산2우B" ## [47] "GS우" "솔루스첨단소재2우B" ## [49] "대교우B" "대한항공우" ## [51] "롯데칠성우" "유화증권우" ## [53] "삼성중공우" "LG하우시스우" ## [55] "BYC우" "유안타증권우" ## [57] "티와이홀딩스우" "일양약품우" ## [59] "남양유업우" "세방우" ## [61] "한진칼우" "대상우" ## [63] "하이트진로2우B" "코리아써우" ## [65] "한화우" "대덕전자1우" ## [67] "SK증권우" "덕성우" ## [69] "현대건설우" "한화투자증권우" ## [71] "태영건설우" "넥센타이어1우B" ## [73] "삼양사우" "코오롱우" ## [75] "삼양홀딩스우" "유유제약1우" ## [77] "DB하이텍1우" "남선알미우" ## [79] "NPC우" "SK네트웍스우" ## [81] "루트로닉3우C" "서울식품우" ## [83] "넥센우" "성신양회우" ## [85] "대덕1우" "계양전기우" ## [87] "금호산업우" "대한제당우" ## [89] "태양금속우" "코오롱글로벌우" ## [91] "한양증권우" "동원시스템즈우" ## [93] "크라운제과우" "CJ씨푸드1우" ## [95] "크라운해태홀딩스우" "대상홀딩스우" ## [97] "현대비앤지스틸우" "대원전선우" ## [99] "흥국화재우" "깨끗한나라우" ## [101] "금강공업우" "하이트진로홀딩스우" ## [103] "JW중외제약우" "KG동부제철우" ## [105] "대호피앤씨우" "노루페인트우" ## [107] "코리아써키트2우B" "진흥기업우B" ## [109] "동부건설우" "성문전자우" ## [111] "JW중외제약2우B" "유유제약2우B" ## [113] "동양우" "소프트센우" ## [115] "동양2우B" "진흥기업2우B" ## [117] "신원우" "노루홀딩스우" ## [119] "흥국화재2우B" "동양3우B" grepl() 함수를 통해 종목명에 ‘스팩’이 들어가는 종목을 찾고, stringr 패키지의 str_sub() 함수를 통해 종목코드 끝이 0이 아닌 우선주 종목을 찾을 수 있습니다. KOR_ticker = KOR_ticker[!grepl('스팩', KOR_ticker[, '종목명']), ] KOR_ticker = KOR_ticker[str_sub(KOR_ticker[, '종목코드'], -1, -1) == 0, ] 마지막으로 행 이름을 초기화한 후 정리된 데이터를 csv 파일로 저장합니다. rownames(KOR_ticker) = NULL write.csv(KOR_ticker, 'data/KOR_ticker.csv') 5.2 WICS 기준 섹터정보 크롤링 일반적으로 주식의 섹터를 나누는 기준은 MSCI와 S&P가 개발한 GICS12를 가장 많이 사용합니다. 국내 종목의 GICS 기준 정보 역시 한국거래소에서 제공하고 있으나, 이는 독점적 지적재산으로 명시했기에 사용하는 데 무리가 있습니다. 그러나 지수제공업체인 와이즈인덱스13에서는 GICS와 비슷한 WICS 산업분류를 발표하고 있습니다. WICS를 크롤링해 필요한 정보를 수집해보겠습니다. 먼저 웹페이지에 접속해 [Index → WISE SECTOR INDEX → WICS → 에너지]를 클릭합니다. 그 후 [Components] 탭을 클릭하면 해당 섹터의 구성종목을 확인할 수 있습니다. 그림 5.2: WICS 기준 구성종목 개발자도구 화면(그림 5.3)을 통해 해당 페이지의 데이터전송 과정을 살펴보도록 하겠습니다. 그림 5.3: WICS 페이지 개발자도구 화면 일자를 선택하면 [Network] 탭의 GetIndexComponets 항목을 통해 데이터 전송 과정이 나타납니다. Request URL의 주소를 살펴보면 다음과 같습니다. http://www.wiseindex.com/Index/GetIndexComponets: 데이터를 요청하는 url 입니다. ceil_yn = 0: 실링 여부를 나타내며, 0은 비실링을 의미합니다. dt=20190607: 조회일자를 나타냅니다. sec_cd=G10: 섹터 코드를 나타냅니다. 이번엔 위 주소의 페이지를 열어보겠습니다. 그림 5.4: WICS 데이터 페이지 글자들은 페이지에 출력된 내용이지만 매우 특이한 형태로 구성되어 있는데 이것은 JSON 형식의 데이터입니다. 기존에 우리가 살펴보았던 대부분의 웹페이지는 XML 형식으로 표현되어 있습니다. XML 형식은 문법이 복잡하고 표현 규칙이 엄격해 데이터의 용량이 커지는 단점이 있습니다. 반면 JSON 형식은 문법이 단순하고 데이터의 용량이 작아 빠른 속도로 데이터를 교환할 수 있습니다. R에서는 jsonlite 패키지의 fromJSON() 함수를 사용해 매우 손쉽게 JSON 형식의 데이터를 크롤링할 수 있습니다. library(jsonlite) url = 'http://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt=20190607&sec_cd=G10' data = fromJSON(url) lapply(data, head) ## $info ## $info$TRD_DT ## [1] "/Date(1559833200000)/" ## ## $info$MKT_VAL ## [1] 19850082 ## ## $info$TRD_AMT ## [1] 70030 ## ## $info$CNT ## [1] 23 ## ## ## $list ## IDX_CD IDX_NM_KOR ALL_MKT_VAL CMP_CD ## 1 G10 WICS 에너지 19850082 096770 ## 2 G10 WICS 에너지 19850082 010950 ## 3 G10 WICS 에너지 19850082 267250 ## 4 G10 WICS 에너지 19850082 078930 ## 5 G10 WICS 에너지 19850082 067630 ## 6 G10 WICS 에너지 19850082 006120 ## CMP_KOR MKT_VAL WGT S_WGT CAL_WGT SEC_CD ## 1 SK이노베이션 9052841 45.61 45.61 1 G10 ## 2 S-Oil 3403265 17.14 62.75 1 G10 ## 3 현대중공업지주 2873204 14.47 77.23 1 G10 ## 4 GS 2491805 12.55 89.78 1 G10 ## 5 에이치엘비생명과학 624986 3.15 92.93 1 G10 ## 6 SK디스커버리 257059 1.30 94.22 1 G10 ## SEC_NM_KOR SEQ TOP60 APT_SHR_CNT ## 1 에너지 1 2 56403994 ## 2 에너지 2 2 41655633 ## 3 에너지 3 2 9283372 ## 4 에너지 4 2 49245150 ## 5 에너지 5 2 39307272 ## 6 에너지 6 2 10470820 ## ## $sector ## SEC_CD SEC_NM_KOR SEC_RATE IDX_RATE ## 1 G25 경기관련소비재 16.05 0 ## 2 G35 건강관리 9.27 0 ## 3 G50 커뮤니케이션서비스 2.26 0 ## 4 G40 금융 10.31 0 ## 5 G10 에너지 2.37 100 ## 6 G20 산업재 12.68 0 ## ## $size ## SEC_CD SEC_NM_KOR SEC_RATE IDX_RATE ## 1 WMI510 WMI500 대형주 69.40 89.78 ## 2 WMI520 WMI500 중형주 13.56 4.44 ## 3 WMI530 WMI500 소형주 17.04 5.78 $list 항목에는 해당 섹터의 구성종목 정보가 있으며, $sector 항목을 통해 다른 섹터의 코드도 확인할 수 있습니다. for loop 구문을 이용해 URL의 sec_cd=에 해당하는 부분만 변경하면 모든 섹터의 구성종목을 매우 쉽게 얻을 수 있습니다. sector_code = c('G25', 'G35', 'G50', 'G40', 'G10', 'G20', 'G55', 'G30', 'G15', 'G45') data_sector = list() for (i in sector_code) { url = paste0( 'http://www.wiseindex.com/Index/GetIndexComponets', '?ceil_yn=0&dt=',biz_day,'&sec_cd=',i) data = fromJSON(url) data = data$list data_sector[[i]] = data Sys.sleep(1) } data_sector = do.call(rbind, data_sector) 해당 데이터를 csv 파일로 저장해주도록 합니다. write.csv(data_sector, 'data/KOR_sector.csv') 특정한 규칙을 가진 문자열의 집합을 표현하는데 사용하는 형식 언어↩︎ https://en.wikipedia.org/wiki/Global_Industry_Classification_Standard↩︎ http://www.wiseindex.com/↩︎ "], -["금융-데이터-수집하기-심화.html", "Chapter 6 금융 데이터 수집하기 (심화) 6.1 수정주가 크롤링 6.2 재무제표 및 가치지표 크롤링 6.3 DART의 Open API를 이용한 데이터 수집하기", " Chapter 6 금융 데이터 수집하기 (심화) 지난 CHAPTER에서 수집한 주식티커를 바탕으로 이번 CHAPTER에서는 퀀트 투자의 핵심 자료인 수정주가, 재무제표, 가치지표를 크롤링하는 방법을 알아보겠습니다. 6.1 수정주가 크롤링 주가 데이터는 투자를 함에 있어 반드시 필요한 데이터이며, 인터넷에서 주가를 수집할 수 있는 방법은 매우 많습니다. 먼저 API를 이용한 데이터 수집에서 살펴본 것과 같이, getSymbols() 함수를 이용해 데이터를 받을 수 있습니다. 그러나 야후 파이낸스에서 제공하는 데이터 중 미국 주가는 이상 없이 다운로드되지만, 국내 중소형주는 주가가 없는 경우가 있습니다. 또한 단순 주가를 구할 수 있는 방법은 많지만, 투자에 필요한 수정주가를 구할 수 있는 방법은 찾기 힘듭니다. 다행히 네이버 금융에서 제공하는 정보를 통해 모든 종목의 수정주가를 매우 손쉽게 구할 수 있습니다. 6.1.1 개별종목 주가 크롤링 먼저 네이버 금융에서 특정종목(예: 삼성전자)의 [차트] 탭14을 선택합니다.15 해당 차트는 주가 데이터를 받아 그래프를 그려주는 형태입니다. 따라서 해당 데이터가 어디에서 오는지 알기 위해 개발자 도구 화면을 이용합니다. 그림 1.3: 네이버금융 차트의 통신기록 화면을 연 상태에서 [일봉] 탭을 선택하면 sise.nhn, schedule.nhn, notice.nhn 총 세 가지 항목이 생성됩니다. 이 중 sise.nhn 항목의 Request URL이 주가 데이터를 요청하는 주소입니다. 해당 URL에 접속해보겠습니다. 그림 2.1: 주가 데이터 페이지 각 날짜별로 시가, 고가, 저가, 종가, 거래량이 있으며, 주가는 모두 수정주가 기준입니다. 또한 해당 데이터가 item 태그 내 data 속성에 위치하고 있습니다. URL에서 symbol= 뒤에 6자리 티커만 변경하면 해당 종목의 주가 데이터가 있는 페이지로 이동할 수 있으며, 우리가 원하는 모든 종목의 주가 데이터를 크롤링할 수 있습니다. library(stringr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1) print(KOR_ticker$'종목코드'[1]) ## [1] 5930 KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, side = c('left'), pad = '0') 먼저 저장해두었던 csv 파일을 불러옵니다. 종목코드를 살펴보면 005930이어야 할 삼성전자의 티커가 5930으로 입력되어 있습니다. 이는 파일을 불러오는 과정에서 0으로 시작하는 숫자들이 지워졌기 때문입니다. stringr 패키지의 str_pad() 함수를 사용해 6자리가 되지 않는 문자는 왼쪽에 0을 추가해 강제로 6자리로 만들어주도록 합니다. 다음은 첫 번째 종목인 삼성전자의 주가를 크롤링한 후 가공하는 방법입니다. library(xts) ifelse(dir.exists('data/KOR_price'), FALSE, dir.create('data/KOR_price')) ## [1] FALSE i = 1 name = KOR_ticker$'종목코드'[i] price = xts(NA, order.by = Sys.Date()) print(price) ## [,1] ## 2021-01-17 NA data 폴더 내에 KOR_price 폴더를 생성합니다. i = 1을 입력합니다. 향후 for loop 구문을 통해 i 값만 변경하면 모든 종목의 주가를 다운로드할 수 있습니다. name에 해당 티커를 입력합니다. xts() 함수를 이용해 빈 시계열 데이터를 생성하며, 인덱스는 Sys.Date()를 통해 현재 날짜를 입력합니다. library(httr) library(rvest) url = paste0( 'https://fchart.stock.naver.com/sise.nhn?symbol=', name,'&timeframe=day&count=500&requestType=0') data = GET(url) data_html = read_html(data, encoding = 'EUC-KR') %>% html_nodes('item') %>% html_attr('data') print(head(data_html)) ## [1] "20190108|38000|39200|37950|38100|12756554" ## [2] "20190109|38650|39600|38300|39600|17452708" ## [3] "20190110|40000|40150|39600|39800|14731699" ## [4] "20190111|40350|40550|39950|40500|11661063" ## [5] "20190114|40450|40700|39850|40050|11984996" ## [6] "20190115|40050|41100|39850|41100|11492756" paste0() 함수를 이용해 원하는 종목의 url을 생성합니다. url 중 티커에 해당하는 6자리 부분만 위에서 입력한 name으로 설정해주면 됩니다. GET() 함수를 통해 페이지의 데이터를 불러옵니다. read_html() 함수를 통해 HTML 정보를 읽어옵니다. html_nodes()와 html_attr() 함수를 통해 item 태그 및 data 속성의 데이터를 추출합니다. 결과적으로 날짜 및 주가, 거래량 데이터가 추출됩니다. 해당 데이터는 |으로 구분되어 있으며, 이를 테이블 형태로 바꿀 필요가 있습니다. library(readr) price = read_delim(data_html, delim = '|') print(head(price)) ## # A tibble: 6 x 6 ## `20190108` `38000` `39200` `37950` `38100` `12756554` ## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 20190109 38650 39600 38300 39600 17452708 ## 2 20190110 40000 40150 39600 39800 14731699 ## 3 20190111 40350 40550 39950 40500 11661063 ## 4 20190114 40450 40700 39850 40050 11984996 ## 5 20190115 40050 41100 39850 41100 11492756 ## 6 20190116 41150 41450 40700 41450 8491595 readr 패키지의 read_delim() 함수를 쓰면 구분자로 이루어진 데이터를 테이블로 쉽게 변경할 수 있습니다. 데이터를 확인해보면 테이블 형태로 변경되었으며 각 열은 날짜, 시가, 고가, 저가, 종가, 거래량을 의미합니다. 이 중 우리가 필요한 날짜와 종가를 선택한 후 데이터 클렌징을 해줍니다. library(lubridate) library(timetk) price = price[c(1, 5)] price = data.frame(price) colnames(price) = c('Date', 'Price') price[, 1] = ymd(price[, 1]) price = tk_xts(price, date_var = Date) print(tail(price)) ## Price ## 2021-01-08 88800 ## 2021-01-11 91000 ## 2021-01-12 90600 ## 2021-01-13 89700 ## 2021-01-14 89700 ## 2021-01-15 88000 날짜에 해당하는 첫 번째 열과, 종가에 해당하는 다섯 번째 열만 선택해 저장합니다. 티블 형태의 데이터를 데이터 프레임 형태로 변경합니다. 열 이름을 Date와 Price로 변경합니다. lubridate 패키지의 ymd() 함수를 이용하면 yyyymmdd 형태가 yyyy-mm-dd로 변경되며 데이터 형태 또한 Date 타입으로 변경됩니다. timetk 패키지의 tk_xts() 함수를 이용해 시계열 형태로 변경하며, 인덱스는 Date 열을 설정합니다. 형태를 변경한 후 해당 열은 자동으로 삭제됩니다. 데이터를 확인해보면 우리에게 필요한 형태로 정리되었습니다. write.csv(price, paste0('data/KOR_price/', name, '_price.csv')) 마지막으로 해당 데이터를 data 폴더의 KOR_price 폴더 내에 티커_price.csv 이름으로 저장합니다. 6.1.2 전 종목 주가 크롤링 위의 코드에서 for loop 구문을 이용해 i 값만 변경해주면 모든 종목의 주가를 다운로드할 수 있습니다. 전 종목 주가를 다운로드하는 전체 코드는 다음과 같습니다. library(httr) library(rvest) library(stringr) library(xts) library(lubridate) library(readr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1) print(KOR_ticker$'종목코드'[1]) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, side = c('left'), pad = '0') ifelse(dir.exists('data/KOR_price'), FALSE, dir.create('data/KOR_price')) for(i in 1 : nrow(KOR_ticker) ) { price = xts(NA, order.by = Sys.Date()) # 빈 시계열 데이터 생성 name = KOR_ticker$'종목코드'[i] # 티커 부분 선택 # 오류 발생 시 이를 무시하고 다음 루프로 진행 tryCatch({ # url 생성 url = paste0( 'https://fchart.stock.naver.com/sise.nhn?symbol=' ,name,'&timeframe=day&count=500&requestType=0') # 이 후 과정은 위와 동일함 # 데이터 다운로드 data = GET(url) data_html = read_html(data, encoding = 'EUC-KR') %>% html_nodes("item") %>% html_attr("data") # 데이터 나누기 price = read_delim(data_html, delim = '|') # 필요한 열만 선택 후 클렌징 price = price[c(1, 5)] price = data.frame(price) colnames(price) = c('Date', 'Price') price[, 1] = ymd(price[, 1]) rownames(price) = price[, 1] price[, 1] = NULL }, error = function(e) { # 오류 발생시 해당 종목명을 출력하고 다음 루프로 이동 warning(paste0("Error in Ticker: ", name)) }) # 다운로드 받은 파일을 생성한 폴더 내 csv 파일로 저장 write.csv(price, paste0('data/KOR_price/', name, '_price.csv')) # 타임슬립 적용 Sys.sleep(2) } 위 코드에서 추가된 점은 다음과 같습니다. 페이지 오류, 통신 오류 등 오류가 발생할 경우 for loop 구문은 멈춰버리는데 전체 데이터를 처음부터 다시 받는 일은 매우 귀찮은 작업입니다. 따라서 tryCatch() 함수를 이용해 오류가 발생할 때 해당 티커를 출력한 후 다음 루프로 넘어가게 합니다. 또한 오류가 발생하면 xts() 함수를 통해 만들어둔 빈 데이터를 저장하게 됩니다. 마지막으로 무한 크롤링을 방지하기 위해 한 번의 루프가 끝날 때마다 2초의 타임슬립을 적용했습니다. 위 코드가 모두 돌아가는 데는 수 시간이 걸립니다. 작업이 끝난 후 data/KOR_price 폴더를 확인해보면 전 종목 주가가 csv 형태로 저장되어 있습니다. 6.2 재무제표 및 가치지표 크롤링 주가와 더불어 재무제표와 가치지표 역시 투자에 있어 핵심이 되는 데이터입니다. 해당 데이터 역시 여러 웹사이트에서 구할 수 있지만, 국내 데이터 제공업체인 FnGuide에서 운영하는 Company Guide 웹사이트16에서 손쉽게 구할 수 있습니다. 6.2.1 재무제표 다운로드 먼저 개별종목의 재무제표를 탭을 선택하면 포괄손익계산서, 재무상태표, 현금흐름표 항목이 보이게 되며, 티커에 해당하는 A005930 뒤의 주소는 불필요한 내용이므로, 이를 제거한 주소로 접속합니다. A 뒤의 6자리 티커만 변경한다면 해당 종목의 재무제표 페이지로 이동하게 됩니다. http://comp.fnguide.com/SVO2/ASP/SVD_Finance.asp?pGB=1&gicode=A005930 우리가 원하는 재무제표 항목들은 모두 테이블 형태로 제공되고 있으므로 html_table() 함수를 이용해 추출할 수 있습니다. library(httr) library(rvest) ifelse(dir.exists('data/KOR_fs'), FALSE, dir.create('data/KOR_fs')) Sys.setlocale("LC_ALL", "English") url = paste0('http://comp.fnguide.com/SVO2/ASP/SVD_Finance.asp?pGB=1&gicode=A005930') data = GET(url, user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36')) data = data %>% read_html() %>% html_table() Sys.setlocale("LC_ALL", "Korean") lapply(data, function(x) { head(x, 3)}) ## [[1]] ## IFRS(연결) 2017/12 2018/12 2019/12 2020/09 ## 1 매출액 2,395,754 2,437,714 2,304,009 1,752,555 ## 2 매출원가 1,292,907 1,323,944 1,472,395 1,066,834 ## 3 매출총이익 1,102,847 1,113,770 831,613 685,721 ## 전년동기 전년동기(%) ## 1 1,705,161 2.8 ## 2 1,086,850 -1.8 ## 3 618,311 10.9 ## ## [[2]] ## IFRS(연결) 2019/12 2020/03 2020/06 2020/09 전년동기 ## 1 매출액 598,848 553,252 529,661 669,642 620,035 ## 2 매출원가 385,545 348,067 319,062 399,705 399,939 ## 3 매출총이익 213,302 205,185 210,599 269,937 220,096 ## 전년동기(%) ## 1 8.0 ## 2 -0.1 ## 3 22.6 ## ## [[3]] ## IFRS(연결) 2017/12 2018/12 ## 1 자산 3,017,521 3,393,572 ## 2 유동자산계산에 참여한 계정 펼치기 1,469,825 1,746,974 ## 3 재고자산 249,834 289,847 ## 2019/12 2020/09 ## 1 3,525,645 3,757,887 ## 2 1,813,853 2,036,349 ## 3 267,665 324,429 ## ## [[4]] ## IFRS(연결) 2019/12 2020/03 ## 1 자산 3,525,645 3,574,575 ## 2 유동자산계산에 참여한 계정 펼치기 1,813,853 1,867,397 ## 3 재고자산 267,665 284,549 ## 2020/06 2020/09 ## 1 3,579,595 3,757,887 ## 2 1,861,368 2,036,349 ## 3 296,455 324,429 ## ## [[5]] ## IFRS(연결) 2017/12 2018/12 2019/12 ## 1 영업활동으로인한현금흐름 621,620 670,319 453,829 ## 2 당기순손익 421,867 443,449 217,389 ## 3 법인세비용차감전계속사업이익 ## 2020/09 ## 1 407,724 ## 2 198,007 ## 3 ## ## [[6]] ## IFRS(연결) 2019/12 2020/03 2020/06 ## 1 영업활동으로인한현금흐름 197,171 118,299 147,982 ## 2 당기순손익 52,270 48,849 55,551 ## 3 법인세비용차감전계속사업이익 ## 2020/09 ## 1 141,444 ## 2 93,607 ## 3 data 폴더 내에 KOR_fs 폴더를 생성합니다. Sys.setlocale() 함수를 통해 로케일 언어를 English로 설정합니다. url을 입력한 후 GET() 함수를 통해 페이지 내용을 받아오며, user_agent() 항목에 웹브라우저 구별을 입력해줍니다. 해당 사이트는 크롤러와 같이 정체가 불분명한 웹브라우저를 통한 접속이 막혀 있어, 마치 모질라 혹은 크롬을 통해 접속한 것 처럼 데이터를 요청합니다. 다양한 웹브라우저 리스트는 아래 링크에 나와있습니다. http://www.useragentstring.com/pages/useragentstring.php read_html() 함수를 통해 HTML 내용을 읽어오며, html_table() 함수를 통해 테이블 내용만 추출합니다. 로케일 언어를 다시 Korean으로 설정합니다. 위의 과정을 거치면 data 변수에는 리스트 형태로 총 6개의 테이블이 들어오게 되며, 그 내용은 표 6.1와 같습니다. 표 6.1: 재무제표 테이블 내역 순서 내용 1 포괄손익계산서 (연간) 2 포괄손익계산서 (분기) 3 재무상태표 (연간) 4 재무상태표 (분기) 5 현금흐름표 (연간) 6 현금흐름표 (분기) 이 중 연간 기준 재무제표에 해당하는 첫 번째, 세 번째, 다섯 번째 테이블을 선택합니다. data_IS = data[[1]] data_BS = data[[3]] data_CF = data[[5]] print(names(data_IS)) ## [1] "IFRS(연결)" "2017/12" "2018/12" ## [4] "2019/12" "2020/09" "전년동기" ## [7] "전년동기(%)" data_IS = data_IS[, 1:(ncol(data_IS)-2)] 포괄손익계산서 테이블(data_IS)에는 전년동기, 전년동기(%) 열이 있는데 통일성을 위해 해당 열을 삭제합니다. 이제 테이블을 묶은 후 클렌징하겠습니다. data_fs = rbind(data_IS, data_BS, data_CF) data_fs[, 1] = gsub('계산에 참여한 계정 펼치기', '', data_fs[, 1]) data_fs = data_fs[!duplicated(data_fs[, 1]), ] rownames(data_fs) = NULL rownames(data_fs) = data_fs[, 1] data_fs[, 1] = NULL data_fs = data_fs[, substr(colnames(data_fs), 6,7) == '12'] rbind() 함수를 이용해 세 테이블을 행으로 묶은 후 data_fs에 저장합니다. 첫 번째 열인 계정명에는 ‘계산에 참여한 계정 펼치기’라는 글자가 들어간 항목이 있습니다. 이는 페이지 내에서 펼치기 역할을 하는 (+) 항목에 해당하며 gsub() 함수를 이용해 해당 글자를 삭제합니다. 중복되는 계정명이 다수 있는데 대부분 불필요한 항목입니다. !duplicated() 함수를 사용해 중복되지 않는 계정명만 선택합니다. 행 이름을 초기화한 후 첫 번째 열의 계정명을 행 이름으로 변경합니다. 그 후 첫 번째 열은 삭제합니다. 간혹 12월 결산법인이 아닌 종목이거나 연간 재무제표임에도 불구하고 분기 재무제표가 들어간 경우가 있습니다. 비교의 통일성을 위해 substr() 함수를 이용해 끝 글자가 12인 열, 즉 12월 결산 데이터만 선택합니다. print(head(data_fs)) ## 2017/12 2018/12 2019/12 ## 매출액 2,395,754 2,437,714 2,304,009 ## 매출원가 1,292,907 1,323,944 1,472,395 ## 매출총이익 1,102,847 1,113,770 831,613 ## 판매비와관리비 566,397 524,903 553,928 ## 인건비 67,972 64,514 64,226 ## 유무형자산상각비 13,366 14,477 20,408 sapply(data_fs, typeof) ## 2017/12 2018/12 2019/12 ## "character" "character" "character" 데이터를 확인해보면 연간 기준 재무제표가 정리되었습니다. 문자형 데이터이므로 숫자형으로 변경합니다. library(stringr) data_fs = sapply(data_fs, function(x) { str_replace_all(x, ',', '') %>% as.numeric() }) %>% data.frame(., row.names = rownames(data_fs)) print(head(data_fs)) ## X2017.12 X2018.12 X2019.12 ## 매출액 2395754 2437714 2304009 ## 매출원가 1292907 1323944 1472395 ## 매출총이익 1102847 1113770 831613 ## 판매비와관리비 566397 524903 553928 ## 인건비 67972 64514 64226 ## 유무형자산상각비 13366 14477 20408 sapply(data_fs, typeof) ## X2017.12 X2018.12 X2019.12 ## "double" "double" "double" sapply() 함수를 이용해 각 열에 stringr 패키지의 str_replace_allr() 함수를 적용해 콤마(,)를 제거한 후 as.numeric() 함수를 통해 숫자형 데이터로 변경합니다. data.frame() 함수를 이용해 데이터 프레임 형태로 만들어주며, 행 이름은 기존 내용을 그대로 유지합니다. 정리된 데이터를 출력해보면 문자형이던 데이터가 숫자형으로 변경되었습니다. write.csv(data_fs, 'data/KOR_fs/005930_fs.csv') data 폴더의 KOR_fs 폴더 내에 티커_fs.csv 이름으로 저장합니다. 6.2.2 가치지표 계산하기 위에서 구한 재무제표 데이터를 이용해 가치지표를 계산할 수 있습니다. 흔히 사용되는 가치지표는 PER, PBR, PCR, PSR이며 분자는 주가, 분모는 재무제표 데이터가 사용됩니다. 표 6.2: 가치지표의 종류 순서 분모 PER Earnings (순이익) PBR Book Value (순자산) PCR Cashflow (영업활동현금흐름) PSR Sales (매출액) 위에서 구한 재무제표 항목에서 분모 부분에 해당하는 데이터만 선택해보겠습니다. ifelse(dir.exists('data/KOR_value'), FALSE, dir.create('data/KOR_value')) ## [1] FALSE value_type = c('지배주주순이익', '자본', '영업활동으로인한현금흐름', '매출액') value_index = data_fs[match(value_type, rownames(data_fs)), ncol(data_fs)] print(value_index) ## [1] 215051 2628804 453829 2304009 data 폴더 내에 KOR_value 폴더를 생성합니다. 분모에 해당하는 항목을 저장한 후 match() 함수를 이용해 해당 항목이 위치하는 지점을 찾습니다. ncol() 함수를 이용해 맨 오른쪽, 즉 최근년도 재무제표 데이터를 선택합니다. 다음으로 분자 부분에 해당하는 현재 주가를 수집해야 합니다. 이 역시 Company Guide 접속 화면에서 구할 수 있습니다. 불필요한 부분을 제거한 URL은 다음과 같습니다. http://comp.fnguide.com/SVO2/ASP/SVD_main.asp?pGB=1&gicode=A005930 위의 주소 역시 A 뒤의 6자리 티커만 변경하면 해당 종목의 스냅샷 페이지로 이동하게 됩니다. 그림 2.12: Company Guide 스냅샷 화면 주가추이 부분에 우리가 원하는 현재 주가가 있습니다. 해당 데이터의 Xpath는 다음과 같습니다. //*[@id="svdMainChartTxt11"] //*[@id=\"svdMainChartTxt11\"] 위에서 구한 주가의 Xpath를 이용해 해당 데이터를 크롤링하겠습니다. library(readr) url = 'http://comp.fnguide.com/SVO2/ASP/SVD_main.asp?pGB=1&gicode=A005930' data = GET(url, user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36')) price = read_html(data) %>% html_node(xpath = '//*[@id="svdMainChartTxt11"]') %>% html_text() %>% parse_number() print(price) ## [1] 88000 url을 입력한 후, GET() 함수를 이용해 데이터를 불러오며, 역시나 user_agent를 추가해 줍니다. read_html() 함수를 이용해 HTML 데이터를 불러온 후 html_node() 함수에 앞서 구한 Xpath를 입력해 해당 지점의 데이터를 추출합니다. html_text() 함수를 통해 텍스트 데이터만을 추출하며, readr 패키지의 parse_number() 함수를 적용합니다. 해당 함수는 문자형 데이터에서 콤마와 같은 불필요한 문자를 제거한 후 숫자형 데이터로 변경해줍니다. 가치지표를 계산하려면 발행주식수 역시 필요합니다. 예를 들어 PER를 계산하는 방법은 다음과 같습니다. \\[ PER = Price / EPS = 주가 / 주당순이익\\] 주당순이익은 순이익을 전체 주식수로 나눈 값이므로, 해당 값의 계산하려면 전체 주식수를 구해야 합니다. 전체 주식수 데이터 역시 웹페이지에 있으므로 앞서 주가를 크롤링한 방법과 동일한 방법으로 구할 수 있습니다. 전체 주식수 데이터의 Xpath는 다음과 같습니다. //*[@id="svdMainGrid1"]/table/tbody/tr[7]/td[1] //*[@id=\"svdMainGrid1\"]/table/tbody/tr[7]/td[1] 이를 이용해 발행주식수 중 보통주를 선택하는 방법은 다음과 같습니다. share = read_html(data) %>% html_node( xpath = '//*[@id="svdMainGrid1"]/table/tbody/tr[7]/td[1]') %>% html_text() print(share) ## [1] "5,969,782,550/ 822,886,700" read_html() 함수와 html_node() 함수를 이용해, HTML 내에서 Xpath에 해당하는 데이터를 추출합니다. 그 후 html_text() 함수를 통해 텍스트 부분만 추출합니다. 해당 과정을 거치면 보통주/우선주의 형태로 발행주식주가 저장됩니다. 이 중 우리가 원하는 데이터는 / 앞에 있는 보통주 발행주식수입니다. share = share %>% strsplit('/') %>% unlist() %>% .[1] %>% parse_number() print(share) ## [1] 5969782550 strsplit() 함수를 통해 /를 기준으로 데이터를 나눕니다. 해당 결과는 리스트 형태로 저장됩니다. unlist() 함수를 통해 리스트를 벡터 형태로 변환합니다. .[1].[1]을 통해 보통주 발행주식수인 첫 번째 데이터를 선택합니다. parse_number() 함수를 통해 문자형 데이터를 숫자형으로 변환합니다. 재무 데이터, 현재 주가, 발행주식수를 이용해 가치지표를 계산해보겠습니다. data_value = price / (value_index * 100000000 / share) names(data_value) = c('PER', 'PBR', 'PCR', 'PSR') data_value[data_value < 0] = NA print(data_value) ## PER PBR PCR PSR ## 24.429 1.998 11.576 2.280 분자에는 현재 주가를 입력하며, 분모에는 재무 데이터를 보통주 발행주식수로 나눈 값을 입력합니다. 단, 주가는 원 단위, 재무 데이터는 억 원 단위이므로, 둘 사이에 단위를 동일하게 맞춰주기 위해 분모에 억을 곱합니다. 또한 가치지표가 음수인 경우는 NA로 변경해줍니다. 결과를 확인해보면 4가지 가치지표가 잘 계산되었습니다.17 write.csv(data_value, 'data/KOR_value/005930_value.csv') data 폴더의 KOR_value 폴더 내에 티커_value.csv 이름으로 저장합니다. 6.2.3 전 종목 재무제표 및 가치지표 다운로드 위 코드에서 for loop 구문을 이용해 URL 중 6자리 티커에 해당하는 값만 변경해주면 모든 종목의 재무제표를 다운로드하고 이를 바탕으로 가치지표를 계산할 수 있습니다. 해당 코드는 다음과 같습니다. library(stringr) library(httr) library(rvest) library(stringr) library(readr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6,side = c('left'), pad = '0') ifelse(dir.exists('data/KOR_fs'), FALSE, dir.create('data/KOR_fs')) ifelse(dir.exists('data/KOR_value'), FALSE, dir.create('data/KOR_value')) for(i in 1 : nrow(KOR_ticker) ) { data_fs = c() data_value = c() name = KOR_ticker$'종목코드'[i] # 오류 발생 시 이를 무시하고 다음 루프로 진행 tryCatch({ Sys.setlocale('LC_ALL', 'English') # url 생성 url = paste0( 'http://comp.fnguide.com/SVO2/ASP/' ,'SVD_Finance.asp?pGB=1&gicode=A', name) # 이 후 과정은 위와 동일함 # 데이터 다운로드 후 테이블 추출 data = GET(url, user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36')) %>% read_html() %>% html_table() Sys.setlocale('LC_ALL', 'Korean') # 3개 재무제표를 하나로 합치기 data_IS = data[[1]] data_BS = data[[3]] data_CF = data[[5]] data_IS = data_IS[, 1:(ncol(data_IS)-2)] data_fs = rbind(data_IS, data_BS, data_CF) # 데이터 클랜징 data_fs[, 1] = gsub('계산에 참여한 계정 펼치기', '', data_fs[, 1]) data_fs = data_fs[!duplicated(data_fs[, 1]), ] rownames(data_fs) = NULL rownames(data_fs) = data_fs[, 1] data_fs[, 1] = NULL # 12월 재무제표만 선택 data_fs = data_fs[, substr(colnames(data_fs), 6,7) == "12"] data_fs = sapply(data_fs, function(x) { str_replace_all(x, ',', '') %>% as.numeric() }) %>% data.frame(., row.names = rownames(data_fs)) # 가치지표 분모부분 value_type = c('지배주주순이익', '자본', '영업활동으로인한현금흐름', '매출액') # 해당 재무데이터만 선택 value_index = data_fs[match(value_type, rownames(data_fs)), ncol(data_fs)] # Snapshot 페이지 불러오기 url = paste0( 'http://comp.fnguide.com/SVO2/ASP/SVD_Main.asp', '?pGB=1&gicode=A',name) data = GET(url, user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36')) # 현재 주가 크롤링 price = read_html(data) %>% html_node(xpath = '//*[@id="svdMainChartTxt11"]') %>% html_text() %>% parse_number() # 보통주 발행주식수 크롤링 share = read_html(data) %>% html_node( xpath = '//*[@id="svdMainGrid1"]/table/tbody/tr[7]/td[1]') %>% html_text() %>% strsplit('/') %>% unlist() %>% .[1] %>% parse_number() # 가치지표 계산 data_value = price / (value_index * 100000000/ share) names(data_value) = c('PER', 'PBR', 'PCR', 'PSR') data_value[data_value < 0] = NA }, error = function(e) { # 오류 발생시 해당 종목명을 출력하고 다음 루프로 이동 data_fs <<- NA data_value <<- NA warning(paste0("Error in Ticker: ", name)) }) # 다운로드 받은 파일을 생성한 각각의 폴더 내 csv 파일로 저장 # 재무제표 저장 write.csv(data_fs, paste0('data/KOR_fs/', name, '_fs.csv')) # 가치지표 저장 write.csv(data_value, paste0('data/KOR_value/', name, '_value.csv')) # 2초간 타임슬립 적용 Sys.sleep(2) } 전 종목 주가 데이터를 받는 과정과 동일하게 KOR_ticker.csv 파일을 불러온 후 for loop를 통해 i 값이 변함에 따라 티커를 변경해가며 모든 종목의 재무제표 및 가치지표를 다운로드합니다. tryCatch() 함수를 이용해 오류가 발생하면 NA로 이루어진 빈 데이터를 저장한 후 다음 루프로 넘어가게 됩니다. data/KOR_fs 폴더에는 전 종목의 재무제표 데이터가 저장되고, data/KOR_value 폴더에는 전 종목의 가치지표 데이터가 csv 형태로 저장됩니다. 6.3 DART의 Open API를 이용한 데이터 수집하기 DART(Data Analysis, Retrieval and Transfer System)는 금융감독원 전자공시시스템으로써, 상장법인 등이 공시서류를 인터넷으로 제출하고, 투자자 등 이용자는 제출 즉시 인터넷을 통해 조회할 수 있도록 하는 종합적 기업공시 시스템입니다. 홈페이지에서도 각종 공시내역을 확인할 수 있지만, 해당 사이트에서 제공하는 API를 이용할 경우 더욱 쉽게 공시 내용을 수집할 수 있습니다. 6.3.1 API Key발급 및 추가하기 먼저 https://opendart.fss.or.kr/에 접속한 후 [인증키 신청/관리] → [인증키 신청]을 통해 API Key를 발급 받습니다. 그림 6.1: OpenAPI 인증키 신청 계정을 생성하고 이메일을 통해 이용자 등록을 한 후 로그인을 합니다. 그 후 [오픈API 이용현황]을 살펴보면 API Key 부분에 발급받은 Key가 있으며, 금일 몇번의 API를 요청했는지가 일일이용현황에 나옵니다. 하루 총 10,000번까지 데이터를 요청할 수 있습니다. 그림 6.2: OpenAPI 이용현황 다음으로 발급받은 API Key를 .Renviron 파일에 추가하도록 합니다. 해당 파일에는 여러 패스워드를 추가해 안전하게 관리할 수 있습니다. file.edit("~/.Renviron") .Renviron 파일이 열리면 다음과 같이 입력을 해줍니다. dart_api_key = '발급받은 API' 파일을 저장한 후 해당 파일을 적용하기 위해 R의 Session을 재시작합니다. 그 후 아래 명령어를 실행하여 API Key를 불러오도록 합니다. (재시작하지 않으면 Key를 불러올 수 없습니다.) dart_api = Sys.getenv("dart_api_key") 6.3.2 고유번호 다운로드 Open API에서 각 기업의 데이터를 받기 위해서는 종목에 해당하는 고유번호를 알아야 합니다. 이에 대한 개발가이드는 아래 페이지에 나와 있습니다. https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS001&apiId=2019018 위 페이지의 내용을 코드로 나타내보도록 합니다. library(httr) library(rvest) codezip_url = paste0( 'https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key=',dart_api) codezip_data = GET(codezip_url) print(codezip_data) ## Response [https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key=b1a630e527b0e5ff5bd58ed81b49825017fa80b8] ## Date: 2021-01-17 05:38 ## Status: 200 ## Content-Type: application/x-msdownload ## Size: 1.4 MB ## <BINARY BODY> ## NULL https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key= 뒤에 본인의 API 키를 입력합니다. GET() 함수를 통해 해당 페이지 내용을 받습니다. 다운로드 받은 내용을 확인해보면 , 즉 바이너리 형태의 데이터가 첨부되어 있습니다. 이에 대해 좀더 자세히 알아보도록 하겠습니다. codezip_data$headers[["content-disposition"]] ## [1] ": attachment; filename=CORPCODE.zip" headers의 “content-disposition” 부분을 확인해보면 CORPCODE.zip 파일이 첨부되어 있습니다. 해당 파일의 압축을 풀어 첨부된 내용을 확인합니다. tf = tempfile(fileext = '.zip') writeBin( content(codezip_data, as = "raw"), file.path(tf) ) nm = unzip(tf, list = TRUE) print(nm) ## Name Length Date ## 1 CORPCODE.xml 16073037 <NA> tempfile() 함수 통해 빈 .zip 파일을 만듭니다. writeBin() 함수는 바이너리 형태의 파일을 저장하는 함수이며, content()를 통해 첨부 파일 내용을 raw 형태로 저장합니다. 파일명은 위에서 만든 tf로 합니다. unzip() 함수를 통해 zip 내 파일 리스트를 확인합니다. zip 파일 내에는 CORPCODE.xml 파일이 있으며, read_xml() 함수를 통해 이를 불러오도록 합니다. code_data = read_xml(unzip(tf, nm$Name)) print(code_data) ## {xml_document} ## <result> ## [1] <list>\\n <corp_code>00434003</corp_code>\\n <co ... ## [2] <list>\\n <corp_code>00434456</corp_code>\\n <co ... ## [3] <list>\\n <corp_code>00430964</corp_code>\\n <co ... ## [4] <list>\\n <corp_code>00432403</corp_code>\\n <co ... ## [5] <list>\\n <corp_code>00388953</corp_code>\\n <co ... ## [6] <list>\\n <corp_code>00179984</corp_code>\\n <co ... ## [7] <list>\\n <corp_code>00420143</corp_code>\\n <co ... ## [8] <list>\\n <corp_code>00401111</corp_code>\\n <co ... ## [9] <list>\\n <corp_code>00435534</corp_code>\\n <co ... ## [10] <list>\\n <corp_code>00430186</corp_code>\\n <co ... ## [11] <list>\\n <corp_code>00430201</corp_code>\\n <co ... ## [12] <list>\\n <corp_code>00430210</corp_code>\\n <co ... ## [13] <list>\\n <corp_code>00430229</corp_code>\\n <co ... ## [14] <list>\\n <corp_code>00140432</corp_code>\\n <co ... ## [15] <list>\\n <corp_code>00426208</corp_code>\\n <co ... ## [16] <list>\\n <corp_code>00433262</corp_code>\\n <co ... ## [17] <list>\\n <corp_code>00433749</corp_code>\\n <co ... ## [18] <list>\\n <corp_code>00433785</corp_code>\\n <co ... ## [19] <list>\\n <corp_code>00196079</corp_code>\\n <co ... ## [20] <list>\\n <corp_code>00435048</corp_code>\\n <co ... ## ... 해당 파일은 HTML 형식으로 되어 있으며 중요부분은 다음과 같습니다. corp_code: 고유번호 corp_name: 종목명 corp_stock: 거래소 상장 티커 HTML의 태그를 이용해 각 부분을 추출한 후 하나의 데이터로 합치도록 하겠습니다. corp_code = code_data %>% html_nodes('corp_code') %>% html_text() corp_name = code_data %>% html_nodes('corp_name') %>% html_text() corp_stock = code_data %>% html_nodes('stock_code') %>% html_text() corp_list = data.frame( 'code' = corp_code, 'name' = corp_name, 'stock' = corp_stock, stringsAsFactors = FALSE ) html_nodes() 함수를 이용해 고유번호, 종목명, 상장티커를 선택한 후, html_text() 함수를 이용해 문자열만 추출하도록 합니다. data.frame() 함수를 통해 데이터프레임 형식으로 묶어주도록 합니다. nrow(corp_list) ## [1] 83370 head(corp_list) ## code name stock ## 1 00434003 다코 ## 2 00434456 일산약품 ## 3 00430964 굿앤엘에스 ## 4 00432403 한라판지 ## 5 00388953 크레디피아제이십오차유동화전문회사 ## 6 00179984 연방건설산업 종목수를 확인해보면 83370 개가 확인되며, 이 중 stock 열이 빈 종목은 거래소에 상장되지 않은 종목입니다. 따라서 해당 데이터는 삭제하여 거래소 상장 종목만을 남긴 후, csv 파일로 저장하도록 합니다. corp_list = corp_list[corp_list$stock != " ", ] write.csv(corp_list, 'data/corp_list.csv') 6.3.3 공시검색 6.3.3.1 전체 공시 검색 먼저 공시검색 API에 대한 이해를 위해 전체 종목의 공시를 수집하도록 하며, 해당 개발가이드는 아래 페이지에 나와 있습니다. https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS001&apiId=2019001 각종 요청인자를 통해 url을 생성 후 전송하여, 요청에 맞는 데이터를 받을 수 있습니다. 공시 검색에 해당하는 인자는 다음과 같습니다. 그림 6.3: OpenAPI 요청 인자 예시 페이지 하단에서 인자를 입력 후 [검색]을 누르면 각 인자에 맞게 생성된 url과 그 결과를 볼 수 있습니다. 그림 6.4: OpenAPI 테스트 예시 먼저 시작일과 종료일을 토대로 최근 공시 100건에 해당하는 url을 생성하도록 하겠습니다. library(lubridate) library(stringr) library(jsonlite) bgn_date = (Sys.Date() - days(7)) %>% str_remove_all('-') end_date = (Sys.Date() ) %>% str_remove_all('-') notice_url = paste0('https://opendart.fss.or.kr/api/list.json?crtfc_key=',dart_api,'&bgn_de=', bgn_date,'&end_de=',end_date,'&page_no=1&page_count=100') bgn_date에는 현재로부터 일주일 전을, end_date는 오늘 날짜를, 페이지별 건수에 해당하는 page_count에는 100을 입력하도록 합니다. 그 후 홈페이지에 나와있는 예시에 맞게 url을 작성해주도록 합니다. XML 보다는 JSON 형식으로 url을 생성 후 요청하는 것이 데이터 처리 측면에서 훨씬 효율적입니다. notice_data = fromJSON(notice_url) notice_data = notice_data[['list']] head(notice_data) ## corp_code corp_name stock_code corp_cls ## 1 00186452 릭스솔루션 029480 K ## 2 00231372 롯데관광개발 032350 Y ## 3 00105138 파라텍 033540 K ## 4 01437186 ESR켄달스퀘어리츠 365550 Y ## 5 00249502 더블유에프엠 035290 K ## 6 00188380 유진자산운용 E ## report_nm ## 1 최대주주변경을수반하는주식담보제공계약체결 ## 2 유상증자또는주식관련사채등의발행결과(자율공시) ## 3 최대주주변경을수반하는주식담보제공계약해제ㆍ취소등 ## 4 자본잠식50%이상또는매출액50억원미만사실발생 ## 5 기타시장안내(최대주주의의무보유관련) ## 6 [기재정정]증권신고서(집합투자증권-신탁형)(유진지수연계증권투자신탁76호(온라인전용)[ELS-파생형]) ## rcept_no flr_nm rcept_dt rm ## 1 20210115900736 릭스솔루션 20210115 코 ## 2 20210115800737 롯데관광개발 20210115 유 ## 3 20210115900735 파라텍 20210115 코 ## 4 20210115800731 ESR켄달스퀘어리츠 20210115 유 ## 5 20210115900726 코스닥시장본부 20210115 코 ## 6 20210113000532 유진자산운용 20210113 fromJSON() 함수를 통해 JSON 데이터를 받은 후 list를 확인해보면 우리가 원하는 공시정보, 즉 일주일 전부터 100건의 공시 정보가 다운로드 되어 있습니다. 6.3.3.2 특정 기업의 공시 검색 이번에는 고유번호를 추가하여 원하는 기업의 공시만 확인해보록 하겠습니다. 고유번호는 위에서 다운받은 corp_list.csv 파일을 통해 확인해볼 수 있으며, 예시로 살펴볼 삼성전자의 고유번호는 00126380 입니다. bgn_date = (Sys.Date() - days(30)) %>% str_remove_all('-') end_date = (Sys.Date() ) %>% str_remove_all('-') corp_code = '00126380' notice_url_ss = paste0( 'https://opendart.fss.or.kr/api/list.json?crtfc_key=',dart_api, '&corp_code=', corp_code, '&bgn_de=', bgn_date,'&end_de=', end_date,'&page_no=1&page_count=100') 시작일을 과거 30일로 수정하였으며, 기존 url에 &corp_code= 부분을 추가하였습니다. notice_data_ss = fromJSON(notice_url_ss) notice_data_ss = notice_data_ss[['list']] head(notice_data_ss) ## corp_code corp_name stock_code corp_cls ## 1 00126380 삼성전자 005930 Y ## 2 00126380 삼성전자 005930 Y ## 3 00126380 삼성전자 005930 Y ## 4 00126380 삼성전자 005930 Y ## 5 00126380 삼성전자 005930 Y ## 6 00126380 삼성전자 005930 Y ## report_nm ## 1 임원ㆍ주요주주특정증권등소유상황보고서 ## 2 최대주주등소유주식변동신고서 ## 3 기업설명회(IR)개최(안내공시) ## 4 연결재무제표기준영업(잠정)실적(공정공시) ## 5 임원ㆍ주요주주특정증권등소유상황보고서 ## 6 임원ㆍ주요주주특정증권등소유상황보고서 ## rcept_no flr_nm rcept_dt rm ## 1 20210111000382 안규리 20210111 ## 2 20210108800652 삼성전자 20210108 유 ## 3 20210108800573 삼성전자 20210108 유 ## 4 20210108800029 삼성전자 20210108 유 ## 5 20210107000257 국민연금공단 20210107 ## 6 20210105000430 이상훈 20210105 역시나 JSON 형태로 손쉽게 공시정보를 다운로드 받을 수 있습니다. 이 중 rcept_no는 공시번호에 해당하며, 해당 데이터를 이용해 공시에 해당하는 url에 접속을 할 수도 있습니다. notice_url_exam = notice_data_ss[1, 'rcept_no'] notice_dart_url = paste0( 'http://dart.fss.or.kr/dsaf001/main.do?rcpNo=',notice_url_exam) print(notice_dart_url) ## [1] "http://dart.fss.or.kr/dsaf001/main.do?rcpNo=20210111000382" dart 홈페이지의 공시에 해당하는 url과 첫번째 공시에 해당하는 공시번호를 합쳐주도록 합니다. 위 url에 접속하여 해당 공시를 좀 더 자세하게 확인할 수 있습니다. 그림 6.5: 공시 정보의 확인 6.3.4 사업보고서 주요 정보 API를 이용하여 사업보고서의 주요 정보 역시 다운로드 받을 수 있으며, 제공하는 목록은 다음과 같습니다. https://opendart.fss.or.kr/guide/main.do?apiGrpCd=DS002 이 중 예시로써 [배당에 관한 사항]을 다운로드 받도록 하며, 개발가이드 페이지는 다음과 같습니다. https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS002&apiId=2019005 url 생성에 필요한 요청 인자는 다음과 같습니다. 표 6.3: 배당에 관한 사항 주요 인자 키 명칭 설명 crtfc_key API 인증키 발급받은 인증키 corp_code 고유번호 공시대상회사의 고유번호(8자리) bsns_year 사업년도 사업연도(4자리) reprt_code 보고서 코드 1분기보고서 : 11013 반기보고서 : 11012 3분기보고서 : 11014 사업보고서 : 11011 이를 바탕으로 삼성전자의 2019년 사업보고서를 통해 배당에 관한 사항을 살펴보도록 하겠습니다. corp_code = '00126380' bsns_year = '2019' reprt_code = '11011' url_div = paste0('https://opendart.fss.or.kr/api/alotMatter.json?crtfc_key=', dart_api, '&corp_code=', corp_code, '&bsns_year=', bsns_year, '&reprt_code=', reprt_code ) API 인증키, 고유번호, 사업년도, 보고서 코드에 각각 해당하는 데이터를 입력하여 url 생성하도록 합니다. div_data_ss = fromJSON(url_div) div_data_ss = div_data_ss[['list']] head(div_data_ss) ## rcept_no corp_cls corp_code corp_name ## 1 20200330003851 Y 00126380 삼성전자 ## 2 20200330003851 Y 00126380 삼성전자 ## 3 20200330003851 Y 00126380 삼성전자 ## 4 20200330003851 Y 00126380 삼성전자 ## 5 20200330003851 Y 00126380 삼성전자 ## 6 20200330003851 Y 00126380 삼성전자 ## se thstrm frmtrm ## 1 주당액면가액(원) 100 100 ## 2 (연결)당기순이익(백만원) 21,505,054 43,890,877 ## 3 (별도)당기순이익(백만원) 15,353,323 32,815,127 ## 4 (연결)주당순이익(원) 3,166 6,461 ## 5 현금배당금총액(백만원) 9,619,243 9,619,243 ## 6 주식배당금총액(백만원) - - ## lwfr stock_knd ## 1 100 <NA> ## 2 41,344,569 <NA> ## 3 28,800,837 <NA> ## 4 5,997 <NA> ## 5 5,826,302 <NA> ## 6 - <NA> JSON 파일을 다운로드 받은 후 데이터를 확인해보면, 사업보고서 중 배당에 관한 사항만이 나타나 있습니다. 위 url의 alotMatter 부분을 각 사업보고서에 해당하는 값으로 변경해주면 다른 정보 역시 동일한 방법으로 수집이 가능합니다. 6.3.5 상장기업 재무정보 Open API에서는 상장기업의 재무정보 중 주요계정, 전체 재무제표, 원본파일을 제공하고 있습니다. 이 중 주요계정 및 전체 재무제표를 다운로드 받는법에 대해 알아보도록 하겠습니다. 6.3.5.1 단일회사 및 다중회사 주요계정 API를 통해 단일회사의 주요계정을, 혹은 한번에 여러 회사의 주요계정을 받을수 있습니다. 각각의 개발가이드는 다음과 같습니다. 단일회사 주요계정: https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS003&apiId=2019016 다중회사 주요계정: https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS003&apiId=2019017 먼저 단일회사(삼성전자)의 주요계정을 다운로드 받도록 하겠습니다. corp_code = '00126380' bsns_year = '2019' reprt_code = '11011' url_single = paste0( 'https://opendart.fss.or.kr/api/fnlttSinglAcnt.json?crtfc_key=', dart_api, '&corp_code=', corp_code, '&bsns_year=', bsns_year, '&reprt_code=', reprt_code ) url을 생성하는 방법이 기존 사업보고서 주요 정보 에서 살펴본 바와 매우 비슷하며, /api 뒷부분을 [fnlttSinglAcnt.json] 으로 변경하기만 하면 됩니다. fs_data_single = fromJSON(url_single) fs_data_single = fs_data_single[['list']] head(fs_data_single) ## rcept_no reprt_code bsns_year corp_code ## 1 20200330003851 11011 2019 00126380 ## 2 20200330003851 11011 2019 00126380 ## 3 20200330003851 11011 2019 00126380 ## 4 20200330003851 11011 2019 00126380 ## 5 20200330003851 11011 2019 00126380 ## 6 20200330003851 11011 2019 00126380 ## stock_code fs_div fs_nm sj_div sj_nm ## 1 005930 CFS 연결재무제표 BS 재무상태표 ## 2 005930 CFS 연결재무제표 BS 재무상태표 ## 3 005930 CFS 연결재무제표 BS 재무상태표 ## 4 005930 CFS 연결재무제표 BS 재무상태표 ## 5 005930 CFS 연결재무제표 BS 재무상태표 ## 6 005930 CFS 연결재무제표 BS 재무상태표 ## account_nm thstrm_nm thstrm_dt ## 1 유동자산 제 51 기 2019.12.31 현재 ## 2 비유동자산 제 51 기 2019.12.31 현재 ## 3 자산총계 제 51 기 2019.12.31 현재 ## 4 유동부채 제 51 기 2019.12.31 현재 ## 5 비유동부채 제 51 기 2019.12.31 현재 ## 6 부채총계 제 51 기 2019.12.31 현재 ## thstrm_amount frmtrm_nm frmtrm_dt ## 1 181,385,260,000,000 제 50 기 2018.12.31 현재 ## 2 171,179,237,000,000 제 50 기 2018.12.31 현재 ## 3 352,564,497,000,000 제 50 기 2018.12.31 현재 ## 4 63,782,764,000,000 제 50 기 2018.12.31 현재 ## 5 25,901,312,000,000 제 50 기 2018.12.31 현재 ## 6 89,684,076,000,000 제 50 기 2018.12.31 현재 ## frmtrm_amount bfefrmtrm_nm bfefrmtrm_dt ## 1 174,697,424,000,000 제 49 기 2017.12.31 현재 ## 2 164,659,820,000,000 제 49 기 2017.12.31 현재 ## 3 339,357,244,000,000 제 49 기 2017.12.31 현재 ## 4 69,081,510,000,000 제 49 기 2017.12.31 현재 ## 5 22,522,557,000,000 제 49 기 2017.12.31 현재 ## 6 91,604,067,000,000 제 49 기 2017.12.31 현재 ## bfefrmtrm_amount ord ## 1 146,982,464,000,000 1 ## 2 154,769,626,000,000 3 ## 3 301,752,090,000,000 5 ## 4 67,175,114,000,000 7 ## 5 20,085,548,000,000 9 ## 6 87,260,662,000,000 11 연결재무제표와 재무상태표에 해당하는 주요 내용이 수집되었으며, 각 열에 해당하는 내용은 페이지의 개발가이드의 [응답 결과]에서 확인할 수 있습니다. 그림 6.6: 단일회사 주요계정 응답 결과 이번에는 url을 수정하여 여러 회사의 주요계정을 한번에 받도록 하겠으며, 그 예로써 삼성전자, 셀트리온, KT의 데이터를 다운로드 받도록 합니다. corp_code = c('00126380,00413046,00190321') bsns_year = '2019' reprt_code = '11011' url_multiple = paste0( 'https://opendart.fss.or.kr/api/fnlttMultiAcnt.json?crtfc_key=', dart_api, '&corp_code=', corp_code, '&bsns_year=', bsns_year, '&reprt_code=', reprt_code ) 먼저 corp에 원하는 기업들의 고유번호를 나열해주며, url 중 [fnlttSinglAcnt]을 [fnlttMultiAcnt]로 수정합니다. fs_data_multiple = fromJSON(url_multiple) fs_data_multiple = fs_data_multiple[['list']] 3개 기업의 주요계정이 하나의 데이터 프레임으로 다운로드 됩니다. 마지막으로 각 회사별로 데이터를 나눠주도록 하겠습니다. fs_data_list = fs_data_multiple %>% split(f = .$corp_code) lapply(fs_data_list, head, 2) ## $`00126380` ## rcept_no reprt_code bsns_year corp_code ## 1 20200330003851 11011 2019 00126380 ## 2 20200330003851 11011 2019 00126380 ## stock_code fs_div fs_nm sj_div sj_nm ## 1 005930 CFS 연결재무제표 BS 재무상태표 ## 2 005930 CFS 연결재무제표 BS 재무상태표 ## account_nm thstrm_nm thstrm_dt ## 1 유동자산 제 51 기 2019.12.31 현재 ## 2 비유동자산 제 51 기 2019.12.31 현재 ## thstrm_amount frmtrm_nm frmtrm_dt ## 1 181,385,260,000,000 제 50 기 2018.12.31 현재 ## 2 171,179,237,000,000 제 50 기 2018.12.31 현재 ## frmtrm_amount bfefrmtrm_nm bfefrmtrm_dt ## 1 174,697,424,000,000 제 49 기 2017.12.31 현재 ## 2 164,659,820,000,000 제 49 기 2017.12.31 현재 ## bfefrmtrm_amount ord ## 1 146,982,464,000,000 1 ## 2 154,769,626,000,000 3 ## ## $`00190321` ## rcept_no reprt_code bsns_year corp_code ## 27 20200330004658 11011 2019 00190321 ## 28 20200330004658 11011 2019 00190321 ## stock_code fs_div fs_nm sj_div sj_nm ## 27 030200 CFS 연결재무제표 BS 재무상태표 ## 28 030200 CFS 연결재무제표 BS 재무상태표 ## account_nm thstrm_nm thstrm_dt ## 27 유동자산 제 38 기 2019.12.31 현재 ## 28 비유동자산 제 38 기 2019.12.31 현재 ## thstrm_amount frmtrm_nm frmtrm_dt ## 27 11,898,255,000,000 제 37 기 2018.12.31 현재 ## 28 22,163,037,000,000 제 37 기 2018.12.31 현재 ## frmtrm_amount bfefrmtrm_nm bfefrmtrm_dt ## 27 11,894,252,000,000 제 36 기 2017.12.31 현재 ## 28 20,294,578,000,000 제 36 기 2017.12.31 현재 ## bfefrmtrm_amount ord ## 27 9,672,412,000,000 1 ## 28 20,058,498,000,000 3 ## ## $`00413046` ## rcept_no reprt_code bsns_year corp_code ## 53 20200410002837 11011 2019 00413046 ## 54 20200410002837 11011 2019 00413046 ## stock_code fs_div fs_nm sj_div sj_nm ## 53 068270 CFS 연결재무제표 BS 재무상태표 ## 54 068270 CFS 연결재무제표 BS 재무상태표 ## account_nm thstrm_nm thstrm_dt ## 53 유동자산 제 29 기 2019.12.31 현재 ## 54 비유동자산 제 29 기 2019.12.31 현재 ## thstrm_amount frmtrm_nm frmtrm_dt ## 53 1,787,340,254,600 제 28 기 2018.12.31 현재 ## 54 2,106,351,351,846 제 28 기 2018.12.31 현재 ## frmtrm_amount bfefrmtrm_nm bfefrmtrm_dt ## 53 1,664,478,918,682 제 27 기 2017.12.31 현재 ## 54 1,876,147,755,272 제 27 기 2017.12.31 현재 ## bfefrmtrm_amount ord ## 53 1,614,033,788,024 1 ## 54 1,701,493,916,629 3 split() 함수 내 f 인자를 통해 corp_code, 즉 고유번호 단위로 각각의 리스트에 데이터가 저장됩니다. 6.3.6 단일회사 전체 재무제표 단일회사의 전체 재무제표 데이터 역시 다운로드 받을 수 있으며 개발가이드는 다음과 같습니다. https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS003&apiId=2019020 예제로써 삼성전자의 2019년 사업보고서에 나와있는 전체 재무제표를 다운로드 받도록 하겠습니다. corp_code = '00126380' bsns_year = 2019 reprt_code = '11011' url_fs_all = paste0( 'https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?crtfc_key=', dart_api, '&corp_code=', corp_code, '&bsns_year=', bsns_year, '&reprt_code=', reprt_code,'&fs_div=CFS' ) 역시나 앞선 예제들과 거의 동일화며, url의 api/ 뒷 부분을 [fnlttSinglAcntAll.json] 으로 변경해주도록 합니다. 연결재무제표와 일반재무제표를 구분하는 fs_div 인자는 연결재무제표를 의미하는 CFS로 선택해줍니다. fs_data_all = fromJSON(url_fs_all) fs_data_all = fs_data_all[['list']] head(fs_data_all) ## rcept_no reprt_code bsns_year corp_code sj_div ## 1 20200330003851 11011 2019 00126380 BS ## 2 20200330003851 11011 2019 00126380 BS ## 3 20200330003851 11011 2019 00126380 BS ## 4 20200330003851 11011 2019 00126380 BS ## 5 20200330003851 11011 2019 00126380 BS ## 6 20200330003851 11011 2019 00126380 BS ## sj_nm ## 1 재무상태표 ## 2 재무상태표 ## 3 재무상태표 ## 4 재무상태표 ## 5 재무상태표 ## 6 재무상태표 ## account_id ## 1 ifrs-full_CurrentAssets ## 2 ifrs-full_CashAndCashEquivalents ## 3 dart_ShortTermDepositsNotClassifiedAsCashEquivalents ## 4 -표준계정코드 미사용- ## 5 -표준계정코드 미사용- ## 6 ifrs-full_CurrentFinancialAssetsAtFairValueThroughProfitOrLossMandatorilyMeasuredAtFairValue ## account_nm account_detail ## 1 유동자산 - ## 2 현금및현금성자산 - ## 3 단기금융상품 - ## 4 단기매도가능금융자산 - ## 5 단기상각후원가금융자산 - ## 6 단기당기손익-공정가치금융자산 - ## thstrm_nm thstrm_amount frmtrm_nm frmtrm_amount ## 1 제 51 기 181385260000000 제 50 기 174697424000000 ## 2 제 51 기 26885999000000 제 50 기 30340505000000 ## 3 제 51 기 76252052000000 제 50 기 65893797000000 ## 4 제 51 기 제 50 기 ## 5 제 51 기 3914216000000 제 50 기 2703693000000 ## 6 제 51 기 1727436000000 제 50 기 2001948000000 ## bfefrmtrm_nm bfefrmtrm_amount ord thstrm_add_amount ## 1 제 49 기 146982464000000 1 <NA> ## 2 제 49 기 30545130000000 2 <NA> ## 3 제 49 기 49447696000000 3 <NA> ## 4 제 49 기 3191375000000 4 <NA> ## 5 제 49 기 5 <NA> ## 6 제 49 기 6 <NA> 총 210개의 재무제표 항목이 다운로드 됩니다. 이 중 thstrm_nm와 thstrm_amount는 당기(금년), frmtrm_nm과 frmtrm_amount는 전기, bfefrmtrm_nm과 bfefrmtrm_amount는 전전기를 의미합니다. 따라서 해당 열을 통해 최근 3년 재무제표 만을 선택할 수도 있습니다. yr_count = str_detect(colnames(fs_data_all), 'trm_amount') %>% sum() yr_name = seq(bsns_year, (bsns_year - yr_count + 1)) fs_data_all = fs_data_all[, c('corp_code', 'sj_nm', 'account_nm', 'account_detail')] %>% cbind(fs_data_all[, str_which(colnames(fs_data_all), 'trm_amount')]) colnames(fs_data_all)[str_which(colnames(fs_data_all), 'amount')] = yr_name head(fs_data_all) ## corp_code sj_nm account_nm ## 1 00126380 재무상태표 유동자산 ## 2 00126380 재무상태표 현금및현금성자산 ## 3 00126380 재무상태표 단기금융상품 ## 4 00126380 재무상태표 단기매도가능금융자산 ## 5 00126380 재무상태표 단기상각후원가금융자산 ## 6 00126380 재무상태표 단기당기손익-공정가치금융자산 ## account_detail 2019 2018 ## 1 - 181385260000000 174697424000000 ## 2 - 26885999000000 30340505000000 ## 3 - 76252052000000 65893797000000 ## 4 - ## 5 - 3914216000000 2703693000000 ## 6 - 1727436000000 2001948000000 ## 2017 ## 1 146982464000000 ## 2 30545130000000 ## 3 49447696000000 ## 4 3191375000000 ## 5 ## 6 str_detect() 함수를 이용해 열 이름에 trm_amount 들어간 갯수를 확인합니다. 이는 최근 3개년 데이터가 없는 경우도 고려하기 위함입니다. (일반적으로 3이 반환될 것이며, 재무데이터가 2년치 밖에 없는 경우 2가 반환될 것입니다.) 위에서 계산된 갯수를 이용해 열이름에 들어갈 년도를 생성합니다. corp_code(고유번호), sj_nm(재무제표명), account_nm(계정명), account_detail(계정상세) 및 연도별 금액에 해당하는 trm_amount가 포함된 열을 선택합니다. 연도별 데이터에 해당하는 열의 이름을 yr_name, 즉 각 연도로 변경합니다. 6.3.6.1 전 종목 전체 재무제표 데이터 수집하기 for loop 구문을 이용해 고유번호에 해당하는 corp_code 부분만 변경해주면 전 종목의 API를 통해 재무제표 데이터를 손쉽게 수집할 수도 있습니다. 단, 일부 종목(대부분 금융주)의 경우 API로 파일이 제공되지 않습니다. library(stringr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1) corp_list = read.csv('data/corp_list.csv', row.names = 1) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, side = c('left'), pad = '0') corp_list$'code' = str_pad(corp_list$'code', 8, side = c('left'), pad = '0') corp_list$'stock' = str_pad(corp_list$'stock', 6, side = c('left'), pad = '0') ticker_list = KOR_ticker %>% left_join(corp_list, by = c('종목코드' = 'stock')) %>% select('종목코드', '종목명', 'code') ifelse(dir.exists('data/dart_fs'), FALSE, dir.create('data/dart_fs')) 먼저 거래소에서 받은 티커 파일과 API를 통해 받은 고유번호 파일을 불러온 후, str_pad() 함수를 통해 0을 채워주며, 고유번호의 경우 8자리로 구성되어 있습니다. 그 후 dart_fs 폴더를 생성해 줍니다. bsns_year = 2019 reprt_code = '11011' for(i in 1 : nrow(ticker_list) ) { data_fs = c() name = ticker_list$code[i] # 오류 발생 시 이를 무시하고 다음 루프로 진행 tryCatch({ # url 생성 url = paste0('https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?crtfc_key=', dart_api, '&corp_code=', name, '&bsns_year=', bsns_year, '&reprt_code=', reprt_code,'&fs_div=CFS' ) # JSON 다운로드 fs_data_all = fromJSON(url) fs_data_all = fs_data_all[['list']] # 만일 연결재무제표 없어서 NULL 반환시 # reprt_code를 OFS 즉 재무제표 다운로드 if (is.null(fs_data_all)) { url = paste0('https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?crtfc_key=', dart_api, '&corp_code=', name, '&bsns_year=', bsns_year, '&reprt_code=', reprt_code,'&fs_div=OFS' ) fs_data_all = fromJSON(url) fs_data_all = fs_data_all[['list']] } # 데이터 선택 후 열이름을 연도로 변경 yr_count = str_detect(colnames(fs_data_all), 'trm_amount') %>% sum() yr_name = seq(bsns_year, (bsns_year - yr_count + 1)) fs_data_all = fs_data_all[, c('corp_code', 'sj_nm', 'account_nm', 'account_detail')] %>% cbind(fs_data_all[, str_which(colnames(fs_data_all), 'trm_amount')]) colnames(fs_data_all)[str_which(colnames(fs_data_all), 'amount')] = yr_name }, error = function(e) { # 오류 발생시 해당 종목명을 출력하고 다음 루프로 이동 data_fs <<- NA warning(paste0("Error in Ticker: ", name)) }) # 다운로드 받은 파일을 생성한 각각의 폴더 내 csv 파일로 저장 # 재무제표 저장 write.csv(fs_data_all, paste0('data/dart_fs/', ticker_list$종목코드[i], '_fs_dart.csv')) # 2초간 타임슬립 적용 Sys.sleep(2) } for loop 구문을 이용해 고유번호에 해당하는 값을 변경합니다. 일부 종목의 경우 연결재무제표가 아닌 재무제표를 업로드 하는 경우가 있으며, if (is.null(fs_data_all)) 부분을 통해 연결재무제표가 없을 경우 fs_div를 OFS로 변경하여 재무제표를 다운로드 받습니다. 이를 제외하고는 앞서 살펴본 예제와 동일합니다. 데이터 수집 및 정리를 해준 후, data 폴더의 dart_fs 폴더 내에 티커_fs_dart.csv 이름으로 저장해 줍니다. Open API 내에서는 2015년 이후 재무제표 데이터를 API 형태로 제공하고 있으므로 bsns_year 부분에도 for loop 구문을 이용하면 해당 데이터를 모두 수집할 수 있습니다. 그러나 간단한 퀀트 투자를 하기에는 최근 3년의 재무제표 데이터만 있어도 충분하며, 시간이 너무 오래 걸린다는 점, API 요청한도를 초과한다는 단점이 있으므로 본 책에서는 다루지 않도록 하겠습니다. https://finance.naver.com/item/fchart.nhn?code=005930↩︎ 플래쉬가 차단되어 화면이 나오지 않는 경우, 주소창의 왼쪽 상단에 위치한 자물쇠 버튼을 클릭한 다음, Flash를 허용으로 바꾼 후 새로고침을 누르면 차트가 나오게 됩니다.↩︎ http://comp.fnguide.com/↩︎ 분모에 사용되는 재무데이터의 구체적인 항목과 발행주식수를 계산하는 방법의 차이로 인해 여러 업체에서 제공하는 가치지표와 다소 차이가 발생할 수 있습니다.↩︎ "], -["데이터-정리하기.html", "Chapter 7 데이터 정리하기 7.1 주가 정리하기 7.2 재무제표 정리하기 7.3 가치지표 정리하기", " Chapter 7 데이터 정리하기 앞 CHAPTER에서는 API와 크롤링을 통해 주가, 재무제표, 가치지표를 수집하는 방법을 배웠습니다. 이번 CHAPTER에서는 각각 csv 파일로 저장된 데이터들을 하나로 합친 후 저장하는 과정을 살펴보겠습니다. 7.1 주가 정리하기 주가는 data/KOR_price 폴더 내에 티커_price.csv 파일로 저장되어 있습니다. 해당 파일들을 불러온 후 데이터를 묶는 작업을 통해 하나의 파일로 합치는 방법을 알아보겠습니다. library(stringr) library(xts) library(magrittr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, side = c('left'), pad = '0') price_list = list() for (i in 1 : nrow(KOR_ticker)) { name = KOR_ticker[i, '종목코드'] price_list[[i]] = read.csv(paste0('data/KOR_price/', name, '_price.csv'),row.names = 1) %>% as.xts() } price_list = do.call(cbind, price_list) %>% na.locf() colnames(price_list) = KOR_ticker$'종목코드' head(price_list[, 1:5]) ## X X005930 X000660 X051910 X005380 ## 1 2019-01-09 39600 63600 352000 123000 ## 2 2019-01-10 39800 65300 347000 123000 ## 3 2019-01-11 40500 65100 349000 123000 ## 4 2019-01-14 40050 62100 350500 121500 ## 5 2019-01-15 41100 64000 356000 127500 ## 6 2019-01-16 41450 64800 366000 128500 tail(price_list[, 1:5]) ## X X005930 X000660 X051910 X005380 ## 494 2021-01-08 88800 138000 999000 246000 ## 495 2021-01-11 91000 133000 998000 267500 ## 496 2021-01-12 90600 129000 962000 261000 ## 497 2021-01-13 89700 133000 1000000 259000 ## 498 2021-01-14 89700 130500 1010000 250500 ## 499 2021-01-15 88000 127500 979000 240000 티커가 저장된 csv 파일을 불러온 후 티커를 6자리로 맞춰줍니다. 빈 리스트인 price_list를 생성합니다. for loop 구문을 이용해 종목별 가격 데이터를 불러온 후 as.xts()를 통해 시계열 형태로 데이터를 변경하고 리스트에 저장합니다. do.call() 함수를 통해 리스트를 열 형태로 묶습니다. 간혹 결측치가 발생할 수 있으므로, na.locf() 함수를 통해 결측치에는 전일 데이터를 사용합니다. 행 이름을 각 종목의 티커로 변경합니다. 해당 작업을 통해 개별 csv 파일로 흩어져 있던 가격 데이터가 하나의 데이터로 묶이게 됩니다. write.csv(data.frame(price_list), 'data/KOR_price.csv') 마지막으로 해당 데이터를 data 폴더에 KOR_price.csv 파일로 저장합니다. 시계열 형태 그대로 저장하면 인덱스가 삭제되므로 데이터 프레임 형태로 변경한 후 저장해야 합니다. 7.2 재무제표 정리하기 재무제표는 data/KOR_fs 폴더 내 티커_fs.csv 파일로 저장되어 있습니다. 주가는 하나의 열로 이루어져 있어 데이터를 정리하는 것이 간단했지만, 재무제표는 각 종목별 재무 항목이 모두 달라 정리하기 번거롭습니다. library(stringr) library(magrittr) library(dplyr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, side = c('left'), pad = '0') data_fs = list() for (i in 1 : nrow(KOR_ticker)){ name = KOR_ticker[i, '종목코드'] data_fs[[i]] = read.csv(paste0('data/KOR_fs/', name, '_fs.csv'), row.names = 1) } 위와 동일하게 티커 데이터를 읽어옵니다. 이를 바탕으로 종목별 재무제표 데이터를 읽어온 후 리스트에 저장합니다. fs_item = data_fs[[1]] %>% rownames() length(fs_item) ## [1] 237 print(head(fs_item)) ## [1] "매출액" "매출원가" ## [3] "매출총이익" "판매비와관리비" ## [5] "인건비" "유무형자산상각비" 다음으로 재무제표 항목의 기준을 정해줄 필요가 있습니다. 재무제표 작성 항목은 각 업종별로 상이하므로, 이를 모두 고려하면 지나치게 데이터가 커지게 됩니다. 또한 퀀트 투자에는 일반적이고 공통적인 항목을 주로 사용하므로 대표적인 재무 항목을 정해 이를 기준으로 데이터를 정리해도 충분합니다. 따라서 기준점으로 첫 번째 리스트, 즉 삼성전자의 재무 항목을 선택하며, 총 237개 재무 항목이 있습니다. 해당 기준을 바탕으로 재무제표 데이터를 정리하며, 전체 항목에 대한 정리 이전에 간단한 예시로 첫 번째 항목인 매출액 기준 데이터 정리를 살펴보겠습니다. select_fs = lapply(data_fs, function(x) { # 해당 항목이 있을시 데이터를 선택 if ( '매출액' %in% rownames(x) ) { x[which(rownames(x) == '매출액'), ] # 해당 항목이 존재하지 않을 시, NA로 된 데이터프레임 생성 } else { data.frame(NA) } }) select_fs = bind_rows(select_fs) print(head(select_fs)) ## X2017.12 X2018.12 X2019.12 NA. ## 매출액...1 2395754 2437714 2304009 NA ## 매출액...2 301094 404451 269907 NA ## 매출액...3 256980 281830 286250 NA ## 매출액...4 963761 968126 1057464 NA ## 매출액...5 4646 5358 7016 NA ## 매출액...6 63466 91583 100974 NA 먼저 lapply() 함수를 이용해 모든 재무 데이터가 들어 있는 data_fs 데이터를 대상으로 함수를 적용합니다. %in% 함수를 통해 만일 매출액이라는 항목이 행 이름에 있으면 해당 부분의 데이터를 select_fs 리스트에 저장하고, 해당 항목이 없는 경우 NA로 이루어진 데이터 프레임을 저장합니다. 그 후, dplyr 패키지의 bind_rows() 함수를 이용해 리스트 내 데이터들을 행으로 묶어줍니다. rbind()에서는 리스트 형태를 테이블로 묶으려면 모든 데이터의 열 개수가 동일해야 하는 반면, bind_rows()에서는 열 개수가 다를 경우 나머지 부분을 NA로 처리해 합쳐주는 장점이 있습니다. 합쳐진 데이터를 살펴보면, 먼저 열 이름이 . 혹은 NA.인 부분이 있습니다. 이는 매출액 항목이 없는 종목의 경우 NA 데이터 프레임을 저장해 생긴 결과입니다. 또한 연도가 순서대로 저장되지 않은 경우가 있습니다. 이 두 가지를 고려해 데이터를 클렌징합니다. select_fs = select_fs[!colnames(select_fs) %in% c('.', 'NA.')] select_fs = select_fs[, order(names(select_fs))] rownames(select_fs) = KOR_ticker[, '종목코드'] print(head(select_fs)) ## X2017.12 X2018.12 X2019.12 ## 5930 2395754 2437714 2304009 ## 660 301094 404451 269907 ## 51910 256980 281830 286250 ## 5380 963761 968126 1057464 ## 207940 4646 5358 7016 ## 6400 63466 91583 100974 !와 %in% 함수를 이용해, 열 이름에 . 혹은 NA.가 들어가지 않은 열만 선택합니다. order() 함수를 이용해 열 이름의 연도별 순서를 구한 후 이를 바탕으로 열을 다시 정리합니다. 행 이름을 티커들로 변경합니다. 해당 과정을 통해 전 종목의 매출액 데이터가 연도별로 정리되었습니다. for loop 구문을 이용해 모든 재무 항목에 대한 데이터를 정리하는 방법은 다음과 같습니다. fs_list = list() for (i in 1 : length(fs_item)) { select_fs = lapply(data_fs, function(x) { # 해당 항목이 있을시 데이터를 선택 if ( fs_item[i] %in% rownames(x) ) { x[which(rownames(x) == fs_item[i]), ] # 해당 항목이 존재하지 않을 시, NA로 된 데이터프레임 생성 } else { data.frame(NA) } }) # 리스트 데이터를 행으로 묶어줌 select_fs = bind_rows(select_fs) # 열이름이 '.' 혹은 'NA.'인 지점은 삭제 (NA 데이터) select_fs = select_fs[!colnames(select_fs) %in% c('.', 'NA.')] # 연도 순별로 정리 select_fs = select_fs[, order(names(select_fs))] # 행이름을 티커로 변경 rownames(select_fs) = KOR_ticker[, '종목코드'] # 리스트에 최종 저장 fs_list[[i]] = select_fs } # 리스트 이름을 재무 항목으로 변경 names(fs_list) = fs_item 위 과정을 거치면 fs_list에 총 237리스트가 생성됩니다. 각 리스트에는 해당 재무 항목에 대한 전 종목의 연도별 데이터가 정리되어 있습니다. saveRDS(fs_list, 'data/KOR_fs.Rds') 마지막으로 해당 데이터를 data 폴더 내에 저장합니다. 리스트 형태 그대로 저장하기 위해 saveRDS() 함수를 이용해 KOR_fs.Rds 파일로 저장합니다. Rds 형식은 파일을 더블 클릭한 후 연결 프로그램을 R Studio로 설정해 파일을 불러올 수 있습니다. 혹은 readRDS() 함수를 이용해 파일을 읽어올 수도 있습니다. 7.3 가치지표 정리하기 가치지표는 data/KOR_value 폴더 내 티커_value.csv 파일로 저장되어 있습니다. 재무제표를 정리하는 방법과 거의 동일합니다. library(stringr) library(magrittr) library(dplyr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, side = c('left'), pad = '0') data_value = list() for (i in 1 : nrow(KOR_ticker)){ name = KOR_ticker[i, '종목코드'] data_value[[i]] = read.csv(paste0('data/KOR_value/', name, '_value.csv'), row.names = 1) %>% t() %>% data.frame() } 먼저 티커에 해당하는 파일을 불러온 후 for loop 구문을 통해 가치지표 데이터를 data_value 리스트에 저장합니다. 단, csv 내에 데이터가 7.1와 같이 행의 형태로 저장되어 있으므로, t() 함수를 이용해 열의 형태로 바꿔주며, 데이터 프레임 형태로 저장합니다. 표 7.1: 가치지표의 저장 예시 value x PER Number 1 PBR Number 2 PCR Number 3 PSR Number 4 data_value = bind_rows(data_value) print(head(data_value)) ## PER PBR PCR PSR X1 ## x...1 24.43 1.9984 11.58 2.2801 NA ## x...2 46.10 1.9360 14.32 3.4390 NA ## x...3 220.52 3.9755 22.14 2.4143 NA ## x...4 17.21 0.6715 122.15 0.4849 NA ## x...5 262.18 12.2165 5215.36 75.8219 NA ## x...6 142.16 4.0030 54.90 5.0191 NA bind_rows() 함수를 이용하여 리스트 내 데이터들을 행으로 묶어준 후 데이터를 확인해보면 PER, PBR, PCR, PSR 열 외에 불필요한 NA로 이루어진 열이 존재합니다. 해당 열을 삭제한 후 정리 작업을 하겠습니다. data_value = data_value[colnames(data_value) %in% c('PER', 'PBR', 'PCR', 'PSR')] data_value = data_value %>% mutate_all(list(~na_if(., Inf))) rownames(data_value) = KOR_ticker[, '종목코드'] print(head(data_value)) ## PER PBR PCR PSR ## 005930 24.43 1.9984 11.58 2.2801 ## 000660 46.10 1.9360 14.32 3.4390 ## 051910 220.52 3.9755 22.14 2.4143 ## 005380 17.21 0.6715 122.15 0.4849 ## 207940 262.18 12.2165 5215.36 75.8219 ## 006400 142.16 4.0030 54.90 5.0191 write.csv(data_value, 'data/KOR_value.csv') 열 이름이 가치지표에 해당하는 부분만 선택합니다. 일부 종목은 재무 데이터가 0으로 표기되어 가치지표가 Inf로 계산되는 경우가 있습니다. mutate_all() 내에 na_if() 함수를 이용해 Inf 데이터를 NA로 변경합니다. 행 이름을 티커들로 변경합니다. data 폴더 내에 KOR_value.csv 파일로 저장합니다. "], -["데이터-분석-및-시각화하기.html", "Chapter 8 데이터 분석 및 시각화하기 8.1 종목정보 데이터 분석 8.2 ggplot() 기초 8.3 종목정보 시각화 8.4 주가 및 수익률 시각화", " Chapter 8 데이터 분석 및 시각화하기 데이터 수집 및 정리가 끝났다면, 내가 가지고 있는 데이터가 어떠한 특성을 가지고 있는지에 대한 분석 및 시각화 과정, 즉 탐색적 데이터 분석(Exploratory Data Analysis)을 할 필요가 있습니다. 이 과정을 통해 데이터를 더 잘 이해할 수 있으며, 극단치나 결측치 등 데이터가 가지고 있는 잠재적인 문제를 발견하고 이를 어떻게 처리할지 고민할 수 있습니다. 이 CHAPTER에서는 dplyr 패키지를 이용한 데이터 분석과 ggplot2 패키지를 이용한 데이터 시각화에 대해 알아보겠습니다. 8.1 종목정보 데이터 분석 먼저 거래소를 통해 수집한 산업별 현황과 개별지표를 정리한 파일, WICS 기준 섹터 지표를 정리한 파일을 통해 국내 상장종목의 데이터를 분석해보겠습니다. library(stringr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_sector = read.csv('data/KOR_sector.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6,'left', 0) KOR_sector$'CMP_CD' = str_pad(KOR_sector$'CMP_CD', 6, 'left', 0) 각 파일을 불러온 후 티커에 해당하는 종목코드와 CMP_CD 열을 6자리 숫자로 만들어줍니다. 이제 dplyr 패키지의 여러 함수들을 이용해 데이터를 분석해보겠습니다. 해당 패키지는 데이터 처리에 특화된 패키지이며, C++로 작성되어 매우 빠른 처리 속도를 자랑합니다. 또한 문법이 SQL과 매우 비슷해 함수의 내용을 직관적으로 이해할 수 있습니다. 8.1.1 *_join: 데이터 합치기 두 테이블을 하나로 합치기 위해 *_join() 함수를 이용합니다. 해당 함수는 기존에 살펴본 merge() 함수와 동일하며, 합치는 방법은 그림 8.1과 표 8.1과 같이 크게 네가지 종류가 있습니다. 그림 8.1: *_ join() 함수의 종류 표 8.1: join 함수의 종류 함수 내용 inner_join() 교집합 full_join() 합집합 left_join() 좌측 기준 right_join() 우측 기준 이 중 거래소 티커 기준으로 데이터를 맞추기 위해 left_join() 함수를 사용해 두 데이터를 합치겠습니다. library(dplyr) data_market = left_join(KOR_ticker, KOR_sector, by = c('종목코드' = 'CMP_CD', '종목명' = 'CMP_KOR')) head(data_market) ## 종목코드 종목명 종가 대비 등락률 ## 1 005930 삼성전자 89700 -900 -0.99 ## 2 000660 SK하이닉스 133000 4000 3.10 ## 3 051910 LG화학 1000000 38000 3.95 ## 4 005380 현대차 259000 -2000 -0.77 ## 5 207940 삼성바이오로직스 830000 12000 1.47 ## 6 006400 삼성SDI 754000 9000 1.21 ## 시장구분 업종명 시가총액 EPS PER BPS PBR ## 1 KOSPI 전기전자 5.355e+14 3166 28.33 37528 2.39 ## 2 KOSPI 전기전자 9.682e+13 2943 45.19 65836 2.02 ## 3 KOSPI 화학 7.059e+13 4085 244.80 217230 4.60 ## 4 KOSPI 운수장비 5.534e+13 11310 22.90 253001 1.02 ## 5 KOSPI 의약품 5.492e+13 3067 270.62 65812 12.61 ## 6 KOSPI 전기전자 5.185e+13 5331 141.44 175114 4.31 ## 주당배당금 배당수익률 IDX_CD IDX_NM_KOR ## 1 1416 1.58 G45 WICS IT ## 2 1000 0.75 G45 WICS IT ## 3 2000 0.20 G15 WICS 소재 ## 4 4000 1.54 G25 WICS 경기관련소비재 ## 5 0 0.00 G35 WICS 건강관리 ## 6 1000 0.13 G45 WICS IT ## ALL_MKT_VAL MKT_VAL WGT S_WGT CAL_WGT SEC_CD ## 1 645689994 401617121 62.20 62.20 1 G45 ## 2 645689994 71649993 11.10 73.30 1 G45 ## 3 119996855 45179100 37.65 37.65 1 G15 ## 4 166748874 36524440 21.90 21.90 1 G25 ## 5 166251685 13729238 8.26 41.91 1 G35 ## 6 645689994 38367857 5.94 79.24 1 G45 ## SEC_NM_KOR SEQ TOP60 APT_SHR_CNT ## 1 IT 1 0 4477336913 ## 2 IT 2 0 538721750 ## 3 소재 1 4 45179100 ## 4 경기관련소비재 1 6 141021003 ## 5 건강관리 3 15 16541250 ## 6 IT 3 0 50885752 left_join() 함수를 이용해 KOR_ticker와 KOR_sector 데이터를 합쳐줍니다. by 인자는 데이터를 합치는 기준점을 의미하며, x 데이터(KOR_ticker)의 종목코드와 y 데이터(KOR_sector)의 CMP_CD는 같음을, x 데이터의 종목명과 y 데이터의 CMP_KOR는 같음을 정의합니다. 8.1.2 glimpse(): 데이터 구조 확인하기 glimpse(data_market) ## Rows: 2,172 ## Columns: 26 ## $ 종목코드 <chr> "005930", "000660", "051910", "… ## $ 종목명 <chr> "삼성전자", "SK하이닉스", "LG화학", "현대차… ## $ 종가 <dbl> 89700, 133000, 1000000, 25900… ## $ 대비 <int> -900, 4000, 38000, -2000, 120… ## $ 등락률 <dbl> -0.99, 3.10, 3.95, -0.77, 1.47… ## $ 시장구분 <chr> "KOSPI", "KOSPI", "KOSPI", "KOS… ## $ 업종명 <chr> "전기전자", "전기전자", "화학", "운수장비", … ## $ 시가총액 <dbl> 5.355e+14, 9.682e+13, 7.059e+13… ## $ EPS <int> 3166, 2943, 4085, 11310, 30… ## $ PER <dbl> 28.33, 45.19, 244.80, 22.90… ## $ BPS <int> 37528, 65836, 217230, 25300… ## $ PBR <dbl> 2.39, 2.02, 4.60, 1.02, 12.… ## $ 주당배당금 <int> 1416, 1000, 2000, 4000, 0, 1000,… ## $ 배당수익률 <dbl> 1.58, 0.75, 0.20, 1.54, 0.00, 0.… ## $ IDX_CD <chr> "G45", "G45", "G15", "G25",… ## $ IDX_NM_KOR <chr> "WICS IT", "WICS IT", "WICS… ## $ ALL_MKT_VAL <int> 645689994, 645689994, 11999… ## $ MKT_VAL <int> 401617121, 71649993, 451791… ## $ WGT <dbl> 62.20, 11.10, 37.65, 21.90,… ## $ S_WGT <dbl> 62.20, 73.30, 37.65, 21.90,… ## $ CAL_WGT <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, … ## $ SEC_CD <chr> "G45", "G45", "G15", "G25",… ## $ SEC_NM_KOR <chr> "IT", "IT", "소재", "경기관련소비재"… ## $ SEQ <int> 1, 2, 1, 1, 3, 3, 1, 1, 2, … ## $ TOP60 <int> 0, 0, 4, 6, 15, 0, 3, 15, 3… ## $ APT_SHR_CNT <dbl> 4477336913, 538721750, 4517… glimpse() 함수는 데이터 내용, 구조, 형식을 확인하는 함수입니다. 기본 함수인 str()과 그 역할은 비슷하지만, tidy 형태로 결과물이 훨씬 깔끔하게 출력됩니다. 총 관측값 및 열의 개수, 각 열의 이름과 데이터 형식, 앞부분 데이터를 확인할 수 있습니다. 8.1.3 rename(): 열 이름 바꾸기 head(names(data_market), 15) ## [1] "종목코드" "종목명" "종가" "대비" ## [5] "등락률" "시장구분" "업종명" "시가총액" ## [9] "EPS" "PER" "BPS" "PBR" ## [13] "주당배당금" "배당수익률" "IDX_CD" data_market = data_market %>% rename(`배당수익률(%)` = `배당수익률`) head(names(data_market), 15) ## [1] "종목코드" "종목명" "종가" ## [4] "대비" "등락률" "시장구분" ## [7] "업종명" "시가총액" "EPS" ## [10] "PER" "BPS" "PBR" ## [13] "주당배당금" "배당수익률(%)" "IDX_CD" rename() 함수는 열 이름을 바꾸는 함수로서, rename(tbl, new_name, old_name) 형태로 입력합니다. 위의 경우 배당수익률 열 이름이 배당수익률(%)로 변경되었습니다. 8.1.4 distinct(): 고유한 값 확인 data_market %>% distinct(SEC_NM_KOR) %>% c() ## $SEC_NM_KOR ## [1] "IT" "소재" ## [3] "경기관련소비재" "건강관리" ## [5] "커뮤니케이션서비스" "산업재" ## [7] "에너지" "금융" ## [9] "유틸리티" "필수소비재" ## [11] NA distinct() 함수는 고유한 값을 반환하며, 기본 함수 중 unique()와 동일한 기능을 합니다. 데이터의 섹터 정보를 확인해보면, WICS 기준 10개 섹터 및 섹터 정보가 없는 종목인 NA 값이 있습니다. 8.1.5 select(): 원하는 열만 선택 data_market %>% select(`종목명`) %>% head() ## 종목명 ## 1 삼성전자 ## 2 SK하이닉스 ## 3 LG화학 ## 4 현대차 ## 5 삼성바이오로직스 ## 6 삼성SDI data_market %>% select(`종목명`, `PBR`, `SEC_NM_KOR`) %>% head() ## 종목명 PBR SEC_NM_KOR ## 1 삼성전자 2.39 IT ## 2 SK하이닉스 2.02 IT ## 3 LG화학 4.60 소재 ## 4 현대차 1.02 경기관련소비재 ## 5 삼성바이오로직스 12.61 건강관리 ## 6 삼성SDI 4.31 IT select() 함수는 원하는 열을 선택해주는 함수이며, 원하는 열 이름을 입력하면 됩니다. 하나의 열뿐만 아니라 다수의 열을 입력하면 해당 열들이 선택됩니다. data_market %>% select(starts_with('시')) %>% head() ## 시장구분 시가총액 ## 1 KOSPI 5.355e+14 ## 2 KOSPI 9.682e+13 ## 3 KOSPI 7.059e+13 ## 4 KOSPI 5.534e+13 ## 5 KOSPI 5.492e+13 ## 6 KOSPI 5.185e+13 data_market %>% select(ends_with('R')) %>% head() ## PER PBR IDX_NM_KOR SEC_NM_KOR ## 1 28.33 2.39 WICS IT IT ## 2 45.19 2.02 WICS IT IT ## 3 244.80 4.60 WICS 소재 소재 ## 4 22.90 1.02 WICS 경기관련소비재 경기관련소비재 ## 5 270.62 12.61 WICS 건강관리 건강관리 ## 6 141.44 4.31 WICS IT IT data_market %>% select(contains('가')) %>% head() ## 종가 시가총액 ## 1 89700 5.355e+14 ## 2 133000 9.682e+13 ## 3 1000000 7.059e+13 ## 4 259000 5.534e+13 ## 5 830000 5.492e+13 ## 6 754000 5.185e+13 해당 함수는 다양한 응용 기능도 제공합니다. starts_with()는 특정 문자로 시작하는 열들을 선택하고, ends_with()는 특정 문자로 끝나는 열들을 선택하며, contains()는 특정 문자가 포함되는 열들을 선택합니다. 8.1.6 mutate(): 열 생성 및 데이터 변형 data_market = data_market %>% mutate(`PBR` = as.numeric(PBR), `PER` = as.numeric(PER), `ROE` = PBR / PER, `ROE` = round(ROE, 4), `size` = ifelse(`시가총액` >= median(`시가총액`, na.rm = TRUE), 'big', 'small') ) data_market %>% select(`종목명`, `ROE`, `size`) %>% head() ## 종목명 ROE size ## 1 삼성전자 0.0844 big ## 2 SK하이닉스 0.0447 big ## 3 LG화학 0.0188 big ## 4 현대차 0.0445 big ## 5 삼성바이오로직스 0.0466 big ## 6 삼성SDI 0.0305 big mutate() 함수는 원하는 형태로 열을 생성하거나 변형하는 함수입니다. 위 예제에 서는 먼저 PBR과 PER 열을 as.numeric() 함수를 통해 숫자형으로 변경한 후 PBR을 PER로 나눈 값을 ROE 열에 생성합니다. 그 후 round() 함수를 통해 ROE 값을 반올림하며, ifelse() 함수를 통해 시가총액의 중앙값보다 큰 기업은 big, 아닐 경우 small임을 size 열에 저장합니다. 이 외에도 mutate_*() 계열 함수에는 mutate_all(), mutate_if(), mutate_at() 처럼 각 상황에 맞게 쓸 수 있는 다양한 함수들이 있습니다. 8.1.7 filter(): 조건을 충족하는 행 선택 data_market %>% select(`종목명`, `PBR`) %>% filter(`PBR` < 1) %>% head() ## 종목명 PBR ## 1 POSCO 0.56 ## 2 SK텔레콤 0.90 ## 3 KB금융 0.51 ## 4 LG 0.97 ## 5 신한지주 0.43 ## 6 삼성생명 0.47 data_market %>% select(`종목명`, `PBR`, `PER`, `ROE`) %>% filter(PBR < 1 & PER < 20 & ROE > 0.1 ) %>% head() ## 종목명 PBR PER ROE ## 1 GS건설 0.86 7.66 0.1123 ## 2 대림산업 0.54 4.82 0.1120 ## 3 메리츠증권 0.67 4.13 0.1622 ## 4 신세계 0.64 4.82 0.1328 ## 5 HDC현대산업개발 0.60 3.21 0.1869 ## 6 두산인프라코어 0.80 7.36 0.1087 filter() 함수는 조건을 충족하는 부분의 데이터를 반환하는 함수입니다. 첫 번째 예제와 같이 PBR이 1 미만인 단일 조건을 입력할 수도 있으며, 두 번째 예제와 같이 PBR 1 미만, PER 20 미만, ROE 0.1 초과 등 복수 조건을 입력할 수도 있습니다. 8.1.8 summarize(): 요약 통곗값 계산 data_market %>% summarize(PBR_max = max(PBR, na.rm = TRUE), PBR_min = min(PBR, na.rm = TRUE)) ## PBR_max PBR_min ## 1 555.2 0.13 summarize() 혹은 summarise() 함수는 원하는 요약 통곗값을 계산합니다. PBR_max는 PBR 열에서 최댓값을, PBR_min은 최솟값을 계산해줍니다. 8.1.9 arrange(): 데이터 정렬 data_market %>% select(PBR) %>% arrange(PBR) %>% head(5) ## PBR ## 1 0.13 ## 2 0.14 ## 3 0.15 ## 4 0.15 ## 5 0.18 data_market %>% select(ROE) %>% arrange(desc(ROE)) %>% head(5) ## ROE ## 1 4.4146 ## 2 1.7647 ## 3 0.7458 ## 4 0.6661 ## 5 0.6350 arrange() 함수는 선택한 열을 기준으로 데이터를 정렬해주며, 오름차순으로 정렬합니다. 내림차순으로 데이터를 정렬하려면 arrange() 내에 desc() 함수를 추가로 입력해주면 됩니다. 8.1.10 row_number(): 순위 계산 data_market %>% mutate(PBR_rank = row_number(PBR)) %>% select(`종목명`, PBR, PBR_rank) %>% arrange(PBR) %>% head(5) ## 종목명 PBR PBR_rank ## 1 감마누 0.13 1 ## 2 휴스틸 0.14 2 ## 3 세아홀딩스 0.15 3 ## 4 지스마트글로벌 0.15 4 ## 5 경동인베스트 0.18 5 data_market %>% mutate(PBR_rank = row_number(desc(ROE))) %>% select(`종목명`, ROE, PBR_rank) %>% arrange(desc(ROE)) %>% head(5) ## 종목명 ROE PBR_rank ## 1 이엠앤아이 4.4146 1 ## 2 한진중공업 1.7647 2 ## 3 양지사 0.7458 3 ## 4 이엔드디 0.6661 4 ## 5 명신산업 0.6350 5 row_number() 함수는 선택한 열의 순위를 구해줍니다. 기본적으로 오름차순으로 순위를 구하며, 내림차순으로 순위를 구하려면 desc() 함수를 추가해주면 됩니다. 순위를 구하는 함수는 이 외에도 min_rank(), dense_rank(), percent_rank()가 있습니다. 8.1.11 ntile(): 분위수 계산 data_market %>% mutate(PBR_tile = ntile(PBR, n = 5)) %>% select(PBR, PBR_tile) %>% head() ## PBR PBR_tile ## 1 2.39 4 ## 2 2.02 4 ## 3 4.60 5 ## 4 1.02 2 ## 5 12.61 5 ## 6 4.31 5 ntile() 함수는 분위수를 계산해주며, n 인자를 통해 몇 분위로 나눌지 선택할 수 있습니다. 해당 함수 역시 오름차순으로 분위수를 나눕니다. 8.1.12 group_by(): 그룹별로 데이터를 묶기 data_market %>% group_by(`SEC_NM_KOR`) %>% summarize(n()) ## # A tibble: 11 x 2 ## SEC_NM_KOR `n()` ## <chr> <int> ## 1 IT 557 ## 2 건강관리 267 ## 3 경기관련소비재 335 ## 4 금융 78 ## 5 산업재 348 ## 6 소재 227 ## 7 에너지 26 ## 8 유틸리티 20 ## 9 커뮤니케이션서비스 113 ## 10 필수소비재 97 ## 11 <NA> 104 group_by() 함수는 선택한 열 중 동일한 데이터를 기준으로 데이터를 묶어줍니다. 위 예제에서는 섹터를 나타내는 SEC_NM_KOR 기준으로 데이터를 묶었으며, n() 함수를 통해 해당 그룹 내 데이터의 개수를 구할 수 있습니다. data_market %>% group_by(`SEC_NM_KOR`) %>% summarize(PBR_median = median(PBR, na.rm = TRUE)) %>% arrange(PBR_median) ## # A tibble: 11 x 2 ## SEC_NM_KOR PBR_median ## <chr> <dbl> ## 1 유틸리티 0.53 ## 2 금융 0.67 ## 3 소재 0.92 ## 4 필수소비재 0.98 ## 5 경기관련소비재 1.06 ## 6 산업재 1.09 ## 7 에너지 1.27 ## 8 <NA> 1.50 ## 9 IT 1.95 ## 10 커뮤니케이션서비스 2.1 ## 11 건강관리 3.75 위 예제는 섹터를 기준으로 데이터를 묶은 후 summarize()를 통해 각 섹터에 속하는 종목의 PBR 중앙값을 구한 후 정렬했습니다. data_market %>% group_by(`시장구분`, `SEC_NM_KOR`) %>% summarize(PBR_median = median(PBR, na.rm = TRUE)) %>% arrange(PBR_median) ## # A tibble: 22 x 3 ## # Groups: 시장구분 [2] ## 시장구분 SEC_NM_KOR PBR_median ## <chr> <chr> <dbl> ## 1 KOSPI 유틸리티 0.47 ## 2 KOSPI 금융 0.555 ## 3 KOSPI 에너지 0.73 ## 4 KOSPI 소재 0.81 ## 5 KOSPI 경기관련소비재 0.86 ## 6 KOSPI 필수소비재 0.86 ## 7 KOSPI 산업재 0.9 ## 8 KOSPI <NA> 0.93 ## 9 KOSDAQ 유틸리티 0.95 ## 10 KOSDAQ 소재 1.27 ## # … with 12 more rows 위 예제는 시장과 섹터를 기준으로 데이터를 그룹화한 후 각 그룹별 PBR 중앙값을 구했습니다. 이처럼 그룹은 하나만이 아닌 원하는 만큼 나눌 수 있습니다. 8.2 ggplot() 기초 R에서 기본적으로 제공하는 plot() 함수를 이용해도 시각화가 충분히 가능합니다. 그러나 데이터 과학자들에게 가장 많이 사랑받는 패키지 중 하나인 ggplot2 패키지의 ggplot() 함수를 사용하면 그림을 훨씬 아름답게 표현할 수 있으며 다양한 기능들을 매우 쉽게 사용할 수도 있습니다. ggplot() 함수는 플러스(+) 기호를 사용한다는 점과 문법이 다소 어색하다는 점 때문에 처음에 배우기가 쉽지는 않습니다. 그러나 해당 패키지의 근본이 되는 철학인 그래픽 문법(The Grammar of Graphics)를 이해하고 조금만 연습해본다면, 충분히 손쉽게 사용이 가능합니다. 그래픽 문법(Grammar of Graphics)은 릴랜드 윌킨스(Leland Wilkinson)의 책 The Grammar of Graphics(Wilkinson 2012)에서 따온 것으로써, 데이터를 어떻게 표현할 것인지에 대한 내용입니다. 문법은 언어의 표현을 풍부하게 만든다. 단어만 있고 문법이 없는 언어가 있다면(단어 = 문장), 오직 단어의 갯수만큼만 생각을 표현할 수 있다. 문장 내에서 단어가 어떻게 구성되는 지를 규정함으로써, 문법은 언어의 범위를 확장한다. — Leland Wilkinson, 《The Grammar of Graphics》 그래픽 문법에서 말하는 요소는 다음과 같습니다. Data: 시각화에 사용될 데이터 Aesthetics: 데이터를 나타내는 시각적인 요소(x축, y축, 사이즈, 색깔, 모양 등) Geometrics: 데이터를 나타내는 도형 Facets: 하위 집합으로 분할하여 시각화 Statistics: 통계값을 표현 Coordinates: 데이터를 표현 할 이차원 좌표계 Theme: 그래프를 꾸밈 그림 4.4: The Grammar of Graphics ggplot2 패키지의 앞글자가 gg인 것에서 알 수 있듯이, 해당 패키지는 그래픽 문법을 토대로 시각화를 표현하며, 전반적인 시각화의 순서는 그래픽 문법의 순서와 같습니다. ggplot2 패키지의 특징은 각 요소를 연결할 때 플러스(+) 기호를 사용한다는 점이며, 이는 그래픽 문법의 순서에 따라 요소들을 쌓아나간 후 최종적인 그래픽을 완성하는 패키지의 특성 때문입니다. 본 책에서 설명하는 것 외에도 다양한 예제와 함수가 궁금하신 분은 아래 링크를 참조하시길 바랍니다. http://r-statistics.co/ggplot2-Tutorial-With-R.html https://ggplot2.tidyverse.org/reference/ 8.2.1 diamonds 데이터셋 ggplot2 패키지에는 데이터분석 및 시각화 연습을 위한 각종 데이터셋이 있으며, 그 중에서도 diamonds 데이터셋이 널리 사용됩니다. 먼저 해당 데이터를 불러오도록 하겠습니다. library(ggplot2) data(diamonds) head(diamonds) ## # A tibble: 6 x 10 ## carat cut color clarity depth table price x ## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> ## 1 0.23 Ideal E SI2 61.5 55 326 3.95 ## 2 0.21 Prem… E SI1 59.8 61 326 3.89 ## 3 0.23 Good E VS1 56.9 65 327 4.05 ## 4 0.290 Prem… I VS2 62.4 58 334 4.2 ## 5 0.31 Good J SI2 63.3 58 335 4.34 ## 6 0.24 Very… J VVS2 62.8 57 336 3.94 ## # … with 2 more variables: y <dbl>, z <dbl> 데이터의 각 변수는 다음과 같습니다. carat: 다이아몬드 무게 cut: 컷팅의 가치 color: 다이아몬스 색상 clarity: 깨끗한 정도 depth: 깊이 비율, z / mean(x, y) table: 가장 넓은 부분의 너비 대비 다이아몬드 꼭대기의 너비 price: 가격 x: 길이 y: 너비 z: 깊이 8.2.2 Data, Aesthetics, Geometrics Data는 사용될 데이터이며, Aesthetics는 x축, y축, 사이즈 등 시각적인 요소를 의미합니다. ggplot(data = diamonds, aes(x = carat, y = price)) ggplot() 함수 내부의 data에 diamonds를 지정해줍니다. aes() 함수를 통해 데이터를 매핑해주며 x축에 carat을, y축에 price를 지정해줍니다. x축과 y축에 우리가 매핑한 carat과 price가 표현되었지만, 어떠한 모양(Geometrics)으로 시각화를 할지 정의하지 않았으므로 빈 그림이 생성됩니다. 다음으로 Geometrics을 통해 데이터를 그림으로 표현해주도록 하겠습니다. ggplot(data = diamonds, aes(x = carat, y = price)) + geom_point() 사전에 정의된 Data와 Aesthetics 위에, 플러스(+) 기호를 통해 geom_point() 함수를 입력하여 산점도가 표현되었습니다. geom은 Geometrics의 약자이며, 이처럼 geom_*() 함수를 통해 원하는 형태로 시각화를 할 수 있습니다. 일반적으로 Data는 ggplot() 함수 내에서 정의하기 보다는, dplyr 패키지의 함수들을 이용하여 데이터를 가공한 후 파이프 오퍼레이터를 통해 연결합니다. 이에 대해서는 나중에 다시 다루도록 하겠습니다. library(magrittr) diamonds %>% ggplot(aes(x = carat, y = price)) + geom_point(aes(color = cut)) diamonds 데이터를 파이프 오퍼레이터(%>%)로 이을 경우 그대로 시각화가 가능하며, ggplot() 함수 내에 데이터를 입력하지 않아도 됩니다. geom_point() 내부에서 aes()를 통해 점의 색깔을 매핑해줄 수 있습니다. color = cut을 지정하여 cut에 따라 점의 색깔이 다르게 표현하였습니다. 이 외에도 shape, size를 통해 모양과 크기를 각각 다르게 표현할 수 있습니다. 8.2.3 Facets Facets은 여러 집합을 하나의 그림에 표현하기 보다 하위 집합으로 나누어 시각화하는 요소입니다. diamonds %>% ggplot(aes(x = carat, y = price)) + geom_point() + facet_grid(. ~ cut) facet_grid() 혹은 facet_wrap() 함수를 통해 그림을 분할할 수 있습니다. 물결 표시(~)를 통해 하위 집합으로 나누고자 하는 변수를 선택할 수 있으며, 위 예제에서는 cut에 따라 각기 다른 그림으로 표현되었습니다. 8.2.4 Statistics Statistics는 통계값을 나타내는 요소입니다. diamonds %>% ggplot(aes(x = cut, y = carat)) + stat_summary_bin(fun.y = 'mean', geom = 'bar') stat_summary_*() 함수를 사용하여 통계값을 표현하였습니다. cut에 따른 carat의 평균값을 구하고자 할 경우, fun.y 인자에 mean을 입력하여 평균값을 구하고, geom 인자에 bar를 입력하여 막대그래프 형태로 표현하였습니다. 8.2.5 Coordinates Coordinates는 좌표를 의미합니다. ggplot2에서는 coord_*() 함수를 이용하여 x축 혹은 y축 정보를 변형할 수 있습니다. diamonds %>% ggplot(aes(x = carat, y = price)) + geom_point(aes(color = cut)) + coord_cartesian(xlim = c(0, 3), ylim = c(0, 20000)) coord_cartesian() 함수를 통해 x축과 y축 범위를 지정해 줄 수 있습니다. xlim과 ylim 내부에 범위의 최소 및 최댓값을 지정해주면, 해당 범위의 데이터만을 보여줍니다. diamonds %>% ggplot(aes(x = carat, y = price)) + geom_boxplot(aes(group = cut)) + coord_flip() coord_flip() 함수는 x축과 y축을 뒤집어 표현합니다. ggplot() 함수의 aes 내부에서 x축은 carat을, y축은 price를 지정해 주었지만, coord_flip() 함수를 통해 x축과 y축이 서로 바뀌었습니다. 8.2.6 Theme Theme은 그림의 제목, 축 제목, 축 단위, 범례, 디자인 등 그림을 꾸며주는 역할을 담당합니다. diamonds %>% ggplot(aes(x = carat, y = price)) + geom_point(aes(color = cut)) + theme_bw() + labs(title = 'Relation between Carat & Price', x = 'Carat', y = 'Price') + theme(legend.position = 'bottom', panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank(), panel.grid.major.y = element_blank(), panel.grid.minor.y = element_blank() ) + scale_y_continuous( labels = function(x) { paste0('$', format(x, big.mark = ',')) }) geom_point() 함수 이후 Theme에 해당하는 부분은 다음과 같습니다. theme_bw() 함수를 통해 배경을 흰색으로 설정합니다. labs() 함수를 통해 그래프의 제목 및 x축, y축 제목을 변경합니다. theme() 함수 내 legend.position을 통해 범례를 하단으로 이동합니다. theme() 함수 내 panel.grid를 통해 격자를 제거합니다. scale_y_continuous() 함수를 통해 y축에서 천원 단위로 콤마(,)를 붙여주며, 이를 달러($) 표시와 합쳐줍니다. 8.3 종목정보 시각화 이번에는 앞서 배운 내용을 바탕으로 종목정보를 시각화하도록 하겠습니다. 8.3.1 geom_point(): 산점도 나타내기 library(ggplot2) ggplot(data_market, aes(x = ROE, y = PBR)) + geom_point() ggplot() 함수 내에 사용될 데이터인 data_market을 입력합니다. aes 인자 내부에 x축은 ROE 열을 사용하고, y축은 PBR 열을 사용하도록 정의합니다. geom_point() 함수를 통해 산점도 그래프를 그려줍니다. 원하는 그림이 그려지기는 했으나, ROE와 PBR에 극단치 데이터가 있어 둘 사이에 관계가 잘 보이지 않습니다. ggplot(data_market, aes(x = ROE, y = PBR)) + geom_point() + coord_cartesian(xlim = c(0, 0.30), ylim = c(0, 3)) 이번에는 극단치 효과를 제거하기 위해 coord_cartesian() 함수 내에 xlim과 ylim, 즉 x축과 y축의 범위를 직접 지정해줍니다. 극단치가 제거되어 데이터를 한눈에 확인할 수 있습니다. ggplot(data_market, aes(x = ROE, y = PBR, color = `시장구분`, shape = `시장구분`)) + geom_point() + geom_smooth(method = 'lm') + coord_cartesian(xlim = c(0, 0.30), ylim = c(0, 3)) ggplot() 함수 내부 aes 인자에 color와 shape를 지정해주면, 해당 그룹별로 모양과 색이 나타납니다. 코스피와 코스닥 종목들에 해당하는 데이터의 색과 점 모양을 다르게 표시할 수 있습니다. geom_smooth() 함수를 통해 평활선을 추가할 수도 있으며, 방법으로 lm(linear model)을 지정할 경우 선형회귀선을 그려주게 됩니다. 이 외에도 glm, gam, loess 등의 다양한 회귀선을 그려줄 수 있습니다. 8.3.2 geom_histogram(): 히스토그램 나타내기 ggplot(data_market, aes(x = PBR)) + geom_histogram(binwidth = 0.1) + coord_cartesian(xlim = c(0, 10)) geom_histogram() 함수는 히스토그램을 나타내주며, binwidth 인자를 통해 막대의 너비를 선택해줄 수 있습니다. 국내 종목들의 PBR 데이터는 왼쪽에 쏠려 있고 오른쪽으로 꼬리가 긴 분포를 가지고 있습니다. ggplot(data_market, aes(x = PBR)) + geom_histogram(aes(y = ..density..), binwidth = 0.1, color = 'sky blue', fill = 'sky blue') + coord_cartesian(xlim = c(0, 10)) + geom_density(color = 'red') + geom_vline(aes(xintercept = median(PBR, na.rm = TRUE)), color = 'blue') + geom_text(aes(label = median(PBR, na.rm = TRUE), x = median(PBR, na.rm = TRUE), y = 0.05), col = 'black', size = 6, hjust = -0.5) PBR 히스토그램을 좀 더 자세하게 나타내보겠습니다. geom_histogram() 함수 내에 aes(y = ..density..)를 추가해 밀도함수로 바꿉니다. geom_density() 함수를 추가해 밀도곡선을 그려줍니다. geom_vline() 함수는 세로선을 그려주며, xintercept 즉 x축으로 PBR의 중앙값을 선택합니다. geom_text() 함수는 그림 내에 글자를 표현해주며, label 인자에 원하는 글자를 입력해준 후 글자가 표현될 x축, y축, 색상, 사이즈 등을 선택할 수 있습니다. 8.3.3 geom_boxplot(): 박스 플롯 나타내기 ggplot(data_market, aes(x = SEC_NM_KOR, y = PBR)) + geom_boxplot() + coord_flip() 박스 플롯 역시 데이터의 분포와 이상치를 확인하기 좋은 그림이며, geom_boxplot() 함수를 통해 나타낼 수 있습니다. x축 데이터로는 섹터 정보, y축 데이터로는 PBR을 선택합니다. geom_boxplot()을 통해 박스 플롯을 그려줍니다. coord_flip() 함수는 x축과 y축을 뒤집어 표현해주며 x축에 PBR, y축에 섹터 정보가 나타나게 됩니다. 결과를 살펴보면 유틸리티나 금융 섹터는 PBR이 잘 모여 있는 반면, IT나 건강관리 섹터 등은 매우 극단적인 PBR을 가지고 있는 종목이 있습니다. 8.3.4 dplyr과 ggplot을 연결하여 사용하기 data_market %>% filter(!is.na(SEC_NM_KOR)) %>% group_by(SEC_NM_KOR) %>% summarize(ROE_sector = median(ROE, na.rm = TRUE), PBR_sector = median(PBR, na.rm = TRUE)) %>% ggplot(aes(x = ROE_sector, y = PBR_sector, color = SEC_NM_KOR, label = SEC_NM_KOR)) + geom_point() + geom_text(color = 'black', size = 3, vjust = 1.3) + theme(legend.position = 'bottom', legend.title = element_blank()) 앞에서 배운 데이터 분석과 시각화를 동시에 연결해 사용할 수도 있습니다. 데이터 분석의 단계로 filter()를 통해 섹터가 NA가 아닌 종목을 선택합니다. group_by()를 통해 섹터별 그룹을 묶습니다. summarize()를 통해 ROE와 PBR의 중앙값을 계산해줍니다. 해당 과정을 거치면 다음의 결과가 계산됩니다. ## # A tibble: 10 x 3 ## SEC_NM_KOR ROE_sector PBR_sector ## <chr> <dbl> <dbl> ## 1 IT 0.0836 1.95 ## 2 건강관리 0.0806 3.75 ## 3 경기관련소비재 0.0584 1.06 ## 4 금융 0.0879 0.67 ## 5 산업재 0.059 1.09 ## 6 소재 0.0566 0.92 ## 7 에너지 0.0591 1.27 ## 8 유틸리티 0.0675 0.53 ## 9 커뮤니케이션서비스 0.0693 2.1 ## 10 필수소비재 0.0592 0.98 해당 결과를 파이프 오퍼레이터(%>%)를 이용하여 ggplot() 함수와 연결해줍니다. 4. x축과 y축을 설정한 후 색상과 라벨을 섹터로 지정해주면 각 섹터별로 색상이 다른 산점도가 그려집니다. 5. geom_text() 함수를 통해 앞에서 라벨로 지정한 섹터 정보들을 출력해줍니다. 6. theme() 함수를 통해 다양한 테마를 지정합니다. legend.position 인자로 범례를 하단에 배치했으며, legend.title 인자로 범례의 제목을 삭제했습니다. 8.3.5 geom_bar(): 막대 그래프 나타내기 data_market %>% group_by(SEC_NM_KOR) %>% summarize(n = n()) %>% ggplot(aes(x = SEC_NM_KOR, y = n)) + geom_bar(stat = 'identity') + theme_classic() geom_bar()는 막대 그래프를 그려주는 함수입니다. group_by()를 통해 섹터별 그룹을 묶어줍니다. summarize() 함수 내부에 n()을 통해 각 그룹별 데이터 개수를 구합니다. ggplot() 함수에서 x축에는 SEC_NM_KOR, y축에는 n을 지정해줍니다. geom_bar()를 통해 막대 그래프를 그려줍니다. y축에 해당하는 n 데이터를 그대로 사용하기 위해서는 stat 인자를 identity로 지정해주어야 합니다. theme_*() 함수를 통해 배경 테마를 바꿀 수도 있습니다. 한편 위 그래프는 데이터 개수에 따라 순서대로 막대가 정렬되지 않아 보기에 좋은 형태는 아닙니다. 이를 반영해 더욱 보기 좋은 그래프로 나타내보겠습니다. data_market %>% filter(!is.na(SEC_NM_KOR)) %>% group_by(SEC_NM_KOR) %>% summarize(n = n()) %>% ggplot(aes(x = reorder(SEC_NM_KOR, n), y = n, label = n)) + geom_bar(stat = 'identity') + geom_text(color = 'black', size = 4, hjust = -0.3) + xlab(NULL) + ylab(NULL) + coord_flip() + scale_y_continuous(expand = c(0, 0, 0.1, 0)) + theme_classic() filter() 함수를 통해 NA 종목은 삭제해준 후 섹터별 종목 개수를 구해줍니다. ggplot()의 x축에 reorder() 함수를 적용해 SEC_NM_KOR 변수를 n 순서대로 정렬해줍니다. geom_bar()를 통해 막대 그래프를 그려준 후 geom_text()를 통해 라벨에 해당하는 종목 개수를 출력합니다. xlab()과 ylab()에 NULL을 입력해 라벨을 삭제합니다. coord_flip() 함수를 통해 x축과 y축을 뒤집어줍니다. scale_y_continuous() 함수를 통해 그림의 간격을 약간 넓혀줍니다. theme_classic()으로 테마를 변경해줍니다. 결과를 보면 종목수가 많은 섹터부터 순서대로 정렬되어 보기도 쉬우며, 종목수도 텍스트로 표현되어 한눈에 확인할 수 있습니다. 이처럼 데이터 시각화를 통해 정보의 분포나 특성을 한눈에 확인할 수 있으며, ggplot()을 이용하면 복잡한 형태의 그림도 매우 간단하고 아름답게 표현할 수 있습니다. 8.4 주가 및 수익률 시각화 주가 혹은 수익률을 그리는 것 역시 매우 중요합니다. R의 기본 함수로도 주가나 수익률을 나타낼 수 있지만, 패키지를 사용하면 더욱 보기 좋은 그래프를 그릴 수 있습니다. 또한 최근에 나온 여러 패키지들을 이용하면 매우 손쉽게 인터랙티브 그래프를 구현할 수도 있습니다. 8.4.1 주가 그래프 나타내기 library(quantmod) getSymbols('SPY') prices = Cl(SPY) getSymbols() 함수를 이용해 미국 S&P 500 지수를 추종하는 ETF인 SPY의 데이터를 다운로드한 후 Cl() 함수를 이용해 종가에 해당하는 데이터만 추출합니다. 이제 해당 가격 및 수익률을 바탕으로 그래프를 그려보겠습니다. plot(prices, main = 'Price') getSymbols() 함수는 데이터를 xts 형식으로 다운로드합니다. R에서는 데이터가 xts 형식일 경우 기본 함수인 plot()으로 그래프를 그려도 x축에 시간을 나타내고 오른쪽 상단에 기간을 표시합니다. 그러나 완벽히 깔끔한 형태의 그래프라고 보기에 어려운 면이 있습니다. library(ggplot2) SPY %>% ggplot(aes(x = Index, y = SPY.Close)) + geom_line() ggplot()을 이용하면 기본 plot()보다 한결 깔끔해지며, 패키지 내의 다양한 함수를 이용해 그래프를 꾸밀 수도 있습니다. 8.4.2 인터랙티브 그래프 나타내기 library(dygraphs) dygraph(prices) %>% dyRangeSelector() dygraphs 패키지의 dygraph() 함수를 이용하면 사용자의 움직임에 따라 반응하는 그래프를 그릴 수 있습니다. 해당 패키지는 JavaScript를 이용해 인터랙티브한 그래프를 구현합니다. 그래프 위에 마우스 커서를 올리면 날짜 및 가격이 표시되기도 하며, 하단의 셀렉터를 이용해 원하는 기간의 수익률을 선택할 수도 있습니다. library(highcharter) highchart(type = 'stock') %>% hc_add_series(prices) %>% hc_scrollbar(enabled = FALSE) highcharter 패키지의 highchart() 함수 역시 이와 비슷하게 인터랙티브 그래프를 생성해줍니다. 왼쪽 상단의 기간을 클릭하면 해당 기간의 수익률만 확인할 수 있으며, 오른쪽 상단에 기간을 직접 입력할 수도 있습니다. library(plotly) p = SPY %>% ggplot(aes(x = Index, y = SPY.Close)) + geom_line() ggplotly(p) plotly 패키지는 R뿐만 아니라 Python, MATLAB, Julia 등 여러 프로그래밍 언어에 사용될 수 있는 그래픽 패키지로서 최근에 많은 사랑을 받고 있습니다. R에서는 단순히 ggplot()을 이용해 나타낸 그림에 ggplotly() 함수를 추가하는 것만으로 인터랙티브한 그래프를 만들어줍니다. 또한 해당 패키지는 최근 샤이니에서도 많이 사용되고 있습니다. 따라서 샤이니를 이용한 웹페이지 제작을 생각하고 있는 분이라면, 원래의 함수 실행 방법도 알아두는 것이 좋습니다. prices %>% fortify.zoo %>% plot_ly(x= ~Index, y = ~SPY.Close ) %>% add_lines() plot_ly() 함수 내부에 x축과 y축을 설정해주며, 변수명 앞에 물결표(~)를 붙여줍니다. 그 후 add_lines() 함수를 추가하면 선 그래프를 표시해줍니다. ggplot() 함수는 플러스 기호(+)를 통해 각 레이어를 연결해주었지만, plot_ly() 함수는 파이프 오퍼레이터(%>%)를 통해 연결할 수 있다는 장점이 있습니다. 8.4.3 연도별 수익률 나타내기 주가 그래프 외에 연도별 수익률을 그리는 것도 중요합니다. ggplot()을 통해 연도별 수익률을 막대 그래프로 나타내는 방법을 살펴보겠습니다. library(PerformanceAnalytics) ret_yearly = prices %>% Return.calculate() %>% apply.yearly(., Return.cumulative) %>% round(4) %>% fortify.zoo() %>% mutate(Index = as.numeric(substring(Index, 1, 4))) ggplot(ret_yearly, aes(x = Index, y = SPY.Close)) + geom_bar(stat = 'identity') + scale_x_continuous(breaks = ret_yearly$Index, expand = c(0.01, 0.01)) + geom_text(aes(label = paste(round(SPY.Close * 100, 2), "%"), vjust = ifelse(SPY.Close >= 0, -0.5, 1.5)), position = position_dodge(width = 1), size = 3) + xlab(NULL) + ylab(NULL) apply.yearly() 함수를 이용해 연도별 수익률을 계산한 뒤 반올림합니다. fortify.zoo() 함수를 통해 인덱스에 있는 시간 데이터를 Index 열로 이동합니다. mutate() 함수 내에 substring() 함수를 통해 Index의 1번째부터 4번째 글자, 즉 연도에 해당하는 부분을 뽑아낸 후 숫자 형태로 저장합니다. ggplot() 함수를 이용해 x축에는 연도가 저장된 Index, y축에는 수익률이 저장된 SPY.Close를 입력합니다. geom_bar() 함수를 통해 막대 그래프를 그려줍니다. scale_x_continuous() 함수를 통해 x축에 모든 연도가 출력되도록 합니다. geom_text()를 통해 막대 그래프에 연도별 수익률이 표시되도록 합니다. vjust() 내에 ifelse() 함수를 사용해 수익률이 0보다 크면 위쪽에 표시하고, 0보다 작으면 아래쪽에 표시되도록 합니다. 해당 과정을 거치면 막대 그래프와 텍스트를 통해 연도별 수익률을 한눈에 확인할 수 있게 됩니다. References "], -["퀀트-전략을-이용한-종목선정-기본.html", "Chapter 9 퀀트 전략을 이용한 종목선정 (기본) 9.1 베타 이해하기 9.2 저변동성 전략 9.3 모멘텀 전략 9.4 밸류 전략 9.5 퀄리티 전략", " Chapter 9 퀀트 전략을 이용한 종목선정 (기본) 투자에 필요한 주가, 재무제표, 가치지표 데이터가 준비되었다면 퀀트 전략을 활용해 투자하고자 하는 종목을 선정해야 합니다. 퀀트 투자는 크게 포트폴리오 운용 전략과 트레이딩 전략으로 나눌 수 있습니다. 포트폴리오 운용 전략은 과거 주식 시장을 분석해 좋은 주식의 기준을 찾아낸 후 해당 기준에 만족하는 종목을 매수하거나, 이와 반대에 있는 나쁜 주식을 공매도하기도 합니다. 투자의 속도가 느리며, 다수의 종목을 하나의 포트폴리오로 구성해 운용하는 특징이 있습니다. 반면 트레이딩 전략은 단기간에 발생되는 주식의 움직임을 연구한 후 예측해 매수 혹은 매도하는 전략입니다. 투자의 속도가 빠르며 소수의 종목을 대상으로 합니다. 표 9.1: 퀀트 투자 종류의 비교 기준 포트폴리오 운용 전략 트레이딩 전략 투자철학 규칙에 기반한 투자 규칙에 기반한 투자 투자목적 좋은 주식을 매수 좋은 시점을 매수 학문적 기반 경제학, 통계학 등 통계학, 공학, 정보처리 등 투자의 속도 느림 빠름 이 중 이 책에서는 포트폴리오에 기반한 운용 전략에 대해 다룹니다. 주식의 수익률에 영향을 미치는 요소를 팩터(Factor)라고 합니다. 즉 팩터의 강도가 양인 종목들로 구성한 포트폴리오는 향후 수익률이 높을 것으로 예상되며, 팩터의 강도가 음인 종목들로 구성한 포트폴리오는 반대로 향후 수익률이 낮을 것으로 예상됩니다. 팩터에 대한 연구는 학자들에 의해 오랫동안 진행되어 왔지만, 일반 투자자들이 이러한 논문을 모두 찾아보고 연구하기는 사실상 불가능에 가깝습니다. 그러나 최근에는 스마트 베타라는 이름으로 팩터 투자가 대중화되고 있습니다. 최근 유행하고 있는 스마트 베타 ETF는 팩터를 기준으로 포트폴리오를 구성한 상품으로서, 학계나 실무에서 검증된 팩터 전략을 기반으로 합니다. 해당 상품들의 웹사이트나 투자설명서에는 종목 선정 기준에 대해 자세히 나와 있으므로 스마트 베타 ETF에 나와 있는 투자 전략을 자세히 분석하는 것만으로도 훌륭한 퀀트 투자 전략을 만들 수 있습니다. 그림 2.1: 스마트베타 ETF 전략 예시 이 CHAPTER에서는 투자에 많이 활용되는 기본적인 팩터에 대해 알아보고, 우리가 구한 데이터를 바탕으로 각 팩터별 투자 종목을 선택하는 방법을 알아보겠습니다. 9.1 베타 이해하기 투자자들이라면 누구나 한 번은 베타(Beta)라는 용어를 들어봤을 것입니다. 기본적으로 주식시장의 움직임은 개별 주식의 수익률에 가장 크게 영향을 주는 요소일 수밖에 없습니다. 아무리 좋은 주식도 주식시장이 폭락한다면 같이 떨어지며, 아무리 나쁜 주식도 주식시장이 상승한다면 대부분 같이 오르기 마련입니다. 개별 주식이 전체 주식시장의 변동에 반응하는 정도를 나타낸 값이 베타입니다. 베타가 1이라는 뜻은 주식시장과 움직임이 정확히 같다는 뜻으로서 시장 그 자체를 나타냅니다. 베타가 1.5라는 뜻은 주식시장이 수익률이 +1%일 때 개별 주식의 수익률은 +1.5% 움직이며, 반대로 주식시장의 수익률이 -1%일 때 개별 주식의 수익률은 -1.5% 움직인다는 뜻입니다. 반면 베타가 0.5라면 주식시장 수익률의 절반 정도만 움직이게 됩니다. 표 9.2: 베타에 따른 개별 주식의 수익률 움직임 베타 주식시장이 +1% 일 경우 주식시장이 -1% 일 경우 0.5 +0.5% -0.5% 1.0 +1.0% -1.0% 1.5 +1.5% -1.5% 이처럼 베타가 큰 주식은 주식시장보다 수익률의 움직임이 크며, 반대로 베타가 낮은 주식은 주식시장보다 수익률의 움직임이 작습니다. 따라서 일반적으로 상승장이 기대될 때는 베타가 큰 주식에, 하락장이 기대될 때는 베타가 낮은 주식에 투자하는 것이 좋습니다. 주식시장에서 베타는 통계학의 회귀분석모형에서 기울기를 나타내는 베타와 정확히 의미가 같습니다. 회귀분석모형은 \\(y = a + bx\\) 형태로 나타나며, 회귀계수인 \\(b\\)는 \\(x\\)의 변화에 따른 \\(y\\)의 변화의 기울기입니다. 이를 주식에 적용한 모형이 자산가격결정모형(CAPM: Capital Asset Pricing Model)(Sharpe 1964)이며, 그 식은 다음과 같습니다. \\[회귀분석모형: y = a + bx\\] \\[자산가격결정모형: R_i = R_f + \\beta_i\\times[R_m - R_f]\\] 먼저 회귀분석모형의 상수항인 \\(a\\)에 해당하는 부분은 무위험 수익률을 나타내는 \\(R_f\\)입니다. 독립변수인 \\(x\\)에 해당하는 부분은 무위험 수익률 대비 주식 시장의 초과 수익률을 나타내는 시장위험 프리미엄인 \\(R_m - R_f\\)입니다. 종속변수인 \\(y\\)에 해당하는 부분은 개별주식의 수익률을 나타내는 \\(R_i\\)이며, 최종적으로 회귀계수인 \\(b\\)에 해당하는 부분은 개별 주식의 베타입니다. 표 2.2: 회귀분석모형과 자산가격결정모형의 비교 구분 회귀분석모형 자산가격결정모형 상수항 a \\(R_f\\) (무위험 수익률) 독립변수 x \\(R_m - R_f\\) (시장위험 프리미엄) 종속변수 y \\(R_i\\) (개별주식의 수익률) 회귀계수 b \\(\\beta_i\\) (개별주식의 베타) 통계학에서 회귀계수는 \\(\\beta = \\frac{cov(x,y)}{\\sigma_x^2}\\) 형태로 구할 수 있으며, \\(x\\)와 \\(y\\)에 각각 시장수익률과 개별주식의 수익률을 대입할 경우 개별주식의 베타는 \\(\\beta_i= \\rho(i,m) \\times\\frac{\\sigma_i}{\\sigma_m}\\) 형태로 구할 수 있습니다. 그러나 이러한 수식을 모르더라도 R에서는 간단히 베타를 구할 수 있습니다. 9.1.1 베타 계산하기 베타를 구하는 방법을 알아보기 위해 주식시장에 대한 대용치로 KOSPI 200 ETF, 개별주식으로는 전통적 고베타주인 증권주를 이용하겠습니다. library(quantmod) library(PerformanceAnalytics) library(magrittr) symbols = c('102110.KS', '039490.KS') getSymbols(symbols) ## [1] "102110.KS" "039490.KS" prices = do.call(cbind, lapply(symbols, function(x)Cl(get(x)))) ret = Return.calculate(prices) ret = ret['2016-01::2018-12'] KOSPI 200 ETF인 TIGER 200(102110.KS), 증권주인 키움증권(039490.KS)의 티커를 입력합니다. getSymbols() 함수를 이용하여 해당 티커들의 데이터를 다운로드 받습니다. lapply() 함수 내에 Cl()과 get()함수를 사용하여 종가에 해당하는 데이터만 추출하며, 리스트 형태의 데이터를 열의 형태로 묶어주기 위해 do.call() 함수와 cbind() 함수를 사용해 줍니다. Return.calculate() 함수를 통해 수익률을 계산해 줍니다. xts 형식의 데이터는 대괄호 속에 [‘시작일자::종료일자’]와 같은 형태로, 원하는 날짜를 편리하게 선택할 수 있으며, 위에서는 2016년 1월부터 2018년 12월 까지 데이터를 선택합니다. rm = ret[, 1] ri = ret[, 2] reg = lm(ri ~ rm) summary(reg) ## ## Call: ## lm(formula = ri ~ rm) ## ## Residuals: ## Min 1Q Median 3Q Max ## -0.06890 -0.01296 -0.00171 0.01082 0.09541 ## ## Coefficients: ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 0.000400 0.000728 0.55 0.58 ## rm 1.764722 0.091131 19.36 <2e-16 *** ## --- ## Signif. codes: ## 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 0.0196 on 721 degrees of freedom ## (8 observations deleted due to missingness) ## Multiple R-squared: 0.342, Adjusted R-squared: 0.341 ## F-statistic: 375 on 1 and 721 DF, p-value: <2e-16 증권주를 대상으로 베타를 구하기 위한 회귀분석을 실시합니다. 자산가격결정모형의 수식인 \\(R_i = R_f + \\beta_i \\times [R_m - R_f]\\) 에서 편의를 위해 무위험 수익률인 \\(R_f\\)를 0으로 가정하면, \\(R_i = \\beta_i \\times R_m\\)의 형태로 나타낼 수 있습니다. 이 중 \\(R_m\\)는 독립변수인 주식시장의 수익률을 의미하고, \\(R_i\\)는 종속변수인 개별 주식의 수익률을 의미합니다. 독립변수는 첫 번째 열인 KOSPI 200 ETF의 수익률을 선택하며, 종속변수는 두번째 열인 증권주의 수익률을 선택합니다. lm() 함수를 통해 손쉽게 선형회귀분석을 실시할 수 있으며, 회귀분석의 결과를 reg 변수에 저장합니다. summary() 함수는 데이터의 요약 정보를 나타내며, 해당 예시에서는 회귀분석 결과에 대한 정보를 보여줍니다. 회귀분석의 결과 중 가장 중요한 부분은 계수를 나타내는 Coefficients입니다. Intercept는 회귀분석의 상수항에 해당하는 부분으로서, 값이 거의 0에 가깝고 t값 또한 매우 작아 유의하지 않음이 보입니다. 우리가 원하는 베타에 해당하는 부분 은 \\(x\\)의 Estimate로서, 베타값이 1.76으로 증권주의 특성인 고베타주임이 확인되며, t값 또한 19.36로 매우 유의한 결과입니다. 조정된 결정계수(Adjusted R-square)는 0.34를 보입니다. 9.1.2 베타 시각화 이제 구해진 베타를 그림으로 표현해보겠습니다. plot(as.numeric(rm), as.numeric(ri), pch = 4, cex = 0.3, xlab = "KOSPI 200", ylab = "Individual Stock", xlim = c(-0.02, 0.02), ylim = c(-0.02, 0.02)) abline(a = 0, b = 1, lty = 2) abline(reg, col = 'red') plot() 함수를 통해 그림을 그려주며, x축과 y축에 주식시장 수익률과 개별 주식 수익률을 입력합니다. pch는 점들의 모양을, cex는 점들의 크기를 나타내며, xlab과 ylab은 각각 x축과 y축에 들어갈 문구를 나타냅니다. xlim과 ylim은 x 축과 y축의 최소 및 최대 범위를 지정해줍니다. 첫번째 abline()에서 a는 상수, b는 직선의 기울기, lty는 선의 유형을 나타냅니다. 이를 통해 기울기, 즉 베타가 1일 경우의 선을 점선으로 표현합니다. 두번째 abline()에 회귀분석 결과를 입력해주면 자동적으로 회귀식을 그려줍니다. 검은색의 점선이 기울기가 1인 경우이며, 주황색의 직선이 증권주의 회귀분석결과를 나타냅니다. 기울기가 1보다 훨씬 가파름이 확인되며, 즉 베타가 1보다 크다는 사실을 알 수 있습니다. 9.2 저변동성 전략 금융 시장에서 변동성은 수익률이 움직이는 정도로서, 일반적으로 표준편차가 사용됩니다. 표준편차는 자료가 평균을 중심으로 얼마나 퍼져 있는지를 나타내는 수치로서, 수식은 다음과 같습니다. \\[\\sigma = \\sqrt{\\frac{\\sum_{i=1}^{n}{(x_i - \\bar{x})^2}}{n-1}}\\] 관측값의 개수가 적을 경우에는 수식에 대입해 계산하는 것이 가능하지만, 관측값이 수백 혹은 수천 개로 늘어날 경우 컴퓨터를 이용하지 않고 계산하기는 사실상 불가능합니다. R에서는 복잡한 계산 과정 없이 sd() 함수를 이용해 간단하게 표준편차를 계산할 수 있습니다. example = c(85, 76, 73, 80, 72) sd(example) ## [1] 5.357 개별 주식의 표준편차를 측정할 때는 주식의 가격이 아닌 수익률로 계산해야 합니다. 수익률의 표준편차가 크면 수익률이 위아래로 많이 움직여 위험한 종목으로 여겨집니다. 반면 표준편차가 작으면 수익률의 움직임이 적어 상대적으로 안전한 종목으로 여겨집니다. 전통적 금융 이론에서는 수익률의 변동성이 클수록 위험이 크고, 이런 위험에 대한 보상으로 기대수익률이 높아야 한다고 보았습니다. 따라서 고변동성 종목의 기대수익률이 크고, 저변동성 종목의 기대수익률이 낮은 고위험 고수익이 당연한 믿음이었습니다. 그러나 현실에서는 오히려 변동성이 낮은 종목들의 수익률이 변동성이 높은 종목들의 수익률보다 높은, 저변동성 효과가 발견되고 있습니다. 이러한 저변동성 효과가 발생하는 원인으로는 여러 가설이 있습니다. 투자자들은 대체로 자신의 능력을 과신하는 경향이 있으며, 복권과 같이 큰 수익을 가져다 주는 고변동성 주식을 선호하는 경향이 있습니다. 이러한 결과로 고변동성 주식은 과대 평가되어 수익률이 낮은 반면, 과소 평가된 저변동성 주식들은 높은 수익률을 보이게 됩니다. (Brunnermeier and Parker 2005) 대부분 기관투자가들이 레버리지 투자가 되지 않는 상황에서, 벤치마크 대비 높은 성과를 얻기 위해 고변동성 주식에 투자하는 경향이 있으며, 이 또한 고변동성 주식이 과대 평가되는 결과로 이어집니다. (Baker, Bradley, and Wurgler 2011) 시장의 상승과 하락이 반복됨에 따라 고변동성 주식이 변동성 손실(Volatility Drag)로 인해 수익률이 하락하게 되는 이유도 있습니다. (Sefton et al. 2011) 주식의 위험은 변동성뿐만 아니라 베타 등 여러 지표로도 측정할 수 있습니다. 저변동성 효과와 비슷하게 고유변동성이 낮은 주식의 수익률이 높은 저고유변동성 효과(Ang et al. 2009), 베타가 낮은 주식의 수익률이 오히려 높은 저베타 효과(Baker, Bradley, and Taliaferro 2014)도 발견되고 있으며, 이러한 효과들을 합쳐 저위험 효과라고 부르기도 합니다. 9.2.1 저변동성 포트폴리오 구하기: 일간 기준 먼저 최근 1년 일간 수익률 기준 변동성이 낮은 30종목을 선택하겠습니다. library(stringr) library(xts) library(PerformanceAnalytics) library(magrittr) library(ggplot2) library(dplyr) KOR_price = read.csv('data/KOR_price.csv', row.names = 1, stringsAsFactors = FALSE) %>% as.xts() KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, 'left', 0) ret = Return.calculate(KOR_price) std_12m_daily = xts::last(ret, 252) %>% apply(., 2, sd) %>% multiply_by(sqrt(252)) 저장해둔 가격 정보와 티커 정보를 불러옵니다. 가격 정보는 as.xts() 함수를 통해 xts 형태로 변경합니다. Return.calculate() 함수를 통해 수익률을 구합니다. last() 함수는 마지막 n개 데이터를 선택해주는 함수이며, 1년 영업일 기준인 252개 데이터를 선택합니다. dplyr 패키지의 last() 함수와 이름이 같으므로, xts::last() 형식을 통해 xts 패키지의 함수임을 정의해줍니다. apply() 함수를 통해 sd 즉 변동성을 계산해주며, 연율화를 해주기 위해 multiply_by() 함수를 통해 \\(\\sqrt{252}\\)를 곱해줍니다. std_12m_daily %>% data.frame() %>% ggplot(aes(x = (`.`))) + geom_histogram(binwidth = 0.01) + annotate("rect", xmin = -0.02, xmax = 0.02, ymin = 0, ymax = sum(std_12m_daily == 0, na.rm = TRUE) * 1.1, alpha=0.3, fill="red") + xlab(NULL) std_12m_daily[std_12m_daily == 0] = NA 변동성을 히스토그램으로 나타내보면, 0에 위치하는 종목들이 다수 있습니다. 해당 종목들은 최근 1년간 거래정지로 인해 가격이 변하지 않았고, 이로 인해 변동성이 없는 종목들입니다. 해당 종목들은 NA로 처리해줍니다. std_12m_daily[rank(std_12m_daily) <= 30] ## X268280 X001720 X072710 X016800 X000480 X015360 X004890 ## 0.1960 0.2435 0.2348 0.1514 0.2099 0.2515 0.2178 ## X100250 X117580 X134380 X171120 X024090 X003460 X007330 ## 0.2159 0.2033 0.1820 0.2565 0.2383 0.2345 0.2053 ## X003650 X034590 X040420 X273060 X007590 X005190 X109860 ## 0.2567 0.1846 0.2383 0.1916 0.2417 0.2543 0.2315 ## X084670 X339950 X037440 X221980 X168330 X043100 X033790 ## 0.2523 0.1776 0.2421 0.2448 0.1563 0.1462 0.1878 ## X028040 X069330 ## 0.2143 0.2196 std_12m_daily[rank(std_12m_daily) <= 30] %>% data.frame() %>% ggplot(aes(x = rep(1:30), y = `.`)) + geom_col() + xlab(NULL) rank() 함수를 통해 순위를 구할 수 있으며, R은 기본적으로 오름차순 즉 가장 낮은값의 순위가 1이 됩니다. 따라서 변동성이 낮을수록 높은 순위가 되며, 30위 이하의 순위를 선택하면 변동성이 낮은 30종목이 선택됩니다. 또한 ggplot() 함수를 이용해 해당 종목들의 변동성을 확인해볼 수도 있습니다. 이번에는 해당 종목들의 티커 및 종목명을 확인하겠습니다. invest_lowvol = rank(std_12m_daily) <= 30 KOR_ticker[invest_lowvol, ] %>% select(`종목코드`, `종목명`) %>% mutate(`변동성` = round(std_12m_daily[invest_lowvol], 4)) ## 종목코드 종목명 변동성 ## 1 268280 미원에스씨 0.1960 ## 2 001720 신영증권 0.2435 ## 3 072710 농심홀딩스 0.2348 ## 4 016800 퍼시스 0.1514 ## 5 000480 조선내화 0.2099 ## 6 015360 예스코홀딩스 0.2515 ## 7 004890 동일산업 0.2178 ## 8 100250 진양홀딩스 0.2159 ## 9 117580 대성에너지 0.2033 ## 10 134380 미원화학 0.1820 ## 11 171120 라이온켐텍 0.2565 ## 12 024090 디씨엠 0.2383 ## 13 003460 유화증권 0.2345 ## 14 007330 푸른저축은행 0.2053 ## 15 003650 미창석유 0.2567 ## 16 034590 인천도시가스 0.1846 ## 17 040420 정상제이엘에스 0.2383 ## 18 273060 와이즈버즈 0.1916 ## 19 007590 동방아그로 0.2417 ## 20 005190 동성화학 0.2543 ## 21 109860 동일금속 0.2315 ## 22 084670 동양고속 0.2523 ## 23 339950 아이비김영 0.1776 ## 24 037440 희림 0.2421 ## 25 221980 케이디켐 0.2448 ## 26 168330 내츄럴엔도텍 0.1563 ## 27 043100 솔고바이오 0.1462 ## 28 033790 스카이문스테크놀로지 0.1878 ## 29 028040 미래SCI 0.2143 ## 30 069330 유아이디 0.2196 티커와 종목명, 연율화 변동성을 확인할 수 있습니다. 9.2.2 저변동성 포트폴리오 구하기: 주간 기준 이번에는 일간 변동성이 아닌 주간 변동성을 기준으로 저변동성 종목을 선택하겠습니다. std_12m_weekly = xts::last(ret, 252) %>% apply.weekly(Return.cumulative) %>% apply(., 2, sd) %>% multiply_by(sqrt(52)) std_12m_weekly[std_12m_weekly == 0] = NA 먼저 최근 252일 수익률울 선택한 후, apply.weekly() 함수 내 Return.cumulative를 입력해 주간 수익률을 계산하며, 연율화를 위해 연간 주수에 해당하는 \\(\\sqrt{52}\\)를 곱해줍니다. 이 외에도 apply.monthly(), apply.yearly() 함수 등으로 일간 수익률을 월간, 연간 수익률 등으로 변환할 수 있습니다. 그 후 과정은 위와 동일합니다. std_12m_weekly[rank(std_12m_weekly) <= 30] ## X268280 X347860 X339770 X072710 X016800 X000480 X199820 ## 0.18197 0.21093 0.19537 0.23357 0.11837 0.21710 0.23833 ## X129890 X356860 X337930 X053080 X100250 X117580 X134380 ## 0.16439 0.22045 0.23000 0.18735 0.23116 0.20788 0.22721 ## X003460 X244920 X007330 X034590 X318410 X040420 X273060 ## 0.17398 0.11117 0.20719 0.16121 0.16810 0.18686 0.15794 ## X005190 X004450 X109860 X339950 X115310 X037440 X168330 ## 0.23732 0.23913 0.16608 0.22903 0.18706 0.21701 0.18646 ## X043100 X347140 ## 0.15252 0.03673 invest_lowvol_weekly = rank(std_12m_weekly) <= 30 KOR_ticker[invest_lowvol_weekly, ] %>% select(`종목코드`, `종목명`) %>% mutate(`변동성` = round(std_12m_weekly[invest_lowvol_weekly], 4)) ## 종목코드 종목명 변동성 ## 1 268280 미원에스씨 0.1820 ## 2 347860 알체라 0.2109 ## 3 339770 교촌에프앤비 0.1954 ## 4 072710 농심홀딩스 0.2336 ## 5 016800 퍼시스 0.1184 ## 6 000480 조선내화 0.2171 ## 7 199820 제일전기공업 0.2383 ## 8 129890 앱코 0.1644 ## 9 356860 티엘비 0.2205 ## 10 337930 브랜드엑스코퍼레이션 0.2300 ## 11 053080 원방테크 0.1873 ## 12 100250 진양홀딩스 0.2312 ## 13 117580 대성에너지 0.2079 ## 14 134380 미원화학 0.2272 ## 15 003460 유화증권 0.1740 ## 16 244920 에이플러스에셋 0.1112 ## 17 007330 푸른저축은행 0.2072 ## 18 034590 인천도시가스 0.1612 ## 19 318410 비비씨 0.1681 ## 20 040420 정상제이엘에스 0.1869 ## 21 273060 와이즈버즈 0.1579 ## 22 005190 동성화학 0.2373 ## 23 004450 삼화왕관 0.2391 ## 24 109860 동일금속 0.1661 ## 25 339950 아이비김영 0.2290 ## 26 115310 인포바인 0.1871 ## 27 037440 희림 0.2170 ## 28 168330 내츄럴엔도텍 0.1865 ## 29 043100 솔고바이오 0.1525 ## 30 347140 케이프이에스제4호 0.0367 주간 수익률의 변동성이 낮은 30종목을 선택해 종목코드, 종목명, 연율화 변동성을 확인합니다. intersect(KOR_ticker[invest_lowvol, '종목명'], KOR_ticker[invest_lowvol_weekly, '종목명']) ## [1] "미원에스씨" "농심홀딩스" "퍼시스" ## [4] "조선내화" "진양홀딩스" "대성에너지" ## [7] "미원화학" "유화증권" "푸른저축은행" ## [10] "인천도시가스" "정상제이엘에스" "와이즈버즈" ## [13] "동성화학" "동일금속" "아이비김영" ## [16] "희림" "내츄럴엔도텍" "솔고바이오" intersect() 함수를 통해 일간 변동성 기준과 주간 변동성 기준 모두에 포함되는 종목을 찾을 수 있습니다. 9.3 모멘텀 전략 투자에서 모멘텀이란 주가 혹은 이익의 추세로서, 상승 추세의 주식은 지속적으로 상승하며 하락 추세의 주식은 지속적으로 하락하는 현상을 말합니다. 모멘텀 현상이 발생하는 가장 큰 원인은 투자자들의 스스로에 대한 과잉 신뢰 때문입니다. 사람들은 자신의 판단을 지지하는 정보에 대해서는 과잉 반응하고, 자신의 판단을 부정하는 정보에 대해서는 과소 반응하는 경향이 있습니다. 이러한 투자자들의 비합리성으로 인해모멘텀 현상이 생겨나게 됩니다. 모멘텀의 종류는 크게 기업의 이익에 대한 추세를 나타내는 이익 모멘텀(Rendleman Jr, Jones, and Latane 1982)과, 주가의 모멘텀에 대한 가격 모멘텀이 있습니다. 또한 가격 모멘텀도 1주일(Lehmann 1990) 혹은 1개월 이하(Jegadeesh 1990)를 의미하는 단기 모멘텀, 3개월에서 12개월을 의미하는 중기 모멘텀(Jegadeesh and Titman 1993), 3년에서 5년을 의미하는 장기 모멘텀(De Bondt and Thaler 1985)이 있으며, 이 중에서도 3개월에서 12개월 가격 모멘텀을 흔히 모멘텀이라고 합니다. 9.3.1 모멘텀 포트폴리오 구하기: 12개월 모멘텀 먼저 최근 1년 동안의 수익률이 높은 30종목을 선택하겠습니다. library(stringr) library(xts) library(PerformanceAnalytics) library(magrittr) library(dplyr) KOR_price = read.csv('data/KOR_price.csv', row.names = 1, stringsAsFactors = FALSE) %>% as.xts() KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, 'left', 0) ret = Return.calculate(KOR_price) %>% xts::last(252) ret_12m = ret %>% sapply(., function(x) { prod(1+x) - 1 }) 가격 정보와 티커 정보를 불러온 후 Return.calculate() 함수를 통해 수익률을 계산합니다. 그 후 최근 252일 수익률을 선택합니다. sapply() 함수 내부에 prod() 함수를 이용해 각 종목의 누적수익률을 계산해줍니다. ret_12m[rank(-ret_12m) <= 30] ## X068760 X019170 X096530 X196170 X285130 X336260 X003090 ## 3.983 15.835 4.502 3.701 4.369 5.697 4.000 ## X235980 X011000 X100090 X222080 X156100 X018000 X001470 ## 4.144 7.598 3.673 4.528 7.964 3.608 4.481 ## X101360 X109820 X025950 X241840 X083420 X256940 X033310 ## 5.593 7.341 6.727 3.618 3.876 3.877 3.563 ## X217330 X050960 X205470 X058110 X080580 X023960 X045340 ## 4.535 5.594 4.294 7.849 11.487 4.694 6.663 ## X051980 X013720 ## 3.864 4.032 rank() 함수를 통해 순위를 구합니다. 모멘텀의 경우 높을수록 좋은 내림차순으로 순위를 계산해야 하므로 수익률 앞에 마이너스(-)를 붙여줍니다. 12개월 누적수익률이 높은 종목들이 선택됨이 확인됩니다. invest_mom = rank(-ret_12m) <= 30 KOR_ticker[invest_mom, ] %>% select(`종목코드`, `종목명`) %>% mutate(`수익률` = round(ret_12m[invest_mom], 4)) ## 종목코드 종목명 수익률 ## 1 068760 셀트리온제약 3.983 ## 2 019170 신풍제약 15.835 ## 3 096530 씨젠 4.502 ## 4 196170 알테오젠 3.701 ## 5 285130 SK케미칼 4.369 ## 6 336260 두산퓨얼셀 5.697 ## 7 003090 대웅 4.000 ## 8 235980 메드팩토 4.144 ## 9 011000 진원생명과학 7.598 ## 10 100090 삼강엠앤티 3.673 ## 11 222080 씨아이에스 4.528 ## 12 156100 엘앤케이바이오 7.964 ## 13 018000 유니슨 3.608 ## 14 001470 삼부토건 4.481 ## 15 101360 이엔드디 5.593 ## 16 109820 진매트릭스 7.341 ## 17 025950 동신건설 6.727 ## 18 241840 에이스토리 3.618 ## 19 083420 그린케미칼 3.876 ## 20 256940 케이피에스 3.877 ## 21 033310 엠투엔 3.563 ## 22 217330 싸이토젠 4.535 ## 23 050960 수산아이앤티 5.594 ## 24 205470 휴마시스 4.294 ## 25 058110 멕아이씨에스 7.849 ## 26 080580 오킨스전자 11.487 ## 27 023960 에쓰씨엔지니어링 4.694 ## 28 045340 토탈소프트 6.663 ## 29 051980 센트럴바이오 3.864 ## 30 013720 청보산업 4.032 티커와 종목명, 누적수익률을 확인할 수 있습니다. 9.3.2 모멘텀 포트폴리오 구하기: 위험조정 수익률 단순히 과거 수익률로만 모멘텀 종목을 선택하면 각종 테마나 이벤트에 따른 급등으로 인해 변동성이 지나치게 높은 종목이 있을 수도 있습니다. 누적수익률을 변동성으로 나누어 위험을 고려해줄 경우, 이러한 종목은 제외되며 상대적으로 안정적인 모멘텀 종목을 선택할 수 있습니다. ret = Return.calculate(KOR_price) %>% xts::last(252) ret_12m = ret %>% sapply(., function(x) { prod(1+x) - 1 }) std_12m = ret %>% apply(., 2, sd) %>% multiply_by(sqrt(252)) sharpe_12m = ret_12m / std_12m 최근 1년에 해당하는 수익률을 선택합니다. sapply()와 prod() 함수를 이용해 분자에 해당하는 누적수익률을 계산합니다. apply()와 multiply_by() 이용해 분모에 해당하는 연율화 변동성을 계산합니다. 수익률을 변동성으로 나누어 위험조정 수익률을 계산해줍니다. 이를 통해 수익률이 높으면서 변동성이 낮은 종목을 선정할 수 있습니다. invest_mom_sharpe = rank(-sharpe_12m) <= 30 KOR_ticker[invest_mom_sharpe, ] %>% select(`종목코드`, `종목명`) %>% mutate(`수익률` = round(ret_12m[invest_mom_sharpe], 2), `변동성` = round(std_12m[invest_mom_sharpe], 2), `위험조정 수익률` = round(sharpe_12m[invest_mom_sharpe], 2)) %>% as_tibble() %>% print(n = Inf) ## # A tibble: 30 x 5 ## 종목코드 종목명 수익률 변동성 `위험조정 수익률`… ## <chr> <chr> <dbl> <dbl> <dbl> ## 1 035720 카카오 1.67 0.42 4.02 ## 2 068760 셀트리온제약 3.98 0.88 4.55 ## 3 019170 신풍제약 15.8 1.43 11.1 ## 4 011200 HMM 2.84 0.63 4.51 ## 5 096530 씨젠 4.5 1.07 4.22 ## 6 285130 SK케미칼 4.37 0.83 5.27 ## 7 247540 에코프로비엠 2.48 0.61 4.08 ## 8 336260 두산퓨얼셀 5.7 0.99 5.73 ## 9 112610 씨에스윈드 3.44 0.7 4.94 ## 10 235980 메드팩토 4.14 0.85 4.88 ## 11 011000 진원생명과학 7.6 1.49 5.1 ## 12 100090 삼강엠앤티 3.67 0.83 4.44 ## 13 222080 씨아이에스 4.53 0.82 5.53 ## 14 156100 엘앤케이바이오… 7.96 0.91 8.72 ## 15 002840 미원상사 1.21 0.26 4.57 ## 16 001470 삼부토건 4.48 0.78 5.77 ## 17 016710 대성홀딩스 2.42 0.3 8.13 ## 18 101360 이엔드디 5.59 0.87 6.43 ## 19 109820 진매트릭스 7.34 1.33 5.51 ## 20 025950 동신건설 6.73 1.14 5.89 ## 21 241840 에이스토리 3.62 0.88 4.11 ## 22 083420 그린케미칼 3.88 0.7 5.53 ## 23 023800 인지컨트롤스 3.54 0.81 4.39 ## 24 217330 싸이토젠 4.53 0.84 5.38 ## 25 050960 수산아이앤티 5.59 0.91 6.14 ## 26 058110 멕아이씨에스 7.85 1.31 6 ## 27 080580 오킨스전자 11.5 0.86 13.4 ## 28 023960 에쓰씨엔지니어링… 4.69 0.94 5 ## 29 045340 토탈소프트 6.66 0.89 7.51 ## 30 013720 청보산업 4.03 0.68 5.94 티커와 종목명, 누적수익률, 변동성, 위험조정 수익률을 확인할 수 있습니다. intersect(KOR_ticker[invest_mom, '종목명'], KOR_ticker[invest_mom_sharpe, '종목명']) ## [1] "셀트리온제약" "신풍제약" ## [3] "씨젠" "SK케미칼" ## [5] "두산퓨얼셀" "메드팩토" ## [7] "진원생명과학" "삼강엠앤티" ## [9] "씨아이에스" "엘앤케이바이오" ## [11] "삼부토건" "이엔드디" ## [13] "진매트릭스" "동신건설" ## [15] "에이스토리" "그린케미칼" ## [17] "싸이토젠" "수산아이앤티" ## [19] "멕아이씨에스" "오킨스전자" ## [21] "에쓰씨엔지니어링" "토탈소프트" ## [23] "청보산업" intersect() 함수를 통해 단순 수익률 및 위험조정 수익률 기준 모두에 포함되는 종목을 찾을 수 있습니다. 다음은 위험조정 수익률 상위 30종목의 가격 그래프입니다. library(xts) library(tidyr) library(ggplot2) KOR_price[, invest_mom_sharpe] %>% fortify.zoo() %>% gather(ticker, price, -Index) %>% ggplot(aes(x = Index, y = price)) + geom_line() + facet_wrap(. ~ ticker, scales = 'free') + xlab(NULL) + ylab(NULL) + theme(axis.text.x=element_blank(), axis.text.y=element_blank()) 9.4 밸류 전략 가치주 효과란 내재 가치 대비 낮은 가격의 주식(저PER, 저PBR 등)이, 내재 가치 대비 비싼 주식보다 수익률이 높은 현상(Basu 1977)을 뜻합니다. 가치 효과가 발생하는 원인에 대한 이론은 다음과 같습니다. 위험한 기업은 시장에서 상대적으로 낮은 가격에 거래되며, 이러한 위험을 감당하는 대가로 수익이 발생합니다. 투자자들의 성장주에 대한 과잉 반응으로 인해 가치주는 시장에서 소외되며, 제자리를 찾아가는 과정에서 수익이 발생합니다. 기업의 가치를 나타내는 지표는 굉장히 많지만, 일반적으로 PER, PBR, PCR, PSR이 많이 사용됩니다. 9.4.1 밸류 포트폴리오 구하기: 저PBR 먼저 기업의 가치 여부를 판단할 때 가장 많이 사용되는 지표인 PBR을 이용한 포트폴리오를 구성하겠습니다. library(stringr) library(ggplot2) library(dplyr) KOR_value = read.csv('data/KOR_value.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, 'left', 0) invest_pbr = rank(KOR_value$PBR) <= 30 KOR_ticker[invest_pbr, ] %>% select(`종목코드`, `종목명`) %>% mutate(`PBR` = round(KOR_value[invest_pbr, 'PBR'], 4)) ## 종목코드 종목명 PBR ## 1 000210 대림산업 0.1902 ## 2 088350 한화생명 0.1905 ## 3 000880 한화 0.1281 ## 4 138930 BNK금융지주 0.2087 ## 5 000150 두산 0.1430 ## 6 032190 다우데이타 0.1827 ## 7 009970 영원무역홀딩스 0.2001 ## 8 001430 세아베스틸 0.2170 ## 9 003300 한일홀딩스 0.1753 ## 10 058650 세아홀딩스 0.1001 ## 11 001940 KISCO홀딩스 0.2011 ## 12 005720 넥센 0.1232 ## 13 092230 KPX홀딩스 0.2049 ## 14 036530 S&T홀딩스 0.1539 ## 15 002030 아세아 0.1383 ## 16 003030 세아제강지주 0.1409 ## 17 054800 아이디스홀딩스 0.2099 ## 18 005990 매일홀딩스 0.2044 ## 19 009200 무림페이퍼 0.1633 ## 20 040610 SG&G 0.1589 ## 21 016250 SGC이테크건설 0.1630 ## 22 050120 라이브플렉스 0.1025 ## 23 012320 경동인베스트 0.1709 ## 24 036000 예림당 0.2189 ## 25 005010 휴스틸 0.1459 ## 26 025530 SJM홀딩스 0.1887 ## 27 006200 한국전자홀딩스 0.1793 ## 28 058220 아리온 0.0184 ## 29 114570 지스마트글로벌 0.1483 ## 30 158310 스타모빌리티 0.2081 가치지표들을 저장한 데이터와 티커 데이터를 불러오며, rank()를 통해 PBR이 낮은 30종목을 선택합니다. 그 후 종목코드와 종목명, PBR을 확인합니다. 홀딩스 등 지주사가 그 특성상 저PBR 포트폴리오에 많이 구성되어 있습니다. 9.4.2 각 지표 결합하기 저PBR 하나의 지표만으로도 우수한 성과를 거둘 수 있음은 오랜 기간 증명되고 있습니다. 그러나 저평가 주식이 계속해서 저평가에 머무르는 가치 함정에 빠지지 않으려면 여러 지표를 동시에 볼 필요도 있습니다. library(corrplot) rank_value = KOR_value %>% mutate_all(list(~min_rank(.))) cor(rank_value, use = 'complete.obs') %>% round(., 2) %>% corrplot(method = 'color', type = 'upper', addCoef.col = 'black', number.cex = 1, tl.cex = 0.6, tl.srt=45, tl.col = 'black', col = colorRampPalette( c('blue', 'white', 'red'))(200), mar=c(0,0,0.5,0)) 먼저 mutate_all() 함수를 이용해 모든 열에 함수를 적용해주며, min_rank()를 통해 순위를 구합니다. 각 열에 해당하는 가치지표별 랭킹을 구한 후 상관관계를 확인하며, NA 종목은 삭제해주기 위해 use = 'complete.obs'를 입력합니다. corrplot 패키지의 corrplot() 함수를 이용해 상관관계를 그려보면, 같은 가치지표임에도 불구하고 서로 간의 상관관계가 꽤 낮은 지표도 있습니다. 따라서 지표를 통합적으로 고려하면 분산효과를 기대할 수도 있습니다. rank_sum = rank_value %>% rowSums() invest_value = rank(rank_sum) <= 30 KOR_ticker[invest_value, ] %>% select(`종목코드`, `종목명`) %>% cbind(round(KOR_value[invest_value, ], 2)) ## 종목코드 종목명 PER PBR PCR PSR ## 83 078930 GS 6.79 0.34 2.33 0.21 ## 105 001040 CJ 12.73 0.23 0.88 0.10 ## 109 000210 대림산업 1.93 0.19 1.35 0.13 ## 277 000150 두산 2.32 0.14 1.24 0.05 ## 284 003380 하림지주 14.65 0.24 1.88 0.11 ## 411 009970 영원무역홀딩스 4.26 0.20 1.82 0.19 ## 473 093050 LF 6.42 0.31 2.84 0.23 ## 517 084690 대상홀딩스 5.38 0.32 1.97 0.11 ## 606 004690 삼천리 8.89 0.24 1.06 0.10 ## 607 006840 AK홀딩스 12.31 0.24 1.27 0.09 ## 648 001390 KG케미칼 3.17 0.23 2.08 0.15 ## 657 058650 세아홀딩스 9.86 0.10 0.83 0.06 ## 731 001940 KISCO홀딩스 6.00 0.20 3.96 0.23 ## 779 005720 넥센 4.20 0.12 0.77 0.11 ## 784 092230 KPX홀딩스 7.06 0.20 2.67 0.24 ## 787 008060 대덕 3.66 0.23 1.32 0.22 ## 791 036530 S&T홀딩스 4.00 0.15 2.81 0.17 ## 905 002030 아세아 4.19 0.14 0.90 0.13 ## 996 065130 탑엔지니어링 4.78 0.46 3.57 0.13 ## 1092 003960 사조대림 1.89 0.37 2.02 0.12 ## 1206 003480 한진중공업홀딩스 4.69 0.39 2.12 0.16 ## 1207 013570 디와이 8.07 0.28 1.66 0.15 ## 1337 005710 대원산업 2.94 0.37 3.25 0.17 ## 1352 005990 매일홀딩스 5.19 0.20 1.17 0.07 ## 1365 267290 경동도시가스 4.55 0.33 3.28 0.08 ## 1447 101330 모베이스 2.15 0.29 1.20 0.15 ## 1478 002880 대유에이텍 9.68 0.41 2.38 0.08 ## 1571 016250 SGC이테크건설 4.36 0.16 0.89 0.05 ## 1643 002920 유성기업 4.55 0.26 2.56 0.30 ## 1734 012320 경동인베스트 7.78 0.17 2.19 0.26 rowSums() 함수를 이용해 종목별 랭킹들의 합을 구해줍니다. 그 후 네 개 지표 랭킹의 합 기준 랭킹이 낮은 30종목을 선택합니다. 즉 하나의 지표보다 네 개 지표가 골고루 낮은 종목을 선택합니다. 해당 종목들의 티커, 종목명과 가치지표를 확인할 수 있습니다. intersect(KOR_ticker[invest_pbr, '종목명'], KOR_ticker[invest_value, '종목명']) ## [1] "대림산업" "두산" "영원무역홀딩스" ## [4] "세아홀딩스" "KISCO홀딩스" "넥센" ## [7] "KPX홀딩스" "S&T홀딩스" "아세아" ## [10] "매일홀딩스" "SGC이테크건설" "경동인베스트" 단순 저PBR 기준 선택된 종목과 비교해봤을 때 겹치는 종목이 상당히 줄어들었습니다. 9.5 퀄리티 전략 기업의 우량성, 즉 퀄리티는 투자자들이 매우 중요하게 생각하는 요소입니다. 그러나 어떠한 지표가 기업의 퀄리티를 나타내는지 한 마디로 정의하기에는 너무나 주관적이고 광범위해 쉽지 않습니다. 학계 혹은 업계에서 사용되는 우량성 관련 지표는 다음과 같이 요약할 수 있습니다. (Hsu, Kalesnik, and Kose 2019) Profitability (수익성) Earnings stability (수익의 안정성) Capital structure (기업 구조) Growth (수익의 성장성) Accounting quality (회계적 우량성) Payout/dilution (배당) Investment (투자) 퀄리티 전략에는 재무제표 데이터가 주로 사용됩니다. 9.5.1 F-Score F-Score 지표는 조셉 피오트로스키 교수가 발표(Piotroski and others 2000)한 지표입니다. 그는 논문에서, 저PBR을 이용한 밸류 전략은 높은 성과를 기록하지만 재무 상태가 불량한 기업이 많으며, 저PBR 종목 중 재무적으로 우량한 기업을 선정해 투자한다면 성과를 훨씬 개선할 수 있다고 보았습니다. F-Score에서는 재무적 우량 정도를 수익성(Profitability), 재무 성과(Financial Performance), 운영 효율성(Operating Efficiency)으로 구분해 총 9개의 지표를 선정합니다. 표 9.3는 이를 요약한 테이블입니다. 표 9.3: F-Score 요약 지표 항목 점수 Profitability \\(ROA\\) ROA가 양수면 1점 \\(CFO\\) CFO가 양수면 1점 \\(\\Delta ROA\\) ROA가 증가했으면 1점 \\(ACCRUAL\\) CFO > ROA면 1점 Financial Performance \\(\\Delta LEVER\\) 레버리지가 감소했으면 1점 \\(\\Delta LIQUID\\) 유동성이 증가했으면 1점 \\(EQ\\_OFFER\\) 발행주식수가 감소했으면 1점 Operating Efficiency \\(\\Delta MARGIN\\) 매출총이익률이 증가했으면 1점 \\(\\Delta TURN\\) 회전율이 증가했으면 1점 각 지표가 우수할 경우 1점, 그렇지 않을 경우 0점을 매겨, 총 0점부터 9점까지의 포트폴리오를 구성합니다. library(stringr) library(ggplot2) library(dplyr) KOR_fs = readRDS('data/KOR_fs.Rds') KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, 'left', 0) 먼저 재무제표와 티커 파일을 불러옵니다. 재무제표 데이터는 Rds 형태로 저장되어 있으며, readRDS() 함수를 이용해 리스트 형태 그대로 불러올 수 있습니다. # 수익성 ROA = KOR_fs$'지배주주순이익' / KOR_fs$'자산' CFO = KOR_fs$'영업활동으로인한현금흐름' / KOR_fs$'자산' ACCURUAL = CFO - ROA # 재무성과 LEV = KOR_fs$'장기차입금' / KOR_fs$'자산' LIQ = KOR_fs$'유동자산' / KOR_fs$'유동부채' OFFER = KOR_fs$'유상증자' # 운영 효율성 MARGIN = KOR_fs$'매출총이익' / KOR_fs$'매출액' TURN = KOR_fs$'매출액' / KOR_fs$'자산' 지표에 해당하는 내용을 계산해줍니다. ROA는 지배주주순이익을 자산으로 나누어 계산합니다. CFO는 영업활동현금흐름을 자산으로 나누어 계산합니다. ACCURUAL은 CFO와 ROA의 차이를 이용해 계산합니다. LEV(Leverage)는 장기차입금을 자산으로 나누어 계산합니다. LIQ(Liquidity)는 유동자산을 유동부채로 나누어 계산합니다. 우리가 받은 데이터에서는 발행주식수 데이터를 구할 수 없으므로, OFFER에 대한 대용치로 유상증자 여부를 사용합니다. MARGIN은 매출총이익을 매출액으로 나누어 계산합니다. TURN(Turnover)은 매출액을 자산으로 나누어 계산합니다. 다음으로 각 지표들이 조건을 충족하는지 여부를 판단해, 지표별로 1점 혹은 0점을 부여합니다. if ( lubridate::month(Sys.Date()) %in% c(1,2,3,4) ) { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 2)) } else { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 1)) } F_1 = as.integer(ROA[, num_col] > 0) F_2 = as.integer(CFO[, num_col] > 0) F_3 = as.integer(ROA[, num_col] - ROA[, (num_col-1)] > 0) F_4 = as.integer(ACCURUAL[, num_col] > 0) F_5 = as.integer(LEV[, num_col] - LEV[, (num_col-1)] <= 0) F_6 = as.integer(LIQ[, num_col] - LIQ[, (num_col-1)] > 0) F_7 = as.integer(is.na(OFFER[,num_col]) | OFFER[,num_col] <= 0) F_8 = as.integer(MARGIN[, num_col] - MARGIN[, (num_col-1)] > 0) F_9 = as.integer(TURN[,num_col] - TURN[,(num_col-1)] > 0) num_col 변수에 원하는 열의 위치를 구해줍니다. 1월~4월에 데이터를 받을 경우 전년도 재무제표가 일부만 들어오는 경향이 있으므로, 전전년도 데이터를 사용해야 합니다. 따라서 Sys.Date() 함수를 통해 현재 날짜를 추출한 후, lubridate 패키지의 month() 함수를 이용해 해당 월을 계산합니다. str_which() 함수를 이용해, 만일 현재 날짜가 1~4월인 경우 열 이름이 2년전 년도를 포함하는 부분을(예: 만일 오늘이 2021년 1월 이라면 열 이름중 2019가 포함된 곳), 그렇지 않을 경우(5~12월) 열 이름이 1년전 년도를 포함하는 부분을(예: 만일 오늘이 2021년 5월 이라면 열 이름중 2020가 포함된 곳) 선택합니다. as.integer() 함수는 TRUE일 경우 1을 반환하고 FALSE일 경우 0을 반환하는 함수로서, F-Score 지표의 점수를 매기는 데 매우 유용합니다. 점수 기준은 다음과 같습니다. ROA가 양수면 1점, 그렇지 않으면 0점 영업활동현금흐름이 양수면 1점, 그렇지 않으면 0점 최근 ROA가 전년 대비 증가했으면ROA[, num_col] > 0 1점, 그렇지 않으면 0점 ACCURUAL(CFO - ROA)이 양수면 1점, 그렇지 않으면 0점 레버리지가 전년 대비 감소했으면 1점, 그렇지 않으면 0점 유동성이 전년 대비 증가했으면 1점, 그렇지 않으면 0점 유상증자 항목이 없거나 0보다 작으면 1점, 그렇지 않으면 0점 매출총이익률이 전년 대비 증가했으면 1점, 그렇지 않으면 0점 회전율이 전년 대비 증가했으면 1점, 그렇지 않으면 0점 F_Table = cbind(F_1, F_2, F_3, F_4, F_5, F_6, F_7, F_8, F_9) F_Score = F_Table %>% apply(., 1, sum, na.rm = TRUE) %>% setNames(KOR_ticker$`종목명`) cbind() 함수를 통해 열의 형태로 묶어줍니다. apply() 함수를 통해 종목별 지표의 합을 더해 F-Score를 계산해줍니다. setNanmes() 함수를 통해 종목명을 입력합니다. (F_dist = prop.table(table(F_Score)) %>% round(3)) ## F_Score ## 0 1 2 3 4 5 6 7 8 ## 0.003 0.051 0.078 0.157 0.193 0.179 0.158 0.110 0.058 ## 9 ## 0.012 F_dist %>% data.frame() %>% ggplot(aes(x = F_Score, y = Freq, label = paste0(Freq * 100, '%'))) + geom_bar(stat = 'identity') + geom_text(color = 'black', size = 3, vjust = -0.4) + scale_y_continuous(expand = c(0, 0, 0, 0.05), labels = scales::percent) + ylab(NULL) + theme_classic() table() 함수를 통해 각 스코어별 개수를 구한 후 prop.table()을 통해 비중으로 변환합니다. 이를 통해 점수별 비중을 살펴보면 3~6점에 상당히 많은 종목이 분포하고 있음이 확인됩니다. invest_F_Score = F_Score %in% c(9) KOR_ticker[invest_F_Score, ] %>% select(`종목코드`, `종목명`) %>% mutate(`F-Score` = F_Score[invest_F_Score]) ## 종목코드 종목명 F-Score ## 1 009900 명신산업 9 ## 2 034230 파라다이스 9 ## 3 007570 일양약품 9 ## 4 204270 제이앤티씨 9 ## 5 008730 율촌화학 9 ## 6 014830 유니드 9 ## 7 002990 금호산업 9 ## 8 234080 JW생명과학 9 ## 9 082920 비츠로셀 9 ## 10 047310 파워로직스 9 ## 11 025320 시노펙스 9 ## 12 067900 와이엔텍 9 ## 13 089470 HDC현대EP 9 ## 14 104460 디와이피엔에프 9 ## 15 014970 삼륭물산 9 ## 16 051360 토비스 9 ## 17 011390 부산산업 9 ## 18 123410 코리아에프티 9 ## 19 221840 하이즈항공 9 ## 20 045060 오공 9 ## 21 089850 유비벨록스 9 ## 22 053620 태양 9 ## 23 021650 한국큐빅 9 ## 24 008370 원풍 9 ## 25 054040 한국컴퓨터 9 ## 26 030720 동원수산 9 F-Score가 9점인 종목의 티커와 종목명을 확인해봅니다. 재무적으로 우량하다고 판단되는 F-Score 9점인 종목은 총 26개가 있습니다. 9.5.2 각 지표를 결합하기 이번에는 퀄리티를 측정하는 요소 중 가장 널리 사용되는 수익성 지표를 결합한 포트폴리오를 만들어보겠습니다. 여기서 사용되는 지표는 자기자본이익률(ROE), 매출총이익(Gross Profit), 영업활동현금흐름(Cash Flow From Operating)입니다. library(stringr) library(ggplot2) library(dplyr) library(tidyr) KOR_fs = readRDS('data/KOR_fs.Rds') KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, 'left', 0) if ( lubridate::month(Sys.Date()) %in% c(1,2,3,4) ) { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 2)) } else { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 1)) } quality_roe = (KOR_fs$'지배주주순이익' / KOR_fs$'자본')[num_col] quality_gpa = (KOR_fs$'매출총이익' / KOR_fs$'자산')[num_col] quality_cfo = (KOR_fs$'영업활동으로인한현금흐름' / KOR_fs$'자산')[num_col] quality_profit = cbind(quality_roe, quality_gpa, quality_cfo) %>% setNames(., c('ROE', 'GPA', 'CFO')) 먼저 재무제표와 티커 파일을 불러온 후 세 가지 지표에 해당하는 값을 구한 뒤 최근년도 데이터만 선택합니다. 그런 다음 cbind() 함수를 이용해 지표들을 하나로 묶어줍니다. 역시나 1~4월의 경우 전년도가 아닌 전전년도 회계 데이터를 사용합니다. rank_quality = quality_profit %>% mutate_all(list(~min_rank(desc(.)))) cor(rank_quality, use = 'complete.obs') %>% round(., 2) %>% corrplot(method = 'color', type = 'upper', addCoef.col = 'black', number.cex = 1, tl.cex = 0.6, tl.srt = 45, tl.col = 'black', col = colorRampPalette(c('blue', 'white', 'red'))(200), mar=c(0,0,0.5,0)) mutate_all() 함수와 min_rank() 함수를 통해 지표별 랭킹을 구하며, 퀄리티 지표는 높을수록 좋은 내림차순으로 계산해야 하므로 desc()를 추가합니다. 수익성 지표 역시 서로 간의 상관관계가 낮아, 지표를 통합적으로 고려 시 분산효과를 기대할 수 있습니다. rank_sum = rank_quality %>% rowSums() invest_quality = rank(rank_sum) <= 30 KOR_ticker[invest_quality, ] %>% select(`종목코드`, `종목명`) %>% cbind(round(quality_profit[invest_quality, ], 4)) ## 종목코드 종목명 ROE GPA CFO ## 15 051900 LG생활건강 0.1836 0.7343 0.1755 ## 50 352820 빅히트 0.4254 0.5551 0.2526 ## 53 021240 코웨이 0.3090 0.7035 0.1889 ## 89 263750 펄어비스 0.2677 0.6178 0.1941 ## 121 282330 BGF리테일 0.2432 0.4863 0.2453 ## 169 007700 F&F 0.2278 0.9015 0.2361 ## 180 030190 NICE평가정보 0.1820 1.2877 0.1930 ## 235 214150 클래시스 0.3800 0.5620 0.3430 ## 251 230360 에코마케팅 0.2888 0.7980 0.1526 ## 257 097520 엠씨넥스 0.3762 0.3863 0.2434 ## 293 067160 아프리카TV 0.2824 0.7348 0.2661 ## 370 138080 오이솔루션 0.3309 0.4740 0.2316 ## 459 339770 교촌에프앤비 0.3678 0.6078 0.2758 ## 514 119860 다나와 0.2151 1.0830 0.3061 ## 680 143240 사람인에이치알 0.2291 0.7161 0.2242 ## 726 207760 미스터블루 0.2797 1.0257 0.2777 ## 733 092130 이크레더블 0.2551 0.6534 0.2268 ## 876 337930 브랜드엑스코퍼레이션 0.5352 0.9351 0.1346 ## 1006 306040 에스제이그룹 0.1902 0.8847 0.1654 ## 1111 130580 나이스디앤비 0.1764 0.9822 0.1703 ## 1118 036120 SCI평가정보 0.2008 1.6335 0.2950 ## 1221 335890 비올 0.3763 0.6860 0.4132 ## 1251 317240 TS트릴리온 0.2459 1.3571 0.1460 ## 1275 244920 에이플러스에셋 0.1989 1.0933 0.1457 ## 1303 060850 영림원소프트랩 0.4257 0.5921 0.1908 ## 1305 058630 엠게임 0.1975 0.7753 0.1629 ## 1508 225330 씨엠에스에듀 0.1576 0.8025 0.2317 ## 1518 225190 삼양옵틱스 0.3461 0.5838 0.2384 ## 1616 049720 고려신용정보 0.2933 2.3922 0.1520 ## 1769 339950 아이비김영 0.4188 0.9356 0.3723 rowSums() 함수를 이용해 종목별 랭킹들의 합을 구합니다. 그 후 세 개 지표 랭킹의 합 기준 랭킹이 낮은 30종목을 선택합니다. 즉 세 가지 수익 지표가 골고루 높은 종목을 선택합니다. 해당 종목들의 티커, 종목명, ROE, GPA, CFO을 출력해 확인합니다. References "], -["퀀트-전략을-이용한-종목선정-심화.html", "Chapter 10 퀀트 전략을 이용한 종목선정 (심화) 10.1 섹터 중립 포트폴리오 10.2 마법공식 10.3 이상치 데이터 제거 및 팩터의 결합 10.4 멀티팩터 포트폴리오", " Chapter 10 퀀트 전략을 이용한 종목선정 (심화) 지난 CHAPTER에서는 팩터를 이용한 투자 전략의 기본이 되는 저변동성, 모멘텀, 밸류, 퀄리티 전략에 대해 알아보았습니다. 물론 이러한 단일 팩터를 이용한 투자도 장기적으로 우수한 성과를 보이지만, 여러 팩터를 결합하거나 정밀하게 전략을 만든다면 더욱 우수한 성과를 거둘 수 있습니다. 이번 CHAPTER에서는 섹터별 효과를 없앤 후 포트폴리오를 구성하는 방법, 이상치 데이터 제거 및 팩터 결합 방법, 그리고 멀티팩터 구성 방법을 알아보겠습니다. 10.1 섹터 중립 포트폴리오 팩터 전략의 단점 중 하나는 선택된 종목들이 특정 섹터로 쏠리는 경우가 있다는 점입니다. 특히 과거 수익률을 토대로 종목을 선정하는 모멘텀 전략은 특정 섹터의 호황기에 동일한 섹터의 모든 종목이 함께 움직이는 경향이 있어 이러한 쏠림이 심할 수 있습니다. 먼저 지난 CHAPTER에서 배운 12개월 모멘텀을 이용한 포트폴리오 구성 방법을 다시 살펴보겠습니다. library(stringr) library(xts) library(PerformanceAnalytics) library(dplyr) library(ggplot2) KOR_price = read.csv('data/KOR_price.csv', row.names = 1, stringsAsFactors = FALSE) %>% as.xts() KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, 'left', 0) ret = Return.calculate(KOR_price) %>% xts::last(252) ret_12m = ret %>% sapply(., function(x) { prod(1+x) - 1 }) invest_mom = rank(-ret_12m) <= 30 기존의 코드와 동일하게, 주식 가격 및 티커 데이터를 불러온 후 최근 12개월 수익률을 구해 상위 30종목을 선택합니다. KOR_sector = read.csv('data/KOR_sector.csv', row.names = 1, stringsAsFactors = FALSE) KOR_sector$'CMP_CD' = str_pad(KOR_sector$'CMP_CD', 6, 'left', 0) data_market = left_join(KOR_ticker, KOR_sector, by = c('종목코드' = 'CMP_CD', '종목명' = 'CMP_KOR')) 해당 종목들의 섹터 정보를 추가로 살펴보기 위해, 섹터 데이터를 불러온 후 left_join() 함수를 이용해 티커와 결합해 data_market에 저장합니다. data_market[invest_mom, ] %>% select(`SEC_NM_KOR`) %>% group_by(`SEC_NM_KOR`) %>% summarize(n = n()) %>% ggplot(aes(x = reorder(`SEC_NM_KOR`, `n`), y = `n`, label = n)) + geom_col() + geom_text(color = 'black', size = 4, hjust = -0.3) + xlab(NULL) + ylab(NULL) + coord_flip() + scale_y_continuous(expand = c(0, 0, 0.1, 0)) + theme_classic() group_by() 함수를 이용해 12개월 기준 모멘텀 포트폴리오 종목들의 섹터별 종목수를 계산해준 후 ggplot() 함수를 이용해 이를 그림으로 나타냅니다. 그림에서 알 수 있듯이 특정 섹터에 대부분의 종목이 몰려 있습니다. 따라서 여러 종목으로 포트폴리오를 구성했지만, 이를 분해해보면 특정 섹터에 쏠림이 심하다는 것을 알 수 있습니다. 이러한 섹터 쏠림 현상을 제거한 섹터 중립 포트폴리오를 구성해보겠습니다. sector_neutral = data_market %>% select(`종목코드`, `SEC_NM_KOR`) %>% mutate(`ret` = ret_12m) %>% group_by(`SEC_NM_KOR`) %>% mutate(scale_per_sector = scale(`ret`), scale_per_sector = ifelse(is.na(`SEC_NM_KOR`), NA, scale_per_sector)) data_market에서 종목코드와 섹터 정보를 선택합니다. mutate() 함수를 통해 미리 계산한 12개월 수익률 정보를 새로운 열에 합쳐줍니다. group_by() 함수를 통해 섹터별 그룹을 만들어줍니다 scale() 함수를 이용해 그룹별 정규화를 해줍니다. 정규화는 \\(\\frac{x- \\mu}{\\sigma}\\)로 계산됩니다. 섹터 정보가 없을 경우 NA로 변경합니다. 위의 정규화 과정을 살펴보면, 전체 종목에서 12개월 수익률을 비교하는 것이 아닌 각 섹터별로 수익률의 강도를 비교하게 됩니다. 따라서 특정 종목의 과거 수익률이 전체 종목과 비교해서 높았더라도 해당 섹터 내에서의 순위가 낮다면, 정규화된 값은 낮아집니다. 따라서 섹터별 정규화 과정을 거친 값으로 비교 분석을 한다면, 섹터 효과가 제거된 포트폴리오를 구성할 수 있습니다. invest_mom_neutral = rank(-sector_neutral$scale_per_sector) <= 30 data_market[invest_mom_neutral, ] %>% select(`SEC_NM_KOR`) %>% group_by(`SEC_NM_KOR`) %>% summarize(n = n()) %>% ggplot(aes(x = reorder(`SEC_NM_KOR`, `n`), y = `n`, label = n)) + geom_col() + geom_text(color = 'black', size = 4, hjust = -0.3) + xlab(NULL) + ylab(NULL) + coord_flip() + scale_y_continuous(expand = c(0, 0, 0.1, 0)) + theme_classic() 정규화된 값의 랭킹이 높은 상위 30종목을 선택하며, 내림차순을 위해 마이너스(-)를 붙여줍니다. 해당 포트폴리오의 섹터별 구성종목을 확인해보면, 단순하게 포트폴리오를 구성한 것에 대비하여 여러 섹터에 종목이 분산되어 있습니다. 이처럼 group_by() 함수를 통해 손쉽게 그룹별 중립화를 할 수 있으며, 글로벌 투자를 하는 경우에는 지역, 국가, 섹터별로도 중립화된 포트폴리오를 구성하기도 합니다. 10.2 마법공식 하나의 팩터만을 보고 투자하는 것보다, 둘 혹은 그 이상의 팩터를 결합해 투자해야 훨씬 좋은 포트폴리오를 구성할 수 있으며, 이러한 방법을 멀티팩터라고 합니다. 그중에서도 밸류와 퀄리티의 조합은 전통적으로 많이 사용된 방법이며, 대표적인 예가 조엘 그린블라트의 마법공식(Greenblatt 2010)입니다. 이에 앞서, 퀄리티와 밸류 간의 관계, 마법공식의 정의와 구성 방법을 알아보겠습니다. 10.2.1 퀄리티와 밸류 간의 관계 투자의 정석 중 하나는 좋은 기업을 싸게 사는 것입니다. 이를 팩터의 관점에서 이해하면 퀄리티 팩터와 밸류 팩터로 이해할 수도 있습니다. 여러 논문에 따르면 흔히 밸류 팩터와 퀄리티 팩터는 반대의 관계(Novy-Marx 2013)에 있습니다. 먼저 가치주들은 위험이 크기 때문에 시장에서 소외를 받아 저평가가 이루어지는 것이며, 이러한 위험에 대한 대가로 밸류 팩터의 수익률이 높게 됩니다. 반대로 사람들은 우량주에 기꺼이 프리미엄을 지불하려 하기 때문에 퀄리티 팩터의 수익률이 높기도 합니다. 이는 마치 동전의 양면과 같지만, 장기적으로 가치주와 우량주 모두 우수한 성과를 기록합니다. 먼저 퀄리티의 지표인 매출총이익과 밸류 지표인 PBR을 통해 둘 사이의 관계를 확인해 보겠습니다. library(stringr) library(dplyr) KOR_value = read.csv('data/KOR_value.csv', row.names = 1, stringsAsFactors = FALSE) KOR_fs = readRDS('data/KOR_fs.Rds') KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) data_pbr = KOR_value['PBR'] if ( lubridate::month(Sys.Date()) %in% c(1,2,3,4) ) { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 2)) } else { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 1)) } data_gpa = (KOR_fs$'매출총이익' / KOR_fs$'자산')[num_col] %>% setNames('GPA') cbind(data_pbr, -data_gpa) %>% cor(method = 'spearman', use = 'complete.obs') %>% round(4) ## PBR GPA ## PBR 1.0000 -0.2005 ## GPA -0.2005 1.0000 데이터를 불러온 후 PBR과 GPA(매출총이익/자산)를 구합니다. 그 후 랭킹의 상관관계인 스피어만 상관관계를 구해보면, 서로 간에 반대 관계가 있음이 확인됩니다. PBR은 오름차순, GPA는 내림차순이므로 GPA 앞에 마이너스(-)를 붙여주었습니다. cbind(data_pbr, data_gpa) %>% mutate(quantile_pbr = ntile(data_pbr, 5)) %>% filter(!is.na(quantile_pbr)) %>% group_by(quantile_pbr) %>% summarise(mean_gpa = mean(GPA, na.rm = TRUE)) %>% ggplot(aes(x = quantile_pbr, y = mean_gpa)) + geom_col() + xlab('PBR') + ylab('GPA') 이번에는 PBR의 분위수별 GPA 평균값을 구하겠습니다. ntile() 함수를 이용해 PBR을 5분위수로 나누어줍니다. PBR이 없는 종목은 제외합니다. group_by()함수를 통해 PBR의 분위수별 그룹을 묶어줍니다. 각 PBR 그룹별 GPA의 평균값을 구해줍니다. ggplot() 함수를 이용해 시각화를 해줍니다 그림에서 알 수 있듯이 PBR이 낮을수록 GPA도 낮으며, 즉 가치주일수록 우량성은 떨어집니다. 반면에 PBR이 높을수록 GPA도 높으며, 이는 주식의 가격이 비쌀수록 우량성도 높다는 뜻입니다. 이를 이용해 밸류 팩터와 퀄리티 팩터 간의 관계를 나타내면 다음과 같습니다. 그림 10.1: 밸류 팩터와 퀄리티 팩터간의 관계 주가가 쌀수록 기업의 우량성은 떨어지며(①번), 반대로 기업의 우량성이 좋으면 주식은 비싼 경향(③번)이 있습니다. 물론 우량성도 떨어지고 비싸기만한 주식(②번)을 사려는 사람들 아마 없을 겁니다. 결과적으로 우리가 원하는 최고의 주식은 우량성이 있으면서도 가격은 싼 주식(④번)입니다. 10.2.2 마법공식 이해하기 마법공식이란 고담 캐피탈의 설립자이자 전설적인 투자자 조엘 그린블라트에 의해 알려진 투자 방법입니다. 그는 본인의 책 《주식 시장을 이기는 작은 책》에서 투자를 하는데 있어 중요한 두 가지 지표가 있으며, 이를 혼합하면 뛰어난 성과를 기록할 수 있다고 했습니다. 첫 번째 지표는 이율(Earnings Yield)로서 기업의 수익을 기업의 가치로 나는 값입니다. 이는 PER의 역수와 비슷하며, 밸류 지표 중 하나입니다. 두 번째 지표는 투하자본 수익률(Return on Capital)로서 기업의 수익을 투자한 자본으로 나눈 값입니다. 이는 ROE와도 비슷하며, 퀄리티 지표 중 하나입니다. 마법공식은 이 두 가지 지표의 랭킹을 각각 구한 후 랭킹의 합 기준 상위 30개 종목을 1년간 보유한 후 매도하는 전략입니다. 해당 전략은 국내 투자자들에게도 많이 사랑받는 전략이지만 두 지표를 계산하기 위한 데이터를 수집하기 어려워 많은 투자자들이 이율 대신 PER를 사용하고, 투하자본 수익률 대신 ROE를 사용합니다. 그러나 우리가 수집한 데이터를 통해 충분히 원래의 마법공식을 구현할 수 있습니다. 표 10.1: 마법공식의 구성 요소 팩터 Value Quality 지표 이율 (Earnings Yield) 투하자본 수익률 (Return On Capital) 계산 \\(\\frac{이자\\,및\\,법인세\\,차감전이익}{기업\\,가치}\\) \\(\\frac{이자\\,및\\,법인세\\,차감전이익}{투하자본}\\) 10.2.3 마법공식 구성하기 재무제표 항목을 통해 이율과 투하자본 수익률을 계산하고, 이를 통해 마법공식 포트폴리오를 구성하겠습니다. 먼저 밸류 지표에 해당하는 이익수익률을 계산해보겠습니다. 이익수익률은 이자 및 법인세 차감전이익(EBIT)을 기업가치(시가총액 + 순차입금)로 나눈 값입니다. 이를 분해하면 다음과 같습니다. \\[\\begin{equation*} \\begin{split} 이익수익률 & = \\frac{이자\\,및\\,법인세\\,차감전이익}{기업\\,가치} \\\\ & = \\frac{이자\\,및\\,법인세\\,차감전이익}{시가총액 + 순차입금} \\\\ & = \\frac{당기순이익 + 법인세 + 이자비용}{시가총액 + 총부채 - 여유자금} \\\\ & = \\frac{당기순이익 + 법인세 + 이자비용}{시가총액 + 총부채 - (현금 - max(0, 유동부채 - 유동자산 + 현금))} \\end{split} \\end{equation*}\\] library(stringr) library(dplyr) KOR_value = read.csv('data/KOR_value.csv', row.names = 1, stringsAsFactors = FALSE) KOR_fs = readRDS('data/KOR_fs.Rds') KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, 'left', 0) if ( lubridate::month(Sys.Date()) %in% c(1,2,3,4) ) { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 2)) } else { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 1)) } # 분자 magic_ebit = (KOR_fs$'지배주주순이익' + KOR_fs$'법인세비용' + KOR_fs$'이자비용')[num_col] # 분모 magic_cap = KOR_value$PER * KOR_fs$'지배주주순이익'[num_col] magic_debt = KOR_fs$'부채'[num_col] magic_excess_cash_1 = KOR_fs$'유동부채' - KOR_fs$'유동자산' + KOR_fs$'현금및현금성자산' magic_excess_cash_1[magic_excess_cash_1 < 0] = 0 magic_excess_cash_2 = (KOR_fs$'현금및현금성자산' - magic_excess_cash_1)[num_col] magic_ev = magic_cap + magic_debt - magic_excess_cash_2 # 이익수익률 magic_ey = magic_ebit / magic_ev 가치지표, 재무제표, 티커 데이터를 불러온 후 재무제표 열 개수를 구합니다. 그후 분자와 분모 항목에 해당하는 부분을 하나씩 계산합니다. 먼저 분자 부분인 이자및 법인세 차감전이익은 지배주주 순이익에 법인세비용과 이자비용을 더해준 후 최근년도 데이터를 선택합니다. 분모 부분은 시가총액, 총 부채, 여유자금 총 세 가지로 구성되어 있습니다. 우리가 가지고 있는 밸류 데이터와 재무제표 데이터를 통해 시가총액을 역산할 수 있습니다. PER 값에 Earnings를 곱해주면 시가총액이 계산됩니다. 이를 통해 계산된 시가총액을 HTS나 금융 웹사이트의 값과 비교하면 거의 비슷함이 확인됩니다. \\[\\begin{equation*} \\begin{split} PER \\times Earnings & = \\frac{Price}{Earnings/Shares} \\times Earnings \\\\ & = \\frac{Price \\times Shares}{Earnings} \\times Earnings \\\\ & = Price \\times Shares = Market\\,Cap \\end{split} \\end{equation*}\\] 총 부채는 부채 항목을 사용합니다. 여유자금은 두 단계에 걸쳐 계산합니다. 먼저 유동부채 - 유동자산 + 현금 값을 구해준 후 0보다 작은 값은 모두 0으로 바꿔줍니다. 이 값을 현금 및 현금성자산 항목에서 차감해 최종적인 여유자금을 구합니다. 분자와 분모 부분을 나누어주면 이익수익률을 계산할 수 있습니다. 다음으로 퀄리티 지표에 해당하는 투하자본 수익률을 계산하겠습니다. 해당 값은 이자 및 법인세 차감전이익(EBIT)를 투하자본(IC)으로 나누어 계산되며, 이를 분해하면 다음과 같습니다. \\[\\begin{equation*} \\begin{split} 투하자본\\,수익률 & = \\frac{이자\\,및\\,법인세\\,차감전이익}{투하자본} \\\\ & = \\frac{당기순이익 + 법인세 + 이자비용}{(유동자산 - 유동부채) + (비유동자산 - 감가상각비)} \\end{split} \\end{equation*}\\] magic_ic = ((KOR_fs$'유동자산' - KOR_fs$'유동부채') + (KOR_fs$'비유동자산' - KOR_fs$'감가상각비'))[num_col] magic_roc = magic_ebit / magic_ic 투하자본 수익률은 비교적 쉽게 계산할 수 있습니다. 분모에 해당하는 투하자본의 경우 재무제표 항목을 그대로 사용하면 되며, 분자인 이자 및 법인세 차감전이익은 위에서 이미 구해둔 값을 사용하면 됩니다. 이제 두 지표를 활용해 마법공식 포트폴리오를 구성하겠습니다. invest_magic = rank(rank(-magic_ey) + rank(-magic_roc)) <= 30 KOR_ticker[invest_magic, ] %>% select(`종목코드`, `종목명`) %>% mutate(`이익수익률` = round(magic_ey[invest_magic, ], 4), `투하자본수익률` = round(magic_roc[invest_magic, ], 4)) ## 종목코드 종목명 이익수익률 투하자본수익률 ## 1 294870 HDC현대산업개발 0.1583 0.2167 ## 2 036830 솔브레인홀딩스 0.1702 0.2160 ## 3 097520 엠씨넥스 0.1058 0.6846 ## 4 204270 제이앤티씨 0.1230 0.2881 ## 5 091700 파트론 0.1447 0.2802 ## 6 097230 한진중공업 0.1314 0.7292 ## 7 033290 코웰패션 0.1179 0.2721 ## 8 066700 테라젠이텍스 0.1527 0.3543 ## 9 053610 프로텍 0.2033 0.2352 ## 10 031330 에스에이엠티 0.1674 0.1988 ## 11 002170 삼양통상 0.2619 0.1780 ## 12 083450 GST 0.1400 0.2246 ## 13 038460 바이오스마트 0.1653 0.2663 ## 14 007720 대명소노시즌 0.8761 0.4021 ## 15 004650 창해에탄올 0.1625 0.2673 ## 16 057540 옴니시스템 0.2036 0.2795 ## 17 056360 코위버 0.1941 0.2021 ## 18 065710 서호전기 0.1755 0.2550 ## 19 052790 액토즈소프트 0.1977 0.2136 ## 20 198080 엔피디 0.1314 0.3047 ## 21 225190 삼양옵틱스 0.1717 0.4109 ## 22 050760 에스폴리텍 0.1194 0.2589 ## 23 032940 원익 0.2939 0.3740 ## 24 005670 푸드웰 0.3436 0.4751 ## 25 032750 삼진 0.1702 0.1954 ## 26 084670 동양고속 0.3151 0.2724 ## 27 003780 진양산업 0.1342 0.2238 ## 28 014100 메디앙스 0.4599 0.3979 ## 29 010640 진양폴리 0.1808 0.2803 ## 30 083470 이엠앤아이 0.6918 9.5517 이익수익률과 투하자본 수익률의 랭킹을 각각 구해주며, 내림차순으로 값을 구하기 위해 마이너스(-)를 붙여줍니다. 그 후 두 값의 합의 랭킹 기준 상위 30종목을 선택한 후 종목코드, 종목명과 각 지표를 확인합니다. 10.3 이상치 데이터 제거 및 팩터의 결합 모든 데이터 분석에서 중요한 문제 중 하나가 이상치(극단치, Outlier) 데이터를 어떻게 처리할 것인가입니다. 과거 12개월 수익률이 10배인 주식이 과연 모멘텀 관점에서 좋기만 한 주식인지, ROE가 100% 넘는 주식이 과연 퀄리티 관점에서 좋기만 한 주식인지 고민이 되기 마련입니다. 따라서 이러한 이상치를 제외하고 분석할지, 포함해서 분석할지를 판단해야 합니다. 만일 이상치를 포함한다면 그대로 사용할 것인지, 보정해 사용할 것인지도 판단해야 합니다. 우리가 가지고 있는 데이터에서 이상치 데이터를 탐색해보겠습니다. library(magrittr) library(ggplot2) KOR_value = read.csv('data/KOR_value.csv', row.names = 1, stringsAsFactors = FALSE) max(KOR_value$PBR, na.rm = TRUE) ## [1] 508.7 KOR_value %>% ggplot(aes(x = PBR)) + geom_histogram(binwidth = 0.1) 국내 종목들의 PBR 히스토그램을 그려보면 오른쪽으로 꼬리가 매우 긴 분포를 보이고 있습니다. 이는 PBR이 무려 508.71인 이상치 데이터가 있기 때문입니다. 이처럼 모든 팩터 지표에는 극단치 데이터가 있기 마련이며, 이를 처리하는 방법을 알아보겠습니다. 10.3.1 트림(Trim): 이상치 데이터 삭제 트림은 이상치 데이터를 삭제하는 방법입니다. 위의 예제에서 이상치에 해당하는 상하위 1% 데이터를 삭제하겠습니다. library(dplyr) value_trim = KOR_value %>% select(PBR) %>% mutate(PBR = ifelse(percent_rank(PBR) > 0.99, NA, PBR), PBR = ifelse(percent_rank(PBR) < 0.01, NA, PBR)) value_trim %>% ggplot(aes(x = PBR)) + geom_histogram(binwidth = 0.1) percent_rank() 함수를 통해 백분위를 구한 후 상하위 1%에 해당하는 데이터들은 NA로 변경했습니다. 결과적으로 지나치게 PBR이 낮은 종목과 높은 종목은 제거되어 x축의 스케일이 많이 줄어든 모습입니다. 평균이나 분산같이 통곗값을 구하는 과정에서는 이상치 데이터를 제거하는 것이 바람직할 수 있습니다. 그러나 팩터를 이용해 포트폴리오를 구하는 과정에서 해당 방법은 잘 사용되지 않습니다. 데이터의 손실이 발생하게 되며, 제거된 종목 중 정말로 좋은 종목이 있을 수도 있기 때문입니다. 10.3.2 윈저라이징(Winsorizing): 이상치 데이터 대체 포트폴리오 구성에서는 일반적으로 이상치 데이터를 다른 데이터로 대체하는 윈저라이징 방법이 사용됩니다. 예를 들어 상위 99%를 초과하는 데이터는 99% 값으로 대체하며, 하위 1% 미만의 데이터는 1% 데이터로 대체합니다. 즉, 좌우로 울타리를 쳐놓고 해당 범위를 넘어가는 값을 강제로 울타리에 맞춰줍니다. value_winsor = KOR_value %>% select(PBR) %>% mutate(PBR = ifelse(percent_rank(PBR) > 0.99, quantile(., 0.99, na.rm = TRUE), PBR), PBR = ifelse(percent_rank(PBR) < 0.01, quantile(., 0.01, na.rm = TRUE), PBR)) value_winsor %>% ggplot(aes(x = PBR)) + geom_histogram(binwidth = 0.1) 역시나 percent_rank() 함수를 통해 백분위를 구한 후 해당 범위를 초과할 경우 각각 상하위 1% 데이터로 변형해줍니다. 그림을 살펴보면 x축 양 끝부분의 막대가 길어진 것을 확인할 수 있습니다. 10.3.3 팩터의 결합 방법 밸류 지표의 결합, 퀄리티 지표의 결합, 마법공식 포트폴리오를 구성할 때 단순히 랭킹을 더하는 방법을 사용했습니다. 물론 투자 종목수가 얼마 되지 않거나, 개인 투자자의 입장에서는 이러한 방법이 가장 단순하면서도 효과적일수 있습니다. 그러나 전문투자자의 입장이거나 팩터를 분석하는 업무를 할 경우 이처럼 단순히 랭킹을 더하는 방법은 여러 가지 문제를 안고 있습니다. library(tidyr) KOR_value %>% mutate_all(list(~min_rank(.))) %>% gather() %>% ggplot(aes(x = value)) + geom_histogram() + facet_wrap(. ~ key) 앞의 그림은 각 밸류 지표의 랭킹을 구한 후 히스토그램으로 나타낸 것입니다. 랭킹을 구하는 것의 가장 큰 장점은 극단치로 인한 효과가 사라진다는 점과 균등한 분포를 가진다는 점입니다. 그러나 각 지표의 x축을 보면 최댓값이 서로 다릅니다. 이는 지표별 결측치로 인해 유효 데이터의 개수가 달라 나타나는 현상이며, 서로 다른 범위의 분포를 단순히 합치는 것은 좋은 방법이 아닙니다. 예를 들어 A, B, C, D 팩터에 각각 비중을 40%, 30%, 20%, 10% 부여해 포트폴리오를 구성한다고 가정해봅시다. 각 랭킹은 분포의 범위가 다르므로, 랭킹과 비중의 가중평균을 통해 포트폴리오를 구성하면 왜곡된 결과를 발생시킵니다. 이러한 문제를 해결하는 가장 좋은 방법은 랭킹을 구한 후 이를 Z-Score로 정규화하는 것입니다. KOR_value %>% mutate_all(list(~min_rank(.))) %>% mutate_all(list(~scale(.))) %>% gather() %>% ggplot(aes(x = value)) + geom_histogram() + facet_wrap(. ~ key) min_rank() 함수를 통해 랭킹을 구한 후, scale() 함수를 통해 정규화를 해주었습니다. 기본적으로 랭킹의 분포가 가진 극단치 효과가 사라지는 점과 균등 분포의 장점을 유지하고 있으며, 분포의 범위 역시 거의 동일하게 바뀌었습니다. 이처럼 여러 팩터를 결합해 포트폴리오를 구성하고자 하는 경우, 먼저 각 팩터(지표)별 랭킹을 정규화한 뒤 더해야 왜곡 효과가 제거되어 안정적입니다. \\[\\begin{equation*} Z - Score(Rank(Factor\\,A)) + Z - Score(Rank(Factor\\,B)) +\\,\\dots\\,+ Z - Score(Rank(Factor\\,N)) \\end{equation*}\\] 10.4 멀티팩터 포트폴리오 앞에서 배웠던 팩터 이론들과 결합 방법들을 응용해 멀티팩터 포트폴리오를 구성해보겠습니다. 각 팩터에 사용되는 지표는 다음과 같습니다. 퀄리티: 자기자본이익률, 매출총이익, 영업활동현금흐름 밸류: PER, PBR, PSR, PCR 모멘텀: 3개월 수익률, 6개월 수익률, 12개월 수익률 library(xts) library(stringr) KOR_fs = readRDS('data/KOR_fs.Rds') KOR_value = read.csv('data/KOR_value.csv', row.names = 1, stringsAsFactors = FALSE) KOR_price = read.csv('data/KOR_price.csv', row.names = 1, stringsAsFactors = FALSE) %>% as.xts() KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, 'left', 0) 먼저 재무제표, 가치지표, 주가 데이터를 불러옵니다. if ( lubridate::month(Sys.Date()) %in% c(1,2,3,4) ) { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 2)) } else { num_col = str_which(colnames(KOR_fs[[1]]), as.character(lubridate::year(Sys.Date()) - 1)) } quality_roe = (KOR_fs$'지배주주순이익' / KOR_fs$'자본')[num_col] quality_gpa = (KOR_fs$'매출총이익' / KOR_fs$'자산')[num_col] quality_cfo = (KOR_fs$'영업활동으로인한현금흐름' / KOR_fs$'자산')[num_col] quality_profit = cbind(quality_roe, quality_gpa, quality_cfo) %>% setNames(., c('ROE', 'GPA', 'CFO')) factor_quality = quality_profit %>% mutate_all(list(~min_rank(desc(.)))) %>% mutate_all(list(~scale(.))) %>% rowSums() factor_quality %>% data.frame() %>% ggplot(aes(x = `.`)) + geom_histogram() 첫 번째로 퀄리티 지표를 계산해줍니다. 코드는 앞에서 살펴본 것과 거의 비슷하며, 자기자본이익률, 매출총이익, 영업활동현금흐름을 계산해줍니다. 그 후 mutate_all() 함수를 통해 랭킹을 구한 후 다시 표준화하며, 내림차순으로 정리하기 위해 랭킹 부분에 desc()를 붙여줍니다. rowSums() 함수를 통해 계산된 Z-Score를 종목별로 합쳐줍니다. Z-Score의 히스토그램을 살펴보면 이상치가 없이 중앙에 데이터가 많이 분포되어 있습니다. factor_value = KOR_value %>% mutate_all(list(~min_rank(.))) %>% mutate_all(list(~scale(.))) %>% rowSums() factor_value %>% data.frame() %>% ggplot(aes(x = `.`)) + geom_histogram() 두 번째로 밸류 지표를 계산해줍니다. 밸류 지표는 이미 테이블 형태로 들어와 있으며, 랭킹과 표준화를 거쳐 합을 구해줍니다. 역시나 이상치가 없이 중앙에 데이터가 많이 분포되어 있습니다. library(PerformanceAnalytics) library(dplyr) ret_3m = Return.calculate(KOR_price) %>% xts::last(60) %>% sapply(., function(x) {prod(1+x) - 1}) ret_6m = Return.calculate(KOR_price) %>% xts::last(120) %>% sapply(., function(x) {prod(1+x) - 1}) ret_12m = Return.calculate(KOR_price) %>% xts::last(252) %>% sapply(., function(x) {prod(1+x) - 1}) ret_bind = cbind(ret_3m, ret_6m, ret_12m) %>% data.frame() factor_mom = ret_bind %>% mutate_all(list(~min_rank(desc(.)))) %>% mutate_all(list(~scale(.))) %>% rowSums() factor_mom %>% data.frame() %>% ggplot(aes(x = `.`)) + geom_histogram() 마지막으로 모멘텀 지표를 계산해줍니다. 최근 60일, 120일, 252일 주가를 통해 3개월, 6개월, 12개월 수익률을 구해준 후 cbind() 함수를 통해 열로 묶어줍니다. 그 후 내림차순 기준 랭킹과 표준화를 거쳐 합을 구합니다. library(corrplot) cbind(factor_quality, factor_value, factor_mom) %>% data.frame() %>% setNames(c('Quality', 'Value', 'Momentum')) %>% cor(use = 'complete.obs') %>% round(., 2) %>% corrplot(method = 'color', type = 'upper', addCoef.col = 'black', number.cex = 1, tl.cex = 0.6, tl.srt = 45, tl.col = 'black', col = colorRampPalette(c('blue', 'white', 'red'))(200), mar=c(0,0,0.5,0)) 퀄리티, 밸류, 모멘텀 팩터 간의 랭크의 서로 간 상관관계가 매우 낮으며, 여러 팩터를 동시에 고려함으로서 분산효과를 기대할 수 있습니다. factor_qvm = cbind(factor_quality, factor_value, factor_mom) %>% data.frame() %>% mutate_all(list(~scale(.))) %>% mutate(factor_quality = factor_quality * 0.33, factor_value = factor_value * 0.33, factor_mom = factor_mom * 0.33) %>% rowSums() invest_qvm = rank(factor_qvm) <= 30 계산된 팩터들을 토대로 최종 포트폴리오를 구성해보겠습니다. 각 팩터의 분포가 역시나 다르기 때문에 다시 한번 scale() 함수를 통해 정규화해주며, 각 팩터에 동일한 비중인 0.33을 곱한 후 이를 더합니다. 물론 팩터별 비중을 [0.2, 0.4, 0.4]와 같이 다르게 줄 수도 있으며, 이는 어떠한 팩터를 더욱 중요하게 생각하는지 혹은 더욱 좋게 보는지에 따라 조정이 가능합니다. 최종적으로 해당 값의 랭킹 기준 상위 30종목을 선택합니다. library(tidyr) quality_profit[invest_qvm, ] %>% gather() %>% ggplot(aes(x = value)) + geom_histogram() + facet_wrap(. ~ key, scale = 'free', ncol = 1) + xlab(NULL) 먼저 선택된 종목의 퀄리티 지표별 분포를 살펴보겠습니다. 대부분 종목의 수익성이 높음이 확인됩니다. KOR_value[invest_qvm, ] %>% gather() %>% ggplot(aes(x = value)) + geom_histogram() + facet_wrap(. ~ key, scale = 'free', ncol = 1) + xlab(NULL) 이번에는 선택된 종목의 가치지표별 분포입니다. 대부분 종목의 값이 낮아 밸류 종목임이 확인됩니다. ret_bind[invest_qvm, ] %>% gather() %>% ggplot(aes(x = value)) + geom_histogram() + facet_wrap(. ~ key, scale = 'free', ncol = 1) + xlab(NULL) 마지막으로 각 종목들의 기간별 수익률 분포입니다. 역시나 대부분의 종목들이 높은 수익률을 보입니다. KOR_ticker[invest_qvm, ] %>% select('종목코드', '종목명') %>% cbind(round(quality_roe[invest_qvm, ], 2)) %>% cbind(round(KOR_value$PBR[invest_qvm], 2)) %>% cbind(round(ret_12m[invest_qvm], 2)) %>% setNames(c('종목코드', '종목명', 'ROE', 'PBR', '12M')) ## 종목코드 종목명 ROE PBR 12M ## 105 001040 CJ 0.02 0.23 0.29 ## 248 298020 효성티앤씨 0.16 1.86 0.70 ## 257 097520 엠씨넥스 0.38 4.14 0.41 ## 269 005880 대한해운 0.10 0.90 0.54 ## 394 298000 효성화학 0.19 1.22 0.34 ## 402 015750 성우하이텍 0.03 0.39 0.81 ## 430 049070 인탑스 0.07 0.93 1.33 ## 517 084690 대상홀딩스 0.06 0.32 0.69 ## 523 035890 서희건설 0.16 0.96 0.45 ## 567 002310 아세아제지 0.10 0.60 0.24 ## 614 123040 엠에스오토텍 0.08 1.47 0.41 ## 648 001390 KG케미칼 0.07 0.23 1.12 ## 685 016450 한세예스24홀딩스 0.03 0.48 0.01 ## 759 248170 샘표식품 0.17 1.55 0.51 ## 852 054210 이랜텍 0.07 1.27 0.63 ## 888 023600 삼보판지 0.07 0.50 0.45 ## 990 079960 동양이엔피 0.11 0.84 0.43 ## 992 115160 휴맥스 0.05 0.42 0.16 ## 1002 013520 화승알앤에이 0.09 0.68 0.21 ## 1028 083930 아바코 0.15 1.44 1.03 ## 1092 003960 사조대림 0.19 0.37 0.07 ## 1332 053690 한미글로벌 0.18 0.95 0.19 ## 1370 010100 한국프랜지 0.05 0.48 0.66 ## 1379 002870 신풍제지 0.28 1.50 1.03 ## 1400 078890 가온미디어 0.12 0.73 0.00 ## 1461 015230 대창단조 0.08 0.55 0.30 ## 1612 009180 한솔로지스틱스 0.13 1.76 1.01 ## 1664 089850 유비벨록스 0.03 0.44 0.71 ## 1742 038010 제일테크노스 0.06 0.88 0.84 ## 1994 007530 영신금속 0.14 1.13 1.19 포트폴리오 내 종목들을 대상으로 팩터별 대표적인 지표인 ROE, PBR, 12개월 수익률을 나타냈습니다. 전반적으로 ROE는 높고 PBR은 낮으며, 12개월 수익률이 높은 모습을 보입니다. 물론 특정 팩터의 강도가 약하더라도 나머지 팩터의 강도가 충분히 강하다면, 포트폴리오에 편입되는 모습을 보이기도 합니다. cbind(quality_profit, KOR_value, ret_bind)[invest_qvm, ] %>% apply(., 2, mean) %>% round(3) %>% t() ## ROE GPA CFO PER PBR PCR PSR ret_3m ## [1,] 0.114 0.218 0.114 9.521 0.974 3.499 0.35 0.415 ## ret_6m ret_12m ## [1,] 0.629 0.558 마지막으로 포트폴리오 내 종목들의 지표별 평균을 계산한 값입니다. References "], -["포트폴리오-구성.html", "Chapter 11 포트폴리오 구성 11.1 최소분산 포트폴리오 11.2 최대분산효과 포트폴리오 11.3 위험균형 포트폴리오 11.4 인덱스 포트폴리오 구성하기", " Chapter 11 포트폴리오 구성 종목별로 비중을 어떻게 배분하느냐에 따라 성과가 달라지므로, 종목의 선택 못지 않게 중요한 것이 포트폴리오를 구성하는 방법입니다. 최적 포트폴리오의 구성은 수식을 기반으로 최적화된 해를 찾습니다. 물론 엑셀의 해 찾기와 같은 기능을 사용해 간단한 형태의 최적화 구현이 가능하지만, 방대한 데이터를 다룰 경우에는 속도가 지나치게 느려지거나 계산할 수 없게 되기도 합니다. 동일한 최적화 방법을 지속적으로 사용한다면 프로그래밍을 통해 함수를 만들고, 입력 변수만 변경하는 것이 훨씬 효율적인 방법입니다. 또한 포트폴리오 최적화에 관한 좋은 패키지들이 이미 많이 나와 있으므로, 대략적인 내용만 이해하고 실제 구현은 패키지를 이용하는 것도 좋은 방법입니다. 이 CHAPTER에서는 일반적으로 많이 사용되는 최소분산 포트폴리오, 최대분산효과 포트폴리오, 위험균형 포트폴리오를 구현해보도록 합니다. 또한 실무에서 많이 사용되는 인덱스 포트폴리오를 구성하는 방법에 대해서도 살펴보겠습니다. 먼저 최소분산, 최대분산효과, 위험균형 포트폴리오 구성을 위해 글로벌 자산을 대표하는 ETF 데이터를 다운로드하겠습니다. library(quantmod) library(PerformanceAnalytics) library(magrittr) symbols = c('SPY', # 미국 주식 'IEV', # 유럽 주식 'EWJ', # 일본 주식 'EEM', # 이머징 주식 'TLT', # 미국 장기채 'IEF', # 미국 중기채 'IYR', # 미국 리츠 'RWX', # 글로벌 리츠 'GLD', # 금 'DBC' # 상품 ) getSymbols(symbols, src = 'yahoo') ## [1] "SPY" "IEV" "EWJ" "EEM" "TLT" "IEF" "IYR" "RWX" ## [9] "GLD" "DBC" prices = do.call(cbind, lapply(symbols, function(x) Ad(get(x)))) %>% setNames(symbols) rets = Return.calculate(prices) %>% na.omit() getSymbols() 함수를 통해 일반적으로 자산배분에서 많이 사용되는 주식과 채권, 대체자산에 해당하는 ETF 가격 데이터를 받은 후 lapply()와 Ad(), get() 함수의 조합을 통해 수정주가만을 선택하고 열의 형태로 묶어줍니다. 그 후 Return.calculate() 함수를 통해 수익률을 계산합니다. library(tidyr) library(dplyr) library(corrplot) cor(rets) %>% corrplot(method = 'color', type = 'upper', addCoef.col = 'black', number.cex = 0.7, tl.cex = 0.6, tl.srt=45, tl.col = 'black', col = colorRampPalette(c('blue', 'white', 'red'))(200), mar = c(0,0,0.5,0)) 각 ETF의 수익률 간 상관관계를 살펴보면 같은 자산군 내에서는 강한 상관관계를 보이며, 주식과 채권 간에는 매우 낮은 상관관계를 보입니다. 또한 주식과 리츠 간에도 꽤 높은 상관관계를 보입니다. 포트폴리오 최적화에는 분산-공분산 행렬이 대부분 사용되며, 이는 cov() 함수를 통해 손쉽게 계산할 수 있습니다. covmat = cov(rets) 11.1 최소분산 포트폴리오 최소분산 포트폴리오(Minimum Variance Portfolio)는 변동성이 최소인 포트폴리오입니다. 포트폴리오의 변동성은 일반적으로 \\(\\sum_{i=1}^{n}\\sum_{j=1}^{n}w_iw_j\\sigma_{ij}\\)의 형태로 표현되지만, 최적화 작업을 위해서는 행렬의 형태인 \\(w'\\Omega w\\)로 표현하는 것이 더욱 편리합니다. 이 중 \\(w\\)는 각 자산들의 비중을 행렬의 형태로 나타낸 것이며, \\(\\Omega\\)는 분산-공분산 행렬을 나타낸 것입니다. 분산-공분산 행렬은 사전에 고정되어 있는 값이므로, 각 자산들의 비중인 \\(w\\)를 변화시킴으로써 포트폴리오의 변동성이 최소인 지점을 찾을 수 있습니다. 최소분산 포트폴리오의 목적함수는 아래의 수식으로 표현할 수 있습니다. 이 중 \\(^1/_2\\)은 단지 미분했을 때 계산을 용이하게 하기 위한 장치일 뿐 결과에는 영향을 미치지 않습니다. \\[ 최소분산\\,포트폴리오의\\,목적함수: min\\,^1/_2\\,w'\\Omega w \\] 다만 단순히 위의 목적함수를 찾는 해를 구하면 결괏값이 음수가 나오기도 하는데 이것은 공매도를 의미합니다. 일반적으로 공매도가 불가능하다는 점과, 투자비중의 합이 100%가 되어야 한다는 점을 고려하면 아래와 같은 제약조건을 추가해야 합니다. \\[ 최소분산\\,포트폴리오의\\,제약조건: \\sum_{i=1}^{n}w_i = 1, w_i \\ge 0 \\] 물론 이 외에도 각 섹터의 투자비중 합에 대한 제약조건이나 회전율에 대한 제약조건 등도 추가할 수 있습니다. 11.1.1 slsqp() 함수를 이용한 최적화 R에서 가장 손쉽게 최적화 작업을 수행하는 방법은 nloptr 패키지의 slsqp() 함수를 이용하는 것입니다. slsqp() 함수는 순차적 이차 계획(Sequential Quadratic Programming)을 이용해 해를 찾으며, 목적함수와 제약조건은 다음과 같습니다. 표 11.1: slsqp() 함수 목적함수와 제약조건 목적함수 제약조건 \\(min\\,f(x)\\) \\(b(x)\\ge0, c(x)=0\\) 목적함수에서 \\(f(x)\\) 는 최소화하고자 하는 값, 즉 포트폴리오의 변동성입니다. 제약조건은 크게 개별 자산의 투자 비중이 0 이상인 것과, 투자 비중의 합이 1이 되도록 하는 것입니다. 첫 번째 제약조건은 자연스럽게 개별 자산의 투자비중이 0 이상인 것을 의미합니다. 두 번째 제약조건은 약간의 변형을 통해 투자비중의 합이 1이 되는 제약조건을 만들 수 있습니다. \\(c(x)\\)를 투자비중의 합 – 1 로 변형할 경우 -1을 우변으로 넘기면 결국 투자비중의 합 = 1의 형태로 나타낼 수 있습니다. slsqp() 함수의 구성은 다음과 같습니다. slsqp(x0, fn, gr = NULL, lower = NULL, upper = NULL, hin = NULL, hinjac = NULL, heq = NULL, heqjac = NULL, nl.info = FALSE, control = list(), ...) 이 중 우리가 구체적으로 입력해야 할 값은 x0, fn, hin, heq 항목 입니다. x0은 초기값이며, 일반적으로 모든 x에 대해 동일한 값을 입력합니다. fn은 최소화하고자 하는 목적함수로, 포트폴리오 변동성에 해당합니다. hin은 부등위 제약조건(inequality constraints)을 의미하며, 프로그래밍 내에서는 hin >= 0로 인식하며, 각 자산의 비중이 0보다 크다는 제약조건과 연결됩니다. heq는 등위 제약조건(equality constraints)을 의미하며, 프로그래밍 내에서는 heq == 0을 의미합니다. 투자비중의 합 - 1의 형태를 입력한다면 투자비중의 합이 1 이라는 제약조건과 연결됩니다. 표 11.2는 최소분산 포트폴리오를 구할 때 필요한 주요 변수에 대한 내용입니다. 표 11.2: slsqp() 함수의 인자와 포트폴리오 내 변수 변수명 내용 포트폴리오 내 변수 x0 초기값 없음 fn 목적함수 포트폴리오 변동성 hin 부등위 제약조건 각 자산의 비중이 0 보다 큰 제약조건 heq 등위 제약조건 투자 비중의 합이 1인 제약조건 slsqp() 함수를 이용해 최소분산 포트폴리오를 만족하는 자산의 투자비중을 구하는 과정은 다음과 같습니다. 먼저 fn, hin, heq에 해당하는 함수들을 각각 만든 후 이를 slsqp() 함수와 결합해 최적화된 결괏값을 얻을 수 있습니다. 구체적인 과정은 아래와 같습니다. objective = function(w) { obj = t(w) %*% covmat %*% w return(obj) } 먼저 목적함수에 해당하는 부분입니다. covmat은 사전에 계산된 분산-공분산 행렬이며, \\(w\\)는 각 자산의 투자비중입니다. obj는 포트폴리오의 변동성인 \\(w'\\Omega w\\)를 계산한 것입니다. 즉, 해당 함수는 계산된 \\(w\\)를 바탕으로 포트폴리오의 변동성을 반환하고, 우리의 목적은 해당 값이 최소가 되도록 하는 것입니다. hin.objective = function(w) { return(w) } \\(w_i \\ge 0\\) 제약조건에 해당하는 부등위 제약조건입니다. 패키지 내에서는 hin >= 0 의 형태로 인식하므로, 계산된 비중인 \\(w\\)를 단순히 입력하기만 하면 됩니다. heq.objective = function(w) { sum_w = sum(w) return( sum_w - 1 ) } \\(\\sum_{i=1}^{n}w_i = 1\\) 제약조건에 해당하는 등위 제약조건입니다. 먼저 계산된 비중인 \\(w\\)들의 합계를 구한 후 해당 값에서 1을 빼주는 값을 반환하도록 합니다. 프로그래밍 내에서는 heq == 0 의 형태로 인식을 하므로 결국 (sum_w – 1) == 0, 즉 sum_w == 1의 제약조건과 동일합니다. library(nloptr) result = slsqp( x0 = rep(0.1, 10), fn = objective, hin = hin.objective, heq = heq.objective) print(result$par) ## [1] 1.427e-01 4.987e-18 1.184e-17 -7.502e-19 ## [5] -8.242e-17 7.924e-01 -4.430e-18 2.472e-18 ## [9] -9.055e-18 6.495e-02 print(result$value) ## [1] 0.000009817 위에서 만들어진 함수들을 바탕으로 최적화 작업을 실행합니다. 초기값인 x0에는 먼저 동일한 비중들을 입력합니다. 예제에서는 종목이 10개 이므로, x0값에는 rep(0.1, 10) 인 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1을 입력합니다. 최소화하고자 하는 목적함수 fn에는 위에서 구성한 objective 함수를 입력합니다. 부등위 제약조건과 등위 제약조건에도 각각 위에서 구성한 hin.objective와 heq.objective 함수를 입력합니다. 즉, 해당 함수는 초기값을 시작점으로 해 주어진 제약조건을 만족하는 해를 찾기 위해 \\(w\\)값들을 조정하는 작업을 반복한 후 목적함수가 최소가 되는 지점의 \\(w\\)를 반환합니다. result 값 중 $par는 최적화된 지점의 해를 의미하며, 최소분산 포트폴리오를 구성하는 자산들의 투자 비중을 의미합니다. $value는 $par에서 산출된 값을 목적함수 fn에 입력하였을 때 나오는 결괏값으로써, 포트폴리오의 분산을 의미합니다. w_1 = result$par %>% round(., 4) %>% setNames(colnames(rets)) print(w_1) ## SPY IEV EWJ EEM TLT IEF IYR RWX ## 0.1427 0.0000 0.0000 0.0000 0.0000 0.7924 0.0000 0.0000 ## GLD DBC ## 0.0000 0.0650 자산들의 투자비중은 result$par를 통해 추출한 후, round() 함수를 이용해 반올림합니다. 마지막으로 이름에 종목명을 입력합니다. 계산된 비중으로 포트폴리오를 구성하면 포트폴리오의 비중이 최소가 됩니다. 11.1.2 solve.QP() 함수를 이용한 최적화 다음으로는 quadprog 패키지 내의 solve.QP() 함수를 이용해 포트폴리오 최적화를 하는 방법이 있습니다. 해당 함수는 쌍대기법(Dual Method)을 이용해 제약조건 내에서 목적함수가 최소화되는 해를 구합니다. 해당 함수의 목적함수와 제약조건은 표 11.3과 같습니다. 표 11.3: solve.QP 함수 목적함수와 제약조건 목적함수 제약조건 \\(min(-d^Tb+^1/_2b^TDb)\\) \\(A^Tb \\ge b_0\\) 최소분산 포트폴리오의 목적함수가 \\(min\\,^1/_2\\,w'\\Omega w\\)로 표시된다는 점을 생각하면, 해당 함수는 매우 이해하기 쉽게 구성되어 있습니다. \\(b\\)를 각 개별 자산의 투자 비중인 \\(w\\), \\(D\\)를 분산-공분산 행렬인 \\(\\Omega\\)라 생각하면, 목적함수 중 \\(min\\,^1/_2\\,wDw\\)는 최소분산 포트폴리오의 목적함수와 정확히 동일합니다. \\(d\\)를 0으로 생각하면 \\(-d^Tb\\) 또한 0이 되어 목적함수에 아무런 영향도 미치지 않습니다. 제약조건 역시 \\(A^T\\) 항목을 적절하게 수정한다면, 개별 자산의 투자비중이 0 이상인 것과, 투자비중의 합이 1이 되도록 만들 수 있습니다. 이에 대해서는 뒤에서 구체적으로 다루도록 합니다. solve.QP() 함수의 사용법은 아래와 같습니다. solve.QP(Dmat, dvec, Amat, bvec, meq = 0, factorized = FALSE) Dmat은 목적함수 중 \\(D\\)에 해당하는 행렬 부분으로서 분산-공분산 행렬과 일치합니다. dvec은 목적함수 중 \\(d\\)에 해당하는 벡터 부분이며, 포트폴리오 최적화에서는 역할이 없습니다. Amat은 제약조건 중 \\(A^T\\)에 해당하는 부분으로써, 제약조건 중 좌변에 위치하는 항목입니다. 제약조건에서 보듯이 제약조건 행렬을 구한 후 이것의 전치(Transpose) 행렬을 입력해야 하는 데 주의합니다. bvec은 제약조건 중 \\(b_0\\)에 해당하는 부분으로써, 제약조건 중 우변에 위치하는 항목입니다. meq는 bvec의 몇 번째까지를 등위 제약조건으로 설정할지에 대한 부분입니다. 표 11.4는 위의 내용을 요약한 것이며, 각 변수를 입력한 후 함수를 실행하면 위의 목적함수와 제약조건을 만족하는 \\(b\\) 값을 찾습니다. 표 11.4: solve.QP 함수의 인자와 포트폴리오 내 변수 변수명 내용 포트폴리오 내 변수 Dmat 목적함수 중 D 분산-공분산 행렬 dvec 목적함수 중 d 해당사항 없음 Amat 제약조건 (좌변) \\(\\sum_{i=1}^{n}w_i, w_i\\) bvec 제약조건 (우변) 비중의 합이 1, 각 비중이 0보다 큼 meq 등위 제약조건 개수 1개 (비중의 합이 1) solve.QP() 함수를 이용해 최소분산 포트폴리오 비중을 구할 때는 Amat 항목을 제대로 입력하는 것이 가장 중요하며, 나머지 항목은 매우 손쉽게 입력이 가능합니다. 설명된 내용에 해당하는 행렬을 손으로 직접 써가며 계산해본다면 훨씬 이해하기가 쉬울 것입니다. 구체적인 과정은 아래와 같습니다. Dmat = covmat dvec = rep(0, 10) Amat = t(rbind(rep(1, 10), diag(10), -diag(10))) bvec = c(1, rep(0, 10), -rep(1, 10)) meq = 1 Dmat에는 분산-공분산 행렬을 입력하며,dvec은 최소분산 포트폴리오를 구하는데는 필요한 값이 아니므로 0벡터를 입력합니다. 등위 제약조건과 부등위 제약조건(\\(A^Tb \\ge b_0\\))을 행렬의 형태로 표현하면 다음과 같습니다. \\[ \\begin{bmatrix} 1 & \\dots & 1 \\\\ 1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots & 1 \\\\ -1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots & -1 \\\\ \\end{bmatrix} \\begin{bmatrix} w_1 \\\\ w_2 \\\\ \\vdots \\\\ w_{10} \\end{bmatrix} = \\begin{bmatrix} w_1 + w_2 + \\dots + w_{10} \\\\ w_1 \\\\ \\vdots \\\\ w_{10} \\\\ -w_1 \\\\ \\vdots \\\\ -w_{10} \\end{bmatrix} \\ge \\begin{bmatrix} 1 \\\\ 0 \\\\ \\vdots \\\\ 0 \\\\ -1 \\\\ \\vdots \\\\ -1 \\end{bmatrix} \\] 이 중 맨 왼쪽 행렬의 전치행렬이 제약조건의 좌변인 Amat에 해당합니다. \\[ Amat = \\begin{bmatrix} 1 & \\dots & 1 \\\\ 1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots & 1 \\\\ -1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots & -1 \\end{bmatrix} ^T \\] 맨 오른쪽 행렬이 제약조건의 우변인 bvec에 해당합니다. \\[ bvec = \\begin{bmatrix} 1 \\\\ 0 \\\\ \\vdots \\\\ 0 \\\\ -1 \\\\ \\vdots \\\\ -1 \\end{bmatrix} \\] 위의 제약조건은 크게 투자비중의 합이 1인 제약조건, 최소 투자비중이 0 이상인 제약조건, 최대 투자비중이 1 이하인 제약조건, 총 세 개 부분으로 나눌 수 있습니다. \\[ (1)\\; \\sum_{i = 1}^nw_i = 1 \\Rightarrow \\begin{bmatrix} w_1 + w_2 + \\dots \\ w_{10} \\end{bmatrix} = \\begin{bmatrix} 1 \\end{bmatrix} \\] \\[(2)\\; w_i \\ge 0 \\Rightarrow \\begin{bmatrix} w_1 \\\\ w_2 \\\\ \\vdots \\\\ w_{10} \\end{bmatrix} \\ge \\begin{bmatrix} 0 \\\\ 0 \\\\ \\vdots \\\\ 0 \\end{bmatrix} \\\\\\] \\[(3)\\;-w_i \\ge -1 \\Rightarrow \\begin{bmatrix} -w_1 \\\\ -w_2 \\\\ \\vdots \\\\ -w_{10} \\end{bmatrix} \\ge \\begin{bmatrix} -1 \\\\ -1 \\\\ \\vdots \\\\ -1 \\end{bmatrix} \\] solve.QP() 함수의 제약조건은 항상 좌변이 큰 형태이므로, 최대 투자비중에 대한 제약조건은 다음 행렬의 양변에 마이너스(-)를 곱해 부등호를 맞춰주었습니다. \\[\\begin{bmatrix} w_1 \\\\ w_2 \\\\ \\vdots \\\\ w_{10} \\end{bmatrix} \\le \\begin{bmatrix} 1 \\\\ 1 \\\\ \\vdots \\\\ 1 \\end{bmatrix}\\] 첫 번째 제약조건은 부등호가 아닌 등호, 즉 투자비중의 합이 1인 조건을 의미하므로 meq = 1을 통해 첫 번째 제약조건은 등식 제약조건임을 선언할 수 있습니다. 제약조건의 좌변에 해당하는 Amat을 만드는 과정은 다음과 같습니다. 먼저 rep(1, 10)을 통해 최상단에 위치한 1로 이루어진 행렬을 만들어줍니다. \\[\\begin{bmatrix} 1 & 1 & \\dots & 1 \\end{bmatrix}\\] 하단의 1과 -1로 이루어진 대각행렬은 diag() 함수를 통해 쉽게 만들 수 있습니다. \\[ diag(10) = \\begin{bmatrix} 1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots &\\ 1\\end{bmatrix}\\] \\[ -diag(10) = \\begin{bmatrix} -1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots &\\ -1\\end{bmatrix}\\] rbind() 함수를 통해 세 개의 행렬을 행으로 묶어주면 제약조건의 맨 왼쪽 행렬과 동일한 형태가 됩니다. 이를 t() 함수를 통해 전치행렬을 만들어 준 뒤 Amat에 입력합니다. 제약조건에 해당하는 bvec은 1, 0, -1로 이루어진 벡터를 통해 손쉽게 만들 수 있습니다. library(quadprog) result = solve.QP(Dmat, dvec, Amat, bvec, meq) print(result$solution) ## [1] 1.427e-01 -5.943e-19 0.000e+00 4.852e-19 ## [5] 1.434e-17 7.924e-01 2.258e-18 -5.797e-19 ## [9] -5.030e-19 6.495e-02 print(result$value) ## [1] 0.000004908 위에 입력된 내역들을 solve.QP() 함수에 넣어 최적화 값을 찾아줍니다. 결과 중 $solution은 최적화된 지점의 해, 즉 최소분산 포트폴리오를 구성하는 자산들의 투자비중을 의미합니다. $value는 $solution에서 산출된 값을 목적함수에 입력했을때 나오는 결괏값으로서, 포트폴리오의 분산을 의미합니다. w_2 = result$solution %>% round(., 4) %>% setNames(colnames(rets)) print(w_2) ## SPY IEV EWJ EEM TLT IEF IYR RWX ## 0.1427 0.0000 0.0000 0.0000 0.0000 0.7924 0.0000 0.0000 ## GLD DBC ## 0.0000 0.0650 자산들의 투자비중은 result$solution을 통해 추출한 후 round() 함수를 이용해 반올림합니다. 마지막으로 이름에 종목명을 입력합니다. 계산된 비중으로 포트폴리오를 구성하면 포트폴리오의 비중이 최소화됩니다. 11.1.3 optimalPortfolio() 함수를 이용한 최적화 RiskPortfolios 패키지의 optimalPortfolio() 함수를 이용해 매우 간단하게 최적화 포트폴리오를 구현할 수도 있습니다. 해당 함수의 사용법은 다음과 같습니다. optimalPortfolio(Sigma, mu = NULL, semiDev = NULL, control = list()) Sigma는 분산-공분산 행렬입니다. mu와 semiDev는 각각 기대수익률과 세미 편차(Semi Deviation)로서, 입력하지 않아도 됩니다. control은 포트폴리오 종류 및 제약조건에 해당하는 부분이며, 자세한 내용은 표 11.5와 같습니다. 표 11.5: optimalPortfolio() 포트폴리오 내 control 인자 종류 입력값 내용 type minvol 최소분산 포트폴리오 invvol 역변동성 포트폴리오 erc 위험 균형 포트폴리오 maxdiv 최대 분산효과 포트폴리오 riskeff 위험-효율적 포트폴리오 constraint lo 최소 투자 비중이 0 보다 클것 user 최소(LB) 및 최대 투자 비중(UB) 설정 control 항목에서 원하는 포트폴리오 타입과 제약조건을 입력해주면, 매우 손쉽게 최적화 포트폴리오를 구현할 수 있습니다. library(RiskPortfolios) w_3 = optimalPortfolio(covmat, control = list(type = 'minvol', constraint = 'lo')) %>% round(., 4) %>% setNames(colnames(rets)) print(w_3) ## SPY IEV EWJ EEM TLT IEF IYR RWX ## 0.1427 0.0000 0.0000 0.0000 0.0000 0.7924 0.0000 0.0000 ## GLD DBC ## 0.0000 0.0650 optimalPortfolio() 함수 내부에 분산-공분산 행렬을 입력합니다. type 부분에 최소분산 포트폴리오에 해당하는 minvol을 입력하며, constraint에는 각 자산의 비중이 0보다 큰 제약조건인 lo(Long Only)를 입력합니다. 비중의 합이 1인 제약조건은 자동적으로 적용이 됩니다. 이처럼 패키지를 이용하면 훨씬 간단하게 원하는 값을 얻을 수 있습니다. GitHub를 통해 해당 함수의 코드를 살펴보면 solve.QP() 함수를 이용해 작성한 방법과 거의 동일합니다. 따라서 위의 과정들을 대략적으로 이해한 후 패키지를 사용해 포트폴리오 최적화를 구현하는 것이 현명한 방법이 될 수도 있습니다. 11.1.4 결괏값들의 비교 아래 표는 slsqp(), solve.QP(), optimalPortfolio()를 이용하여 구한 값들의 비교입니다. 표 11.6: 최적화 결과 비교 SPY IEV EWJ EEM TLT IEF IYR RWX GLD DBC slsqp 0.1427 0 0 0 0 0.7924 0 0 0 0.065 solve.QP 0.1427 0 0 0 0 0.7924 0 0 0 0.065 optimalPortfolio 0.1427 0 0 0 0 0.7924 0 0 0 0.065 세 가지 방법 모두 결과가 동일합니다. 그러나 여기서 나온 결과를 이용해 그대로 투자하기에는 문제가 있습니다. 일부 자산은 투자비중이 0%, 즉 전혀 투자하지 않는 반면, 특정 자산에 대부분의 비중인 79.24%를 투자하는 편중된 결과가 나옵니다. library(ggplot2) data.frame(w_1) %>% ggplot(aes(x = factor(rownames(.), levels = rownames(.)), y = w_1)) + geom_col() + xlab(NULL) + ylab(NULL) 이처럼 변동성이 가장 낮은 종목에 대부분의 비중이 투자되는 구석해(Corner Solution) 문제를 해결하기 위해 각 자산의 최소 및 최대 투자비중 제약조건을 추가해 줄 필요가 있습니다. 11.1.5 최소 및 최대 투자비중 제약조건 구석해 문제를 방지하고, 모든 자산에 골고루 투자하기 위해 개별 투자비중을 최소 5%, 최대 20%로 하는 제약조건을 추가하겠습니다. 먼저 slsqp() 함수에서 제약조건을 추가하는 방법은 다음과 같습니다. result = slsqp( x0 = rep(0.1, 10), fn = objective, hin = hin.objective, heq = heq.objective, lower = rep(0.05, 10), upper = rep(0.20, 10)) w_4 = result$par %>% round(., 4) %>% setNames(colnames(rets)) print(w_4) ## SPY IEV EWJ EEM TLT IEF IYR RWX GLD DBC ## 0.05 0.05 0.05 0.05 0.20 0.20 0.05 0.05 0.20 0.10 함수의 마지막에 lower와 upper 제약조건을 추가로 입력하면 해당 값 사이에서 최적화를 만족하는 해를 찾게 되며, 해당 예에서는 5%와 20% 사이에서 해를 찾게 됩니다. 추가로 입력한 제약조건에 맞게, 최소 투자비중은 5%이며, 최대 투자비중은 20%임을 확인할 수 있습니다. 다음은 solve.QP() 함수 내에서 제약조건을 추가하는 방법입니다. 해당 함수 역시 다른 입력값은 모두 동일하며, 제약조건의 우변에 해당하는 bvec 항목만 수정하면 됩니다. 최소, 최대 투자비중 제약조건을 기존 [0, 1]에서 [0.05, 0.20] 로 변경하면, bvec에 해당하는 행렬은 다음과 같이 변경됩니다. \\[ 기존: \\begin{bmatrix} w_1 + w_2 + \\dots + w_{10} \\\\ w_1 \\\\ \\vdots \\\\ w_{10} \\\\ -w_1 \\\\ \\vdots \\\\ -w_{10} \\end{bmatrix} \\ge \\begin{bmatrix} 1 \\\\ 0 \\\\ \\vdots \\\\ 0 \\\\ -1 \\\\ \\vdots \\\\ -1 \\end{bmatrix} \\\\ \\] \\[변경: \\begin{bmatrix} w_1 + w_2 + \\dots + w_{10} \\\\ w_1 \\\\ \\vdots \\\\ w_{10} \\\\ -w_1 \\\\ \\vdots \\\\ -w_{10} \\end{bmatrix} \\ge \\begin{bmatrix} 1 \\\\ 0.05 \\\\ \\vdots \\\\ 0.05 \\\\ -0.20 \\\\ \\vdots \\\\ -0.20 \\end{bmatrix} \\] Dmat = covmat dvec = rep(0, 10) Amat = t(rbind(rep(1, 10), diag(10), -diag(10))) bvec = c(1, rep(0.05, 10), -rep(0.20, 10)) meq = 1 result = solve.QP(Dmat, dvec, Amat, bvec, meq) w_5 = result$solution %>% round(., 4) %>% setNames(colnames(rets)) print(w_5) ## SPY IEV EWJ EEM TLT IEF IYR RWX GLD DBC ## 0.05 0.05 0.05 0.05 0.20 0.20 0.05 0.05 0.20 0.10 bvec 항목을 제외한 모든 코드는 기존과 동일하며, 조건함수의 우변인 bvec만 각각 최소 투자비중과 최대 투자비중이 [0, 1]에서 [0.05, 0.20]으로 변경되었습니다. 해당 방법 역시 추가적인 투자비중 제약이 잘 적용되었음이 확인됩니다. 마지막으로 optimalPortfolio() 함수 내에서 최소 및 최대 투자비중을 추가하는 방법입니다. 입력변수의 control 항목 중 constraint 부분을 간단하게 수정해 원하는 조건을 입력할 수 있습니다. w_6 = optimalPortfolio(covmat, control = list(type = 'minvol', constraint = 'user', LB = rep(0.05, 10), UB = rep(0.20, 10))) %>% round(., 4) %>% setNames(colnames(rets)) print(w_6) ## SPY IEV EWJ EEM TLT IEF IYR RWX GLD DBC ## 0.05 0.05 0.05 0.05 0.20 0.20 0.05 0.05 0.20 0.10 constraint 부분에 롱온리 제약조건에 해당하는 lo 대신 직접 제약값들을 입력할 수 있는 user를 입력하며, LB에는 최소 투자비중 벡터를, UB에는 최대 투자비중 벡터를 입력합니다. 따라서 원하는 제약조건 내에서 결괏값이 계산됩니다. 표 11.7: 최소 및 최대 비중제약 조건 후 결과 비교 SPY IEV EWJ EEM TLT IEF IYR RWX GLD DBC slsqp 0.05 0.05 0.05 0.05 0.2 0.2 0.05 0.05 0.2 0.1 solve.QP 0.05 0.05 0.05 0.05 0.2 0.2 0.05 0.05 0.2 0.1 optimalPortfolio 0.05 0.05 0.05 0.05 0.2 0.2 0.05 0.05 0.2 0.1 최소 및 최대 제약조건을 추가한 경우도 세 가지 방법 모두 동일한 결과가 나오게 되며, 비중도 각각 5%와 20%로 제한되어 구석해 문제 또한 해결되었음이 확인됩니다. data.frame(w_4) %>% ggplot(aes(x = factor(rownames(.), levels = rownames(.)), y = w_4)) + geom_col() + geom_hline(aes(yintercept = 0.05), color = 'red') + geom_hline(aes(yintercept = 0.20), color = 'red') + xlab(NULL) + ylab(NULL) 11.1.6 각 자산별 제약조건의 추가 투자 규모가 크지 않다면 위에서 추가한 제약조건만으로도 충분히 훌륭한 포트폴리오가 구성됩니다. 그러나 투자 규모가 커지면 추가적인 제약조건들을 고려해야 할 경우가 생깁니다. 벤치마크 비중과의 괴리로 인한 추적오차(Tracking Error)를 고려해야 할 수도 있고, 투자 대상별 거래량을 고려한 제약조건을 추가해야 할 때도 있습니다. 기존 제약조건에는 자산별로 동일한 최소 및 최대 투자비중 제약조건을 다루었지만, 자산별로 상이한 제약조건이 필요할 경우도 있습니다. slsqp()와 optimalPortfolio() 함수에서는 복잡한 제약조건을 다루기가 힘들지만, solve.QP() 함수는 bvec 부분을 간단하게 수정해 어렵지 않게 구현이 가능합니다. 먼저 표 11.8은 새롭게 설정하고자 하는 각 자산별 최소 및 최대 제약조건입니다. 표 11.8: 각 자산 별 최소 및 최대 제약조건 제약 1 2 3 4 5 6 7 8 9 10 최소 0.10 0.10 0.05 0.05 0.10 0.10 0.05 0.05 0.03 0.03 최대 0.25 0.25 0.20 0.20 0.20 0.20 0.10 0.10 0.08 0.08 이를 행렬의 형태로 나타내면 다음과 같습니다. \\[ \\begin{bmatrix} 1 & \\dots & 1 \\\\ 1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots & 1 \\\\ -1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots & -1 \\\\ \\end{bmatrix} \\begin{bmatrix} w_1 \\\\ w_2 \\\\ \\vdots \\\\ w_{10} \\end{bmatrix} = \\begin{bmatrix} w_1 + w_2 + \\dots + w_{10} \\\\ w_1 \\\\ w_2 \\\\ w_3 \\\\ w_4 \\\\ w_5 \\\\ w_6 \\\\ w_7\\\\ w_8\\\\ w_9\\\\ w_{10} \\\\ -w_1 \\\\ -w_2 \\\\ -w_3 \\\\ -w_4 \\\\ -w_5 \\\\ -w_6 \\\\ -w_7 \\\\ -w_8 \\\\ -w_9 \\\\ -w_{10} \\end{bmatrix} \\ge \\begin{bmatrix} 1 \\\\ 0.10 \\\\ 0.10 \\\\ 0.05 \\\\ 0.05 \\\\ 0.10 \\\\ 0.10 \\\\ 0.05 \\\\ 0.05 \\\\ 0.03 \\\\ 0.03 \\\\ -0.25 \\\\ -0.25 \\\\ -0.20 \\\\ -0.20 \\\\ -0.20 \\\\ -0.20 \\\\ -0.10 \\\\ -0.10 \\\\ -0.08 \\\\ -0.08 \\end{bmatrix} \\] 위의 행렬 중 오른쪽 부분을 bvec에 그대로 입력합니다. Dmat = covmat dvec = rep(0, 10) Amat = t(rbind(rep(1, 10), diag(10), -diag(10))) bvec = c(1, c(0.10, 0.10, 0.05, 0.05, 0.10, 0.10, 0.05, 0.05, 0.03, 0.03), -c(0.25, 0.25, 0.20, 0.20, 0.20, 0.20, 0.10, 0.10, 0.08, 0.08)) meq = 1 result = solve.QP(Dmat, dvec, Amat, bvec, meq) result$solution %>% round(., 4) %>% setNames(colnames(rets)) ## SPY IEV EWJ EEM TLT IEF IYR RWX GLD ## 0.104 0.100 0.086 0.050 0.200 0.200 0.050 0.050 0.080 ## DBC ## 0.080 결괏값을 확인해보면 각 자산별 제약조건 내에 위치함을 확인할 수 있습니다. 11.2 최대분산효과 포트폴리오 앞서 설명했듯이 포트폴리오의 변동성은 \\(\\sum_{i=1}^{n}\\sum_{j=1}^{n}w_iw_j\\sigma_{ij}\\) 형태로 나타나며, 이는 다음과 같이 표현할 수도 있습니다. \\[\\sigma_p^2 = \\sum_{i=1}^{n}\\sum_{j=1}^{n}w_iw_j\\sigma_{ij} = \\sum_{i=1}^nw_i^2\\sigma_i^2 + \\sum_{i=1}^{n}\\sum_{i \\ne j}^{n}w_iw_j\\rho_{ij}\\sigma_i\\sigma_j \\] 이 중 \\(\\sum_{i=1}^{n}\\sum_{i \\ne j}^{n}w_iw_j\\rho_{ij}\\sigma_i\\sigma_j\\) 부분에는 자산 간 상관관계\\((\\rho_{ij})\\)가 포함되어 있습니다. 상관관계는 -1과 1 사이에 위치하며 상관관계가 1, 즉 두 자산이 완벽하게 동일한 경우에는 포트폴리오의 변동성은 개별 자산 변동성의 가중합과 같습니다. 그러나 상관관계가 낮아질수록 포트폴리오의 변동성 또한 점차 낮아집니다. 이러한 효과를 투자에서는 분산효과라고 합니다. 이러한 분산효과의 정도를 측정하는 지표가 분산 비율(DR: Diversification Ratio)입니다. 분산 비율의 분자는 개별 변동성의 가중합이며, 분모는 포트폴리오의 변동성입니다. 이를 수식으로 나타내면 다음과 같습니다. \\[ 분산\\,비율 = \\frac{\\sum w_i \\sigma_i}{\\sigma_p} = \\frac{w'\\sigma}{\\sqrt{w'\\Omega w}}\\] 모든 자산 간의 상관관계가 1일 경우, 위의 예시에서 살펴본 것과 같이 포트폴리오의 변동성은 개별 자산 변동성의 가중합과 같아지게 됩니다. 즉, \\(\\sum w_i \\sigma_i = \\sigma_p\\)가 되어, 분산 비율은 1이 됩니다. 그러나 대부분의 경우에서 자산 간의 상관관계는 1보다 낮으며, 이로 인해 포트폴리오의 분산은 단순 가중합보다 작아지게 되고(\\(\\sigma_p < \\sum w_i\\sigma_i\\)), 분산 비율은 1보다 커지게 됩니다. 자산 간 상관관계가 낮은 종목을 위주로 포트폴리오를 구성할수록 분산효과로 인해 포트폴리오의 변동성은 낮아지고, 분산 비율은 점점 커집니다. 최대분산효과 포트폴리오(Most Diversified Portfolio)는 분산효과가 최대가 되는, 즉 분산 비율이 최대가 되는 포트폴리오를 구성하는 방법입니다. 이에 대한 목적함수와 제약조건은 다음과 같습니다. \\[목적함수: max\\,DR = max \\frac{\\sum w_i \\sigma_i}{\\sigma_p}\\] \\[제약조건: \\sum_{i=1}^n w_i = 1, w_i \\ge 0\\] 최대분산효과 포트폴리오의 목적함수는 분산비율을 최대화하는 데 있는 반면, 대부분의 최적화 프로그래밍은 목적함수를 최소화하는 형태로 이루어집니다. 따라서 목적함수인 \\(maxDR\\)을 최소화하는 형태로 바꿀 필요가 있는데 크게 세 가지 방법이 있습니다. Choueifaty Synthetic Asset Back-Transformation을 이용하는 방법(Choueifaty and Coignard 2008) Duality를 이용하는 방법(Choueifaty, Froidure, and Reynier 2013) Min (-)DR 방법 먼저 Choueifaty Synthetic Asset Back-Transformation 방법은 목적함수 \\(min\\,w_s'cw_s\\)와 제약조건 \\(\\sum_{i=1}^n w_i = 1, w_i \\ge 0\\)을 만족하는 자산별 비중을 구합니다. 그 후, 구해진 비중을 각각의 표준편차로 나누어주며, 비중의 합이 1이 되도록 표준화해줍니다. 여기서 주의할 점은 목적함수의 \\(c\\)가 우리가 지금까지 사용하던 분산-공분산 행렬이 아닌, 상관관계 행렬이라는 점입니다. Duality 방법의 목적함수는 최소분산 포트폴리오와 동일한 \\(min^1/_2w'\\sigma w\\)이며, 제약조건만 \\(\\sum_{i=1}^n w_i \\sigma_i = 1, w_i \\ge 0\\), 즉 개별 자산의 비중이 0보다 크고 개별 표준편차의 가중합이 1인 조건으로 바뀝니다. 그 후, 비중의 합이 1이 되도록 표준화를 해줍니다. 기존 두 방법이 수학적 증명에 의해 \\(maxDR\\)을 최소화하는 형태로 풀어준 반면, 간단하게 목적함수를 \\(min(-DR)\\)의 형태로 바꾸어 풀 수도 있습니다. 표 11.3는 세 가지 방법을 요약한 내용입니다. 표 11.9: MDP 방법 비교 방법 목적함수 제약조건 표준화 Transformation \\(min\\,w_s'cw_s\\) \\(\\sum_{i=1}^n w_i = 1\\) \\(w_i \\ge 0\\) 비중을 각각의 표준편차로 나눈 후 비중의 합으로 표준화 Duality \\(min\\,^1/_2w'\\sigma w\\) \\(\\sum_{i=1}^n w_i \\sigma_i = 1\\) \\(w_i \\ge 0\\) 비중의 합으로 표준화 -DR min(-DR) \\(\\sum_{i=1}^n w_i = 1\\) \\(w_i \\ge0\\) 불필요 11.2.1 solve.QP() 함수를 이용한 최적화 먼저 solve.QP() 함수를 이용해 Duality 방법을 통해 최대분산효과 포트폴리오를 만족하는 해를 찾도록 하겠습니다. Duality 방법에서 목적함수는 \\(min\\,^1/_2w'\\sigma w\\)로 최소분산 포트폴리오와 동일하며, 제약조건은 \\(\\sum_{i=1}^n w_i \\sigma_i = 1, w_i \\ge 0\\)입니다. 제약조건 부분인 Amat과 bvec 부분을 입력할 때 이 부분을 고려해야 합니다. Dmat = covmat dvec = rep(0, 10) Amat = t(rbind(sqrt(diag(covmat)), diag(10))) bvec = c(1, rep(0, 10)) meq = 1 제약조건에 해당하는 Amat 부분과 bvec 부분은 최소분산 포트폴리오와 다소 다릅니다. 표 11.10에는 둘 간에 코드가 어떻게 다른지 나타나 있습니다. 표 11.10: Amat과 bvec 차이 비교 인자 최소분산 포트폴리오 최대분산효과 포트폴리오 Amat t(rbind(rep(1, 10), diag(10), -diag(10))) t(rbind(sqrt(diag(covmat)), diag(10))) bvec c(1, rep(0, 10), -rep(1, 10)) c(1, rep(0, 10)) 이해를 위해 Duality 방법의 제약조건을 행렬의 형태로 표현하면 다음과 같습니다. \\[ \\begin{bmatrix} \\sigma_1 & \\dots & \\sigma_{10} \\\\ 1 & \\dots & 0 \\\\ \\vdots & \\ddots & \\vdots \\\\ 0 & \\dots & 1 \\end{bmatrix} \\begin{bmatrix} w_1 \\\\ w_2 \\\\ \\vdots \\\\ w_{10} \\end{bmatrix} = \\begin{bmatrix} \\sigma_1w_1 + \\sigma_2w_2 + \\dots + \\sigma_{10}w_{10} \\\\ w_1 \\\\ \\vdots \\\\ w_{10} \\\\ \\end{bmatrix} \\ge \\begin{bmatrix} 1 \\\\ 0 \\\\ \\vdots \\\\ 0 \\end{bmatrix} \\] 1행의 \\(\\sigma_1w_1 + \\sigma_2w_2 + \\dots + \\sigma_{10}w_{10}\\)은 \\(\\sum_{i=1}^n w_i \\sigma_i = 1\\)과 같으며, 해당 식은 등위제약조건으로서 \\(\\sum_{i=1}^n w_i \\sigma_i = \\sigma_1w_1 + \\sigma_2w_2 + \\dots + \\sigma_{10}w_{10} = 1\\)을 의미합니다. 2행부터 마지막 행까지는 모두 \\(w_i \\ge 0\\) 조건으로써, 개별 자산의 투자비중이 0보다 큰 조건을 의미합니다. 행렬의 맨 왼쪽에 해당하는 Amat은 각 자산의 표준편차로 이루어진 벡터 행렬과, 1로 이루어진 대각행렬로 구성되어 있습니다. 먼저 diag(covmat)을 통해 분산-공분산 부분에서 대각 부분, 즉 분산 부분만을 추출할 수 있습니다. 개별 자산의 분산인 \\(\\sigma_{i,i}\\)는 \\(\\sigma_i\\sigma_i\\rho_{1,1}\\) 형태로 쓸 수 있으며, \\(\\rho_{1,1} = 1\\)을 적용하면 \\(\\sigma_i^2\\)와 같습니다. 따라서 대각부분 값에 제곱근을 계산하는 sqrt() 함수를 적용하면 각각의 표준편차만 남게 됩니다. 이를 diag(10)을 통해 만든 대각행렬과 행으로 묶어준 후 전치행렬을 입력해 줍니다. bvec는 행렬의 맨 오른쪽과 같이 등위 제약조건에 해당하는 1과 부등위 제약조건에 해당하는 0들로 구성되어 있습니다. 차후에 표준화 과정을 거쳐야 하므로 Duality 방법에서는 개별 자산의 투자비중이 1보다 작은 조건을 입력하지 않아도 됩니다. result = solve.QP(Dmat, dvec, Amat, bvec, meq) w = result$solution %>% round(., 4) %>% setNames(colnames(rets)) print(w) ## SPY IEV EWJ EEM TLT IEF IYR RWX ## 16.918 1.288 4.649 0.000 26.177 37.986 2.858 0.000 ## GLD DBC ## 7.882 11.965 입력된 목적함수와 제약조건들을 바탕으로 solve.QP() 함수를 통해 최적화를 수행한 후 최대분산효과를 만족하는 해를 구해보면, 비중의 합이 1을 초과하게 됩니다. \\(w_i = \\frac{w_i}{\\sum_{i=1}^nw_i}\\)를 통해 비중의 합이 1이 되도록 표준화를 해줍니다. w = (w / sum(w)) %>% round(., 4) print(w) ## SPY IEV EWJ EEM TLT IEF IYR RWX ## 0.1542 0.0117 0.0424 0.0000 0.2386 0.3462 0.0260 0.0000 ## GLD DBC ## 0.0718 0.1091 표준화 과정을 통해 비중의 합이 1이 되었습니다. data.frame(w) %>% ggplot(aes(x = factor(rownames(.), levels = rownames(.)), y = w)) + geom_col() + geom_col() + xlab(NULL) + ylab(NULL) 11.2.2 optimalPortfolio() 함수를 이용한 최적화 최소분산 포트폴리오와 동일하게 optimalPortfolio() 함수를 이용해 매우 간단하게 최대분산효과 포트폴리오를 구현할 수 있습니다. w = optimalPortfolio(covmat, control = list(type = 'maxdiv', constraint = 'lo')) %>% round(., 4) print(w) ## [1] 0.1542 0.0117 0.0424 0.0000 0.2386 0.3462 0.0260 ## [8] 0.0000 0.0718 0.1091 control 항목의 type에 maximum diversification을 의미하는 ’maxdiv’를 입력해주며, 제약조건에는 투자비중이 0보다 큰 lo(Long Only) 조건을 입력합니다. 패키지를 활용해 매우 간단하게 최대분산효과 포트폴리오를 구현할 수 있으며, 그 결과 또한 앞에서 계산한 것과 동일합니다. 해당 함수의 코드를 확인해보면, 최대분산효과 포트폴리오 계산 시 Min -DR 방법을 사용합니다. 11.2.3 최소 및 최대 투자비중 제약조건 최대분산효과 포트폴리오 역시 구석해 문제가 발생하며, 모든 자산에 골고루 투자하기 위해 개별 투자비중을 최소 5%, 최대 20%로 하는 제약조건을 추가하겠습니다. Duality 방법에서는 목적함수인 \\(min\\,^1/_2w'\\sigma w\\)과 제약조건인 \\(\\sum_{i=1}^n w_i \\sigma_i = 1, w_i \\ge 0\\)에 맞게 해를 구한 후 비중의 합이 1이 되도록 표준화하는 과정을 거쳤습니다. 따라서 비중의 최소 및 최대 제약조건은 단순히 \\(lb \\le w_i \\le ub\\)가 아닌 표준화 과정인 \\(w_i = \\frac{w_i}{\\sum_{i=1}^nw_i}\\)까지 고려해 적용해야 합니다. 표 11.11는 이를 수식으로 나타낸 것입니다. 표 11.11: 최소 및 최대비중 제약조건 최소비중 제약조건 최대비중 제약조건 \\(\\frac{w_i}{\\sum_{i=1}^nw_i} \\ge lb\\) \\(\\frac{w_i}{\\sum_{i=1}^nw_i} \\le ub\\) \\(\\Rightarrow -lb + \\frac{w_i}{\\sum_{i=1}^nw_i} \\ge 0\\) \\(\\Rightarrow ub - \\frac{w_i}{\\sum_{i=1}^nw_i} \\ge 0\\) \\(\\Rightarrow -lb + \\frac{w_i}{e^Tw} \\ge 0\\) \\(\\Rightarrow ub - \\frac{w_i}{e^Tw} \\ge 0\\) \\(\\Rightarrow -lb \\times e^Tw + w \\ge 0\\) \\(\\Rightarrow ub \\times e^Tw - w \\ge 0\\) \\(\\Rightarrow (-lb \\times e^T + I)w \\ge 0\\) \\(\\Rightarrow (ub \\times e^T - I)w \\ge 0\\) 최소 비중 제약조건인 \\(-lb \\times e^T + I\\)의 예를 행렬로 풀어보도록 하겠습니다. \\(-lb \\times e^T\\)의 경우 행렬로 표현하면 다음과 같으며, \\(-lb\\)로 이루어진 \\(n \\times n\\) 행렬입니다. \\[ \\begin{bmatrix} -lb \\\\ \\vdots \\\\ -lb \\end{bmatrix} \\begin{bmatrix} 1 \\\\ \\vdots \\\\ 1 \\end{bmatrix}^T = \\begin{bmatrix} -lb \\\\ \\vdots \\\\ -lb \\end{bmatrix} \\begin{bmatrix} 1 & \\dots & 1 \\end{bmatrix} = \\begin{bmatrix} -lb & \\dots & -lb \\\\ \\vdots & \\ddots & \\vdots \\\\ -lb & \\dots & -lb \\end{bmatrix}\\] \\(I\\)는 대각선 부분이 1, 나머지가 0인 항등행렬을 의미합니다. 따라서 \\((-lb \\times e^T + I)\\)를 계산하면 다음과 같습니다. \\[\\begin{bmatrix} -lb + 1 & \\dots & -lb \\\\ \\vdots & \\ddots & \\vdots \\\\ -lb & \\dots & -lb + 1 \\end{bmatrix}\\] 최소분산 포트폴리오와는 다르게 Duality 방법으로 최대분산효과 포트폴리오를 구현하면 최소 및 최대 제약조건이 우변이 아닌 좌변에 들어가게 되며, 해당 제약조건을 고려해 행렬로 표현하면 다음과 같습니다. \\[ \\begin{bmatrix} \\sigma_1 & \\dots & \\sigma_{10} \\\\ -lb + 1 & \\dots & -lb \\\\ \\vdots & \\ddots & \\vdots \\\\ -lb & \\dots & -lb + 1 \\\\ ub-1 & \\dots & ub \\\\ \\vdots & \\ddots & \\vdots \\\\ ub & \\dots & ub-1 \\end{bmatrix} \\begin{bmatrix} w_1 \\\\ w_2 \\\\ \\vdots \\\\ w_{10} \\end{bmatrix} = \\begin{bmatrix} \\sigma_1w_1 + \\sigma_2w_2 + \\dots + \\sigma_{10}w_{10} \\\\ -lb(w_1 + w_2 + \\dots + w_{10}) + w_1 \\\\ \\vdots \\\\ -lb(w_1 + w_2 + \\dots + w_{10}) + w_{10} \\\\ wb(w_1 + w_2 + \\dots + w_{10}) - w_1 \\\\ \\vdots \\\\ ub(w_1 + w_2 + \\dots + w_{10}) - w_{10} \\end{bmatrix} \\ge \\begin{bmatrix} 1 \\\\ 0 \\\\ \\vdots \\\\ 0 \\\\ 0 \\\\ \\vdots \\\\ 0 \\end{bmatrix} \\] 첫 번째 행 \\(\\sigma_1w_1 + \\sigma_2w_2 + \\dots + \\sigma_{10}w_{10}\\)은 등위 제약조건인 \\(\\sum_{i=1}^n w_i \\sigma_i = 1\\)에 해당하며, 두 번째 행부터는 부등위 제약조건에 해당합니다. 두 번째 행을 정리하면 \\(\\frac{w_1}{w_1+w_2+\\dots+w_{10}} \\ge lb\\), 즉 비중의 표준화가 고려된 최소비중 제약조건입니다. 마지막 행 역시 정리하면 \\(\\frac{w_1}{w_1+w_2+\\dots+w_{10}} \\le ub\\)가 되어 비중의 표준화가 고려된 최대비중 제약조건을 의미합니다. 위 행렬을 고려해 Amat과 bvec을 수정한 코드는 다음과 같습니다. Dmat = covmat dvec = rep(0, 10) Alb = -rep(0.05, 10) %*% matrix(1, 1, 10) + diag(10) Aub = rep(0.20, 10) %*% matrix(1, 1, 10) - diag(10) Amat = t(rbind(sqrt(diag(covmat)), Alb, Aub)) bvec = c(1, rep(0, 10), rep(0, 10)) meq = 1 result = solve.QP(Dmat, dvec, Amat, bvec, meq) w = result$solution w = (w / sum(w)) %>% round(., 4) %>% setNames(colnames(rets)) print(w) ## SPY IEV EWJ EEM TLT IEF IYR RWX ## 0.0500 0.0500 0.0500 0.0500 0.2000 0.2000 0.0500 0.0500 ## GLD DBC ## 0.1895 0.1105 Alb의 -rep(0.05, 10)는 \\(-lb\\) 부분, matrix(1, 1, 10)은 \\(e^T\\) 부분, diag(10)부분은 \\(I\\) 부분을 의미하며, 이는 최소비중 제약조건의 좌변(\\(-lb \\times e^T + I\\))과 같습니다. 동일하게 Aub는 최대비중 제약조건의 좌변(\\(ub \\times e^T - I\\))과 같으며, 결과를 확인하면 최소 및 최대비중 제약조건인 [5%, 20%]가 제대로 반영되었습니다. data.frame(w) %>% ggplot(aes(x = factor(rownames(.), levels = rownames(.)), y = w)) + geom_col() + geom_hline(aes(yintercept = 0.05), color = 'red') + geom_hline(aes(yintercept = 0.20), color = 'red') + xlab(NULL) + ylab(NULL) 11.2.4 각 자산 별 제약조건의 추가 최소분산 포트폴리오와 동일하게 자산별로 다른 제약조건을 추가해 포트폴리오를 구성하겠습니다. 표 11.12는 각 자산별 최소 및 최대 투자비중 값이며, 변경된 제약조건을 행렬의 형태로 나타내었습니다. 주의해야 할 점은 최소비중과 최대비중의 제약조건 추가 시 \\(\\frac{w_1}{w_1+w_2+\\dots+w_{10}} \\ge lb\\) 형태로 고려해야 한다는 점입니다. 표 11.12: 각 자산 별 최소 및 최대 제약조건 제약 1 2 3 4 5 6 7 8 9 10 최소 0.10 0.10 0.05 0.05 0.10 0.10 0.05 0.05 0.03 0.03 최대 0.25 0.25 0.20 0.20 0.20 0.20 0.10 0.10 0.08 0.08 \\[ \\scriptsize \\begin{bmatrix} \\sigma_1 & \\sigma_2 & \\dots & \\sigma_{10} \\\\ -lb_1 + 1 & -lb_1 & \\dots & -lb_1 \\\\ -lb_2 & -lb_2 + 1 & \\dots & -lb_2 \\\\ \\vdots & \\ddots & \\dots & \\vdots \\\\ -lb_{10} & -lb_{10} & \\dots & -lb_{10} + 1 \\\\ ub_1 - 1 & ub_1 & \\dots & ub_1 \\\\ ub_2 & ub_2 - 1 & \\dots & ub_2 \\\\ \\vdots & \\ddots & \\dots & \\vdots \\\\ ub_{10} & ub_{10} & \\dots & ub_{10} - 1 \\end{bmatrix} \\begin{bmatrix} w_1 \\\\ w_2 \\\\ \\vdots \\\\ w_{10} \\end{bmatrix} = \\begin{bmatrix} \\sigma_1w_1 + \\sigma_2w_2 + \\dots + \\sigma_{10}w_{10} \\\\ -lb_1(w_1 + w_2 + \\dots + w_{10}) + w_1 \\\\ -lb_2(w_1 + w_2 + \\dots + w_{10}) + w_2\\\\ \\vdots \\\\ -lb_{10}(w_1 + w_2 + \\dots + w_{10}) + w_{10} \\\\ wb_1(w_1 + w_2 + \\dots + w_{10}) - w_1 \\\\ wb_2(w_1 + w_2 + \\dots + w_{10}) - w_2 \\\\ \\vdots \\\\ ub_{10}(w_1 + w_2 + \\dots + w_{10}) - w_{10} \\end{bmatrix} \\ge \\begin{bmatrix} 1 \\\\ 0 \\\\ \\vdots \\\\ 0 \\\\ 0 \\\\ \\vdots \\\\ 0 \\end{bmatrix} \\] 기존에 공통적으로 적용되던 최소 및 최대 투자비중이 자산별로 다르게 구성되었습니다. 따라서 \\(-lb \\times e^T + I\\)와 \\(ub \\times e^T - I\\)만 \\(-lb_i \\times e^T + I\\) \\(ub_i \\times e^T - I\\)로 수정하면 해당 제약조건 역시 손쉽게 구현이 가능합니다. Dmat = covmat dvec = rep(0, 10) Alb = -c(0.10, 0.10, 0.05, 0.05, 0.10, 0.10, 0.05, 0.05, 0.03, 0.03) %*% matrix(1, 1, 10) + diag(10) Aub = c(0.25, 0.25, 0.20, 0.20, 0.20, 0.20, 0.10, 0.10, 0.08, 0.08) %*% matrix(1, 1, 10) - diag(10) Amat = t(rbind(sqrt(diag(covmat)), Alb, Aub)) bvec = c(1, rep(0, 10), rep(0, 10)) meq = 1 result = solve.QP(Dmat, dvec, Amat, bvec, meq) w = result$solution w = (w / sum(w)) %>% round(., 4) %>% setNames(colnames(rets)) print(w) ## SPY IEV EWJ EEM TLT IEF IYR RWX GLD DBC ## 0.10 0.10 0.09 0.05 0.20 0.20 0.05 0.05 0.08 0.08 최소 및 최대투자비중 제약조건을 나타내는 Alb와 Aub 부분이 자산별 각각의 제약비중으로 변경되었으며, 나머지 부분은 모두 동일합니다. 결괏값들이 모두 제약조건 내에 위치함을 확인할 수 있습니다. 11.3 위험균형 포트폴리오 포트폴리오를 구성하는 자산들과 전체 위험의 관계를 이해하기 위해서는, 먼저 한계 위험기여도(MRC: Marginal Risk Contribution)와 위험기여도(RC: Risk Contribution)에 대해 알아야 합니다. 한계 위험기여도는 특정 자산의 비중을 한 단위 증가시켰을 때 전체 포트폴리오의 위험의 증가를 나타내는 단위로서, 수학의 편미분과 같은 개념입니다. \\(i\\)번째 자산의 한계 위험기여도는 아래와 같이 나타낼 수 있습니다. \\[MRC_i = \\frac{\\partial\\sigma_p}{\\partial w_i} \\] \\(\\sqrt {f'(x)} = \\frac{f'(x)}{2\\sqrt{f(x)}}\\)인 사실을 이용하면, 한계 위험기여도는 다음과 같이 풀 수 있습 니다. 결과적으로 분자는 분산-공분산 행렬과 각 자산의 비중의 곱, 분모는 포트폴리오의 표준편차 형태로 나타납니다. \\[\\begin{equation*} \\begin{split} \\frac{\\partial\\sigma_p}{\\partial w} & = \\frac{\\partial(\\sqrt{w'\\Omega w})}{\\partial w} \\\\ & =\\frac{\\partial(w'\\Omega w)}{\\partial w} \\times \\frac{1}{2\\sqrt{w'\\Omega w}} \\\\ & =\\frac{2\\Omega w}{2\\sqrt{w'\\Omega w}} \\\\ & =\\frac{\\Omega w}{\\sqrt{w'\\Omega w}} \\end{split} \\end{equation*}\\] 위험기여도는 특정 자산이 포트폴리오 내에서 차지하는 위험의 비중입니다. 한계 위험기여도가 큰 자산도 포트폴리오 내에서 비중이 작다면, 포트폴리오 내에서 차지하는 위험의 비중은 작을 것입니다. 반면에, 한계 위험기여도가 작은 자산일지라도 비중이 압도적으로 많다면, 포트폴리오 내에서 차지하는 위험의 비중은 클 것입니다. 결과적으로 \\(i\\)번째 자산의 위험기여도는, \\(i\\)번째 자산의 한계 위험기여도와 포트폴리오 내 비중의 곱으로 이루어집니다. \\[RC_i = \\frac{\\partial\\sigma_p}{\\partial w_i} \\times w_i\\] 위험기여도를 코드로 나타내면 다음과 같습니다. 먼저 포트폴리오 비중인 \\(w\\)와 분산-공분산 행렬인 covmat을 이용해 한계 위험기여도를 계산합니다. 그 후 비중 \\(w\\)를 곱해 위험기여도를 계산해 준 후 합계가 1이 되도록 표준화를 해줍니다. get_RC = function(w, covmat) { port_vol = t(w) %*% covmat %*% w port_std = sqrt(port_vol) MRC = (covmat %*% w) / as.numeric(port_std) RC = MRC * w RC = c(RC / sum(RC)) return(RC) } 11.3.1 주식 60%와 채권 40% 포트폴리오의 위험기여도 자산배분에서 가장 많이 사용되는 투자방법은 주식에 60%, 채권에 40% 가량의 비율로 투자하는 것입니다. 주식과 채권이 서로 상관관계가 낮아 분산효과가 있다는 점, 장기적으로 주식이 채권에 비해 장기적으로 수익률이 높다는 점을 감안하면 이는 꽤나 합리적인 방법으로 보입니다. 그러나 눈에 보이는 비중이 60대 40이라도, 포트폴리오 내에서 각 자산이 가지고 있는 위험기여도 60대 40의 비중이 아닌 전혀 다른 비중을 가지고 있습니다. ret_stock_bond = rets[, c(1, 5)] cov_stock_bond = cov(ret_stock_bond) RC_stock_bond = get_RC(c(0.6, 0.4), cov_stock_bond) RC_stock_bond = round(RC_stock_bond, 4) print(RC_stock_bond) ## [1] 0.9759 0.0241 rets 데이터에서 첫 번째 행은 미국 주식 수익률을, 다섯 번째 행은 미국 장기채를 의미하므로, 해당 부분을 ret_stock_bond 변수에 지정합니다. 그 후 cov() 함수를 이용해 두 자산의 분산-공분산 행렬을 만들어주며, 위에서 만든 get_RC 함수를 통해 자산별 위험기여도를 계산합니다. 주식과 채권이 가지는 위험기여도는 각각 97.59%, 2.41%로서 투자 비중인 60%, 40%와는 전혀 다른 위험 비중을 보입니다. 즉, 주식이 포트폴리오 위험의 대부분을 차지하고 있습니다. 11.3.2 rp() 함수를 이용한 최적화 앞의 예제와 같이 특정 자산이 포트폴리오의 위험을 대부분 차지하는 문제를 막고, 모든 자산이 동일한 위험기여도를 가지는 포트폴리오가 위험균형 포트폴리오(Risk Parity Portfolio)(Qian 2011) 혹은 동일 위험기여도 포트폴리오(Equal Risk Contribution Portfolio)입니다. 이를 수식으로 쓰면 다음과 같습니다. \\[RC_1 = RC_2 = \\dots = RC_n\\] \\[\\frac{\\partial\\sigma_p}{\\partial w_1} \\times w_1 = \\frac{\\partial\\sigma_p}{\\partial w_2} \\times w_2 = \\dots = \\frac{\\partial\\sigma_p}{\\partial w_n} \\times w_n = \\frac{1}{n}\\] 위험균형 포트폴리오 역시 slsqp()나 optimalPortfolio() 함수를 이용해 구현할 수 있으나, 간혹 최적화된 값을 찾지 못할 때도 있습니다. 반면 cccp 패키지의 rp() 함수를 사용하면 매우 정확하게 위험균형 포트폴리오를 구성하는 비중을 계산할 수 있습니다. library(cccp) opt = rp(x0 = rep(0.1, 10), P = covmat, mrc = rep(0.1, 10)) w = getx(opt) %>% drop() w = (w / sum(w)) %>% round(., 4) %>% setNames(colnames(rets)) print(w) ## SPY IEV EWJ EEM TLT IEF IYR RWX ## 0.0600 0.0465 0.0565 0.0371 0.1747 0.3721 0.0395 0.0505 ## GLD DBC ## 0.0868 0.0763 x0은 최적화를 위한 초기 입력값이며 동일 비중인 10%씩을 입력합니다. P는 분산-공분산 행렬을 입력해줍니다. mrc는 목표로 하는 각 자산별 위험기여도 값18이며, 위험균형 포트폴리오의 경우 모든 자산의 위험기여도가 동일해야 하므로 10%씩을 입력합니다. rp() 함수는 위 입력 변수를 바탕으로 최적해를 찾아줍니다. getx() 함수를 통해 해를 추출할 수 있으며, drop()을 통해 벡터 형태로 변환합니다. 마지막으로 비중의 합이 1이 되기 위해 비중들의 합으로 나눠줍니다. 최종적으로 계산된 비중이 위험균형 포트폴리오를 만족하는 해가 됩니다. get_RC(w, covmat) ## [1] 0.09996 0.10006 0.09998 0.10008 0.09990 0.09991 ## [7] 0.10011 0.10002 0.09997 0.10001 get_RC() 함수를 통해 위험기여도를 확인해보면, 모든 자산이 거의 동일한 위험기여도를 가지는 것을 알 수 있습니다. 11.3.3 위험예산 포트폴리오 모든 자산의 위험기여도가 동일한 값이 아닌, 자산별로 다른 위험기여도를 가지는 포트폴리오를 구성해야 할 경우도 있습니다. 이러한 포트폴리오를 위험예산 포트폴리오(Risk Budget Portfolio)라고 합니다. 위험균형 포트폴리오 역시 각 자산의 위험예산이 \\(\\frac{1}{n}\\)로 동일한 특수 형태이며, rp() 함수를 이용하면 위험예산 포트폴리오 역시 손쉽게 구현할 수 있습니다. 먼저 각 자산 별 위험예산을 표 11.13과 같이 정합니다. 1~4번 자산은 각각 15%씩, 5~6번 자산은 각각 10%씩, 7~10번 자산은 각각 5%씩 위험예산을 부여하고자 합니다. 표 11.13: 위험예산 포트폴리오 예시 자산 1 2 3 4 5 6 7 8 9 10 예산 0.15 0.15 0.15 0.15 0.1 0.1 0.05 0.05 0.05 0.05 library(cccp) opt = rp(x0 = rep(0.1, 10), P = covmat, mrc = c(0.15, 0.15, 0.15, 0.15, 0.10, 0.10, 0.05, 0.05, 0.05, 0.05)) w = getx(opt) %>% drop() w = (w / sum(w)) %>% round(., 4) %>% setNames(colnames(rets)) print(w) ## SPY IEV EWJ EEM TLT IEF IYR RWX ## 0.0843 0.0663 0.0781 0.0529 0.1822 0.3935 0.0197 0.0250 ## GLD DBC ## 0.0543 0.0437 mrc에 목표로 하는 각 자산별 위험기여도를 입력하며, 나머지는 기존 위험균형 포트폴리오와 동일하게 입력합니다. get_RC(w, covmat) ## [1] 0.14995 0.14992 0.15004 0.15004 0.09996 0.09997 ## [7] 0.05007 0.04997 0.05005 0.05002 get_RC() 함수를 통해 위험기여도를 확인해보면 우리가 원하던 자산별 위험예산과 거의 동일한 것을 알 수 있습니다. 11.4 인덱스 포트폴리오 구성하기 이번에는 실제로 운용사에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성법에 대해 살펴보겠습니다. 투자는 크게 액티브 전략과 패시브 전략으로 나뉩니다. 액티브 전략이 벤치마크 대비 초과수익을 거두기 위해 적극적으로 투자를 하는 반면, 패시브 전략은 벤치마크를 그대로 추종하는 것을 목표로 합니다. 예를 들어 벤치마크가 2% 상승하였을 경우 액티브 전략은 이를 넘어서는 수익률을 얻고자 하지만, 패시브 전략은 정확히 2%의 수익률을 얻고자 합니다. 이러한 패시브 전략을 사용하는 펀드가 패시브 펀드 혹은 인덱스 펀드입니다. 흔히 벤치마크가 되는 지수(Index) 중 가장 많이 사용되는 것은 각 국가의 주가지수이며, 우리나라의 경우 KOSPI200 지수가 그 예입니다. 이 외에도 여러 국가를 대표하는 주가지수는 표 11.14 와 같습니다. 표 11.14: 각 국가의 대표 주가지수 국가 지수명 미국 S&P 500 영국 FTSE 100 일본 Nikkei 225 중국 CSI 300 한국 KOSPI 200 11.4.1 시가총액비중 계산하기 주가지수는 대부분 각 주식의 시가총액비중을 이용해 구성되며, 간단한 예제는 표 11.15와 같습니다. 표 11.15: 시가총액비중 계산 예시 종목 주가 상장 주식수 유동비 시가총액 지수 내 비중 A 40,000 4,000,000 70% 112,000,000,000 44.80% B 50,000 3,000,000 60% 90,000,000,000 36.00% C 30,000 2,000,000 80% 48,000,000,000 19.20% 합계 250,000,000,000 100.00% 일반적으로 시가총액은 [주가 X 상장 주식수]로 계산됩니다. 그러나 자사주 등의 이유로 기업의 상장된 모든 주식이 거래가 되는 것은 아니며, 상장된 주식 중 유동적으로 거래되는 비율을 유동비라고 합니다. 따라서 지수를 구성할 때는 시가총액을 [주가 X 상장 주식수 X 유동비]로 계산합니다. 이렇게 계산된 각 기업의 시가총액을 전체 시가총액의 합으로 나누어 지수 내 비중을 계산합니다. 11.4.2 인덱스 포트폴리오 복제하기 이번에는 앞서 구한 데이터를 바탕으로 KOSPI 200 지수를 복제하는 예제를 살펴보겠습니다. 해당 지수의 산출 방법은 다음과 같습니다. 한국거래소 유가증권시장(Stock Market)의 보통주 전 종목 가운데 시장 대표성, 유동성(거래량), 업종 대표성(업종은 9개 업종으로 구분)을 기준으로 한다. 즉 시가총액이 상위군에 속하고 거래량이 많은 종목 서열에 따른 200 종목이 편입된다. 즉 일정 규칙에 따라 200 종목이 선정되고, 각 종목들의 시가총액비중 만큼을 지수 내 비중으로 가지고 갑니다. 물론 선정된 200 종목이 단순하게 시가총액의 순서대로 선택되는 것은 아니며, 유동비의 경우 지수제공업체인 한국거래소에서 유료로 제공하므로 이를 구매하지 않는 이상 정확하게 알 수 없습니다. 그러나 지수 내 종목 및 대략의 시가총액비중은 해당 지수를 추종하는 ETF(예: KODEX 200)의 PDF를 통해 확인할 수 있으며, 본 책에서는 편의를 위해 시가총액상위 200 종목을 선택하고 유동비는 모두 100%로 가정하겠습니다. 앞서 구한 데이터를 이용해 상위 200 종목의 시가총액비중을 계산해보도록 하겠습니다. library(stringr) library(dplyr) KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1, stringsAsFactors = FALSE) KOSPI200 = KOR_ticker %>% filter(시장구분 == 'KOSPI') %>% slice(1:200) %>% mutate(시가총액비중 = 시가총액 / sum(시가총액)) 저장해둔 티커 정보를 불러옵니다. filter() 함수를 통해 코스피 시장에 해당하는 종목만을 선택합니다. slice() 함수를 통해 1번부터 200번 행 까지 데이터를 선택합니다. 각 주식의 시가총액을 전체 시가총액으로 나눈 후, 시가총액비중에 저장합니다. 계산된 시가총액비중을 시각화하도록 하겠습니다. library(ggplot2) KOSPI200 %>% ggplot(aes(x = reorder(종목명, -시가총액비중), y = 시가총액비중)) + geom_point() + xlab('종목명') + ylab('시가총액비중(%)') + scale_y_continuous(labels = scales::percent) ggplot() 함수를 이용해 시각화를 해주도록 하며, reorder()를 통해 시가총액비중으로 \\(x\\)축을 정리합니다. geom_point() 함수를 통해 산점도를 나타냅니다. \\(x\\)축과 \\(y\\)축의 이름을 변경합니다. scale_y_continuous() 함수 내 scales::percent 인자를 입력하여 \\(y\\) 축을 퍼센트 형식으로 변경합니다. 위 과정을 통해 코스피 시가총액 상위 200 종목의 시가총액비중을 계산 및 시각화 하였습니다. 그러나 \\(x\\)축에 해당하는 종목이 200개나 되어 종목명이 잘 보이지 않으며, 국내의 경우 삼성전자의 시가총액비중이 지나치게 커 다른 종목들의 비중이 잘 보이지 않습니다. 이를 고려하여 그림을 다시 수정해주도록 합니다. KOSPI200 %>% ggplot(aes(x = reorder(종목명, -시가총액비중), y = 시가총액비중)) + geom_point() + xlab('종목명') + ylab('시가총액비중(로그 스케일링)') + scale_y_log10() + scale_x_discrete(breaks = KOSPI200[seq(1, 200, by = 5), '종목명']) + theme(axis.text.x = element_text(angle = 60, hjust = 1)) scale_y_log10() 함수를 통해 \\(y\\)축을 로그값으로 스케일링 하였습니다. scale_x_discrete() 함수를 통해 \\(x\\)축에 일부 종목만을 표현하였습니다. theme() 내부에 element_text() 인자를 통해 \\(x\\)축 글자를 회전시키고 위치를 조정하였습니다. 다음으로 만일 여러분에게 1억이 있을 경우 KOSPI 200을 복제하는 방법을 알아보겠습니다. KOSPI200 = KOSPI200 %>% mutate(매수금액 = 100000000 * 시가총액비중, 매수주수 = 매수금액 / 종가) KOSPI200 %>% select(매수금액, 매수주수) %>% head() ## 매수금액 매수주수 ## 1 27758938 309.464 ## 2 5019221 37.739 ## 3 3659397 3.659 ## 4 2868742 11.076 ## 5 2846809 3.430 ## 6 2687743 3.565 여러분이 가지고 있는 금액에 시가총액비중을 곱해 각 주식당 매수해야 하는 금액을 구합니다. 그 후 각 금액을 현재가로 나누어 매수해야 하는 주식의 수를 계산합니다. 이론적으로는 계산된 주식수만큼 매수하여야 인덱스를 정확히 복제합니다. 그러나 주식은 1주 단위로 거래할 수 있으므로 소수점 단위로는 거래할 수는 없습니다. 만일 매수주수를 올림처리할 경우 총 매수금액이 보유금액보다 많이질 수 있으므로, 내림처리를 통해 매수주수를 수정해줍니다. KOSPI200 = KOSPI200 %>% mutate(매수주수 = floor(매수주수)) KOSPI200 %>% select(매수금액, 매수주수) %>% head() ## 매수금액 매수주수 ## 1 27758938 309 ## 2 5019221 37 ## 3 3659397 3 ## 4 2868742 11 ## 5 2846809 3 ## 6 2687743 3 floor() 함수를 통해 내림처리를 하였으며, 각 주수만큼 매수할 경우 KOSPI 200 지수를 매우 유사하게 복제할 수 있습니다. inv_money = KOSPI200 %>% mutate(실제매수금액 = 종가 * 매수주수) %>% summarize(sum(실제매수금액)) print(inv_money) ## sum(실제매수금액) ## 1 87662750 주수를 내림 처리하였으므로 실제 매수에 사용되는 금액은 1억원 보다 약간 모자르게 되며, 1억과 해당금액의 차이는 현금으로 보유하거나 해당 지수를 추종하는 ETF 및 펀드 투자에 사용해도 됩니다. 11.4.3 팩터를 이용한 인핸스드 포트폴리오 구성하기 위 방법을 통해 포트폴리오를 구성할 경우 이론적으로는 벤치마크와 수익률이 거의 동일합니다. 그러나 약간의 위험을 감수하여 벤치마크 대비 미세한 초과수익을 원하는 수요가 존재하며, 이러한 펀드가 인핸스드 인덱스 펀드입니다. 이를 위해 층화추출법, 비중조절법, 차익거래 등의 전략이 활용되며, 본 책에서는 가장 널리 사용되는 비중조절법에 대해 알아보겠습니다. 그 예제로써 PBR을 이용해 시가총액비중을 조절해보도록 하겠습니다. KOSPI200 = KOSPI200 %>% select(종목명, PBR, 시가총액비중) %>% mutate(PBR = as.numeric(PBR)) 먼저 필요한 종목코드, 종목명, PBR, 시가총액비중 열만 선택합니다. 그 후 문자열 형태의 PBR을 숫자 형태로 변경해주며, PBR 데이터가 없어 [-]로 표시되었던 종목의 PBR은 NA로 변경됩니다. 11.4.3.1 단순 가감법 먼저 가장 손쉬운 방법은 PBR의 랭킹을 구한 후, PBR이 낮은 상위 n개 종목에는 일정 비중씩을 더하며, 나머지 종목들에서 해당 비중만큼을 빼는 방법입니다. 몇개의 종목에서 얼마씩의 비중을 조절할지는 투자자의 재량에 달렸으며, 본 예제에서는 상위 100 종목에 각각 5bp를 더해주며, 나머지 100 종목에서 각각 5bp를 빼주도록 하겠습니다. KOSPI200 = KOSPI200 %>% mutate(랭킹 = rank(PBR), 조절비중 = ifelse(랭킹 <= 100, 시가총액비중 + 0.0005, 시가총액비중 - 0.0005), 조절비중 = ifelse(조절비중 < 0, 0, 조절비중), 조절비중 = 조절비중 / sum(조절비중), 차이 = 조절비중 - 시가총액비중) library(tidyr) head(KOSPI200) ## 종목명 PBR 시가총액비중 랭킹 조절비중 ## 1 삼성전자 2.39 0.27759 134.0 0.27729 ## 2 SK하이닉스 2.02 0.05019 123.0 0.04973 ## 3 LG화학 4.60 0.03659 167.0 0.03612 ## 4 현대차 1.02 0.02869 87.0 0.02921 ## 5 삼성바이오로직스 12.61 0.02847 189.0 0.02799 ## 6 삼성SDI 4.31 0.02688 164.5 0.02640 ## 차이 ## 1 -0.0002972 ## 2 -0.0004636 ## 3 -0.0004736 ## 4 0.0005214 ## 5 -0.0004795 ## 6 -0.0004807 tail(KOSPI200) ## 종목명 PBR 시가총액비중 랭킹 조절비중 ## 195 롯데하이마트 0.45 0.0004528 19.5 0.0009535 ## 196 SK디앤디 2.41 0.0004509 136.0 0.0000000 ## 197 동국제강 0.47 0.0004482 23.0 0.0009489 ## 198 대한해운 1.02 0.0004381 87.0 0.0009388 ## 199 쿠쿠홈시스 1.95 0.0004373 122.0 0.0000000 ## 200 풍산 0.61 0.0004365 39.0 0.0009372 ## 차이 ## 195 0.0005007 ## 196 -0.0004509 ## 197 0.0005007 ## 198 0.0005007 ## 199 -0.0004373 ## 200 0.0005007 rank() 함수를 통해 PBR의 랭킹을 구합니다. 랭킹이 100 이하일 시, 즉 저PBR 100개 종목에는 시가총액비중에서 5bp씩을 더해주며, 반대로 고PBR 100개 종목에는 5bp 씩을 빼줍니다. 시가총액비중이 5bp 미만인 종목에서 5bp를 차감할 경우 비중이 0 미만이 되므로, 이러한 경우는 투자비중을 0으로 만들어 줍니다. 3번 결과에 따라 비중의 합이 1과 다르게 되므로, 각각의 비중을 합으로 나누어 값을 다시 계산해 줍니다. 시가총액비중과 조절비중의 차이를 계산합니다. PBR 랭킹이 100 미만인 종목, 즉 저PBR 종목은 시가총액비중 대비 투자비중이 많으며, 이와 반대로 고PBR 종목은 시가총액비중 대비 투자비중이 작습니다. 즉 장기적으로 저PBR 종목이 고PBR 종목 대비 우수한 성과를 보일 경우, 저PBR 종목에 더 높은 비중을 준 해당 포트폴리오 역시 벤치마크 대비 우수한 성과를 기록할 수 있을 것입니다. KOSPI200 %>% ggplot(aes(x = reorder(종목명, -시가총액비중), y = 시가총액비중)) + geom_point() + geom_point(data = KOSPI200, aes(x = reorder(종목명, -시가총액비중), y = 조절비중), color = 'red', shape = 4) + xlab('종목명') + ylab('비중(%)') + coord_cartesian(ylim = c(0, 0.03)) + scale_x_discrete(breaks = KOSPI200[seq(1, 200, by = 5), '종목명']) + scale_y_continuous(labels = scales::percent) + theme(axis.text.x = element_text(angle = 60, hjust = 1)) 검은색 점은 인덱스 내 시가총액비중이며, 붉은색 엑스표시는 5bp씩 더하거나 뺀 투자비중입니다. 약간씩의 베팅만 했으므로 기초지수와 크게 차이가 없습니다. KOSPI200_mod = KOSPI200 %>% arrange(PBR) KOSPI200_mod %>% ggplot(aes(x = reorder(종목명, PBR), y = 차이)) + geom_point() + geom_col(aes(x = reorder(종목명, PBR), y = PBR /10000), fill = 'blue', alpha = 0.2) + xlab('종목명') + ylab('차이(%)') + scale_y_continuous(labels = scales::percent, sec.axis = sec_axis(~. * 10000, name = "PBR")) + scale_x_discrete(breaks = KOSPI200_mod[seq(1, 200, by = 10), '종목명']) + theme(axis.text.x = element_text(angle = 60, hjust = 1)) PBR 기준 오름차순을 한 후 그림을 그려보면, PBR이 낮은 종목에는 비중이 추가되며 PBR이 높은 종목에는 비중이 감소되는 것이 쉽게 확인됩니다. 11.4.3.2 팩터에 대한 전체 종목의 틸트 위 방법의 경우 상위 종목과 하위 종목에 동일한 비중을 더하거나 빼주었습니다. 그러나 팩터가 강한 종목의 경우 더욱 많은 비중을 더하고, 팩터가 약한 종목의 경우 더욱 많은 비중을 빼는 등 훨씬 적극적으로 포트폴리오를 구성할 수도 있습니다. 이를 위해서는 먼저 확률밀도함수와 누적분포함수를 이해해야 합니다. 파란색 선은 확률밀도함수로써, 각 \\(x\\)축에 대한 확률값을 나타냅니다. 빨간색 선은 누적분포함수로써, 각 \\(x\\)축이 오른쪽으로 이동함에 따른 분포의 누적확률을 계산합니다. 확률의 합은 1이므로 가장 오른쪽은 1의 값을 가지게 됩니다. 이를 [팩터의 결합 방법]에서 계산한 Z-Score에 응용할 수도 있으며, 이 과정은 표 11.16에 나와있습니다. 표 11.16: 누적분포함수를 이용한 비중 조절 종목 PBR 랭킹 Z-Score X (-1) 누적확률 시가총액비중 시총 X 누적확률 비중 재계산 차이 A 0.50 1 1.26 0.90 20.69% 18.56% 33.10% 12.38% B 0.70 2 0.63 0.74 31.03% 22.86% 40.72% 9.68% C 1.00 3 0.00 0.50 18.97% 9.48% 16.89% -2.07% D 1.20 4 -0.63 0.26 13.79% 3.64% 6.48% -7.32% E 1.50 5 -1.26 0.10 15.52% 1.60% 2.85% -12.67% 각각의 PBR에 대해 랭킹을 구합니다. 랭킹을 바탕으로 Z-Score를 구하며, 결과에 (-1)을 곱해줍니다. 이는 랭킹이 높은 종목의 경우 Z-Score가 음수로 나오므로, 해당 종목의 누적확률 값을 높게하기 위해 양수로 전환해주는 것입니다. 구해진 Z-Score를 바탕으로 누적확률을 구합니다. 랭킹이 높은 종목, 즉 저PBR일 수록 해당 값이 크게 나옵니다. 지수 내 시가총액비중에 누적확률값을 곱해줍니다. 저PBR 종목일수록 원래의 시가총액비중과 비슷하게 유지되며, 고PBR 종목의 경우 시가총액비중 대비 훨씬 낮을 값을 보입니다. 투자비중의 합이 1이 되도록 재표준화를 해줍니다. 각 종목의 차이를 보면, PBR이 낮을수록 증가되는 비중이 크며, PBR아 높을수록 감소되는 비중 역시 큽니다. 이처럼 Z-Score와 누적확률을 이용할 경우 훨씬 팩터에 대한 노출을 크게 할 수 있습니다. 이번에는 KOSPI 200 전 종목을 대상으로 PBR 대상 팩터 틸트 포트폴리오를 구성하도록 하겠습니다. KOSPI200_tilt = KOSPI200 %>% select(종목명, PBR, 시가총액비중, 랭킹) %>% mutate(zscore = -scale(랭킹), cdf = pnorm(zscore), 투자비중 = 시가총액비중 * cdf, 투자비중 = 투자비중 / sum(투자비중), 차이 = 투자비중 - 시가총액비중) head(KOSPI200_tilt) ## 종목명 PBR 시가총액비중 랭킹 zscore ## 1 삼성전자 2.39 0.27759 134.0 -0.5788 ## 2 SK하이닉스 2.02 0.05019 123.0 -0.3888 ## 3 LG화학 4.60 0.03659 167.0 -1.1490 ## 4 현대차 1.02 0.02869 87.0 0.2333 ## 5 삼성바이오로직스 12.61 0.02847 189.0 -1.5291 ## 6 삼성SDI 4.31 0.02688 164.5 -1.1058 ## cdf 투자비중 차이 ## 1 0.28136 0.206593 -0.070997 ## 2 0.34873 0.046300 -0.003893 ## 3 0.12528 0.012127 -0.024467 ## 4 0.59222 0.044939 0.016252 ## 5 0.06312 0.004753 -0.023715 ## 6 0.13441 0.009556 -0.017322 tail(KOSPI200_tilt) ## 종목명 PBR 시가총액비중 랭킹 zscore cdf ## 195 롯데하이마트 0.45 0.0004528 19.5 1.3995 0.9192 ## 196 SK디앤디 2.41 0.0004509 136.0 -0.6134 0.2698 ## 197 동국제강 0.47 0.0004482 23.0 1.3390 0.9097 ## 198 대한해운 1.02 0.0004381 87.0 0.2333 0.5922 ## 199 쿠쿠홈시스 1.95 0.0004373 122.0 -0.3715 0.3551 ## 200 풍산 0.61 0.0004365 39.0 1.0626 0.8560 ## 투자비중 차이 ## 195 0.0011009 0.0006481 ## 196 0.0003218 -0.0001291 ## 197 0.0010785 0.0006303 ## 198 0.0006863 0.0002482 ## 199 0.0004108 -0.0000265 ## 200 0.0009885 0.0005519 먼저 필요한 열만 선택합니다. scale() 함수를 통해 Z-Score를 구하며, (-1)을 곱해줍니다. pnorm() 함수를 통해 누적확률 값을 구해집니다. 시가총액비중에 누적확률을 곱해 새로운 투자비중을 구한 후, 이를 재표준화 해줍니다. 틸트된 비중과 기존 비중간의 차이를 구합니다. 위 방법은 시가총액비중이 클수록, 그리고 Z-Score의 절대값이 클수록 비중의 차이가 많이 발생하게 됩니다. 각 종목의 투자비중을 그림으로 나타내겠습니다. KOSPI200 %>% ggplot(aes(x = reorder(종목명, -시가총액비중), y = 시가총액비중)) + geom_point() + geom_point(data = KOSPI200_tilt, aes(x = reorder(종목명, -시가총액비중), y = 투자비중), color = 'red', shape = 4) + xlab('종목명') + ylab('비중(%)') + coord_cartesian(ylim = c(0, 0.03)) + scale_x_discrete(breaks = KOSPI200[seq(1, 200, by = 5), '종목명']) + scale_y_continuous(labels = scales::percent) + theme(axis.text.x = element_text(angle = 60, hjust = 1)) 검은색 점은 인덱스 내 시가총액비중이며, 붉은색 엑스표시는 새롭게 구한 투자비중입니다. 단순이 동일한 비중을 더하거나 빼는것 보다 비중 차이의 폭이 훨씬 크며, 이는 전체 포트폴리오가 팩터에 노출된 정도가 크다는 것을 의미합니다. 그러나 실무에서는 이러한 차이가 지나치게 벌어지는 것을 방지하기 위한 제약이 있습니다. 그 예로써 종목당 시가총액비중과 투자비중의 차이가 50bp 이상이 되지 않는 제약이 있는 경우를 생각해봅시다. (제약을 더 크게 설정할 수록, 지수 대비 베팅의 크기가 커집니다.) KOSPI200_tilt %>% ggplot(aes(x = reorder(종목명, -시가총액비중), y = 차이)) + geom_point() + geom_hline(aes(yintercept = 0.005), color = 'red') + geom_hline(aes(yintercept = -0.005), color = 'red') + xlab('종목명') + ylab('비중 차이(%)') + scale_x_discrete(breaks = KOSPI200[seq(1, 200, by = 5), '종목명']) + scale_y_continuous(labels = scales::percent) + theme(axis.text.x = element_text(angle = 60, hjust = 1)) 시가총액이 큰 종목의 경우 허용치인 50bp를 넘어가는 경우가 다수 존재합니다. 이를 방지하기 위해 비중에 제약조건을 두어야 합니다. 예를 들어 삼성전자의 경우 둘 간의 비중의 차이가 -0.071 로 지나치게 크므로 [시가총액비중 - 50bp] 가 투자되도록 변경해 줍니다. 타 종목 역시 이와 동일하게 제약조건을 추가해 줍니다. KOSPI200_tilt = KOSPI200_tilt %>% mutate_at(vars(투자비중), list(~ifelse(차이 < -0.005, 시가총액비중 - 0.005, 투자비중))) %>% mutate_at(vars(투자비중), list(~ifelse(차이 > 0.005, 시가총액비중 + 0.005, 투자비중))) %>% mutate(투자비중 = 투자비중 / sum(투자비중), 차이 = 투자비중 - 시가총액비중) head(KOSPI200_tilt) ## 종목명 PBR 시가총액비중 랭킹 zscore ## 1 삼성전자 2.39 0.27759 134.0 -0.5788 ## 2 SK하이닉스 2.02 0.05019 123.0 -0.3888 ## 3 LG화학 4.60 0.03659 167.0 -1.1490 ## 4 현대차 1.02 0.02869 87.0 0.2333 ## 5 삼성바이오로직스 12.61 0.02847 189.0 -1.5291 ## 6 삼성SDI 4.31 0.02688 164.5 -1.1058 ## cdf 투자비중 차이 ## 1 0.28136 0.24942 -0.028174 ## 2 0.34873 0.04236 -0.007829 ## 3 0.12528 0.02891 -0.007686 ## 4 0.59222 0.03082 0.002136 ## 5 0.06312 0.02147 -0.006995 ## 6 0.13441 0.02002 -0.006860 mutate_at() 함수를 이용해 시가총액비중과 투자비중의 차이가 50bp 미만일 경우 투자비중을 [시가총액 - 50bp]로 변경해주며, 50bp 초과일 경우 [시가총액 + 50bp]로 변경해줍니다. 재표준화 작업을 거쳐준 후 차이를 다시 계산합니다. 위 방법을 통해 차이가 50bp가 되도록 강제로 설정하였으나, 재표준화를 거치는 과정에서 차이가 50bp를 넘는 종목이 다시 발생하게 됩니다. 모든 종목의 차이가 50bp 이내가 되도록 해당 작업을 반복해줍니다. while (max(abs(KOSPI200_tilt$차이)) > (0.005 + 0.00001)) { KOSPI200_tilt = KOSPI200_tilt %>% mutate_at(vars(투자비중), list(~ifelse(차이 < -0.005, 시가총액비중 - 0.005, 투자비중))) %>% mutate_at(vars(투자비중), list(~ifelse(차이 > 0.005, 시가총액비중 + 0.005, 투자비중))) %>% mutate(투자비중 = 투자비중 / sum(투자비중), 차이 = 투자비중 - 시가총액비중) } head(KOSPI200_tilt) ## 종목명 PBR 시가총액비중 랭킹 zscore ## 1 삼성전자 2.39 0.27759 134.0 -0.5788 ## 2 SK하이닉스 2.02 0.05019 123.0 -0.3888 ## 3 LG화학 4.60 0.03659 167.0 -1.1490 ## 4 현대차 1.02 0.02869 87.0 0.2333 ## 5 삼성바이오로직스 12.61 0.02847 189.0 -1.5291 ## 6 삼성SDI 4.31 0.02688 164.5 -1.1058 ## cdf 투자비중 차이 ## 1 0.28136 0.27258 -0.00500506 ## 2 0.34873 0.04519 -0.00500084 ## 3 0.12528 0.03159 -0.00500059 ## 4 0.59222 0.02872 0.00003588 ## 5 0.06312 0.02347 -0.00500044 ## 6 0.13441 0.02188 -0.00500041 위와 동일한 코드에 while() 구문을 활용하여 둘 간의 차이가 50bp 보다 클 경우 해당 작업을 계속해서 반복하며, 결과적으로 차이가 거의 50bp에 수렴합니다. KOSPI200_tilt %>% ggplot(aes(x = reorder(종목명, -시가총액비중), y = 차이)) + geom_point() + geom_hline(aes(yintercept = 0.005), color = 'red') + geom_hline(aes(yintercept = -0.005), color = 'red') + xlab('종목명') + ylab('비중 차이(%)') + scale_x_discrete(breaks = KOSPI200[seq(1, 200, by = 5), '종목명']) + scale_y_continuous(labels = scales::percent) + theme(axis.text.x = element_text(angle = 60, hjust = 1)) 모든 종목이 제약조건 내에 들어오게 되었습니다. KOSPI200 %>% ggplot(aes(x = reorder(종목명, -시가총액비중), y = 시가총액비중)) + geom_point() + geom_point(data = KOSPI200_tilt, aes(x = reorder(종목명, -시가총액비중), y = 투자비중), color = 'red', shape = 4) + xlab('종목명') + ylab('비중(%)') + coord_cartesian(ylim = c(0, 0.03)) + scale_x_discrete(breaks = KOSPI200[seq(1, 200, by = 5), '종목명']) + scale_y_continuous(labels = scales::percent) + theme(axis.text.x = element_text(angle = 60, hjust = 1)) 제약조건으로 인해 기초지수와의 차이가 줄어들었지만, 기존 단순가감법 보다는 적극적으로 베팅이 되었습니다. KOSPI200_tilt_mod = KOSPI200_tilt %>% arrange(PBR) KOSPI200_tilt_mod %>% ggplot(aes(x = reorder(종목명, PBR), y = 차이)) + geom_point() + geom_col(aes(x = reorder(종목명, PBR), y = PBR /2000), fill = 'blue', alpha = 0.2) + xlab('종목명') + ylab('차이(%)') + scale_y_continuous(labels = scales::percent, sec.axis = sec_axis(~. * 2000, name = "PBR")) + scale_x_discrete(breaks = KOSPI200_mod[seq(1, 200, by = 10), '종목명']) + theme(axis.text.x = element_text(angle = 60, hjust = 1)) PBR에 따른 비중의 차이 역시 단순 가감법보다 훨씬 증가했습니다. 실무에서는 단순히 PBR처럼 하나의 지표만 살펴보기 보다는 앞서 살펴본 멀티팩터를 이용해 비중을 틸트하기도 하며, 좀 더 다양한 제약조건을 추가하기도 합니다. References "], -["포트폴리오-백테스트.html", "Chapter 12 포트폴리오 백테스트 12.1 Return.Portfolio() 함수 12.2 전통적인 60대 40 포트폴리오 백테스트 12.3 시점 선택 전략 백테스트 12.4 동적 자산배분 백테스트", " Chapter 12 포트폴리오 백테스트 백테스트란 현재 생각하는 전략을 과거부터 실행했을 때 어떠한 성과가 발생하는지 테스트해보는 과정입니다. 과거의 데이터를 기반으로 전략을 실행하는 퀀트 투자에 있어서 이는 핵심 단계이기도 합니다. 백테스트 결과를 통해 해당 전략의 손익뿐만 아니라 각종 위험을 대략적으로 판단할 수 있으며, 어떤 구간에서 전략이 좋았는지 혹은 나빴는지에 대한 이해도 키울 수 있습니다. 이러한 이해를 바탕으로 퀀트 투자를 지속한다면 단기적으로 수익이 나쁜 구간에서도 그 이유에 대한 객관적인 안목을 키울 수 있으며, 확신을 가지고 전략을 지속할 수 있습니다. 그러나 백테스트를 아무리 보수적으로 혹은 엄밀하게 진행하더라도 이미 일어난 결과를 대상으로 한다는 사실은 변하지 않습니다. 백테스트 수익률만을 보고 투자에 대해 판단하거나, 혹은 동일한 수익률이 미래에도 반복될 것이라고 믿는다면 이는 백미러만 보고 운전하는 것처럼 매우 위험한 결과를 초래할 수도 있습니다. R에서 백테스트는 PerformanceAnalytics 패키지의 Return.portfolio() 함수를 사용해 매우 간단하게 수행할 수 있습니다. 이 CHAPTER에서는 해당 함수를 알아보고 구체적인 사용 방법에 대한 예시로서 전통적인 주식 60% & 채권 40% 포트폴리오, 시점 선택 전략, 동적 자산배분에 대한 백테스트를 실시합니다. 12.1 Return.Portfolio() 함수 프로그래밍을 이용해 백테스트할 때 전략이 단순하다면 단 몇 줄만으로도 테스트가 가능합니다. 그러나 전략이 복잡해지거나 적용해야 할 요소가 많아질 경우, 패키지를 이용하는 것이 효율적인 방법입니다. PerformanceAnalytics 패키지의 Return.portfolio() 함수는 백테스트를 수행하는데 가장 대중적으로 사용되는 함수입니다. 해당 함수의 가장 큰 장점은 각 자산의 수익률과 리밸런싱 비중만 있으면 백테스트 수익률, 회전율 등을 쉽게 계산할 수 있으며, 리밸런싱 시점과 수익률의 시점이 일치하지 않아도 된다는 점입니다. 즉, 수익률 데이터는 일간, 리밸런싱 시점은 분기 혹은 연간으로 된 경우에도 매우 쉽게 백테스트를 수행할 수 있습니다. 12.1.1 인자 목록 살펴보기 먼저 Return.portfolio() 함수는 다음과 같은 형태로 구성되어 있으며, 표 12.1는 인자의 내용을 정리한 것입니다. Return.portfolio(R, weights = NULL, wealth.index = FALSE, contribution = FALSE, geometric = TRUE, rebalance_on = c(NA, "years", "quarters", "months", "weeks", "days"), value = 1, verbose = FALSE, ...) 표 12.1: Return.portfolio() 함수 내 인자 설명 인자 내용 R 각 자산 수익률 데이터 weights 리밸런싱 시기의 자산별 목표 비중. 미 입력시 동일비중 포트폴리오를 가정해 백테스트가 이루어짐 wealth.index 포트폴리오 시작점이 1인 wealth index에 대한 생성 여부이며, 디폴트는 FALSE로 설정 contribution 포트폴리오 내에서 자산별 성과기여를 나타내는지에 대한 여부이며, 디폴트는 FALSE로 설정 geometric 포트폴리오 수익률 계산시 복리(기하)수익률 적용 여부이며, 디폴트는 TRUE로서 복리수익률을 계산 rebalance_on weight 값이 미입력 혹은 매번 같은 비중일 경우, 리밸런싱 주기를 선택할 수 있음 value 초기 포트폴리오 가치를 의미하며, 디폴트는 1 verbose 부가적인 결과를 표시할지에 대한 여부. 디폴트인 FALSE를 입력하면 포트폴리오 수익률만이 시계열 형태로 계산되며, TRUE를 입력하면 수익률 외에 자산 별 성과기여, 비중, 성과 등이 리스트 형태로 계산됨 이 중 가장 중요한 인자는 개별 자산의 수익률인 R과 리밸런싱 시기의 자산별 목표 비중인 weights입니다. 매 리밸런싱 시점마다 적용되는 자산별 비중이 동일할 경우(예: 매월 말 60%대 40% 비중으로 리밸런싱) 상수 형태로 입력해도 되지만, 시점마다 자산별 목표비중이 다를 경우 weights는 시계열 형태로 입력되어야 합니다. 목표 비중을 시계열 형태로 입력할 때 주의해야 할 점은 다음과 같습니다. 시계열 형태로 인식할 수 있도록 행 이름 혹은 인덱스가 날짜 형태로 입력되어야 합니다. 수익률 데이터와 비중 데이터의 열 개수는 동일해야 하며, 각 열에 해당하는 자산은 동일해야 합니다. 즉, 수익률 데이터의 첫 번째 열에 A주식 데이터가 있다면, 비중 데이터의 첫 번째 열도 A주식의 목표 비중을 입력해야 합니다. 각 시점의 비중의 합은 1이 되어야 합니다. 그렇지 않을 경우 제대로 된 수익률이 계산되지 않습니다. weights에 값을 입력하지 않을 경우 동일비중 포트폴리오를 구성하며, 포트폴리오 리밸런싱은 하지 않습니다. 12.1.2 출력값 살펴보기 해당 함수는 verbose를 TRUE로 설정하면 다양한 결괏값을 리스트 형태로 반환합니다 표 12.2: Return.portfolio() 함수 반환값 결과 내용 returns 포트폴리오 수익률 contribution 일자별 개별 자산의 포트폴리오 수익률 기여도 BOP.Weight 일자별 개별 자산의 포트폴리오 내 비중(시작시점). 리밸런싱이 없을 시 직전 기간 EOP.Weight와 동일 EOP.Weight 일자별 개별 자산의 포트폴리오 내 비중(종료시점) BOP.Value 일자별 개별 자산의 가치(시작시점). 리밸런싱이 없을 시 직전 기간 EOP.Value와 동일 EOP.Value 일자별 개별 자산의 가치(종료시점) 12.2 전통적인 60대 40 포트폴리오 백테스트 Return.portfolio() 함수의 가장 간단한 예제로서 전통적인 60대 40 포트폴리오를 백테스트합니다. 해당 포트폴리오는 주식과 채권에 각각 60%와 40%를 투자하며, 특정 시점마다 해당 비중을 맞춰주기 위해 리밸런싱을 수행합니다. 매해 말 리밸런싱을 가정하는 예제를 살펴보겠습니다. library(quantmod) library(PerformanceAnalytics) library(magrittr) ticker = c('SPY', 'TLT') getSymbols(ticker) ## [1] "SPY" "TLT" prices = do.call(cbind, lapply(ticker, function(x) Ad(get(x)))) rets = Return.calculate(prices) %>% na.omit() 글로벌 자산의 ETF 데이터 중 주식(S&P 500)과 채권(미국 장기채)에 해당하는 데이터를 다운로드한 후 수익률을 계산합니다. cor(rets) ## SPY.Adjusted TLT.Adjusted ## SPY.Adjusted 1.0000 -0.4402 ## TLT.Adjusted -0.4402 1.0000 cor() 함수를 통해 두 자산간의 상관관계를 확인해보면 -0.44로써 매우 낮은 상관관계를 보이며, 강한 분산효과를 기대해볼 수 있습니다. portfolio = Return.portfolio(R = rets, weights = c(0.6, 0.4), rebalance_on = 'years', verbose = TRUE) Return.portfolio() 함수를 이용하여 백테스트를 실행합니다. 자산의 수익률인 R에는 수익률 테이블인 rets를 입력합니다. 리밸런싱 비중인 weights에는 60%와 40%를 의미하는 c(0.6, 0.4)를 입력합니다. 리밸런싱 시기인 rebalance_on에는 연간 리밸런싱에 해당하는 years를 입력합니다. 리밸런싱 주기는 이 외에도 quarters, months, weeks, days도 입력이 가능합니다. 결과물들을 리스트로 확인하기 위해 verbose를 TRUE로 설정합니다. 위 과정을 통해 주식과 채권 투자비중을 매해 60%와 40%로 리밸런싱하는 포트폴리오의 백테스트가 실행됩니다. 표 12.3은 함수 내에서 포트폴리오의 수익률이 어떻게 계산되는지를 요약한 과정입니다. 표 12.3: Return.portfolio() 계산 과정 시작금액 시작합계 시작비중 수익률 종료금액 종료합계 종료비중 최종수익률 1.주식 2.채권 3.1+2 4.주식 5.채권 6.주식 7.채권 8.주식 9.채권 10.8+9 11.주식 12.채권 13.최종 2017-12-26 1.603 0.940 2.543 0.630 0.370 -0.001 0.003 1.601 0.943 2.544 0.629 0.371 0.000 2017-12-27 1.601 0.943 2.544 0.629 0.371 0.000 0.013 1.602 0.956 2.557 0.626 0.374 0.005 2017-12-28 1.602 0.956 2.557 0.626 0.374 0.002 -0.001 1.605 0.955 2.560 0.627 0.373 0.001 2017-12-29 1.605 0.955 2.560 0.627 0.373 -0.004 0.002 1.599 0.956 2.555 0.626 0.374 -0.002 2018-01-02 1.533 1.022 2.555 0.600 0.400 0.007 -0.011 1.544 1.011 2.555 0.604 0.396 0.000 2018-01-03 1.544 1.011 2.555 0.604 0.396 0.006 0.005 1.554 1.016 2.570 0.605 0.395 0.006 2018-01-04 1.554 1.016 2.570 0.605 0.395 0.004 0.000 1.560 1.016 2.576 0.606 0.394 0.002 먼저 2017-12-27에 해당하는 데이터를 보면 시작시점에 주식과 채권에는 각각 1.601과 0.943이 투자되어 있으며, 이를 합하면 2.544이 됩니다. 이를 포트폴리오 내 비중으로 환산하면 비중은 각각 0.629와 0.371가 됩니다. 해당일의 주식과 채권의 수익률은 각각 0, 0.013이 되며, 이를 시작금액에 곱하면 종료시점의 금액은 1.602와 0.956이 됩니다. 각각의 금액을 종료금액의 합인 2.557로 나누게 되면, 포트폴리오 내 비중은 0.626, 0.374로 변하게 됩니다. 포트폴리오 수익률은 2017-12-27 포트폴리오 금액인 2.557을 전일의 포트폴리오 금액인 2.544로 나누어 계산된 값인 0.005가 됩니다. 리밸런싱이 없다면 2017-12-27일의 종료금액과 종료비중은 다음 날인 2017-12-28의 시작금액과 시작비중에 그대로 적용되며, 위와 동일한 단계를 통해 포트폴리오 수익률이 계산됩니다. 그러나 매해 리밸런싱을 가정했으므로, 첫 영업일인 2018-01-02에는 포트폴리오 리밸런싱이 이루어집니다. 따라서 전일 2017-12-29의 종료금액의 합인 2.555를 사전에 정의한 0.6과 0.4에 맞게 각 자산을 시작시점에 매수 혹은 매도하게 됩니다. 이후에는 기존과 동일하게 해당일의 수익률을 곱해 종료시점의 금액과 비중을 구한 후 포트폴리오 수익률을 계산하게 됩니다. 리밸런싱 전일 종료시점의 비중과 리밸런싱 당일 시작시점의 비중 차이의 절대값을 합하면, 포트폴리오의 회전율을 계산할 수도 있습니다. 해당 예제에서는 2017-12-29 종료시점의 비중인 0.626, 0.374와 2018-01-02 시작시점의 비중인 0.6, 0.4의 차이인 0.026, -0.026의 절대값의 합계인 0.052가 회전율이 됩니다. 이처럼 리밸런싱을 원하는 시점과 비중을 정의하면, Return.portfolio() 함수 내에서는 이러한 단계를 거쳐 포트폴리오의 수익률, 시작과 종료시점의 금액 및 비중이 계산되며, 이를 응용하여 회전율을 계산할 수도 있습니다. portfolios = cbind(rets, portfolio$returns) %>% setNames(c('주식', '채권', '60대 40')) charts.PerformanceSummary(portfolios, main = '60대 40 포트폴리오') PerformanceAnalytics 패키지의 charts.PerformanceSummary() 함수는 기간별 수익률을 입력 시 누적수익률, 일별 수익률, 드로우다운(낙폭) 그래프를 자동으로 그려줍니다. 그래프는 색으로 구분되어 각각 주식 수익률(SPY), 채권 수익률(TLT), 60대 40 포트폴리오 수익률을 나타냅니다. 주식과 채권은 상반되는 움직임을 보이며 상승하며, 분산 투자 포트폴리오는 각 개별 자산에 비해 훨씬 안정적인 수익률을 보입니다. turnover = xts( rowSums(abs(portfolio$BOP.Weight - timeSeries::lag(portfolio$EOP.Weight)), na.rm = TRUE), order.by = index(portfolio$BOP.Weight)) chart.TimeSeries(turnover) 전일 종료시점의 비중인 EOP.Weight를 lag() 함수를 이용해 한 단계씩 내린 후 시작시점의 비중인 BOP.Weight와의 차이의 절댓값을 더해주면 해당 시점에서의 회전율이 계산됩니다. lag() 함수의 경우 dplyr 패키지에도 동일한 이름의 함수가 있으므로, 충돌을 방지하기 위해 timeSeries 패키지의 함수임을 선언해줍니다. 이를 xts() 함수를 이용해 시계열 형태로 만든 후 chart.TimeSeries() 함수를 이용해 그래프로 나타내줍니다. 리밸런싱 시점에 해당하는 매해 첫 영업일에 회전율이 발생하며, 그렇지 않은 날은 매수 혹은 매도가 없으므로 회전율 역시 0을 기록합니다. 2008년에는 주식과 채권의 등락폭이 심했으므로 이듬해엔 2009년 리밸런싱으로 인한 회전율이 심하지만, 이를 제외한 해는 회전율이 그리 심하지 않습니다. 12.3 시점 선택 전략 백테스트 이전 테스트가 리밸런싱 시점별 비중이 60%와 40%로 고정되어 있었다면, 이번에는 시점별 비중이 다른 형태의 예제를 살펴보겠습니다. 메브 파버(Meb Faber)는 본인의 논문(Faber 2007)을 통해, 시점 선택(Market Timing) 전략을 사용할 경우 단순 매수 후 보유 대비 극심한 하락장에서 낙폭을 줄일 수 있으며, 이로 인해 위험 대비 수익률을 올릴 수 있다고 설명합니다. 논문에서 말하는 시점 선택의 투자 규칙은 다음과 같습니다. \\[주가 > 10개월\\,이동평균 \\to 매수\\] \\[주가 < 10개월\\,이동평균 \\to 매도\\,및\\,현금\\,보유\\] 해당 규칙을 미국 S&P 500에 적용하는 예제를 살펴보겠습니다. 현재 주식 가격이 과거 10개월 주식 가격의 단순 평균 대비 이상이면 매수, 그렇지 않으면 전량 매도 후 현금을 보유하는 전략이며, 리밸런싱은 매월 실행합니다. library(quantmod) library(PerformanceAnalytics) symbols = c('SPY', 'SHY') getSymbols(symbols, src = 'yahoo') ## [1] "SPY" "SHY" prices = do.call(cbind, lapply(symbols, function(x) Ad(get(x)))) rets = na.omit(Return.calculate(prices)) 먼저 주식과 현금에 해당하는 ETF 데이터를 다운로드합니다. 주식에 해당하는 ETF로 는 S&P 500 수익률을 추종하는 SPY를 사용하며, 현금에 해당하는 ETF로는 미국 단기채 수익률을 추종하는 SHY를 사용합니다. ep = endpoints(rets, on = 'months') print(ep) ## [1] 0 19 38 60 80 102 123 144 167 186 ## [11] 209 230 250 271 291 311 333 354 375 397 ## [21] 418 439 462 481 503 523 542 564 585 605 ## [31] 627 649 670 691 713 733 755 774 793 816 ## [41] 837 857 879 900 922 943 964 985 1007 1027 ## [51] 1046 1069 1089 1110 1132 1152 1175 1196 1217 1238 ## [61] 1259 1279 1299 1321 1341 1363 1384 1405 1428 1447 ## [71] 1468 1489 1509 1530 1549 1569 1591 1613 1633 1655 ## [81] 1677 1697 1720 1740 1761 1782 1801 1822 1843 1864 ## [91] 1885 1907 1928 1949 1972 1991 2013 2033 2052 2074 ## [101] 2095 2115 2137 2159 2180 2201 2223 2243 2265 2284 ## [111] 2304 2326 2347 2368 2390 2410 2433 2454 2475 2496 ## [121] 2517 2537 2556 2579 2598 2620 2642 2662 2685 2705 ## [131] 2727 2748 2768 2789 2808 2829 2850 2872 2893 2914 ## [141] 2937 2956 2979 3000 3019 3040 3059 3080 3101 3123 ## [151] 3143 3165 3187 3207 3230 3250 3271 3292 3311 3333 ## [161] 3354 3374 3396 3418 3439 3460 3482 3502 3524 3534 wts = list() lookback = 10 먼저 xts 패키지의 endpoints() 함수를 이용해 매월 말일의 위치를 구합니다. 해당 함수는 endpoints(x, on= 'months', k=1)의 형태로 이루어지며 x는 시계열 데이터, on은 원하는 기간, k는 구간 길이를 의미합니다. 즉, 시계열 데이터에서 월말에 해당하는 부분의 위치를 반환하며, 매월이 아닌 weeks, quarters, years도 입력이 가능합니다. 결과적으로 ep에는 rets의 인덱스 중 매월 말일에 해당하는 부분의 위치가 구해집니다. 각 시점별 비중이 입력될 wts를 공백의 리스트 형식으로 저장해주며, n개월 이동평균값에 해당하는 lookback 변수는 10을 입력합니다. i = lookback + 1 sub_price = prices[ep[i-lookback] : ep[i] , 1] head(sub_price, 3) ## SPY.Adjusted ## 2007-01-03 106.5 ## 2007-01-04 106.7 ## 2007-01-05 105.9 tail(sub_price, 3) ## SPY.Adjusted ## 2007-10-26 117.2 ## 2007-10-29 117.6 ## 2007-10-30 116.8 sma = mean(sub_price) wt = rep(0, 2) wt[1] = ifelse(last(sub_price) > sma, 1, 0) wt[2] = 1 - wt[1] wts[[i]] = xts(t(wt), order.by = index(rets[ep[i]])) 해당 전략은 for loop 구문을 통해, 매월 말 과거 10개월 이동평균을 구한 후 매수 혹은 매도를 선택한 후 비중을 계산합니다. 예시를 위해 첫 번째 시점의 테스트 과정을 살펴보며, 과거 10개월에 해당하는 가격의 이동평균이 필요하므로 처음 시작은 i+1 인 11부터 가능합니다. 주가는 일별 데이터이며, 현재부터 과거 10개월에 해당하는 주가를 선택해야 합니다. 앞서 endpoints() 함수를 통해 주가에서 월말 기준점의 위치를 찾았으며, ep[i]는 현재시점 주가의 위치를, ep[i-lookback]는 현재부터 10개월 전 주가 위치를 의미합니다. 이를 통해 과거 10개월 간 주가를 찾은 후 sub_price에 저장합니다. mean()을 통해 10개월 주가의 평균을 계산합니다. rep(0, 2)를 통해 비중이 들어갈 0벡터를 생성합니다. ifelse() 구문을 통해 해당 전략의 조건에 맞는 비중을 계산합니다. wt[1]은 주식의 투자비중이며, 만일 현재 주가가 10개월 이동평균보다 클 경우 주식에 해당하는 비중은 1을, 그렇지 않을 경우 0을 부여합니다. wt[2]는 현금의 투자비중이며, 1에서 주식의 투자비중을 뺀 값을 입력합니다. 표 12.4는 해당 규칙이 요약되어 있습니다. 위에서 만들어진 벡터를 xts()를 통해 시계열 형태로 바꾼 후, wts의 i번째 리스트에 저장해줍니다. 표 12.4: 시점선택 조건 별 비중 자산 현재주가 > 10개월 이동평균 현재 주가 < 10개월 이동평균 주식비중 wt[1] = 1 wt[1] = 0 현금비중 wt[2] = 0 wt[2] = 1 위 과정을 for loop 구문을 통해 전체 기간에 적용한 백테스트는 다음과 같습니다. ep = endpoints(rets, on = 'months') wts = list() lookback = 10 for (i in (lookback+1) : length(ep)) { sub_price = prices[ep[i-lookback] : ep[i] , 1] sma = mean(sub_price) wt = rep(0, 2) wt[1] = ifelse(last(sub_price) > sma, 1, 0) wt[2] = 1 - wt[1] wts[[i]] = xts(t(wt), order.by = index(rets[ep[i]])) } wts = do.call(rbind, wts) 매월 말 과거 10개월 이동평균을 구한 후 현재 주가와 비교해 주식 혹은 현금 투자비중을 구한 후 wts 리스트에 저장합니다. 그 후 do.call() 함수를 통해 리스트를 테이블로 묶어줍니다. 수익률 데이터와 비중 데이터가 구해졌으므로 Return.portfolio() 함수를 통해 포트폴리오의 수익률을 계산합니다. Tactical = Return.portfolio(rets, wts, verbose = TRUE) portfolios = na.omit(cbind(rets[,1], Tactical$returns)) %>% setNames(c('매수 후 보유', '시점 선택 전략')) charts.PerformanceSummary(portfolios, main = "Buy & Hold vs Tactical") 수익률 데이터와 비중 데이터의 입력을 통해 백테스트를 실행합니다. cbind() 함수를 통해 SPY 데이터와 포트폴리오 수익률을 합쳐줍니다. 시점 선택 포트폴리오의 경우 lookback 기간인 초기 10개월에 대한 수익률이 없어 NA로 표시되므로 na.omit()을 통해 해당 부분을 제거합니다. charts.PerformanceSummary() 함수를 통해 수익률을 그래프로 나타냅니다. 검은색 그래프는 S&P 500 에 매수 후 보유 시 수익률이고, 주황색 그래프는 시점 선택 전략을 적용한 수익률입니다. 2008년과 같은 하락장에서 낙폭이 훨씬 낮음이 확인됩니다. turnover = xts(rowSums(abs(Tactical$BOP.Weight - timeSeries::lag(Tactical$EOP.Weight)), na.rm = TRUE), order.by = index(Tactical$BOP.Weight)) chart.TimeSeries(turnover) 해당 전략의 회전율을 확인해보면, 몇 년간 매매가 없는 경우도 있습니다. 그러나 매매가 발생할 시 매수와 매도 포지션 양쪽의 매매로 인해 200%의 회전율이 발생하게 됩니다. 12.4 동적 자산배분 백테스트 마지막으로 기존에 배웠던 것들을 응용해 동적 자산배분의 백테스트를 수행하겠습니다. 일반적인 자산배분이 주식과 채권, 대체자산에 투자비중을 사전에 정해놓고 약간의 비율만 수정하는 정적 자산배분인 반면, 동적 자산배분이란 투자비중에 대한 제한이 없이 동적으로 포트폴리오를 구성하는 방법입니다. (Butler et al. 2012) 동적 자산배분을 이용한 포트폴리오는 다음과 같이 구성됩니다. 글로벌 10개 자산 중 과거 12개월 수익률이 높은 5개 자산을 선택합니다. 최소분산 포트폴리오를 구성하며, 개별 투자비중은 최소 10%, 최대 30% 제약조건을 설정합니다. 매월 리밸런싱을 실시합니다. library(quantmod) library(PerformanceAnalytics) library(RiskPortfolios) library(tidyr) library(dplyr) library(ggplot2) symbols = c('SPY', # 미국 주식 'IEV', # 유럽 주식 'EWJ', # 일본 주식 'EEM', # 이머징 주식 'TLT', # 미국 장기채 'IEF', # 미국 중기채 'IYR', # 미국 리츠 'RWX', # 글로벌 리츠 'GLD', # 금 'DBC' # 상품 ) getSymbols(symbols, src = 'yahoo') prices = do.call(cbind, lapply(symbols, function(x) Ad(get(x)))) %>% setNames(symbols) rets = Return.calculate(prices) %>% na.omit() 먼저 이전 CHAPTER와 동일하게 글로벌 자산을 대표하는 ETF 데이터를 다운로드한 후 수정주가의 수익률을 계산합니다. ep = endpoints(rets, on = 'months') wts = list() lookback = 12 wt_zero = rep(0, 10) %>% setNames(colnames(rets)) 백테스트에 사용되는 각종 값을 사전에 정의합니다. endpoints() 함수를 통해 매월 말일의 위치를 구합니다. 매월의 투자비중이 들어갈 빈 리스트를 wts에 설정합니다. 수익률을 측정할 과거 n기간을 12개월로 설정합니다. rep() 함수를 통해 비중이 들어갈 0으로 이루어진 벡터를 만들며 이름을 설정합니다. 다음은 매월 말 투자 규칙에 따라 포트폴리오의 비중을 구하는 백테스트 과정입니다. for (i in (lookback+1) : length(ep)) { sub_ret = rets[ep[i-lookback] : ep[i] , ] cum = Return.cumulative(sub_ret) K = rank(-cum) <= 5 covmat = cov(sub_ret[, K]) wt = wt_zero wt[K] = optimalPortfolio(covmat, control = list(type = 'minvol', constraint = 'user', LB = rep(0.10, 5), UB = rep(0.30, 5))) wts[[i]] = xts(t(wt), order.by = index(rets[ep[i]])) } wts = do.call(rbind, wts) for loop 구문을 통해 매월 말 과거 12개월 수익률을 구한 후 비중을 계산하므로, 처음 시작은 i+1인 13부터 가능합니다. ep[i]는 현재시점 수익률의 위치를, ep[i-lookback]는 현재부터 12개월 전 수익률의 위치를 의미합니다. 이를 통해 과거 12개월 간 수익률을 찾은 후 sub_ret에 저장합니다. Return.cumulative() 함수를 통해 해당 기간의 자산별 누적수익률을 구합니다. rank() 함수를 통해 수익률 상위 5개 자산을 선택하며, 내림차순으로 정렬해야하므로 마이너스(-)를 붙여줍니다. cov() 함수를 통해 수익률 상위 5개 자산의 분산-공분산 행렬을 구하도록 합니다. 임시로 비중이 저장될 wt 변수에 위에서 만든 0벡터(wt_zero)를 입력한 후 optimalPortfolio() 함수를 통해 최소분산 포트폴리오를 구성하는 해를 찾습니다. 개별 투자비중의 제한은 최소 10%, 최대 30%를 설정하며, 구해진 해를 wt의 K번째 값에 입력합니다. 위에서 만들어진 벡터를 xts()를 통해 시계열 형태로 바꾼 후 wts의 i번째 리스트에 저장합니다. for loop 구문이 끝난 후 do.call() 함수를 통해 투자비중이 저장된 리스트를 테이블 형태로 바꿔줍니다. 이를 통해 동적 자산배분의 투자 규칙에 맞는 매월 말 투자비중이 계산되었습니다. GDAA = Return.portfolio(rets, wts, verbose = TRUE) charts.PerformanceSummary(GDAA$returns, main = '동적자산배분') 수익률과 비중 데이터가 있으므로 Return.portfolio() 함수를 통해 백테스트 수익률을 계산할 수 있습니다. charts.PerformanceSummary() 함수를 통해 누적수익률을 확인하면 해당 전략을 이용한 포트폴리오가 꾸준히 우상향하는 모습을 보이게됩니다. wts %>% fortify.zoo() %>% gather(key, value, -Index) %>% mutate(Index = as.Date(Index)) %>% mutate(key = factor(key, levels = unique(key))) %>% ggplot(aes(x = Index, y = value)) + geom_area(aes(color = key, fill = key), position = 'stack') + xlab(NULL) + ylab(NULL) + theme_bw() + scale_x_date(date_breaks="years", date_labels="%Y", expand = c(0, 0)) + scale_y_continuous(expand = c(0, 0)) + theme(plot.title = element_text(hjust = 0.5, size = 12), legend.position = 'bottom', legend.title = element_blank(), axis.text.x = element_text(angle = 45, hjust = 1, size = 8), panel.grid.minor.x = element_blank()) + guides(color = guide_legend(byrow = TRUE)) 반면 자산별 투자비중의 변화가 많은 것을 알 수 있습니다. 그 원인은 수익률 상위 5개에 해당하는 자산이 매월 말 바뀌며, 최소분산 포트폴리오를 구성하는 비중이 계속해서 바뀌기 때문입니다. 회전율이 상대적으로 낮았던 기존 백테스트에서는 매매비용, 세금, 기타비용 등을 고려하지 않아도 수익률에 크게 영향이 없지만, 회전율이 상대적으로 높은 전략에서는 이러한 것들을 무시하지 않을 수 없습니다. GDAA$turnover = xts( rowSums(abs(GDAA$BOP.Weight - timeSeries::lag(GDAA$EOP.Weight)), na.rm = TRUE), order.by = index(GDAA$BOP.Weight)) chart.TimeSeries(GDAA$turnover) 기존에 살펴본 방법으로 회전율을 계산한다면 매월 상당한 매매회전이 발생함이 확인됩니다. fee = 0.0030 GDAA$net = GDAA$returns - GDAA$turnover*fee 매수 혹은 매도당 발생하는 세금, 수수료, 시장충격 등 총 비용을 0.3%로 가정합니다. 포트폴리오 수익률에서 회전율과 총 비용의 곱을 빼면, 비용 후 포트폴리오의 순수익률이 계산됩니다. cbind(GDAA$returns, GDAA$net) %>% setNames(c('No Fee', 'After Fee')) %>% charts.PerformanceSummary(main = 'GDAA') 기존 비용을 고려하지 않은 포트폴리오(검은색)에 비해, 비용을 차감한 포트폴리오(붉은색)의 수익률이 시간이 지남에 따라 서서히 감소합니다. 이러한 차이는 비용이 크거나 매매회전율이 높을수록 더욱 벌어지게 됩니다. References "], -["성과-및-위험-평가.html", "Chapter 13 성과 및 위험 평가 13.1 결과 측정 지표 13.2 팩터 회귀분석 및 테이블로 나타내기", " Chapter 13 성과 및 위험 평가 백테스트를 통해 포트폴리오 수익률을 구했다면, 이를 바탕으로 각종 성과 및 위험을 평가해야 합니다. 아무리 성과가 좋은 전략이라도 위험이 너무 크다면 투자를 하기 부담스럽습니다. 또한 전략의 수익률이 지속적으로 감소하는 추세라면 경쟁이 치열해져 더 이상 작동하지 않는 전략일 가능성도 있습니다. 이 CHAPTER에서는 포트폴리오의 예시로 퀄리티 팩터를 종합적으로 고려한 QMJ(Quality Minus Junk) 팩터(Asness, Frazzini, and Pedersen 2019)의 수익률을 이용하겠습니다. QMJ 팩터란 우량성이 높은 종목들을 매수하고, 우량성이 낮은 종목들을 공매도하는 전략을 지수의 형태로 나타낸 것입니다. 해당 팩터의 수익률을 통해 성과 및 위험을 평가해보고, 회귀분석을 통해 다른 팩터와의 관계도 살펴보겠습니다. QMJ 팩터의 수익률은 AQR Capital Management의 Datasets19에서 엑셀 파일을 다운로 드한 후 가공할 수도 있습니다. 그러나 해당 작업을 매번 하는 것은 지나치게 번거로우므로, R에서 엑셀 파일을 다운로드한 후 가공하겠습니다. library(dplyr) library(readxl) library(xts) library(timetk) url = 'https://images.aqr.com/-/media/AQR/Documents/Insights/Data-Sets/Quality-Minus-Junk-Factors-Monthly.xlsx' tf = tempfile(fileext = '.xlsx') download.file(url, tf, mode = 'wb') excel_sheets(tf) ## [1] "QMJ Factors" ## [2] "Definition" ## [3] "Data Sources" ## [4] "--> Additional Global Factors" ## [5] "MKT" ## [6] "SMB" ## [7] "HML FF" ## [8] "HML Devil" ## [9] "UMD" ## [10] "ME(t-1)" ## [11] "RF" ## [12] "Sources and Definitions" ## [13] "Disclosures" 해당 데이터의 엑셀 url을 저장합니다. tempfile() 함수 내 .xlsx 인자를 입력함으로써, 임시로 엑셀 파일을 만들도록 합니다. download.file() 함수를 통해 url 파일을 tf 파일명에 저장하며, 엑셀 파일은 바이너리 파일이므로 wb 인자를 입력합니다. readxl 패키지의 excel_sheets() 함수를 통해 해당 엑셀의 시트명들을 확인합니다. 우리가 필요한 데이터는 수익률을 계산할 QMJ Factors, 회귀분석에 필요한 MKT, SMB, HML Devil, UMD, 무위험 이자율인 RF 시트의 데이터입니다. df_QMJ = read_xlsx(tf, sheet = 'QMJ Factors', skip = 18) %>% select(DATE, Global) df_MKT = read_xlsx(tf, sheet = 'MKT', skip = 18) %>% select(DATE, Global) df_SMB = read_xlsx(tf, sheet = 'SMB', skip = 18) %>% select(DATE, Global) df_HML_Devil = read_xlsx(tf, sheet = 'HML Devil', skip = 18) %>% select(DATE, Global) df_UMD = read_xlsx(tf, sheet = 'UMD', skip = 18) %>% select(DATE, Global) df_RF = read_xlsx(tf, sheet = 'RF', skip = 18) readxl 패키지의 read_xlsx() 함수를 통해 엑셀 데이터를 읽어올 수 있으며, 시트명을 정해줄 수도 있습니다. 또한 각 시트 내 18행까지는 데이터를 설명하는 텍스트이므로, skip 인자를 통해 해당 부분은 읽어오지 않도록 합니다. 그 후 select() 함수를 통해 날짜에 해당하는 DATE와 수익률에 해당하는 Global 열만을 선택합니다. df = Reduce(function(x, y) inner_join(x, y, by = 'DATE'), list(df_QMJ, df_MKT, df_SMB, df_HML_Devil,df_UMD, df_RF)) %>% setNames(c('DATE','QMJ', 'MKT', 'SMB', 'HML', 'UMD', 'RF')) %>% na.omit() %>% mutate(DATE = as.Date(DATE, "%m/%d/%Y"), R_excess = QMJ - RF, Mkt_excess = MKT - RF) %>% tk_xts(date_var = DATE) inner_join() 함수를 통해 DATE를 기준으로 데이터를 묶어주어야 합니다. 해당 함수는 한 번에 두 개 테이블만을 선택할 수 있으므로, Reduce() 함수를 통해 모든 데이터에 inner_join() 함수를 적용합니다. setNames() 함수를 통해 열 이름을 입력합니다. 각 팩터별 시작시점이 다르므로 na.omit() 함수를 통해 NA 데이터를 삭제해줍니다. mutate() 함수를 통해 데이터를 변형해줍니다. DATE 열은 mm/dd/yy의 문자열 형식이므로 이를 날짜 형식으로 변경해줍니다. QMJ 팩터 수익률에서 무위험 수익률을 차감해 초과수익률을 구해주며, 시장 수익률에서 무위험 수익률을 차감해 시장위험 프리미엄을 계산해줍니다. tk_xts() 함수를 이용해 티블 형태를 시계열 형태로 변경하며, 인덱스는 DATE 열을 설정합니다. 형태를 변경한 후 해당 열은 자동으로 삭제됩니다. 위 과정을 통해 구한 데이터를 바탕으로 성과 및 위험을 평가하겠습니다. 13.1 결과 측정 지표 포트폴리오의 평가에서 가장 중요한 지표는 수익률과 위험입니다. 수익률은 누적수익률과 연율화 수익률, 연도별 수익률이 주요 지표이며, 위험은 변동성과 낙폭이 주요 지표입니다. 이 외에도 승률, 롤링 윈도우 값 등 다양한 지표를 살펴보기도 합니다. 이러한 지표를 수식을 이용해 직접 계산할 수도 있지만, PerformanceAnalytics 패키지에서 제공하는 다양한 함수들을 이용해 편하게 계산할 수 있습니다. 13.1.1 수익률 및 변동성 library(PerformanceAnalytics) chart.CumReturns(df$QMJ) 먼저 chart.CumReturns() 함수를 이용해 QMJ 팩터의 누적수익률을 그래프로 나타내봅니다. 1989-07-31부터 2020-11-30까지 장기간동안 우상향하는 모습을 보이고 있습니다. prod((1+df$QMJ)) - 1 # 누적수익률 ## [1] 3.829 mean(df$QMJ) * 12 # 연율화 수익률(산술) ## [1] 0.05294 (prod((1+df$QMJ)))^(12 / nrow(df$QMJ)) - 1 # 연율화 수익률(기하) ## [1] 0.0514 수익률 중 가장 많이보는 지표는 누적 수익률, 연율화 수익률(산술), 연율화 수익률(기하)입니다. 각 수익률을 구하는 법은 다음과 같습니다. 누적 수익률: \\((1+r_1) \\times (1+r_2) \\times \\dots \\ \\times (1+r_n) - 1 = \\{\\prod_{i=1}^n(1+r_i)\\}-1\\), 연율화 수익률(산술): \\(\\frac{(r_1 + r_2 + \\dots + r_i)}{n} \\times scale\\) 연율화 수익률(기하): \\(\\{\\prod_{i=1}^n(1+r_i)\\}^{scale / Days} - 1\\) 먼저 누적수익률은 각 수익률에 1을 더한 값을 모두 곱한 후 1을 빼면 됩니다. 연율화 수익률(산술)은 단순히 수익률의 평균을 구한 후 연율화를 위한 조정값(\\(scale\\))을 곱해주면 됩니다. 데이터가 일간일 경우 조정값은 252, 주간일 경우 52, 월간일 경우 12입니다. 현재 데이터는 월간 기준이므로 조정값은 12가 됩니다. 마지막으로 연율화 수익률(기하)은 각 수익률에 1을 더한 값의 곱을 구한 후 연율화를 위해 승수를 적용한 후 1을 빼주며, Days는 시계열의 관측 기간입니다. 마지막으로 연율화 수익률(기하)의 경우 각 수익률에 1을 더한 값의 곱을 구한 후, 연율화를 위해 승수를 곱한 후 1을 빼주면 되며, \\(Days\\)는 시계열의 관측 기간입니다. Return.cumulative(df$QMJ) # 누적수익률 ## QMJ ## Cumulative Return 3.829 Return.annualized(df$QMJ, geometric = FALSE) # 연율화 수익률(산술) ## QMJ ## Annualized Return 0.05294 Return.annualized(df$QMJ) # 연율화 수익률(기하) ## QMJ ## Annualized Return 0.0514 수식에 맞게 값을 입력해 계산할 수도 있지만, 함수를 이용하면 더욱 손쉽게 계산이 가능하며 실수할 가능성도 줄어듭니다. 누적수익률은 Return.cumulative() 함수를 통해, 연율화 수익률(산술)은 Return.annualized() 함수 내 geometric 인자를 FALSE로 선택해줌으로써, 연율화 수익률(기하)는 Return.annualized() 함수를 통해 계산이 가능합니다. 수식으로 계산한 값과 함수를 통해 계산한 값을 비교하면 동일함이 확인됩니다. sd(df$QMJ) * sqrt(12) # 연율화 변동성 ## [1] 0.07389 StdDev.annualized(df$QMJ) # 연율화 변동성 ## QMJ ## Annualized Standard Deviation 0.07389 SharpeRatio.annualized(df$QMJ, Rf = df$RF, geometric = TRUE) ## QMJ ## Annualized Sharpe Ratio (Rf=2.7%) 0.3136 위험으로 가장 많이 사용되는 지표는 변동성입니다. 연율화 변동성은 sd() 함수를 통해 변동성을 계산한 후 조정값을 곱해 계산합니다. 그러나 StdDev.annualized() 함수를 사용해 더욱 쉽게 계산할 수도 있습니다. 수익을 위험으로 나누어 위험 조정 수익률을 보는 지표가 샤프 지수(Sharpe Ratio)입니다. 해당 지수는 \\(\\frac {R_i - R_f}{\\sigma_i}\\)로 계산되며, 분자에는 포트폴리오 수익률에서 무위험 수익률을 차감한 값이, 분모에는 포트폴리오의 변동성이 오게 됩니다. SharpeRatio.annualized() 함수를 이용하면 포트폴리오 수익률에서 무위험 수익률을 차감한 값을 연율화로 변경한 후 연율화 변동성으로 나누어 샤프 지수를 계산합니다. geometric을 TRUE로 설정하면 기하평균 기준 연율화 수익률을, FALSE로 설정하면 산술평균 기준 연율화 수익률을 계산합니다. 13.1.2 낙폭과 최대낙폭 먼저 낙폭(Drawdown)은 수익률이 하락한 후 반등하기 전까지 얼마나 하락했는지를 나타냅니다. 최대낙폭(Maximum Drawdown)은 이러한 낙폭 중 가장 값이 큰 값으로서, 최고점에서 최저점까지 얼마나 손실을 보는지를 나타냅니다. 투자를 함에 있어 수익률이 하락하는 것은 어쩔 수 없지만, 최대낙폭이 지나치게 큰 전략에 투자하는 것은 매우 위험한 선택이 될 수 있습니다. 그림 10.1: 낙폭과 최대낙폭 table.Drawdowns(df$QMJ) ## From Trough To Depth Length ## 1 2002-10-31 2004-01-31 2008-08-31 -0.2134 71 ## 2 2009-03-31 2009-09-30 2011-12-31 -0.2002 34 ## 3 1992-11-30 1993-08-31 1997-01-31 -0.1408 51 ## 4 2020-04-30 2020-11-30 <NA> -0.1009 9 ## 5 1998-10-31 1999-04-30 2000-05-31 -0.0869 20 ## To Trough Recovery ## 1 16 55 ## 2 7 27 ## 3 10 41 ## 4 8 NA ## 5 7 13 maxDrawdown(df$QMJ) ## [1] 0.2134 chart.Drawdown(df$QMJ) 이러한 낙폭에 대한 지표들은 손으로 계산하기 번거롭지만, 패키지 내 함수를 사용한 다면 매우 손쉽게 계산할 수 있습니다. 먼저 table.Drawdowns() 함수를 이용하면 역대 낙폭이 가 장 심했던 순서대로 낙폭 정도, 하락 기간과 상승 기간, 원금 회복 기간 등을 테이블로 나타내줍니다. maxDrawdown() 함수는 포트폴리오의 최대낙폭을 계산해주며, chart.Drawdown() 함수는 낙폭만을 그래프로 그려줍니다. CalmarRatio(df$QMJ) ## QMJ ## Calmar Ratio 0.2409 위험 조정 수익률 중 사용되는 지표 중 칼마 지수(Calmar Ratio)도 있습니다. 칼마 지수는 연율화 수익률을 최대낙폭으로 나눈 값으로서, 특히나 안정적인 절대 수익률을 추구하는 헤지펀드에서 많이 참조하는 지표입니다. 13.1.3 연도별 수익률 apply.yearly(df$QMJ, Return.cumulative) %>% head() ## QMJ ## 1989-12-31 0.07695 ## 1990-12-31 0.21971 ## 1991-12-31 0.01086 ## 1992-12-31 0.02983 ## 1993-12-31 -0.09306 ## 1994-12-31 0.03875 apply.yearly() 함수 내 계산 함수를 Return.cumulative로 설정한다면 연도별 수익률을 계산할 수 있습니다. library(lubridate) library(tidyr) library(ggplot2) R.yr = apply.yearly(df$QMJ, Return.cumulative) %>% fortify.zoo() %>% mutate(Index = year(Index)) %>% gather(key, value, -Index) %>% mutate(key = factor(key, levels = unique(key))) ggplot(R.yr, aes(x = Index, y = value, fill = key)) + geom_bar(position = "dodge", stat = "identity") + ggtitle('Yearly Return') + xlab(NULL) + ylab(NULL) + theme_bw() + scale_y_continuous(expand = c(0.03, 0.03)) + scale_x_continuous(breaks = R.yr$Index, expand = c(0.01, 0.01)) + theme(plot.title = element_text(hjust = 0.5, size = 12), legend.position = 'bottom', legend.title = element_blank(), legend.text = element_text(size=7), axis.text.x = element_text(angle = 45, hjust = 1, size = 8), panel.grid.minor.x = element_blank() ) + guides(fill = guide_legend(byrow = TRUE)) + geom_text(aes(label = paste(round(value * 100, 2), "%"), vjust = ifelse(value >= 0, -0.5, 1.5)), position = position_dodge(width = 1), size = 3) apply.yearly() 함수를 통해 계산한 연도별 수익률에 ggplot() 함수를 응용하면 막대 그래프로 나타낼 수도 있으며, 시각화를 통해 포트폴리오의 수익률 추이가 더욱 쉽게 확인됩니다. 13.1.4 승률 및 롤링 윈도우 값 승률이란 포트폴리오가 벤치마크 대비 높은 성과를 기록한 비율을 의미하며 다음과 같이 계산됩니다. \\[\\frac {(포트폴리오\\,수익률 > 벤치마크)\\,일수}{전체\\,기간}\\] 벤치마크가 S&P 500 지수, KOSPI 200 지수처럼 구체적으로 존재하는 경우도 있지만, 절대수익을 추구하는 경우에는 이러한 벤치마크가 0 혹은 무위험 수익률이 되기도 합니다. UpsideFrequency(df$QMJ, MAR = 0) ## [1] 0.5942 UpsideFrequency() 함수는 벤치마크 대비 승률을 계산해줍니다. MAR 인자는 0이 기본값으로 설정되어 있으며, 원하는 벤치마크가 있을 시 이를 입력해주면 됩니다. QMJ 팩터는 월간 기준 수익률이 플러스를 기록했던 비율이 59.42%입니다. 위에서 구한 각종 지표들은 투자자가 포트폴리오의 시작부터 현재까지 투자를 했다는 전제 하에 계산됩니다. 그러나 투자를 시작하는 시점은 사람마다 다르기에, 무작위 시점에 투자했을 때 향후 n개월 후 승률 혹은 연율화 수익률 등을 계산할 필요도 있습니다. 이러한 기법을 롤링 윈도우라고 합니다. roll_12 = df$QMJ %>% apply.monthly(., Return.cumulative) %>% rollapply(., 12, Return.annualized) %>% na.omit() %>% UpsideFrequency() roll_24 = df$QMJ %>% apply.monthly(., Return.cumulative) %>% rollapply(., 24, Return.annualized) %>% na.omit() %>% UpsideFrequency() roll_36 = df$QMJ %>% apply.monthly(., Return.cumulative) %>% rollapply(., 36, Return.annualized) %>% na.omit() %>% UpsideFrequency() roll_win = cbind(roll_12, roll_24, roll_36) print(roll_win) ## roll_12 roll_24 roll_36 ## [1,] 0.7623 0.8023 0.8772 롤링 윈도우 승률은 무작위 시점에 투자했을 시 미래 n개월 동안의 연율화 수익률을 구하고, 해당 값이 벤치마크 대비 수익이 높았던 비율을 계산합니다. 만일 12개월 롤링 윈도우 승률이 100%라면, 어떠한 시점에 투자해도 12개월 후에는 언제나 벤치마크를 이겼음을 의미합니다. 반면 아무리 연율화 수익률이 높은 전략도 이러한 롤링 윈도우 승률이 지나치게 낮다면, 단순히 한 번의 운으로 인해 수익률이 높은 것처럼 보일수 있습니다. 함수를 이용해 해당 값을 구하는 과정은 다음과 같습니다. apply.*() 함수를 이용해 원하는 기간의 수익률로 변경하며, 위 예제에서는 월간 수익률로 변경했습니다. rollapply() 함수를 통해 원하는 기간의 롤링 윈도우 통곗값을 구해줍니다. 각각 12개월, 24개월, 36개월 기간에 대해 연율화 수익률을 계산해줍니다. 계산에 필요한 n개월 동안은 수익률이 없으므로 na.omit()을 통해 삭제해줍니다. UpsideFrequency() 함수를 통해 승률을 계산합니다. 해당 과정을 통해 계산된 12개월, 24개월, 36개월 롤링 승률은 각각 76.23%, 80.23%, 87.72%이며, 투자 기간이 길어질수록 승률이 높아집니다. df$QMJ %>% apply.monthly(., Return.cumulative) %>% rollapply(., 12, Return.annualized) %>% na.omit() %>% fortify.zoo() %>% ggplot(aes(x = Index, y = QMJ)) + geom_line() + geom_hline(aes(yintercept = 0), color = 'red') + xlab(NULL) + ylab(NULL) 롤링 윈도우 연율화 수익률 역시 매우 중요한 지표입니다. 해당 값이 지속적으로 하락할 경우 전략이 더 이상 동작하지 않는 것인지 혹은 가장 험난한 시기를 지났기에 인내심을 갖고 기다려야 할지 판단해야 합니다. 13.2 팩터 회귀분석 및 테이블로 나타내기 포트폴리오 수익률에 대한 성과 평가만큼 중요한 것이, 수익률이 어디에서 발생했는가에 대한 요인을 분석하는 것입니다. 베타를 통한 개별 주식과 주식시장과의 관계를 시작으로, 수익률을 설명하기 위한 여러 모형들이 개발되고 발표되었습니다. 그중 일반적으로 많이 사용되는 모형은 기존의 CAPM에 사이즈 팩터(SMB), 밸류 팩터(HML)를 추가한 파마-프렌치의 3팩터 모형(Fama and French 1993), 그리고 3팩터 모형에 모멘텀 팩터(UMD)를 추가한 카하트의 4팩터 모형(Carhart 1997)입니다. QMJ 팩터를 위 4개 팩터에 회귀분석한 결과를 토대로, 퀄리티 팩터의 수익률에 대한 요인 분석을 해보겠습니다. reg = lm(R_excess ~ Mkt_excess + SMB + HML + UMD, data = df) # summary(reg) summary(reg)$coefficient ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 0.002554 0.0007049 3.623 3.316e-04 ## Mkt_excess -0.262459 0.0162821 -16.119 1.038e-44 ## SMB -0.348949 0.0362560 -9.625 1.001e-19 ## HML -0.097755 0.0326155 -2.997 2.908e-03 ## UMD 0.081063 0.0265079 3.058 2.389e-03 먼저 우리가 구한 데이터를 통해 다음과 같은 회귀분석을 실시합니다. 즉 QMJ 팩터의 초과수익률을 시장위험 프리미엄, 사이즈 팩터, 밸류 팩터, 모멘텀 팩터에 회귀분석을 수행합니다. \\[QMJ - R_f= \\beta_m \\times \\ [R_m - R_f] + \\beta_{SMB} \\times R_{SMB} + \\beta_{HML} \\times R_{HML} + \\beta_{UMD} \\times R_{UMD}\\] lm() 함수 내에서 R_excess는 \\(QMJ - R_f\\)와 동일하며, Mkt_excess는 \\(R_m - R_f\\)와 동일합니다. 베타의 절댓값이 크다는 의미는 QMJ 팩터의 수익률이 해당 팩터와의 관계가 높다는 의미이며, 양수일 경우에는 양의 관계가, 음수일 경우에는 음의 관계가 높다는 의미입니다. 또한 t값 혹은 P값을 통해 관계가 얼마나 유의한지도 확인할 수 있습니다. 시장 베타에 해당하는 \\(\\beta_m\\)은 -0.262로 음숫값을 보이며, 퀄리티 팩터의 경우 시장과 역의 관계에 있다고 볼 수 있습니다. 또한 t값이 -16.119로 충분히 유의합니다. 사이즈 베타에 해당하는 \\(\\beta_{SMB}\\)는 -0.349이며 역시나 음숫값을 보입니다. 즉 퀄리티 팩터는 소형주보다는 대형주 수익률과 관계가 있으며, t값 역시 -9.625로 충분히 유의합니다. 밸류 베타에 해당하는 \\(\\beta_{HML}\\)은 -0.098이며 이 역시 음숫값을 보입니다. 즉 퀄리티와 밸류 간의 관계에서 살펴본 것처럼, 두 팩터는 서로 역의 관계가 있습니다. t값 역시 -2.997로 유의합니다. 모멘텀 베타에 해당하는 \\(\\beta_{UMD}\\)는 0.081로 양의 관계가 있으며, 모멘텀 팩터가 좋은 시기에 퀄리티 팩터도 좋을 수 있습니다. t값은 3.058로 유의하다고 볼 수 있습니다. 이러한 설명변수를 제외하고도 월간 초과수익률에 해당하는 계숫값이 0.003이며, t값은 3.623로 유의합니다. 즉, 퀄리티 팩터는 기존의 여러 팩터들로 설명되지 않는 새로운 팩터라고도 볼 수 있습니다. library(broom) tidy(reg) ## # A tibble: 5 x 5 ## term estimate std.error statistic p.value ## <chr> <dbl> <dbl> <dbl> <dbl> ## 1 (Intercept) 0.00255 0.000705 3.62 3.32e- 4 ## 2 Mkt_excess -0.262 0.0163 -16.1 1.04e-44 ## 3 SMB -0.349 0.0363 -9.62 1.00e-19 ## 4 HML -0.0978 0.0326 -3.00 2.91e- 3 ## 5 UMD 0.0811 0.0265 3.06 2.39e- 3 broom 패키지의 tidy() 함수를 사용하면 분석 결과 중 계수에 해당하는 값만을 요약해서 볼 수 있습니다. library(stargazer) stargazer(reg, type = 'text', out = 'data/reg_table.html') ## ## =============================================== ## Dependent variable: ## --------------------------- ## R_excess ## ----------------------------------------------- ## Mkt_excess -0.262*** ## (0.016) ## ## SMB -0.349*** ## (0.036) ## ## HML -0.098*** ## (0.033) ## ## UMD 0.081*** ## (0.027) ## ## Constant 0.003*** ## (0.001) ## ## ----------------------------------------------- ## Observations 377 ## R2 0.638 ## Adjusted R2 0.634 ## Residual Std. Error 0.013 (df = 372) ## F Statistic 164.000*** (df = 4; 372) ## =============================================== ## Note: *p<0.1; **p<0.05; ***p<0.01 stargazer 패키지를 사용하면, 회귀분석 결과를 논문에서 많이 사용되는 테이블 형식으로 손쉽게 출력과 저장을 할 수 있습니다.테이블이 출력과 함께 data 폴더 내에 reg_table.html 이름으로 HTML 파일도 저장됩니다. References "], -["references.html", "References", " References "] +["index.html", "R을 이용한 퀀트 투자 포트폴리오 만들기 Welcome 지은이 소개 머리말 이 책의 구성 이 책에서 다루지 않은 주제 도움이 될 만한 자료들 이 책의 지원 페이지 종목과 관련된 유의사항 세션정보", " R을 이용한 퀀트 투자 포트폴리오 만들기 이현열 2021-01-19 Welcome 현재 개정판이 작업중에 있습니다. 2021년 2월 즈음 만나보실 수 있습니다. R을 이용한 퀀트 투자 포트폴리오 만들기 구매 링크 본 페이지는 R을 이용한 퀀트 투자 포트폴리오 만들기의 웹사이트 입니다. 책의 수정 사항이 있을시 즉시 반영할 예정이며, 책에서 다루지 못했던 추가적인 내용도 지속적으로 업데이트 할 예정입니다. 패스트캠퍼스에서 본 책의 내용을 바탕으로 강의가 진행중이니, 수강을 원하시는 분은 참조하시기 바랍니다. 강의 링크 책 발간 이후 업데이트 내용은 다음과 같습니다. 2021년 1월 17일: 한국거래소가 사이트를 개편함에 따라 5장 [한국거래소의 산업별 현황 및 개별지표 크롤링] 부분을 새롭게 작성했습니다. 2020년 1월 17일: 9장 [퀀트 전략을 이용한 종목선정 (기본)]과 10장 [퀀트 전략을 이용한 종목선정 (심화)]에서 재무제표를 이용한 전략의 경우, 1~4월에는 최근년도 데이터가 일부 종목에 대해서만 들어옵니다. 따라서 해당 기간에는 전전년도 데이터를 사용해야 하며, 이를 고려하도록 코드를 변경하였습니다. 2020년 9월 26일: 5장 [한국거래소의 산업별 현황 및 개별지표 크롤링] 부분이 R 4.0 이상 버젼에서 잘 작동하지 않는 문제가 발생하고 있습니다. R 3.6 버젼에서 실행해주시길 바랍니다. (https://rstudio.cloud/ 에서는 원하는 R 버젼으로 프로그램 실행이 가능합니다.) 해당 버젼에서도 안될 경우 session을 재시작하면 제대로 실행이 됩니다. 2020년 9월 26일: 4장 [기업공시채널에서 오늘의 공시 불러오기]에서 POST 부분의 url이 기존 http://kind.krx.co.kr/disclosure/todaydisclosure.do 에서 https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do 로 변경되었습니다. 2020년 4월 27일: 9장 [금융 데이터 수집하기 (심화)]에 [DART의 Open API를 이용한 데이터 수집하기] 챕터를 추가하였습니다. 이를 통해 더욱 다양한 데이터를 수집할 수 있습니다. 2020년 4월 7일: 각 페이지 하단에 질문/답변 기능을 추가하였습니다. 이제 블로그나 이메일, SNS 보다는 웹북에 질문을 남겨주시기 바랍니다. 2020년 3월 22일: 11장 [포트폴리오 구성]에 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 추가하였습니다. 2020년 3월 22일: 8장 [데이터 분석 및 시각화하기]에서 종목정보 시각화 이전에 ggplot() 기초 챕터를 추가하였습니다. 이로써 기존에 해당 패키지를 모르던 분도 쉽게 배울수 있도록 하였습니다. 2020년 3월 15일: 6장 [금융 데이터 수집하기 (심화)]에 [재무제표 및 가치지표 크롤링]에서 사용하는 페이지가 크롤러의 접근을 막음에 따라, user_agent() 를 이용하여 웹브라우저 인자를 추가해 주었습니다. 2020년 1월 19일: 5장의 [거래소 데이터 정리하기] 부분에서 substr() 함수 대신 stringr 패키지의 str_sub() 함수를 사용하여 코드를 훨씬 간결하게 표현했습니다. 또한 종목코드 끝이 0이 아닐 경우 우선주인 점을 이용하여 더욱 쉽게 클렌징 처리를 하였습니다. 2020년 1월 18일: 야후 파이낸스 웹페이지의 구조가 바뀌어 동적 크롤링을 통해서만 데이터 수집이 가능하게 되었습니다. 이는 본 책에서는 다루지 않으므로, 6장 [금융 데이터 수집하기 (심화)]에서 해당 부분을 삭제하였습니다. 지은이 소개 이현열 한양대학교에서 경영학을 전공하고, 카이스트 대학원에서 금융공학 석사 학위를 받았다. 졸업 후 증권사에서 주식운용, 자산운용사에서 퀀트 포트폴리오 매니저, 보험사에서 데이터 분석 업무를 거쳐, 현재 핀테크 스타트업에서 퀀트 및 자산배분 리서치 업무를 하고 있다. 평소 꾸준한 SNS와 블로그 활동으로 퀀트 아이디어 및 백테스트 결과 등을 공유하면서 퀀트 투자의 대중화를 위해 노력하고 있다. 한양대학교 재무금융 박사 과정을 수료했으며, 패스트캠퍼스에서 R과 퀀트 투자 강의를 맡고 있다. 지은 책으로는 《스마트베타》(2017)가 있다. 머리말 퀀트 투자 중 팩터에 관한 이론적 내용을 다룬 《SMART BETA(스마트 베타): 감정을 이기는 퀀트 투자》(김병규, 이현열, 워터베어프레스, 2017) 출간 이후 강의와 세미나를 통해많은 분들을 만났고, 공통적인 어려움을 느낄 수 있었습니다. 기관 투자자들이 손쉽게 데이터를 구할 수 있는 것과는 다르게, 일반 투자자들은 퀀트 투자를 하기 위한 데이터를 구하는 시작점부터 어려움을 겪는다는 것이었습니다. 그러나 프로그래밍을 이용하면 일반 투자자들도 얼마든지 금융 데이터 수집 및 처리, 퀀트 모델 개발, 포트폴리오 분석 및 자동화 등이 가능합니다. 이 책을 읽는 독자분들이 스스로 이러한 퀀트 투자 프로세스를 만들 수 있기를 바라는 마음으로 책을 구성했습니다. 또한 실제 전문 투자자들이 사용하는 기술들도 포함했으니 책의 내용을 넘어 더욱 훌륭한 모델을 만드는 데 도움이 되시리라 생각합니다. 이 책에서 데이터 수집을 위해 주로 다루는 크롤링은 웹페이지의 데이터를 가져오는 것입니다. 기존에 책 발간 이후 참고자료로 사용된 페이지 중 형태가 바뀐곳이 많아 개정판을 작성하게 되었습니다. 수정된 내역은 다음과 같습니다. 야후 파이낸스의 웹페이지 구조가 바뀌어 크롤링이 사실상 어렵게 되었고, 책 내용에서 제외하였습니다. 한국거래소 사이트가 개편되어 해당 부분은 바뀐 페이지에 맞게 새로 작성하였습니다. 일부 페이지가 크롤러의 접근을 막음에 따라 user_agent() 함수를 사용해 크롤링이 가능해지게 하였습니다. 많은 문의가 있었던 DART 크롤링에 대한 내용을 추가했습니다. 포트폴리오 구성 부분에 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 추가하였습니다. ggplot2 패키지의 기본적인 사용법을 추가하였습니다. 일부 코드를 수정하여 더욱 데이터 처리를 쉽게 하거나, 종목 선택을 강건하게 하였습니다. 앞으로도 페이지 변경 등 코드를 수정해야 하거나 추가된 내용이 있을 경우 책의 공식 페이지인 https://hyunyulhenry.github.io/quant_cookbook/ 에 즉각적으로 업데이트 할 예정이며, 질문사항이 있을 경우 페이지에 남겨주시면 답변드리고 있습니다. 어느때보다 주식과 투자에 대한 관심이 뜨거워진 지금, 유행이라는 파도에 휩쓸리는 투자보다는 데이터를 이용한 객관적이고 장기적인 투자로 성공하시길 기원합니다. 2021년 1월 이현열(leebisu@gmail.com) 이 책의 구성 이 책은 API와 크롤링을 통한 금융 데이터 수집, 투자 종목 선택 및 포트폴리오 구성, 백테스트와 성과 분석으로 이루어져 있습니다. CHAPTER 1 퀀트 투자의 심장: 데이터와 프로그래밍 퀀트 투자란 무엇인지, 왜 프로그래밍이 필요한지, 여러 언어 중 R을 사용해야 하는 이유에 대해 살펴봅니다. CHAPTER 2 크롤링을 위한 기본 지식 크롤링을 통한 데이터 수집에 앞서 인코딩, 웹의 동작 방식, HTML에 대한 기본 정보와 데이터 처리에 편리한 R 코드를 살펴봅니다. CHAPTER 3 API를 이용한 데이터 수집 API를 통한 데이터 수집과 getSymbols() 함수의 사용 방법에 대해 살펴봅니다. CHAPTER 4 크롤링 이해하기 크롤링이 무엇인가에 대해 살펴보며, GET과 POST 방식을 이용한 간단한 예제를 살펴봅니다. CHAPTER 5 금융 데이터 수집하기 기본 한국거래소에서 제공하는 데이터를 크롤링하는 방법, 섹터의 구성종목을 수집하는 방법에 대해 살펴봅니다. CHAPTER 6 금융 데이터 수집하기 심화 퀀트 투자의 핵심 자료인 수정주가, 재무제표 및 가치지표를 크롤링하는 방법을 살펴봅니다. CHAPTER 7 데이터 정리하기 앞에서 수집한 주가, 재무제표, 가치지표를 하나의 파일로 정리하는 방법을 살펴봅니다. CHAPTER 8 데이터 분석 및 시각화하기 수집한 데이터를 바탕으로 dplyr 패키지를 이용한 데이터 분석 및 ggplot2 패키지를 이용한 데이터 시각화, 인터랙티브 그래프를 나타내는 방법을 살펴봅니다. CHAPTER 9 퀀트 전략을 이용한 종목 선정 기본 베타에 대한 이해 및 기본적 팩터인 저변동성, 모멘텀, 밸류, 퀄리티를 이용한 종목 선정에 대해 살펴봅니다. CHAPTER 10 퀀트 전략을 이용한 종목 선정 심화 단순 종목 선정을 넘어 실무에서 사용되는 섹터 중립 포트폴리오 및 이상치 제거와 팩터 결합 방법, 마법공식 및 멀티팩터에 대해 살펴봅니다. CHAPTER 11 포트폴리오 구성 최적화 패키지를 이용한 포트폴리오 구성에서 가장 대중적으로 사용되는 최소분산 포트폴리오, 최대분산효과 포트폴리오, 위험균형 포트폴리오를 구현합니다. 또한 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 살펴봅니다. CHAPTER 12 포트폴리오 백테스트 Return.portfolio() 함수를 이용한 백테스트 방법에 대해 살펴보겠습니다. CHAPTER 13 성과 및 위험 평가 포트폴리오의 수익률을 바탕으로 성과 및 위험 평가에 사용되는 각종 지표에 대해 알아보며, 4팩터 회귀분석을 통한 요인 분석을 실행합니다. 이 책에서 다루지 않은 주제 이 책은 R을 기본적으로 사용할 줄 아는 독자를 대상으로 작성되었습니다. 따라서 내용의 효율적 전달을 위해 R과 R Studio 설치, 기초적인 프로그래밍 등의 내용은 생략했습니다. 따라서 프로그래밍을 처음 접하는 독자라면 프로그래밍 기초를 먼저 익히신후 본 책을 읽으시길 추천드립니다. 또한 이 책에서는 프로그램 언어로 R을 이용했기 때문에 Python 혹은 다른 언어를 사용하는 분들에게는 직접적으로 도움이 되지 않을 수 있다고 생각할 수 있습니다. 그러나 투자에 필요한 금융 데이터 수집을 어디서 어떻게 하는지, 종목 선택을 어떻게 하고 포트폴리오를 어떻게 구성하는지에 대한 이론적 내용을 이해한 후 본인들이 사용하는 언어로 구현해보는 것도 좋은 도전이 될 것입니다. 도움이 될 만한 자료들 먼저 팩터 투자와 관련하여 심화된 내용을 알고 싶은 분은 저의 이전 책 및 책에서 인용된 논문을 읽어볼 것을 권합니다. R 프로그래밍과 관련하여 기초부터 tidyverse 패키지까지 이해하는 데 도움이 될만한 책 목록은 다음과 같습니다. 《SMART BETA(스마트 베타): 감정을 이기는 퀀트 투자》(김병규, 이현열, 워터베어프레스, 2017) 《손에 잡히는 R 프로그래밍》(가렛 그롤먼드, 한빛미디어, 2015) 《R Cookbook》(폴 티터, 인사이트, 2012) 《R을 활용한 데이터 과학》(해들리 위컴, 개럿 그롤문드, 인사이트, 2019) 《Do it! 쉽게 배우는 R 데이터 분석》(김영우, 이지스퍼블리싱, 2017) 《ggplot2: R로 분석한 데이터를 멋진 그래픽으로》(해들리 위컴, 프리렉, 2017) 이 책의 지원 페이지 이 책은 R의 bookdown 패키지로 작성되어 웹페이지 및 GitHub 저장소에 공유되어 있습니다. 따라서 책에 포함되어 있는 각종 코드를 웹페이지에 방문하여 얻으실 수 있습니다. 웹페이지: https://hyunyulhenry.github.io/quant_cookbook GitHub 저장소: https://github.com/hyunyulhenry/quant_cookbook 크롤링 대상 웹페이지의 구조가 바뀌어 코드의 수정이 필요할 경우 즉각적으로 반영할 것이며, 인쇄본에서 다루지 않은 내용도 추가적으로 업데이트될 예정입니다. 또한 bookdown 패키지를 이용하여 책을 집필하고자 하는 분들에게도 많은 도움이 될 것입 니다. 이 외에도 퀀트 투자 혹은 R을 이용한 투자 활용법 등의 내용은 저자의 블로그에 많은글들이 있으니 참조하기 바랍니다. Henry’s Quantopia: http://henryquant.blogspot.com 종목과 관련된 유의사항 팩터 모델을 이용한 종목 선택과 관련된 CHAPTER에서는 해당 조건으로 선택된 종목들이 나열되어 있습니다. 그러나 이는 해당 종목에 대한 매수 추천이 아님을 밝히며, 데이터를 받은 시점의 종목이기에 독자 여러분이 책을 읽는 시점에서 선택된 종목과는 상당한 차이가 있습니다. 또한 이 책에서 다루는 모델을 이용하여 투자를 할 경우, 이로 인한 이익과 손해는 본인에게 귀속됨을 알립니다. 세션정보 본 책에서 사용된 R 버젼 및 각종 정보는 다음과 같습니다. ## R version 3.6.3 (2020-02-29) ## Platform: x86_64-pc-linux-gnu (64-bit) ## Running under: Ubuntu 16.04.7 LTS ## ## Matrix products: default ## BLAS: /usr/lib/atlas-base/atlas/libblas.so.3.0 ## LAPACK: /usr/lib/atlas-base/atlas/liblapack.so.3.0 ## ## locale: ## [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C ## [3] LC_TIME=C.UTF-8 LC_COLLATE=C.UTF-8 ## [5] LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8 ## [7] LC_PAPER=C.UTF-8 LC_NAME=C ## [9] LC_ADDRESS=C LC_TELEPHONE=C ## [11] LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C ## ## attached base packages: ## [1] stats graphics grDevices utils datasets ## [6] methods base ## ## other attached packages: ## [1] showtext_0.9 showtextdb_3.0 sysfonts_0.8.1 ## ## loaded via a namespace (and not attached): ## [1] compiler_3.6.3 magrittr_1.5 bookdown_0.20 ## [4] htmltools_0.5.0 tools_3.6.3 yaml_2.2.1 ## [7] stringi_1.5.3 rmarkdown_2.3 knitr_1.30 ## [10] stringr_1.4.0 digest_0.6.25 xfun_0.17 ## [13] rlang_0.4.7 evaluate_0.14 "] ] diff --git a/index.Rmd b/index.Rmd index 43fbe084..147f60b5 100644 --- a/index.Rmd +++ b/index.Rmd @@ -20,6 +20,11 @@ github-repo: hyunyulhenry/quant_cookbook knitr::include_graphics('images/cover.png') ``` +
    +현재 개정판이 작업중에 있습니다. 2021년 2월 즈음 만나보실 수 있습니다. +
    + + [R을 이용한 퀀트 투자 포트폴리오 만들기 구매 링크](https://book.naver.com/bookdb/book_detail.nhn?bid=15369836) 본 페이지는 **R을 이용한 퀀트 투자 포트폴리오 만들기**의 웹사이트 입니다. 책의 수정 사항이 있을시 즉시 반영할 예정이며, 책에서 다루지 못했던 추가적인 내용도 지속적으로 업데이트 할 예정입니다. @@ -36,7 +41,7 @@ knitr::include_graphics('images/cover.png') - 2020년 9월 26일: 5장 [한국거래소의 산업별 현황 및 개별지표 크롤링] 부분이 R 4.0 이상 버젼에서 잘 작동하지 않는 문제가 발생하고 있습니다. R 3.6 버젼에서 실행해주시길 바랍니다. (https://rstudio.cloud/ 에서는 원하는 R 버젼으로 프로그램 실행이 가능합니다.) 해당 버젼에서도 안될 경우 session을 재시작하면 제대로 실행이 됩니다. -- 2020년 9월 26일: 4장 [기업공시채널에서 오늘의 공시 불러오기]에서 POST 부분의 url이 기존 *htt*://kind.krx.co.kr/disclosure/todaydisclosure.do* 에서 *https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do* 로 변경되었습니다. +- 2020년 9월 26일: 4장 [기업공시채널에서 오늘의 공시 불러오기]에서 POST 부분의 url이 기존 *http://kind.krx.co.kr/disclosure/todaydisclosure.do* 에서 *https://dev-kind.krx.co.kr/disclosure/todaydisclosure.do* 로 변경되었습니다. - 2020년 4월 27일: 9장 [금융 데이터 수집하기 (심화)]에 [DART의 Open API를 이용한 데이터 수집하기] 챕터를 추가하였습니다. 이를 통해 더욱 다양한 데이터를 수집할 수 있습니다. @@ -66,11 +71,20 @@ knitr::include_graphics('images/cover.png') 그러나 프로그래밍을 이용하면 일반 투자자들도 얼마든지 금융 데이터 수집 및 처리, 퀀트 모델 개발, 포트폴리오 분석 및 자동화 등이 가능합니다. 이 책을 읽는 독자분들이 스스로 이러한 퀀트 투자 프로세스를 만들 수 있기를 바라는 마음으로 책을 구성했습니다. 또한 실제 전문 투자자들이 사용하는 기술들도 포함했으니 책의 내용을 넘어 더욱 훌륭한 모델을 만드는 데 도움이 되시리라 생각합니다. -이 책에서 데이터 수집을 위해 주로 다루는 크롤링은 웹페이지의 데이터를 가져오는 것입니다. 따라서 페이지의 형태가 바뀌면 코드도 다시 작성해야 합니다. 책 발간 이후 참고자료로 사용된 페이지 중 형태가 바뀐곳이 많아 개정판을 작성하게 되었습니다. 또한 많은 문의가 있었던 DART 크롤링에 대한 내용도 추가했습니다. +이 책에서 데이터 수집을 위해 주로 다루는 크롤링은 웹페이지의 데이터를 가져오는 것입니다. 기존에 책 발간 이후 참고자료로 사용된 페이지 중 형태가 바뀐곳이 많아 개정판을 작성하게 되었습니다. 수정된 내역은 다음과 같습니다. + +1. 야후 파이낸스의 웹페이지 구조가 바뀌어 크롤링이 사실상 어렵게 되었고, 책 내용에서 제외하였습니다. +2. 한국거래소 사이트가 개편되어 해당 부분은 바뀐 페이지에 맞게 새로 작성하였습니다. +3. 일부 페이지가 크롤러의 접근을 막음에 따라 user_agent() 함수를 사용해 크롤링이 가능해지게 하였습니다. +4. 많은 문의가 있었던 DART 크롤링에 대한 내용을 추가했습니다. +5. 포트폴리오 구성 부분에 실무에서 많이 사용되는 인덱스 포트폴리오 및 인핸스드 인덱스 포트폴리오 구성 방법을 추가하였습니다. +6. ggplot2 패키지의 기본적인 사용법을 추가하였습니다. +7. 일부 코드를 수정하여 더욱 데이터 처리를 쉽게 하거나, 종목 선택을 강건하게 하였습니다. + +앞으로도 페이지 변경 등 코드를 수정해야 하거나 추가된 내용이 있을 경우 책의 공식 페이지인 https://hyunyulhenry.github.io/quant_cookbook/ 에 즉각적으로 업데이트 할 예정이며, 질문사항이 있을 경우 페이지에 남겨주시면 답변드리고 있습니다. -앞으로도 페이지 변경 등 코드를 수정해야 하거나 추가된 내용이 있을 경우 책의 공식 페이지인 https://hyunyulhenry.github.io/quant_cookbook/ 에 즉각적으로 업데이트 할 예정입니다. 어느때보다 주식과 투자에 대한 관심이 뜨거워진 지금, 유행이라는 파도에 휩쓸리는 투자보다는 데이터를 이용한 객관적이고 장기적인 투자로 성공하시길 기원합니다. - + 2021년 1월 이현열(leebisu@gmail.com)