KRX에서 공매도 데이터 수집하기
이번 포스트에서는 KRX에서 개별종목 공매도 거래 및 개별종목 공매도 잔고 데이터를 파이썬으로 수집하는 방법을 다룹니다.
필요한 파이썬 라이브러리는 다음과 같습니다.
- pip install requests
- pip install numpy
- pip install pandas
먼저 KRX 정보데이터시스템에서 다음 두 메뉴를 확인해 주세요.
통계/공매도 통계/공매도 거래/개별종목 공매도 거래통계/공매도 통계/공매도 잔고/개별종목 공매도 잔고
개별종목 공매도 거래 데이터 수집하기
Section titled “개별종목 공매도 거래 데이터 수집하기”먼저 개별종목 공매도 거래 데이터를 받아오겠습니다. 크롬이나 웨일 브라우저에서 F12 키를 눌러 개발자 도구를 열어줍니다. 그리고 다음과 같이 Network 탭에서 Fetch/XHR 하위 탭을 확인합니다. 그리고 조회 버튼을 다시 누르면 아래에 getJsonData.cmd 내역이 뜨고 여기서 요청 정보를 확인할 수 있습니다.

여기서 Request URL, Request Headers, Form Data를 확인합니다. 다음과 같이 확인한 정보로 요청 준비를 합니다.
url = 'http://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd'headers = { 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', 'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Host': 'data.krx.co.kr', 'Origin': 'http://data.krx.co.kr', 'Referer': 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader/index.cmd?menuId=MDC0203', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.118 Whale/2.11.126.23 Safari/537.36', 'X-Requested-With': 'XMLHttpRequest',}data = [ ('bld', 'dbms/MDC/STAT/srt/MDCSTAT30101'), ('searchType', '1'), ('mktId', 'STK'), ('secugrpId', 'STMFRTSCIFDRFS'), ('secugrpId', 'SRSW'), ('secugrpId', 'BC'), ('inqCond', 'STMFRTSCIFDRFSSRSWBC'), ('trdDd', '20211105'), ('share', '1'), ('money', '1'), ('csvxls_isNo', 'false'),]이렇게 준비한 url, headers, data로 requests 요청을 보냅니다. Request Method를 보면 POST를 확인할 수 있습니다.
import requestsres = requests.post(url, headers=headers, data=data)resOutput: <Response [200]>Status Code가 200으로 요청이 성공했음을 확인했습니다. 다음으로 받아온 데이터를 DataFrame으로 저장합니다.
import jsonshort_list = json.loads(res.text)['OutBlock_1']
import pandas as pddf = pd.DataFrame(short_list)df.columns = ['종목코드', '종목명', '증권구분', '공매도거래량', '공매도거래량업틱룰', '공매도거래량업틱룰예외', '거래량', '거래량비중', '공매도거래대금', '공매도거래대금업틱룰', '공매도거래대금업틱룰예외', '거래대금', '거래대금비중']
df| index | 종목코드 | 종목명 | 증권구분 | 공매도거래량 | 공매도거래량업틱룰 | 공매도거래량업틱룰예외 | 거래량 | 거래량비중 | 공매도거래대금 | 공매도거래대금업틱룰 | 공매도거래대금업틱룰예외 | 거래대금 | 거래대금비중 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 095570 | AJ네트웍스 | 주권 | 0 | 0 | 0 | 59,739 | 0.00 | 0 | 0 | 0 | 335,609,280 | 0.00 |
| 1 | 006840 | AK홀딩스 | 주권 | 0 | 0 | 0 | 20,225 | 0.00 | 0 | 0 | 0 | 508,366,000 | 0.00 |
| 2 | 027410 | BGF | 주권 | 147,290 | 0 | 147,290 | 8,276,904 | 1.78 | 959,076,130 | 0 | 959,076,130 | 54,013,149,770 | 1.78 |
| 3 | 282330 | BGF리테일 | 주권 | 1,480 | 1,402 | 78 | 22,489 | 6.58 | 244,731,000 | 231,828,500 | 12,902,500 | 3,734,658,000 | 6.55 |
| 4 | 138930 | BNK금융지주 | 주권 | 54,962 | 54,962 | 0 | 2,228,770 | 2.47 | 463,644,520 | 463,644,520 | 0 | 18,819,972,080 | 2.46 |
| … | … | … | … | … | … | … | … | … | … | … | … | … | … |
| 1028 | 069260 | 휴켐스 | 주권 | 72,498 | 72,073 | 425 | 1,137,325 | 6.37 | 1,919,680,550 | 1,908,522,050 | 11,158,500 | 30,290,522,850 | 6.34 |
| 1029 | 000540 | 흥국화재 | 주권 | 0 | 0 | 0 | 83,281 | 0.00 | 0 | 0 | 0 | 318,332,140 | 0.00 |
| 1030 | 000547 | 흥국화재2우B | 주권 | 0 | 0 | 0 | 192 | 0.00 | 0 | 0 | 0 | 5,323,000 | 0.00 |
| 1031 | 000545 | 흥국화재우 | 주권 | 0 | 0 | 0 | 988 | 0.00 | 0 | 0 | 0 | 8,514,770 | 0.00 |
| 1032 | 003280 | 흥아해운 | 주권 | 0 | 0 | 0 | 1,114,059 | 0.00 | 0 | 0 | 0 | 3,560,429,545 | 0.00 |
거래량비중으로 내림차순 정렬을 해보면 공매도 비중이 높은 종목 순위를 확인할 수 있습니다.
df.sort_values('거래량비중', ascending=False)| index | 종목코드 | 종목명 | 증권구분 | 공매도거래량 | 공매도거래량업틱룰 | 공매도거래량업틱룰예외 | 거래량 | 거래량비중 | 공매도거래대금 | 공매도거래대금업틱룰 | 공매도거래대금업틱룰예외 | 거래대금 | 거래대금비중 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 51 | 294870 | HDC현대산업개발 | 주권 | 54,935 | 54,935 | 0 | 562,433 | 9.77 | 1,353,057,750 | 1,353,057,750 | 0 | 13,880,230,400 | 9.75 |
| 1004 | 008770 | 호텔신라 | 주권 | 66,040 | 56,388 | 9,652 | 684,458 | 9.65 | 5,269,414,800 | 4,501,247,500 | 768,167,300 | 54,557,366,500 | 9.66 |
| 800 | 039490 | 키움증권 | 주권 | 4,561 | 4,071 | 490 | 47,299 | 9.64 | 484,180,000 | 432,296,500 | 51,883,500 | 5,018,881,500 | 9.65 |
| 813 | 003240 | 태광산업 | 주권 | 67 | 67 | 0 | 697 | 9.61 | 66,460,000 | 66,460,000 | 0 | 692,149,000 | 9.60 |
| 940 | 180640 | 한진칼 | 주권 | 5,587 | 4,961 | 626 | 58,832 | 9.50 | 315,127,600 | 279,921,100 | 35,206,500 | 3,317,193,800 | 9.50 |
| … | … | … | … | … | … | … | … | … | … | … | … | … | … |
| 433 | 007210 | 벽산 | 주권 | 2 | 0 | 2 | 3,489,885 | 0.00 | 8,060 | 0 | 8,060 | 14,442,649,335 | 0.00 |
| 434 | 002760 | 보락 | 주권 | 0 | 0 | 0 | 230,608 | 0.00 | 0 | 0 | 0 | 490,745,865 | 0.00 |
| 436 | 000890 | 보해양조 | 주권 | 0 | 0 | 0 | 1,282,073 | 0.00 | 0 | 0 | 0 | 1,233,822,775 | 0.00 |
| 438 | 001270 | 부국증권 | 주권 | 0 | 0 | 0 | 6,828 | 0.00 | 0 | 0 | 0 | 166,036,950 | 0.00 |
| 1032 | 003280 | 흥아해운 | 주권 | 0 | 0 | 0 | 1,114,059 | 0.00 | 0 | 0 | 0 | 3,560,429,545 | 0.00 |
그런데 조금 이상합니다. 거래량비중이 생각보다 적고 9.XX 값이 상위에 랭크됩니다. 이는 데이터 형식이 문자열(str)로 되어 있을 때 자주 겪는 문제입니다. 다음과 같이 거래량비중 컬럼의 데이터 형식을 float로 변경합니다.
df['거래량비중'] = df['거래량비중'].astype(float)다시 정렬 결과를 확인하면 다음과 같이 제대로된 랭킹이 된 것을 확인할 수 있습니다.
df.sort_values('거래량비중', ascending=False)| index | 종목코드 | 종목명 | 증권구분 | 공매도거래량 | 공매도거래량업틱룰 | 공매도거래량업틱룰예외 | 거래량 | 거래량비중 | 공매도거래대금 | 공매도거래대금업틱룰 | 공매도거래대금업틱룰예외 | 거래대금 | 거래대금비중 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 539 | 068270 | 셀트리온 | 주권 | 150,827 | 133,030 | 17,797 | 488,779 | 30.86 | 31,363,423,000 | 27,715,592,500 | 3,647,830,500 | 101,280,970,500 | 30.97 |
| 1024 | 081660 | 휠라홀딩스 | 주권 | 161,370 | 160,878 | 492 | 665,890 | 24.23 | 5,958,633,450 | 5,940,402,900 | 18,230,550 | 24,692,808,600 | 24.13 |
| 673 | 214320 | 이노션 | 주권 | 6,994 | 6,856 | 138 | 30,810 | 22.70 | 403,284,700 | 395,322,100 | 7,962,600 | 1,776,867,000 | 22.70 |
| 382 | 011170 | 롯데케미칼 | 주권 | 45,956 | 34,176 | 11,780 | 206,116 | 22.30 | 10,110,596,000 | 7,520,623,000 | 2,589,973,000 | 45,414,691,500 | 22.26 |
| 240 | 002350 | 넥센타이어 | 주권 | 27,712 | 25,176 | 2,536 | 125,204 | 22.13 | 208,348,430 | 189,322,060 | 19,026,370 | 941,065,640 | 22.14 |
| … | … | … | … | … | … | … | … | … | … | … | … | … | … |
| 433 | 007210 | 벽산 | 주권 | 2 | 0 | 2 | 3,489,885 | 0.00 | 8,060 | 0 | 8,060 | 14,442,649,335 | 0.00 |
| 434 | 002760 | 보락 | 주권 | 0 | 0 | 0 | 230,608 | 0.00 | 0 | 0 | 0 | 490,745,865 | 0.00 |
| 436 | 000890 | 보해양조 | 주권 | 0 | 0 | 0 | 1,282,073 | 0.00 | 0 | 0 | 0 | 1,233,822,775 | 0.00 |
| 438 | 001270 | 부국증권 | 주권 | 0 | 0 | 0 | 6,828 | 0.00 | 0 | 0 | 0 | 166,036,950 | 0.00 |
| 1032 | 003280 | 흥아해운 | 주권 | 0 | 0 | 0 | 1,114,059 | 0.00 | 0 | 0 | 0 | 3,560,429,545 | 0.00 |
개별종목 공매도 잔고 데이터 수집하기
Section titled “개별종목 공매도 잔고 데이터 수집하기”이제 개별종목 공매도 잔고 데이터를 받아 보겠습니다. 비슷하게 요청 준비를 합니다.
url = 'http://data.krx.co.kr/comm/bldAttendant/getJsonData.cmd'headers = { 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Origin': 'http://data.krx.co.kr', 'Referer': 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader/index.cmd?menuId=MDC0201',}data = [ ('bld', 'dbms/MDC/STAT/srt/MDCSTAT30501'), ('searchType', '1'), ('mktTpCd', '1'), ('trdDd', '20211102'), ('share', '1'), ('money', '1'), ('csvxls_isNo', 'false'),]
res = requests.post(url, headers=headers, data=data)resOutput: <Response [200]>다음과 같이 받아온 데이터를 DataFrame으로 변환합니다.
df_shortbalance = pd.DataFrame(json.loads(res.text)['OutBlock_1'])df_shortbalance| index | ISU_CD | ISU_ABBRV | BAL_QTY | LIST_SHRS | BAL_AMT | MKTCAP | BAL_RTO |
|---|---|---|---|---|---|---|---|
| 0 | 095570 | AJ네트웍스 | 34,255 | 46,822,295 | 190,115,250 | 259,863,737,250 | 0.07 |
| 1 | 006840 | AK홀딩스 | 4,593 | 13,247,561 | 121,484,850 | 350,397,988,450 | 0.03 |
| 2 | 027410 | BGF | 48,440 | 95,716,791 | 279,014,400 | 551,328,716,160 | 0.05 |
| 3 | 282330 | BGF리테일 | 5,016 | 17,283,906 | 825,132,000 | 2,843,202,537,000 | 0.03 |
| 4 | 138930 | BNK금융지주 | 524,715 | 325,935,246 | 4,601,750,550 | 2,858,452,107,420 | 0.16 |
| … | … | … | … | … | … | … | … |
| 934 | 069260 | 휴켐스 | 953,658 | 40,878,588 | 26,320,960,800 | 1,128,249,028,800 | 2.33 |
| 935 | 000540 | 흥국화재 | 33,301 | 64,242,645 | 129,540,890 | 249,903,889,050 | 0.05 |
| 936 | 000547 | 흥국화재2우B | 0 | 153,600 | 0 | 4,331,520,000 | 0.00 |
| 937 | 000545 | 흥국화재우 | 0 | 768,000 | 0 | 6,673,920,000 | 0.00 |
| 938 | 003280 | 흥아해운 | 84,187 | 240,424,899 | 311,070,965 | 888,370,001,805 | 0.04 |
df_shortbalance.columns = ['종목코드', '종목명', '공매도잔고수량', '상장주식수', '공매도잔고금액', '시가총액', '비중']df_shortbalance| index | 종목코드 | 종목명 | 공매도잔고수량 | 상장주식수 | 공매도잔고금액 | 시가총액 | 비중 |
|---|---|---|---|---|---|---|---|
| 0 | 095570 | AJ네트웍스 | 34,255 | 46,822,295 | 190,115,250 | 259,863,737,250 | 0.07 |
| 1 | 006840 | AK홀딩스 | 4,593 | 13,247,561 | 121,484,850 | 350,397,988,450 | 0.03 |
| 2 | 027410 | BGF | 48,440 | 95,716,791 | 279,014,400 | 551,328,716,160 | 0.05 |
| 3 | 282330 | BGF리테일 | 5,016 | 17,283,906 | 825,132,000 | 2,843,202,537,000 | 0.03 |
| 4 | 138930 | BNK금융지주 | 524,715 | 325,935,246 | 4,601,750,550 | 2,858,452,107,420 | 0.16 |
| … | … | … | … | … | … | … | … |
| 934 | 069260 | 휴켐스 | 953,658 | 40,878,588 | 26,320,960,800 | 1,128,249,028,800 | 2.33 |
| 935 | 000540 | 흥국화재 | 33,301 | 64,242,645 | 129,540,890 | 249,903,889,050 | 0.05 |
| 936 | 000547 | 흥국화재2우B | 0 | 153,600 | 0 | 4,331,520,000 | 0.00 |
| 937 | 000545 | 흥국화재우 | 0 | 768,000 | 0 | 6,673,920,000 | 0.00 |
| 938 | 003280 | 흥아해운 | 84,187 | 240,424,899 | 311,070,965 | 888,370,001,805 | 0.04 |
이번에는 비중 컬럼으로 내림차순 정렬을 해봅니다. 마찬가지로 문자열 정렬 문제가 있을 수 있으므로 이 컬럼을 float으로 변환하고 정렬합니다.
df_shortbalance['비중'] = df_shortbalance['비중'].astype(float)df_shortbalance.sort_values('비중', ascending=False)| index | 종목코드 | 종목명 | 공매도잔고수량 | 상장주식수 | 공매도잔고금액 | 시가총액 | 비중 |
|---|---|---|---|---|---|---|---|
| 78 | 034220 | LG디스플레이 | 28,935,291 | 357,815,700 | 568,578,468,150 | 7,031,078,505,000 | 8.09 |
| 339 | 032350 | 롯데관광개발 | 4,726,867 | 69,275,662 | 94,300,996,650 | 1,382,049,456,900 | 6.82 |
| 910 | 008770 | 호텔신라 | 2,462,557 | 39,248,121 | 198,974,605,600 | 3,171,248,176,800 | 6.27 |
| 46 | 011200 | HMM | 20,968,085 | 405,392,487 | 574,525,529,000 | 11,107,754,143,800 | 5.17 |
| 523 | 019170 | 신풍제약 | 2,499,527 | 52,984,990 | 128,225,735,100 | 2,718,129,987,000 | 4.72 |
| … | … | … | … | … | … | … | … |
| 313 | 030720 | 동원수산 | 0 | 4,653,805 | 0 | 45,886,517,300 | 0.00 |
| 315 | 014825 | 동원시스템즈우 | 0 | 265,358 | 0 | 8,809,885,600 | 0.00 |
| 644 | 000230 | 일동홀딩스 | 0 | 11,540,400 | 0 | 156,949,440,000 | 0.00 |
| 316 | 163560 | 동일고무벨트 | 0 | 13,900,000 | 0 | 116,760,000,000 | 0.00 |
| 261 | 006340 | 대원전선 | 0 | 71,016,967 | 0 | 142,033,934,000 | 0.00 |
공매도 거래와 공매도 잔고 데이터 합치기
Section titled “공매도 거래와 공매도 잔고 데이터 합치기”이제 두 데이터를 합쳐서 확인해 보겠습니다. df는 공매도 거래 데이터이고 df_shortbalance는 공매도 잔고 데이터입니다. 이들 둘을 pandas merge() 메소드로 합치겠습니다.
_df = df.merge(df_shortbalance, how='outer', on='종목코드', suffixes=('', '_dup'))이 데이터로 다양한 분석을 해볼 수 있겠습니다. 여기서는 공매도 잔고는 많으면서 공매도 거래는 적은 순으로 정렬해 봅니다.
_df['score'] = _df['비중'] / (_df['거래량비중'] + 1)_df.sort_values('score', ascending=False)| index | 종목코드 | 종목명 | 증권구분 | 공매도거래량 | 공매도거래량업틱룰 | 공매도거래량업틱룰예외 | 거래량 | 거래량비중 | 공매도거래대금 | 공매도거래대금업틱룰 | 공매도거래대금업틱룰예외 | 거래대금 | 거래대금비중 | 종목명_dup | 공매도잔고수량 | 상장주식수 | 공매도잔고금액 | 시가총액 | 비중 | score |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 97 | 034220 | LG디스플레이 | 주권 | 4,420 | 2,290 | 2,130 | 1,949,194 | 0.23 | 89,668,950 | 46,585,150 | 43,083,800 | 39,626,663,800 | 0.23 | LG디스플레이 | 28,935,291 | 357,815,700 | 568,578,468,150 | 7,031,078,505,000 | 8.09 | 6.577236 |
| 620 | 097520 | 엠씨넥스 | 주권 | 30 | 0 | 30 | 83,676 | 0.04 | 1,333,050 | 0 | 1,333,050 | 3,713,085,850 | 0.04 | 엠씨넥스 | 435,409 | 17,963,282 | 19,332,159,600 | 797,569,720,800 | 2.42 | 2.326923 |
| 370 | 032350 | 롯데관광개발 | 주권 | 17,567 | 16,928 | 639 | 882,332 | 1.99 | 324,702,700 | 312,941,650 | 11,761,050 | 16,281,919,250 | 1.99 | 롯데관광개발 | 4,726,867 | 69,275,662 | 94,300,996,650 | 1,382,049,456,900 | 6.82 | 2.280936 |
| 699 | 006490 | 인스코비 | 주권 | 675 | 0 | 675 | 1,384,422 | 0.05 | 2,285,320 | 0 | 2,285,320 | 4,702,224,255 | 0.05 | 인스코비 | 1,824,640 | 109,848,100 | 6,541,334,400 | 393,805,438,500 | 1.66 | 1.580952 |
| 578 | 003620 | 쌍용차 | 주권 | 0 | 0 | 0 | 0 | 0.00 | 0 | 0 | 0 | 0 | 0.00 | 쌍용차 | 2,180,965 | 149,840,002 | 6,041,273,050 | 415,056,805,540 | 1.46 | 1.460000 |
| … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … |
| 983 | 75701B93 | 현대유퍼스트부동산25호 A | 수익증권 | 0 | 0 | 0 | 0 | 0.00 | 0 | 0 | 0 | 0 | 0.00 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 984 | 75702B93 | 현대유퍼스트부동산25호 C-i | 수익증권 | 0 | 0 | 0 | 0 | 0.00 | 0 | 0 | 0 | 0 | 0.00 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 985 | 75701BA7 | 현대유퍼스트부동산30호[파생형] A | 수익증권 | 0 | 0 | 0 | 0 | 0.00 | 0 | 0 | 0 | 0 | 0.00 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 986 | 75702BA7 | 현대유퍼스트부동산30호[파생형] C-i | 수익증권 | 0 | 0 | 0 | 0 | 0.00 | 0 | 0 | 0 | 0 | 0.00 | NaN | NaN | NaN | NaN |
공매도 잔고가 많다는 것은 다시 상환해야 할 물량이 많다는 것이고 동시에 공매도 거래 비중이 적다면 주가가 바닥에 가까워 졌다고 추측해 볼 여지가 있습니다. 주식 거래에서 확실한 것은 없습니다. 다양한 금융데이터 분석을 통해 성공적인 투자에 한걸음씩 다가가는 것이 중요하다고 봅니다.





