KRX에서 공매도 데이터 수집하기
이번 포스트에서는 KRX에서 개별종목 공매도 거래
및 개별종목 공매도 잔고
데이터를 파이썬으로 수집하는 방법을 다룹니다.
필요한 파이썬 라이브러리는 다음과 같습니다.
- pip install requests
- pip install numpy
- pip install pandas
먼저 KRX 정보데이터시스템에서 다음 두 메뉴를 확인해 주세요.
통계
/공매도 통계
/공매도 거래
/개별종목 공매도 거래
통계
/공매도 통계
/공매도 잔고
/개별종목 공매도 잔고
개별종목 공매도 거래 데이터 수집하기
먼저 개별종목 공매도 거래
데이터를 받아오겠습니다. 크롬이나 웨일 브라우저에서 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 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 |
공매도 잔고가 많다는 것은 다시 상환해야 할 물량이 많다는 것이고 동시에 공매도 거래 비중이 적다면 주가가 바닥에 가까워 졌다고 추측해 볼 여지가 있습니다. 주식 거래에서 확실한 것은 없습니다. 다양한 금융데이터 분석을 통해 성공적인 투자에 한걸음씩 다가가는 것이 중요하다고 봅니다.