KRX에서 공매도 데이터 수집하기

2021-11-05 • quant금융데이터수집, krx, 공매도 • 6 min read

이번 포스트에서는 KRX에서 개별종목 공매도 거래개별종목 공매도 잔고 데이터를 파이썬으로 수집하는 방법을 다룹니다.

필요한 파이썬 라이브러리는 다음과 같습니다.

  • pip install requests
  • pip install numpy
  • pip install pandas

먼저 KRX 정보데이터시스템에서 다음 두 메뉴를 확인해 주세요.

  • 통계 / 공매도 통계 / 공매도 거래 / 개별종목 공매도 거래
  • 통계 / 공매도 통계 / 공매도 잔고 / 개별종목 공매도 잔고

개별종목 공매도 거래 데이터 수집하기

먼저 개별종목 공매도 거래 데이터를 받아오겠습니다. 크롬이나 웨일 브라우저에서 F12 키를 눌러 개발자 도구를 열어줍니다. 그리고 다음과 같이 Network 탭에서 Fetch/XHR 하위 탭을 확인합니다. 그리고 조회 버튼을 다시 누르면 아래에 getJsonData.cmd 내역이 뜨고 여기서 요청 정보를 확인할 수 있습니다.

1

여기서 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 requests
res = requests.post(url, headers=headers, data=data)
res
Output: <Response [200]>

Status Code가 200으로 요청이 성공했음을 확인했습니다. 다음으로 받아온 데이터를 DataFrame으로 저장합니다.

import json
short_list = json.loads(res.text)['OutBlock_1']

import pandas as pd
df = 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

개별종목 공매도 잔고 데이터 수집하기

이제 개별종목 공매도 잔고 데이터를 받아 보겠습니다. 비슷하게 요청 준비를 합니다.

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)
res
Output: <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

공매도 거래와 공매도 잔고 데이터 합치기

이제 두 데이터를 합쳐서 확인해 보겠습니다. 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

공매도 잔고가 많다는 것은 다시 상환해야 할 물량이 많다는 것이고 동시에 공매도 거래 비중이 적다면 주가가 바닥에 가까워 졌다고 추측해 볼 여지가 있습니다. 주식 거래에서 확실한 것은 없습니다. 다양한 금융데이터 분석을 통해 성공적인 투자에 한걸음씩 다가가는 것이 중요하다고 봅니다.