이번 포스트에서는 파이썬으로 공매도 데이터를 분석해 봅니다. 공매도 데이터 수집에 대해서는 이 포스트를 확인해 주세요.
공매도 거래, 공매도 잔고, 종목 정보 데이터를 가져옵니다. 이 부분은 개인적으로 사용하는 데이터베이스를 활용하므로 퀀티랩 카페에서 CSV 파일을 다운받아 사용하시기 바랍니다.
import pandas as pdfrom quantylab.common.db.mongodb import MongoDBClientcoll = MongoDBClient.get_coll('short')cursor = coll.find({'date': {'$gte': '20210601', '$lte': '20220114'}}, {'_id': 0, 'dt_update': 0, 'date_update': 0}, sort=[('date', -1)])df_short = pd.DataFrame(list(cursor))coll = MongoDBClient.get_coll('shortbalance')cursor = coll.find({'date': {'$gte': '20210601', '$lte': '20220114'}}, {'_id': 0, 'dt_update': 0, 'date_update': 0}, sort=[('date', -1)])df_shortbalance = pd.DataFrame(list(cursor))coll = MongoDBClient.get_coll('stockcodes')cursor = coll.find({}, {'_id': 0, 'code': 1, 'name': 1})df_stockcodes = pd.DataFrame(list(cursor))위에서 준비한 세 DataFrame을 합칩니다. 이 합친 데이터를 CSV 파일로 저장해 둡니다. 이 CSV 데이터를 카페에 올려 놓았습니다. 카페 가입하셔서 다운받으시고 질문도 해주시면 감사하겠습니다.
df = pd.merge(df_short, df_shortbalance[['code', 'date', 'bal_rto', 'bal_amt', 'bal_qty']], on=['code', 'date'], how='left')df = pd.merge(df, df_stockcodes, on='code', how='left')df = df[['name'] + df.columns.tolisZt()[:-1]]df.to_csv('20220113_analysis_short.csv', index=False)CSV 파일을 다운받으셨다면 다음 부분부터 직접 해보실 수 있습니다. 다음 코드는 CSV 파일을 불러오는 부분입니다.
import pandas as pdstr_columns = ['code', 'date']df = pd.read_csv('20220113_analysis_short.csv', converters={k: lambda x: str(x) for k in str_columns})code, date를 숫자로 읽는 것을 방지하기 위해서 converters 인자를 넣어줍니다. 그리고 다음 코드에서처럼 date로 정렬하고 % 단위를 떼고 NaN 값이 포함된 행을 버립니다.
df = df.sort_values(by='date')# % 단위 떼기df['short_ratio'] /= 100df['bal_rto'] /= 100df = df.dropna().copy()| name | code | date | avg_price | avg_price_ratio | close | diff | diffratio | short_amount | short_ratio | short_volume | volume | bal_rto | bal_amt | bal_qty |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| LX홀딩스 | 383800 | 20210601 | 11078 | -78 | 11000 | 50 | 0.46 | 26214 | 0.012591000000000001 | 23662 | 1879288 | 0.0 | 34639000.0 | 3149.0 |
| DB금융투자 | 016610 | 20210601 | 7215 | 5 | 7220 | 50 | 0.7 | 412 | 0.003169 | 571 | 180173 | 0.0 | 0.0 | 0.0 |
| CJ제일제당 | 097950 | 20210601 | 484327 | 1173 | 485500 | -3000 | -0.61 | 122535 | 0.0437 | 2530 | 57895 | 0.0029 | 21517360000.0 | 44320.0 |
| CJ씨푸드 | 011150 | 20210601 | 4819 | 1 | 4820 | 105 | 2.23 | 22 | 3.8e-05 | 45 | 1188132 | 0.0083 | 1435482760.0 | 297818.0 |
| BGF리테일 | 282330 | 20210601 | 183995 | 5 | 184000 | 2500 | 1.38 | 64950 | 0.089838 | 3530 | 39293 | 0.0007000000000000001 | 2272032000.0 | 12348.0 |
| … | … | … | … | … | … | … | … | … | … | … | … | … | … | … |
| 엠씨넥스 | 097520 | 20220114 | 56312 | -12 | 56300 | 3800 | 7.24 | 141 | 1.9e-05 | 25 | 1321720 | 0.0029 | 2909752900.0 | 51683.0 |
| 더블유게임즈 | 192080 | 20220114 | 53966 | 234 | 54200 | -400 | -0.73 | 47851 | 0.10421200000000001 | 8867 | 85086 | 0.0139 | 13883763600.0 | 256158.0 |
| PI첨단소재 | 178920 | 20220114 | 52734 | 366 | 53100 | 1000 | 1.92 | 34556 | 0.044195000000000005 | 6553 | 148275 | 0.0086 | 13446300600.0 | 253226.0 |
| 포스코케미칼 | 003670 | 20220114 | 129380 | 620 | 130000 | -2000 | -1.52 | 94033 | 0.043436 | 7268 | 167325 | 0.0235 | 236509260000.0 | 1819302.0 |
| LG이노텍 | 011070 | 20220114 | 381022 | 3478 | 384500 | 32000 | 9.08 | 3552349 | 0.05383 | 93232 | 1731987 | 0.005600000000000001 | 50517917000.0 | 131386.0 |
이 데이터에서 종목별로 상환 규모를 확인하고 공매도 거래량, 공매도 잔고, 상환 규모의 5일 이동평균을 구합니다. 이 과정은 몇 분 정도 걸릴 수 있습니다. 그래서 tqdm으로 진행율을 확인합니다.
from tqdm import tqdmfor k, g in tqdm(df.groupby('code')): # 상환 규모 확인 # 당일 short_volume - (당일 bal_qty - 전일 bal_qty) df.loc[df['code'] == k, 'bal_qty_diff'] = g['bal_qty'].diff() df.loc[df['code'] == k, 'repay_volume'] = (df.loc[df['code'] == k, 'short_volume'] - df.loc[df['code'] == k, 'bal_qty_diff']).clip(0) df.loc[df['code'] == k, 'repay_ratio'] = df.loc[df['code'] == k, 'repay_volume'] / df.loc[df['code'] == k, 'volume'] # 이동평균 df.loc[df['code'] == k, 'short_ratio_ma5'] = g['short_ratio'].fillna(0).rolling(5).mean() df.loc[df['code'] == k, 'bal_rto_ma5'] = g['bal_rto'].fillna(0).rolling(5).mean() df.loc[df['code'] == k, 'repay_ratio_ma5'] = df.loc[df['code'] == k, 'repay_ratio'] .fillna(0).rolling(5).mean()이렇게 구한 repay_ratio_ma5로 내림차순 정렬을 해보면 다음과 같은 결과를 얻을 수 있습니다.
_df = df[df['date'] == '20220114'].sort_values(by='repay_ratio_ma5', ascending=False)_df| name | code | date | avg_price | avg_price_ratio | close | diff | diffratio | short_amount | short_ratio | short_volume | volume | bal_rto | bal_amt | bal_qty | bal_qty_diff | repay_volume | repay_ratio | short_ratio_ma5 | bal_rto_ma5 | repay_ratio_ma5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| BGF리테일 | 282330 | 20220114 | 145501 | -1501 | 144000 | -2000 | -1.37 | 188743 | 0.31445 | 12972 | 41253 | 0.0013 | 3342384000.0 | 23211.0 | 4353.0 | 8619.0 | 0.20893025961748238 | 0.24250639999999998 | 0.0013000000000000002 | 0.2374309360178176 |
| 세방전지 | 004490 | 20220114 | 69346 | -146 | 69200 | -1800 | -2.54 | 22322 | 0.094635 | 3219 | 34015 | 0.0191 | 18506225200.0 | 267431.0 | -6377.0 | 9596.0 | 0.2821108334558283 | 0.1048104 | 0.02026 | 0.22422523200232863 |
| 호텔신라 | 008770 | 20220114 | 77304 | 96 | 77400 | -1100 | -1.4 | 594543 | 0.222531 | 76910 | 345615 | 0.0717 | 217719311400.0 | 2812911.0 | 83286.0 | 0.0 | 0.0 | 0.271113 | 0.06978000000000001 | 0.2142213088295697 |
| 금호석유화학 | 011780 | 20220114 | 174115 | -115 | 174000 | 2500 | 1.46 | 293854 | 0.075005 | 16877 | 225011 | 0.05 | 263459838000.0 | 1514137.0 | -44184.0 | 61061.0 | 0.2713689552955189 | 0.0773396 | 0.0519 | 0.20765174156183894 |
| 효성첨단소재 | 298050 | 20220114 | 532588 | 412 | 533000 | -8000 | -1.48 | 314067 | 0.159077 | 5897 | 37070 | 0.0147 | 35143888000.0 | 65936.0 | 15093.0 | 0.0 | 0.0 | 0.1540592 | 0.01316 | 0.18448044554531187 |
| … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … |
| 콤텍시스템 | 031820 | 20220114 | 957 | 0 | 957 | -16 | -1.64 | 1 | 9e-06 | 8 | 937184 | 0.0011 | 123620475.0 | 129175.0 | 0.0 | 8.0 | 8.536210605388056e-06 | 4.259999999999999e-05 | 0.0010999999999999998 | 4.258825517099485e-05 |
| TCC스틸 | 002710 | 20220114 | 12950 | 0 | 12950 | 800 | 6.58 | 3 | 0.0 | 2 | 5084832 | 0.0 | 0.0 | 0.0 | 0.0 | 2.0 | 3.933266625131371e-07 | 3.48e-05 | 0.0 | 3.473958840739652e-05 |
| 미래산업 | 025560 | 20220114 | 13650 | 100 | 13750 | -350 | -2.48 | 1 | 2.5e-05 | 1 | 39706 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 2.518511056263537e-05 | 2.46e-05 | 0.0 | 2.4705969543888606e-05 |
| 이스타코 | 015020 | 20220114 | 3105 | 0 | 3105 | -110 | -3.42 | 1 | 5.999999999999999e-06 | 4 | 721686 | 0.0 | 0.0 | 0.0 | 0.0 | 4.0 | 5.542576688476706e-06 | 2.3000000000000024e-05 | 0.0 | 2.2816870909533658e-05 |
| 오리엔트바이오 | 002630 | 20220114 | 1315 | 5 | 1320 | 20 | 1.54 | 0 | 0.0 | 1 | 2479455 | 0.0013 | 198262680.0 | 150199.0 | 0.0 | 1.0 | 4.033144380519106e-07 | 5.200000000000005e-06 | 0.0013 | 5.125393445035716e-06 |
이 종목들 중에서 공매도 상환율이 높은 상위 5개 종목을 살펴보겠습니다.
BGF리테일
섹션 제목: “BGF리테일”
최근 공매도 거래가 높은데 공매도 잔고는 크게 늘지 않고 유지되고 있습니다. 즉, 공매도 상환율도 높은 상황입니다. 공매도자들의 판단이 엇갈리고 있다고 보이는 지점입니다.
세방전지
섹션 제목: “세방전지”
공매도 거래는 꾸준히 있는데 공매도 잔고가 떨어지고 있습니다.
호텔신라
섹션 제목: “호텔신라”
11월에 어닝쇼크로 공매도 잔고가 급격하게 늘었습니다. 이후 공매도 거래는 많은데 공매도 잔고는 조금씩 오르는 모습입니다.
금호석유화학
섹션 제목: “금호석유화학”
공매도 잔고가 꾸준히 늘다가 최근 하락 전환하는 모습입니다.
효성첨단소재
섹션 제목: “효성첨단소재”
최근 공매도 거래가 상당한 상황입니다.