주식 관련 데이터 크롤링 하기
주식 관련 데이터 크롤링
1. 섹터, 종목 (kor_ticker, kor_sector)
- “kor_ticker.pickle” : (krx) 코스피/코스닥 기준일의 종목코드, 종가, 시가총액, EPS, BPS, 업종명(소분류)
- “kor_sector.pickle” : (wiseindex) 코스피/코스닥 업종명
날짜 크롤링
import requests as rq
from bs4 import BeautifulSoup
import re
#네이버증권-증시자금동향 #deposit : 예탁금
url = 'https://finance.naver.com/sise/sise_deposit.nhn'
data = rq.get(url)
data # Response[200] : 데이터를 제대로 받아왔음d
<Response [200]>
data_html = BeautifulSoup(data.content)
parse_day = data_html.select_one('span.tah').text #copy element
parse_day
'\xa0\xa0|\xa0\xa02023.04.05'
biz_day = re.findall('[0-9]+', parse_day) #['2023','04','05']
biz_day = "".join(biz_day)
biz_day
'20230405'
(참고)
re.findall() 함수는 첫 번째 인수로 지정된 정규표현식 패턴(‘[0-9]+’ 이 경우)을 두 번째 인수인 문자열(parse_day 이 경우)에서 모두 찾아서, 찾은 모든 비중첩 매치(match)를 리스트로 반환합니다.
[0-9] : 0부터 9까지의 숫자 중 하나를 의미합니다.
- : 앞의 패턴([0-9])이 하나 이상 반복되는 것을 의미합니다. 따라서, [0-9]+ 패턴은 숫자(0~9)가 하나 이상 연속으로 나오는 문자열과 매치됩니다. 예를 들어, “123”, “45”, “6789” 등이 매치됩니다.
반대로, “abc”, “12a”, “23b4” 등은 숫자가 하나 이상 연속되는 문자열이 아니므로 매치되지 않습니다.
❍ 업종분류 현황 & 개별종목 지표(kor_ticker)
업종분류
# data.krx.co.kr > 주식 > 세부안내 > 업종분류 현황
# 개발자 도구 열고, csv 다운로드 누르면 generate.cmd, download.cmd 파일이 생김
# generate.cmd > Headers > Request URL :
import requests as rq
from io import BytesIO
import pandas as pd
def crawling_sector(market='STK'):
# generate.cmd > Headers > Request URL
gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
# generate.cmd > Payload
gen_otp_stk = {
'mktId' : market, # KOSPI : "STK", KOSDAQ : "KSQ"
'trdDd' : biz_day,
'money' : '1',
'csvxls_isNo' : 'false',
'name' : 'fileDown',
'url' : 'dbms/MDC/STAT/standard/MDCSTAT03901'
}
# generate.cmd > Headers > Referer (로봇이 아님을..)
headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
otp_stk = rq.post(gen_otp_url, gen_otp_stk, headers=headers).text
# download.cmd > headers > Request URL
down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
down_sector_stk = rq.post(down_url, {'code':otp_stk}, headers=headers)
df= pd.read_csv(BytesIO(down_sector_stk.content), encoding='EUC-KR') # Binary String 형태로 변경
return df
sector_stk = crawling_sector(market='STK') #KOSPI
sector_ksq = crawling_sector(market='KSQ') #KOSDAQ
krx_sector = pd.concat([sector_stk, sector_ksq]).reset_index(drop=True)
krx_sector['종목명'] = krx_sector['종목명'].str.strip() # 공백 제거
krx_sector['기준일'] = biz_day
krx_sector
종목코드 | 종목명 | 시장구분 | 업종명 | 종가 | 대비 | 등락률 | 시가총액 | 기준일 | |
---|---|---|---|---|---|---|---|---|---|
0 | 095570 | AJ네트웍스 | KOSPI | 서비스업 | 4670 | -65 | -1.37 | 218660117650 | 20230405 |
1 | 006840 | AK홀딩스 | KOSPI | 기타금융 | 18450 | 120 | 0.65 | 244417500450 | 20230405 |
2 | 027410 | BGF | KOSPI | 기타금융 | 4480 | 0 | 0.00 | 428811223680 | 20230405 |
3 | 282330 | BGF리테일 | KOSPI | 유통업 | 189300 | 1700 | 0.91 | 3271843405800 | 20230405 |
4 | 138930 | BNK금융지주 | KOSPI | 기타금융 | 6510 | 20 | 0.31 | 2121838451460 | 20230405 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2574 | 024060 | 흥구석유 | KOSDAQ | 유통 | 5890 | -260 | -4.23 | 88350000000 | 20230405 |
2575 | 010240 | 흥국 | KOSDAQ | 기계·장비 | 6260 | -40 | -0.63 | 77140076960 | 20230405 |
2576 | 189980 | 흥국에프엔비 | KOSDAQ | 음식료·담배 | 2695 | -60 | -2.18 | 108171443765 | 20230405 |
2577 | 037440 | 희림 | KOSDAQ | 기타서비스 | 9430 | -20 | -0.21 | 131288939250 | 20230405 |
2578 | 238490 | 힘스 | KOSDAQ | 반도체 | 7220 | -60 | -0.82 | 81674343920 | 20230405 |
2579 rows × 9 columns
bizday를 기준으로 코스피와 코스닥의 업종명을 확인할 수 있습니다.
개별종목
# 주식 > 세부안내 > PER/PBR/배당수익률(개별종목)
# generate.cmd > Headers > Request URL
gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
# generate.cmd > Payload
gen_otp_data = {
'searchType':'1',
'mktId' : 'ALL',
'trdDd' : biz_day, #데이터가 안나올 경우 bizday 문제
'csvxls_isNo' : 'false',
'name' : 'fileDown',
'url' : 'dbms/MDC/STAT/standard/MDCSTAT03501'
}
# generate.cmd > Headers > Referer (로봇이 아님을..)
headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
otp = rq.post(gen_otp_url, gen_otp_data, headers=headers).text
# download.cmd > headers > Request URL
down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
krx_ind = rq.post(down_url, {'code': otp}, headers=headers)
krx_ind = pd.read_csv(BytesIO(krx_ind.content), encoding='EUC-KR') # Binary String 형태로 변경
krx_ind['종목명'] = krx_ind['종목명'].str.strip()
krx_ind['기준일'] = biz_day
krx_ind
sysmmetric difference
집합론에서, 두 집합의 대칭차 또는 대칭차집합은 둘 중 한 집합에는 속하지만 둘 모두에는 속하지는 않는 원소들의 집합이다.
set(krx_sector['종목명']).symmetric_difference(set(krx_ind['종목명']))
{'ESR켄달스퀘어리츠',
'GRT',
'JTC',
'KB스타리츠',
'NH올원리츠',
'NH프라임리츠',
'SBI핀테크솔루션즈',
'SK리츠',
'골든센츄리',
'글로벌에스엠',
'네오이뮨텍',
'디앤디플랫폼리츠',
'로스웰',
'롯데리츠',
'마스턴프리미어리츠',
'맥쿼리인프라',
'맵스리얼티1',
'모두투어리츠',
'미래에셋글로벌리츠',
'미래에셋맵스리츠',
'미투젠',
'바다로19호',
'소마젠',
'신한서부티엔디리츠',
'신한알파리츠',
'씨케이에이치',
'애머릿지',
'에이리츠',
'엑세스바이오',
'엘브이엠씨홀딩스',
'오가닉티코스메틱',
'윙입푸드',
'이리츠코크렙',
'이스트아시아홀딩스',
'이지스레지던스리츠',
'이지스밸류리츠',
'잉글우드랩',
'제이알글로벌리츠',
'컬러레이',
'케이탑리츠',
'코람코더원리츠',
'코람코에너지리츠',
'코오롱티슈진',
'크리스탈신소재',
'프레스티지바이오파마',
'한국ANKOR유전',
'한국패러랠',
'한화리츠',
'헝셩그룹'}
데이터 머징
# 섹터와 지표 데이터 간 공통된 칼럼
krx_sector.columns.intersection(krx_ind.columns).tolist()
['종목코드', '종목명', '종가', '대비', '등락률', '기준일']
kor_ticker = pd.merge(krx_sector,
krx_ind,
on= krx_sector.columns.intersection(krx_ind.columns).tolist(),
how='outer')
kor_ticker
종목코드 | 종목명 | 시장구분 | 업종명 | 종가 | 대비 | 등락률 | 시가총액 | 기준일 | EPS | PER | 선행 EPS | 선행 PER | BPS | PBR | 주당배당금 | 배당수익률 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 095570 | AJ네트웍스 | KOSPI | 서비스업 | 4670 | -65 | -1.37 | 218660117650 | 20230405 | 1707.0 | 2.74 | 619.0 | 7.55 | 8075.0 | 0.58 | 270.0 | 5.78 |
1 | 006840 | AK홀딩스 | KOSPI | 기타금융 | 18450 | 120 | 0.65 | 244417500450 | 20230405 | NaN | NaN | NaN | NaN | 45961.0 | 0.40 | 200.0 | 1.08 |
2 | 027410 | BGF | KOSPI | 기타금융 | 4480 | 0 | 0.00 | 428811223680 | 20230405 | 684.0 | 6.55 | 700.0 | 6.40 | 16393.0 | 0.27 | 110.0 | 2.46 |
3 | 282330 | BGF리테일 | KOSPI | 유통업 | 189300 | 1700 | 0.91 | 3271843405800 | 20230405 | 8547.0 | 22.15 | 12749.0 | 14.85 | 46849.0 | 4.04 | 3000.0 | 1.58 |
4 | 138930 | BNK금융지주 | KOSPI | 기타금융 | 6510 | 20 | 0.31 | 2121838451460 | 20230405 | 2341.0 | 2.78 | 2637.0 | 2.47 | 28745.0 | 0.23 | 560.0 | 8.60 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2574 | 024060 | 흥구석유 | KOSDAQ | 유통 | 5890 | -260 | -4.23 | 88350000000 | 20230405 | 96.0 | 61.35 | NaN | NaN | 5426.0 | 1.09 | 100.0 | 1.70 |
2575 | 010240 | 흥국 | KOSDAQ | 기계·장비 | 6260 | -40 | -0.63 | 77140076960 | 20230405 | 1027.0 | 6.10 | NaN | NaN | 7439.0 | 0.84 | 200.0 | 3.19 |
2576 | 189980 | 흥국에프엔비 | KOSDAQ | 음식료·담배 | 2695 | -60 | -2.18 | 108171443765 | 20230405 | 164.0 | 16.43 | NaN | NaN | 2004.0 | 1.34 | 30.0 | 1.11 |
2577 | 037440 | 희림 | KOSDAQ | 기타서비스 | 9430 | -20 | -0.21 | 131288939250 | 20230405 | 490.0 | 19.24 | NaN | NaN | 4769.0 | 1.98 | 150.0 | 1.59 |
2578 | 238490 | 힘스 | KOSDAQ | 반도체 | 7220 | -60 | -0.82 | 81674343920 | 20230405 | NaN | NaN | NaN | NaN | 6598.0 | 1.09 | 100.0 | 1.39 |
2579 rows × 17 columns
특정 종목들 찾기
# 스팩 / 제x호를 포함하는 종목
cond = kor_ticker['종목명'].str.contains('스팩|제[0-9]+호')
kor_ticker.loc[cond, '종목명']
577 엔에이치스팩19호
960 DB금융스팩10호
961 DB금융스팩8호
962 DB금융스팩9호
984 IBKS제17호스팩
...
2485 하이제7호스팩
2504 한국제10호스팩
2505 한국제11호스팩
2531 한화플러스제2호스팩
2532 한화플러스제3호스팩
Name: 종목명, Length: 72, dtype: object
# 우선주 찾기 : 우리나라 ticker는 끝자리가 0이면 보통주, 0이 아니면 우선주
cond = kor_ticker['종목코드'].str[-1:] != '0'
kor_ticker.loc[cond, '종목명']
6 BYC우
9 CJ4우(전환)
12 CJ씨푸드1우
13 CJ우
15 CJ제일제당 우
...
944 흥국화재우
1203 대호특수강우
1310 루트로닉3우C
1563 소프트센우
2534 해성산업1우
Name: 종목명, Length: 123, dtype: object
# 리츠 : 리츠로 끝나는 종목명
cond = kor_ticker['종목명'].str.endswith('리츠')
kor_ticker.loc[cond, '종목명']
35 ESR켄달스퀘어리츠
64 KB스타리츠
112 NH올원리츠
115 NH프라임리츠
143 SK리츠
344 디앤디플랫폼리츠
350 롯데리츠
364 마스턴프리미어리츠
375 모두투어리츠
383 미래에셋글로벌리츠
384 미래에셋맵스리츠
534 신한서부티엔디리츠
535 신한알파리츠
570 에이리츠
641 이지스레지던스리츠
642 이지스밸류리츠
669 제이알글로벌리츠
717 케이탑리츠
718 코람코더원리츠
719 코람코에너지리츠
871 한화리츠
Name: 종목명, dtype: object
종목 구분 및 저장
import numpy as np
# 한쪽만 있는 데이터
diff = list(set(krx_sector['종목명']).symmetric_difference(set(krx_ind['종목명'])))
# 조건
kor_ticker['종목구분'] = np.where(kor_ticker['종목명'].str.contains('스팩|제[0-9]+호'), '스팩',
np.where(kor_ticker['종목코드'].str[-1:] != '0', '우선주',
np.where(kor_ticker['종목명'].str.endswith('리츠'), '리츠',
np.where(kor_ticker['종목명'].isin(diff), '기타',
'보통주'))))
kor_ticker = kor_ticker.reset_index(drop=True)
kor_ticker.columns = kor_ticker.columns.str.replace(" ", "") #공백제거
kor_ticker = kor_ticker[['종목코드','종목명','시장구분','업종명','종가','시가총액',
'기준일','EPS','선행EPS','BPS','주당배당금','종목구분']] # 열 선택
kor_ticker = kor_ticker.replace({np.nan : None}) # nan값을 None으로 변경 : SQL에서는 nan값을 저장할 수 없음
kor_ticker
종목코드 | 종목명 | 시장구분 | 업종명 | 종가 | 시가총액 | 기준일 | EPS | 선행EPS | BPS | 주당배당금 | 종목구분 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 095570 | AJ네트웍스 | KOSPI | 서비스업 | 4670 | 218660117650 | 20230405 | 1707.0 | 619.0 | 8075.0 | 270.0 | 보통주 |
1 | 006840 | AK홀딩스 | KOSPI | 기타금융 | 18450 | 244417500450 | 20230405 | None | None | 45961.0 | 200.0 | 보통주 |
2 | 027410 | BGF | KOSPI | 기타금융 | 4480 | 428811223680 | 20230405 | 684.0 | 700.0 | 16393.0 | 110.0 | 보통주 |
3 | 282330 | BGF리테일 | KOSPI | 유통업 | 189300 | 3271843405800 | 20230405 | 8547.0 | 12749.0 | 46849.0 | 3000.0 | 보통주 |
4 | 138930 | BNK금융지주 | KOSPI | 기타금융 | 6510 | 2121838451460 | 20230405 | 2341.0 | 2637.0 | 28745.0 | 560.0 | 보통주 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2574 | 024060 | 흥구석유 | KOSDAQ | 유통 | 5890 | 88350000000 | 20230405 | 96.0 | None | 5426.0 | 100.0 | 보통주 |
2575 | 010240 | 흥국 | KOSDAQ | 기계·장비 | 6260 | 77140076960 | 20230405 | 1027.0 | None | 7439.0 | 200.0 | 보통주 |
2576 | 189980 | 흥국에프엔비 | KOSDAQ | 음식료·담배 | 2695 | 108171443765 | 20230405 | 164.0 | None | 2004.0 | 30.0 | 보통주 |
2577 | 037440 | 희림 | KOSDAQ | 기타서비스 | 9430 | 131288939250 | 20230405 | 490.0 | None | 4769.0 | 150.0 | 보통주 |
2578 | 238490 | 힘스 | KOSDAQ | 반도체 | 7220 | 81674343920 | 20230405 | None | None | 6598.0 | 100.0 | 보통주 |
2579 rows × 12 columns
# null값 확인
kor_ticker.isnull().sum()
종목코드 0
종목명 0
시장구분 0
업종명 0
종가 0
시가총액 0
기준일 0
EPS 960
선행EPS 2069
BPS 274
주당배당금 49
종목구분 0
dtype: int64
temp = pd.read_pickle("./data/kor_ticker.pickle")
temp = pd.concat([temp, kor_ticker]).drop_duplicates().reset_index(drop=True)
temp
종목코드 | 종목명 | 시장구분 | 종가 | 시가총액 | 기준일 | EPS | 선행EPS | BPS | 주당배당금 | 종목구분 | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 095570 | AJ네트웍스 | KOSPI | 4735 | 221703566825 | 20230404 | 1707.0 | 619.0 | 8075.0 | 270.0 | 보통주 |
1 | 006840 | AK홀딩스 | KOSPI | 18330 | 242827793130 | 20230404 | None | None | 45961.0 | 200.0 | 보통주 |
2 | 027410 | BGF | KOSPI | 4480 | 428811223680 | 20230404 | 684.0 | 700.0 | 16393.0 | 110.0 | 보통주 |
3 | 282330 | BGF리테일 | KOSPI | 187600 | 3242460765600 | 20230404 | 8547.0 | 13676.0 | 46849.0 | 3000.0 | 보통주 |
4 | 138930 | BNK금융지주 | KOSPI | 6490 | 2115319746540 | 20230404 | 2341.0 | 2644.0 | 28745.0 | 560.0 | 보통주 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
5153 | 024060 | 흥구석유 | KOSDAQ | 5890 | 88350000000 | 20230405 | 96.0 | None | 5426.0 | 100.0 | 보통주 |
5154 | 010240 | 흥국 | KOSDAQ | 6260 | 77140076960 | 20230405 | 1027.0 | None | 7439.0 | 200.0 | 보통주 |
5155 | 189980 | 흥국에프엔비 | KOSDAQ | 2695 | 108171443765 | 20230405 | 164.0 | None | 2004.0 | 30.0 | 보통주 |
5156 | 037440 | 희림 | KOSDAQ | 9430 | 131288939250 | 20230405 | 490.0 | None | 4769.0 | 150.0 | 보통주 |
5157 | 238490 | 힘스 | KOSDAQ | 7220 | 81674343920 | 20230405 | None | None | 6598.0 | 100.0 | 보통주 |
5158 rows × 11 columns
temp.to_pickle("./data/kor_ticker.pickle")
temp = pd.read_pickle("./data/kor_ticker.pickle")
temp
종목코드 | 종목명 | 시장구분 | 업종명 | 종가 | 시가총액 | 기준일 | EPS | 선행EPS | BPS | 주당배당금 | 종목구분 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 095570 | AJ네트웍스 | KOSPI | 서비스업 | 4670 | 218660117650 | 20230405 | 1707.0 | 619.0 | 8075.0 | 270.0 | 보통주 |
1 | 006840 | AK홀딩스 | KOSPI | 기타금융 | 18450 | 244417500450 | 20230405 | None | None | 45961.0 | 200.0 | 보통주 |
2 | 027410 | BGF | KOSPI | 기타금융 | 4480 | 428811223680 | 20230405 | 684.0 | 700.0 | 16393.0 | 110.0 | 보통주 |
3 | 282330 | BGF리테일 | KOSPI | 유통업 | 189300 | 3271843405800 | 20230405 | 8547.0 | 12749.0 | 46849.0 | 3000.0 | 보통주 |
4 | 138930 | BNK금융지주 | KOSPI | 기타금융 | 6510 | 2121838451460 | 20230405 | 2341.0 | 2637.0 | 28745.0 | 560.0 | 보통주 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2574 | 024060 | 흥구석유 | KOSDAQ | 유통 | 5890 | 88350000000 | 20230405 | 96.0 | None | 5426.0 | 100.0 | 보통주 |
2575 | 010240 | 흥국 | KOSDAQ | 기계·장비 | 6260 | 77140076960 | 20230405 | 1027.0 | None | 7439.0 | 200.0 | 보통주 |
2576 | 189980 | 흥국에프엔비 | KOSDAQ | 음식료·담배 | 2695 | 108171443765 | 20230405 | 164.0 | None | 2004.0 | 30.0 | 보통주 |
2577 | 037440 | 희림 | KOSDAQ | 기타서비스 | 9430 | 131288939250 | 20230405 | 490.0 | None | 4769.0 | 150.0 | 보통주 |
2578 | 238490 | 힘스 | KOSDAQ | 반도체 | 7220 | 81674343920 | 20230405 | None | None | 6598.0 | 100.0 | 보통주 |
2579 rows × 12 columns
temp['업종명'].unique()
array(['서비스업', '기타금융', '유통업', '섬유의복', '운수창고업', '음식료품', '증권', '보험', '전기전자',
'건설업', '화학', '철강금속', '광업', '운수장비', '기계', '의약품', '비금속광물', '통신업',
'기타제조업', '전기가스업', '종이목재', '은행', '의료정밀', '농업, 임업 및 어업', '기계·장비',
'금융', '반도체', '통신장비', '운송장비·부품', '방송서비스', '기타서비스', '유통', '제약', '건설',
'일반전기전자', '출판·매체복제', '섬유·의류', '오락·문화', '금속', '소프트웨어', 'IT부품',
'디지털컨텐츠', '기타제조', '비금속', '운송', '인터넷', '정보기기', '음식료·담배', '종이·목재',
'의료·정밀기기', '통신서비스', '컴퓨터서비스', '전기·가스·수도', '숙박·음식'], dtype=object)
❍ 섹터 데이터(kor_sector)
### wiseindex.com/index
### WICS > 에너지 > Components > 개발자도구 > 날짜 선택 > 개발자도구 Headers
### json형식 : https://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt=20230224&sec_cd=G10
import json
import requests as rq
import pandas as pd
url = f'''https://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt={biz_day}&sec_cd=G10'''
data = rq.get(url).json()
data.keys()
dict_keys(['info', 'list', 'sector', 'size'])
# 구성종목 data['list']
# 섹터코드 data['sector']
data['list'][0]
{'IDX_CD': 'G10',
'IDX_NM_KOR': 'WICS 에너지',
'ALL_MKT_VAL': 22080412,
'CMP_CD': '096770',
'CMP_KOR': 'SK이노베이션',
'MKT_VAL': 9273926,
'WGT': 42.0,
'S_WGT': 42.0,
'CAL_WGT': 1.0,
'SEC_CD': 'G10',
'SEC_NM_KOR': '에너지',
'SEQ': 1,
'TOP60': 3,
'APT_SHR_CNT': 51780716}
data_pd = pd.json_normalize(data['list'])
data_pd.head()
IDX_CD | IDX_NM_KOR | ALL_MKT_VAL | CMP_CD | CMP_KOR | MKT_VAL | WGT | S_WGT | CAL_WGT | SEC_CD | SEC_NM_KOR | SEQ | TOP60 | APT_SHR_CNT | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | G10 | WICS 에너지 | 22080412 | 096770 | SK이노베이션 | 9273926 | 42.00 | 42.00 | 1.0 | G10 | 에너지 | 1 | 3 | 51780716 |
1 | G10 | WICS 에너지 | 22080412 | 010950 | S-Oil | 3399100 | 15.39 | 57.39 | 1.0 | G10 | 에너지 | 2 | 3 | 41655633 |
2 | G10 | WICS 에너지 | 22080412 | 267250 | HD현대 | 2601084 | 11.78 | 69.17 | 1.0 | G10 | 에너지 | 3 | 3 | 44236128 |
3 | G10 | WICS 에너지 | 22080412 | 078930 | GS | 1989504 | 9.01 | 78.19 | 1.0 | G10 | 에너지 | 4 | 3 | 49245150 |
4 | G10 | WICS 에너지 | 22080412 | 112610 | 씨에스윈드 | 1719244 | 7.79 | 85.97 | 1.0 | G10 | 에너지 | 5 | 3 | 23615986 |
import json
import time
import requests as rq
import pandas as pd
from tqdm import tqdm
sector_code = ['G25','G35','G50','G40','G10','G20','G55','G30','G15','G45']
data_sector = []
for i in tqdm(sector_code):
url = f'''https://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt={biz_day}&sec_cd={i}'''
data = rq.get(url).json()
data_pd = pd.json_normalize(data['list'])
data_sector.append(data_pd)
time.sleep(2)
100%|███████████████████████████████████████████| 10/10 [00:28<00:00, 2.81s/it]
# 데이터프레임 10개를 품은 리스트
type(data_sector)
list
kor_sector = pd.concat(data_sector, axis=0)
kor_sector = kor_sector[['IDX_CD','CMP_CD','CMP_KOR','SEC_NM_KOR']]
kor_sector['기준일'] = biz_day
kor_sector['기준일'] = pd.to_datetime(kor_sector['기준일'])
kor_sector
IDX_CD | CMP_CD | CMP_KOR | SEC_NM_KOR | 기준일 | |
---|---|---|---|---|---|
0 | G25 | 005380 | 현대차 | 경기관련소비재 | 2023-04-05 |
1 | G25 | 000270 | 기아 | 경기관련소비재 | 2023-04-05 |
2 | G25 | 012330 | 현대모비스 | 경기관련소비재 | 2023-04-05 |
3 | G25 | 051900 | LG생활건강 | 경기관련소비재 | 2023-04-05 |
4 | G25 | 090430 | 아모레퍼시픽 | 경기관련소비재 | 2023-04-05 |
... | ... | ... | ... | ... | ... |
643 | G45 | 009140 | 경인전자 | IT | 2023-04-05 |
644 | G45 | 321260 | 프로이천 | IT | 2023-04-05 |
645 | G45 | 033200 | 모아텍 | IT | 2023-04-05 |
646 | G45 | 038340 | MIT | IT | 2023-04-05 |
647 | G45 | 174880 | 장원테크 | IT | 2023-04-05 |
2384 rows × 5 columns
kor_sector['SEC_NM_KOR'].unique()
array(['경기관련소비재', '건강관리', '커뮤니케이션서비스', '금융', '에너지', '산업재', '유틸리티',
'필수소비재', '소재', 'IT'], dtype=object)
temp = pd.read_pickle('./data/kor_sector.pickle')
temp = pd.concat([temp, kor_sector]).drop_duplicates().reset_index(drop=True)
temp
IDX_CD | CMP_CD | CMP_KOR | SEC_NM_KOR | 기준일 | |
---|---|---|---|---|---|
0 | G25 | 005380 | 현대차 | 경기관련소비재 | 2023-04-05 |
1 | G25 | 000270 | 기아 | 경기관련소비재 | 2023-04-05 |
2 | G25 | 012330 | 현대모비스 | 경기관련소비재 | 2023-04-05 |
3 | G25 | 051900 | LG생활건강 | 경기관련소비재 | 2023-04-05 |
4 | G25 | 090430 | 아모레퍼시픽 | 경기관련소비재 | 2023-04-05 |
... | ... | ... | ... | ... | ... |
2379 | G45 | 009140 | 경인전자 | IT | 2023-04-05 |
2380 | G45 | 321260 | 프로이천 | IT | 2023-04-05 |
2381 | G45 | 033200 | 모아텍 | IT | 2023-04-05 |
2382 | G45 | 038340 | MIT | IT | 2023-04-05 |
2383 | G45 | 174880 | 장원테크 | IT | 2023-04-05 |
2384 rows × 5 columns
temp.to_pickle('./data/kor_sector.pickle')
❍ 월말기준으로 크롤링 (kor_ticker, kor_sector)
import datetime
import pandas as pd
from pandas.tseries.offsets import BMonthEnd
# create a date range from January 2010 to today
date_range = pd.date_range(start='2010-01-01', end='2023-04-30', freq='BM')
# get the last business day of each month
last_business_days = [d.date() for d in date_range - BMonthEnd()]
# 12월 마지막 영업일은 주식시장 문 닫음
last_business_days = [i-datetime.timedelta(days=1) if i.month==12 else i for i in last_business_days]
last_business_days = [i.strftime("%Y%m%d") for i in last_business_days]
# 수동조절
manual_dict = {'20121230':'20121228','20140131':'20140129','20181230':'20181228', '20200430':'20200429', '20200930':'20200929', '20220131':'20220128'}
last_business_days = [manual_dict[i] if i in manual_dict.keys() else i for i in last_business_days]
print(last_business_days)
dates = last_business_days
['20091230', '20100129', '20100226', '20100331', '20100430', '20100531', '20100630', '20100730', '20100831', '20100930', '20101029', '20101130', '20101230', '20110131', '20110228', '20110331', '20110429', '20110531', '20110630', '20110729', '20110831', '20110930', '20111031', '20111130', '20111229', '20120131', '20120229', '20120330', '20120430', '20120531', '20120629', '20120731', '20120831', '20120928', '20121031', '20121130', '20121228', '20130131', '20130228', '20130329', '20130430', '20130531', '20130628', '20130731', '20130830', '20130930', '20131031', '20131129', '20131230', '20140129', '20140228', '20140331', '20140430', '20140530', '20140630', '20140731', '20140829', '20140930', '20141031', '20141128', '20141230', '20150130', '20150227', '20150331', '20150430', '20150529', '20150630', '20150731', '20150831', '20150930', '20151030', '20151130', '20151230', '20160129', '20160229', '20160331', '20160429', '20160531', '20160630', '20160729', '20160831', '20160930', '20161031', '20161130', '20161229', '20170131', '20170228', '20170331', '20170428', '20170531', '20170630', '20170731', '20170831', '20170929', '20171031', '20171130', '20171228', '20180131', '20180228', '20180330', '20180430', '20180531', '20180629', '20180731', '20180831', '20180928', '20181031', '20181130', '20181228', '20190131', '20190228', '20190329', '20190430', '20190531', '20190628', '20190731', '20190830', '20190930', '20191031', '20191129', '20191230', '20200131', '20200228', '20200331', '20200429', '20200529', '20200630', '20200731', '20200831', '20200929', '20201030', '20201130', '20201230', '20210129', '20210226', '20210331', '20210430', '20210531', '20210630', '20210730', '20210831', '20210930', '20211029', '20211130', '20211230', '20220128', '20220228', '20220331', '20220429', '20220531', '20220630', '20220729', '20220831', '20220930', '20221031', '20221130', '20221229', '20230131', '20230228', '20230331']
kor_ticker
def crawling_kor_ticker(biz_day):
def crawling_sector(market='STK'):
# generate.cmd > Headers > Request URL
gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
# generate.cmd > Payload
gen_otp_stk = {
'mktId' : market, # KOSPI : "STK", KOSDAQ : "KSQ"
'trdDd' : biz_day,
'money' : '1',
'csvxls_isNo' : 'false',
'name' : 'fileDown',
'url' : 'dbms/MDC/STAT/standard/MDCSTAT03901'
}
# generate.cmd > Headers > Referer (로봇이 아님을..)
headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
otp_stk = rq.post(gen_otp_url, gen_otp_stk, headers=headers).text
# download.cmd > headers > Request URL
down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
down_sector_stk = rq.post(down_url, {'code':otp_stk}, headers=headers)
df= pd.read_csv(BytesIO(down_sector_stk.content), encoding='EUC-KR') # Binary String 형태로 변경
return df
sector_stk = crawling_sector(market='STK') #KOSPI
sector_ksq = crawling_sector(market='KSQ') #KOSDAQ
krx_sector = pd.concat([sector_stk, sector_ksq]).reset_index(drop=True)
krx_sector['종목명'] = krx_sector['종목명'].str.strip() # 공백 제거
krx_sector['기준일'] = biz_day
# generate.cmd > Headers > Request URL
gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
# generate.cmd > Payload
gen_otp_data = {
'searchType':'1',
'mktId' : 'ALL',
'trdDd' : biz_day, #데이터가 안나올 경우 bizday 문제
'csvxls_isNo' : 'false',
'name' : 'fileDown',
'url' : 'dbms/MDC/STAT/standard/MDCSTAT03501'
}
# generate.cmd > Headers > Referer (로봇이 아님을..)
headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
otp = rq.post(gen_otp_url, gen_otp_data, headers=headers).text
# download.cmd > headers > Request URL
down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
krx_ind = rq.post(down_url, {'code': otp}, headers=headers)
krx_ind = pd.read_csv(BytesIO(krx_ind.content), encoding='EUC-KR') # Binary String 형태로 변경
krx_ind['종목명'] = krx_ind['종목명'].str.strip()
krx_ind['기준일'] = biz_day
# 합치기
kor_ticker = pd.merge(krx_sector,
krx_ind,
on = krx_sector.columns.intersection(krx_ind.columns).tolist(),
how='outer')
# 한쪽만 있는 데이터
diff = list(set(krx_sector['종목명']).symmetric_difference(set(krx_ind['종목명'])))
# 조건
kor_ticker['종목구분'] = np.where(kor_ticker['종목명'].str.contains('스팩|제[0-9]+호'), '스팩',
np.where(kor_ticker['종목코드'].str[-1:] != '0', '우선주',
np.where(kor_ticker['종목명'].str.endswith('리츠'),'리츠',
np.where(kor_ticker['종목명'].isin(diff), '기타',
'보통주'))))
kor_ticker = kor_ticker.reset_index(drop=True)
kor_ticker.columns = kor_ticker.columns.str.replace(" ", "") #공백제거
kor_ticker = kor_ticker[['종목코드','종목명','시장구분','업종명','종가',
'시가총액','기준일','EPS','선행EPS','BPS','주당배당금','종목구분']] # 열 선택
kor_ticker = kor_ticker.replace({np.nan : None}) # nan값을 None으로 변경 : SQL에 nan을 저장할 수 없음
return kor_ticker
kor_ticker = pd.DataFrame(columns = ['종목코드', '종목명', '시장구분', '업종명',
'종가', '시가총액', '기준일', 'EPS', '선행EPS',
'BPS', '주당배당금', '종목구분'])
kor_ticker
종목코드 | 종목명 | 시장구분 | 업종명 | 종가 | 시가총액 | 기준일 | EPS | 선행EPS | BPS | 주당배당금 | 종목구분 |
---|
for date in tqdm(dates):
temp = crawling_kor_ticker(date)
kor_ticker = pd.concat([kor_ticker,temp])
kor_ticker = kor_ticker.drop_duplicates().reset_index(drop=True)
kor_ticker
100%|█████████████████████████████████████████| 160/160 [09:09<00:00, 3.44s/it]
종목코드 | 종목명 | 시장구분 | 업종명 | 종가 | 시가총액 | 기준일 | EPS | 선행EPS | BPS | 주당배당금 | 종목구분 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 004560 | BNG스틸 | KOSPI | 철강금속 | 8650 | 130431715150.0 | 20091230 | None | None | 11755.0 | 0.0 | 보통주 |
1 | 004565 | BNG스틸우 | KOSPI | 철강금속 | 8370 | 919461240.0 | 20091230 | None | None | None | 0.0 | 우선주 |
2 | 001460 | BYC | KOSPI | 섬유의복 | 125000 | 78076875000.0 | 20091230 | 7741.0 | None | 288027.0 | 750.0 | 보통주 |
3 | 001465 | BYC우 | KOSPI | 섬유의복 | 55000 | 11846175000.0 | 20091230 | None | None | None | 800.0 | 우선주 |
4 | 084680 | C&우방랜드 | KOSPI | 서비스업 | 1130 | 37149976050.0 | 20091230 | None | None | 656.0 | 0.0 | 보통주 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
428558 | 024060 | 흥구석유 | KOSDAQ | 유통 | 5600 | 84000000000 | 20230331 | 96.0 | None | 5426.0 | 100.0 | 보통주 |
428559 | 010240 | 흥국 | KOSDAQ | 기계·장비 | 6140 | 75661353440 | 20230331 | 1027.0 | None | 7439.0 | 200.0 | 보통주 |
428560 | 189980 | 흥국에프엔비 | KOSDAQ | 음식료·담배 | 2720 | 109174889440 | 20230331 | 164.0 | None | 2004.0 | 30.0 | 보통주 |
428561 | 037440 | 희림 | KOSDAQ | 기타서비스 | 9120 | 126972972000 | 20230331 | 490.0 | None | 4769.0 | 150.0 | 보통주 |
428562 | 238490 | 힘스 | KOSDAQ | 반도체 | 7100 | 80316875600 | 20230331 | None | None | 6598.0 | 100.0 | 보통주 |
428563 rows × 12 columns
kor_ticker.isnull().sum()
종목코드 0
종목명 0
시장구분 87536
업종명 87536
종가 0
시가총액 87536
기준일 0
EPS 215076
선행EPS 419184
BPS 124273
주당배당금 95744
종목구분 0
dtype: int64
# kor_ticker.to_pickle('./data/kor_ticker.pickle')
kor_ticker_history = pd.read_pickle('./data/kor_ticker.pickle')
kor_ticker_history = pd.concat([kor_ticker_history, kor_ticker]).drop_duplicates().reset_index(drop=True)
kor_ticker_history
#kor_ticker_history.to_pickle('./data/kor_ticker.pickle')
종목코드 | 종목명 | 시장구분 | 업종명 | 종가 | 시가총액 | 기준일 | EPS | 선행EPS | BPS | 주당배당금 | 종목구분 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 004560 | BNG스틸 | KOSPI | 철강금속 | 8650 | 130431715150.0 | 20091230 | None | None | 11755.0 | 0.0 | 보통주 |
1 | 004565 | BNG스틸우 | KOSPI | 철강금속 | 8370 | 919461240.0 | 20091230 | None | None | None | 0.0 | 우선주 |
2 | 001460 | BYC | KOSPI | 섬유의복 | 125000 | 78076875000.0 | 20091230 | 7741.0 | None | 288027.0 | 750.0 | 보통주 |
3 | 001465 | BYC우 | KOSPI | 섬유의복 | 55000 | 11846175000.0 | 20091230 | None | None | None | 800.0 | 우선주 |
4 | 084680 | C&우방랜드 | KOSPI | 서비스업 | 1130 | 37149976050.0 | 20091230 | None | None | 656.0 | 0.0 | 보통주 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
428558 | 024060 | 흥구석유 | KOSDAQ | 유통 | 5600 | 84000000000 | 20230331 | 96.0 | None | 5426.0 | 100.0 | 보통주 |
428559 | 010240 | 흥국 | KOSDAQ | 기계·장비 | 6140 | 75661353440 | 20230331 | 1027.0 | None | 7439.0 | 200.0 | 보통주 |
428560 | 189980 | 흥국에프엔비 | KOSDAQ | 음식료·담배 | 2720 | 109174889440 | 20230331 | 164.0 | None | 2004.0 | 30.0 | 보통주 |
428561 | 037440 | 희림 | KOSDAQ | 기타서비스 | 9120 | 126972972000 | 20230331 | 490.0 | None | 4769.0 | 150.0 | 보통주 |
428562 | 238490 | 힘스 | KOSDAQ | 반도체 | 7100 | 80316875600 | 20230331 | None | None | 6598.0 | 100.0 | 보통주 |
428563 rows × 12 columns
kor_sector
- 2010년 1월 데이터부터 존재함
def crawling_kor_sector(biz_day):
sector_code = ['G25','G35','G50','G40','G10','G20','G55','G30','G15','G45']
data_sector = []
for i in sector_code:
url = f'''https://www.wiseindex.com/Index/GetIndexComponets?ceil_yn=0&dt={biz_day}&sec_cd={i}'''
data = rq.get(url).json()
data_pd = pd.json_normalize(data['list'])
data_sector.append(data_pd)
time.sleep(2)
kor_sector = pd.concat(data_sector, axis=0)
kor_sector = kor_sector[['IDX_CD','CMP_CD','CMP_KOR','SEC_NM_KOR']]
kor_sector['기준일'] = biz_day
kor_sector['기준일'] = pd.to_datetime(kor_sector['기준일'])
return kor_sector
kor_sector = pd.DataFrame(columns = ['IDX_CD','CMP_CD','CMP_KOR','SEC_NM_KOR','기준일'])
kor_sector
IDX_CD | CMP_CD | CMP_KOR | SEC_NM_KOR | 기준일 |
---|
for date in tqdm(dates[1:]): # 2010-01 부터
temp = crawling_kor_sector(date)
kor_sector = pd.concat([kor_sector,temp])
kor_sector = kor_sector.drop_duplicates().reset_index(drop=True)
kor_sector
100%|█████████████████████████████████████████| 158/158 [17:09<00:00, 6.52s/it]
IDX_CD | CMP_CD | CMP_KOR | SEC_NM_KOR | 기준일 | |
---|---|---|---|---|---|
0 | G25 | 005380 | 현대차 | 경기관련소비재 | 2010-01-29 |
1 | G25 | 012330 | 현대모비스 | 경기관련소비재 | 2010-01-29 |
2 | G25 | 004170 | 신세계 | 경기관련소비재 | 2010-01-29 |
3 | G25 | 000270 | 기아 | 경기관련소비재 | 2010-01-29 |
4 | G25 | 023530 | 롯데쇼핑 | 경기관련소비재 | 2010-01-29 |
... | ... | ... | ... | ... | ... |
294780 | G45 | 174880 | 장원테크 | IT | 2023-02-28 |
294781 | G45 | 043590 | 크로바하이텍 | IT | 2023-02-28 |
294782 | G45 | 033200 | 모아텍 | IT | 2023-02-28 |
294783 | G45 | 321260 | 프로이천 | IT | 2023-02-28 |
294784 | G45 | 038340 | MIT | IT | 2023-02-28 |
294785 rows × 5 columns
#kor_sector.to_pickle("./data/kor_sector.pickle")
kor_sector_history = pd.read_pickle("./data/kor_sector.pickle")
kor_sector_history = pd.concat([kor_sector_history, kor_sector]).drop_duplicates().reset_index(drop=True)
kor_sector_history
# kor_sector_history.to_pickle('./data/kor_sector.pickle')
IDX_CD | CMP_CD | CMP_KOR | SEC_NM_KOR | 기준일 | |
---|---|---|---|---|---|
0 | G25 | 005380 | 현대차 | 경기관련소비재 | 2010-01-29 |
1 | G25 | 012330 | 현대모비스 | 경기관련소비재 | 2010-01-29 |
2 | G25 | 004170 | 신세계 | 경기관련소비재 | 2010-01-29 |
3 | G25 | 000270 | 기아 | 경기관련소비재 | 2010-01-29 |
4 | G25 | 023530 | 롯데쇼핑 | 경기관련소비재 | 2010-01-29 |
... | ... | ... | ... | ... | ... |
297166 | G45 | 009140 | 경인전자 | IT | 2023-03-31 |
297167 | G45 | 321260 | 프로이천 | IT | 2023-03-31 |
297168 | G45 | 033200 | 모아텍 | IT | 2023-03-31 |
297169 | G45 | 038340 | MIT | IT | 2023-03-31 |
297170 | G45 | 174880 | 장원테크 | IT | 2023-03-31 |
297171 rows × 5 columns
2. 주식 가격 크롤링 (stock_price)
# 수정주가가 필요
# 네이버 증권을 추천함 (차트는 수정주가를 기준으로 함)
# 네이버증권 > 개발자도구 > 차트 > 일
# https://api.finance.naver.com/siseJson.naver?symbol=005930&requestType=1&startTime=20210414&endTime=20211220&timeframe=day
import pandas as pd
from dateutil.relativedelta import relativedelta
import requests as rq
from io import BytesIO
from datetime import date
import time
from tqdm import tqdm
kor_sector = pd.read_pickle('./data/kor_sector.pickle')
kor_ticker = pd.read_pickle('./data/kor_ticker.pickle')
# 마지막 날 기준으로
kor_ticker_last_day = kor_ticker['기준일'].unique()[-1]
kor_sector_last_day = kor_sector['기준일'].unique()[-1]
cond = (kor_ticker['종목구분'] == '보통주') & \
(kor_ticker['기준일']==kor_ticker_last_day)
ticker_list = kor_ticker[cond].reset_index(drop=True)
ticker_name_list = ticker_list[['종목코드','종목명']].set_index("종목코드")
ticker_name_list
종목명 | |
---|---|
종목코드 | |
095570 | AJ네트웍스 |
006840 | AK홀딩스 |
027410 | BGF |
282330 | BGF리테일 |
138930 | BNK금융지주 |
... | ... |
024060 | 흥구석유 |
010240 | 흥국 |
189980 | 흥국에프엔비 |
037440 | 희림 |
238490 | 힘스 |
2334 rows × 1 columns
❍ 한종목 가격 가져오기
# 조회 시작일 ~ 종료일
fr = (date.today() + relativedelta(years=-5)).strftime("%Y%m%d")
to = (date.today()).strftime("%Y%m%d")
print(fr, to)
ticker = ticker_name_list.index[0]
print(ticker)
url = f'''https://fchart.stock.naver.com/siseJson.naver?symbol={ticker}&requestType=1&startTime={fr}&endTime={to}&timeframe=day'''
data = rq.get(url).content
data_price = pd.read_csv(BytesIO(data))
data_price
20180408 20230408
095570
[['날짜' | '시가' | '고가' | '저가' | '종가' | '거래량' | '외국인소진율'] | Unnamed: 7 | |
---|---|---|---|---|---|---|---|---|
0 | ["20180409" | 6870.0 | 7030.0 | 6810.0 | 6870.0 | 26772.0 | 8.34] | NaN |
1 | ["20180410" | 6870.0 | 6960.0 | 6590.0 | 6940.0 | 70302.0 | 8.33] | NaN |
2 | ["20180411" | 6970.0 | 7000.0 | 6860.0 | 6860.0 | 31123.0 | 8.32] | NaN |
3 | ["20180412" | 6810.0 | 7000.0 | 6810.0 | 6950.0 | 19823.0 | 8.33] | NaN |
4 | ["20180413" | 6950.0 | 7000.0 | 6790.0 | 6990.0 | 35496.0 | 8.33] | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... |
1229 | ["20230404" | 4680.0 | 4800.0 | 4580.0 | 4735.0 | 163332.0 | 4.03] | NaN |
1230 | ["20230405" | 4685.0 | 4765.0 | 4635.0 | 4670.0 | 147912.0 | 3.93] | NaN |
1231 | ["20230406" | 4685.0 | 4730.0 | 4590.0 | 4660.0 | 189535.0 | 3.9] | NaN |
1232 | ["20230407" | 4715.0 | 4745.0 | 4615.0 | 4660.0 | 156870.0 | 3.9] | NaN |
1233 | ] | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1234 rows × 8 columns
import re
price = data_price.iloc[:, 0:6]
price.columns = ['날짜','시가','고가','저가','종가','거래량']
price = price.dropna()
price['날짜'] = price['날짜'].str.extract('(\d+)') #숫자만 뽑기
price['날짜'] = pd.to_datetime(price['날짜'])
price['종목코드'] = ticker
price.tail(3)
날짜 | 시가 | 고가 | 저가 | 종가 | 거래량 | 종목코드 | |
---|---|---|---|---|---|---|---|
1230 | 2023-04-05 | 4685.0 | 4765.0 | 4635.0 | 4670.0 | 147912.0 | 095570 |
1231 | 2023-04-06 | 4685.0 | 4730.0 | 4590.0 | 4660.0 | 189535.0 | 095570 |
1232 | 2023-04-07 | 4715.0 | 4745.0 | 4615.0 | 4660.0 | 156870.0 | 095570 |
❍ 여러종목 가격 한번에 불러오기
stock_price = pd.read_pickle('./data/stock_price.pickle')
stock_price.tail(1)
날짜 | 시가 | 고가 | 저가 | 종가 | 거래량 | 종목코드 | 종목명 | |
---|---|---|---|---|---|---|---|---|
5873725 | 2023-03-31 | 4130.0 | 4260.0 | 4030.0 | 4220.0 | 637385.0 | 450140 | 코오롱모빌리티그룹 |
df = pd.DataFrame(columns = ['날짜','시가','고가','저가','종가','거래량','종목코드'])
error_list = []
#fr = "20100101"
fr = (pd.to_datetime(sorted(stock_price['날짜'].unique())[-1]) + relativedelta(days=1)).strftime("%Y%m%d")
to = (date.today()).strftime("%Y%m%d")
print(fr, to)
for i in tqdm(range(0, len(ticker_list))):
#for i in tqdm(range(0, 2)):
ticker = ticker_list['종목코드'][i]
# ticker = ticker_list[0][i]
try :
url = f'''https://fchart.stock.naver.com/siseJson.naver?symbol={ticker}&requestType=1&startTime={fr}&endTime={to}&timeframe=day'''
# 데이터 다운로드
data = rq.get(url).content
data_price = pd.read_csv(BytesIO(data))
# 데이터 클렌징
price = data_price.iloc[:, 0:6]
price.columns = ['날짜','시가','고가','저가','종가','거래량']
price = price.dropna()
price['날짜'] = price['날짜'].str.extract('(\d+)') #숫자만 뽑기
price['날짜'] = pd.to_datetime(price['날짜'])
price['종목코드'] = ticker
df = pd.concat([df, price])
except:
error_list.append(ticker)
time.sleep(2)
print('error list : ')
print(error_list)
20230401 20230408
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 2334/2334 [02:57<00:00, 13.12it/s]
error list :
[]
stock_price = pd.concat([stock_price, df]).sort_values(by=['날짜','종목코드']).drop_duplicates(['날짜','종목코드'],keep='last').reset_index(drop=True)
# 종목명 매핑
stock_price['종목명'] = stock_price['종목코드'].map(ticker_name_list['종목명'])
stock_price
날짜 | 시가 | 고가 | 저가 | 종가 | 거래량 | 종목코드 | 종목명 | |
---|---|---|---|---|---|---|---|---|
0 | 2010-01-04 | 7540.0 | 7820.0 | 7480.0 | 7520.0 | 177197.0 | 000020 | 동화약품 |
1 | 2010-01-04 | 3419.0 | 3724.0 | 3419.0 | 3635.0 | 1123647.0 | 000040 | KR모터스 |
2 | 2010-01-04 | 9883.0 | 10186.0 | 9768.0 | 9921.0 | 721.0 | 000050 | 경방 |
3 | 2010-01-04 | 46309.0 | 46516.0 | 45789.0 | 46517.0 | 13390.0 | 000070 | 삼양홀딩스 |
4 | 2010-01-04 | 40100.0 | 40300.0 | 39600.0 | 39950.0 | 100021.0 | 000080 | 하이트진로 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
5882123 | 2023-04-07 | 10820.0 | 10920.0 | 10250.0 | 10330.0 | 249003.0 | 425420 | 티에프이 |
5882124 | 2023-04-07 | 10250.0 | 10480.0 | 10030.0 | 10450.0 | 336032.0 | 441270 | 파인엠텍 |
5882125 | 2023-04-07 | 10360.0 | 10480.0 | 9980.0 | 10030.0 | 265553.0 | 446070 | 유니드비티플러스 |
5882126 | 2023-04-07 | 4795.0 | 4890.0 | 4480.0 | 4560.0 | 1546455.0 | 450140 | 코오롱모빌리티그룹 |
5882127 | 2023-04-07 | 2050.0 | 2090.0 | 2040.0 | 2045.0 | 6223645.0 | 452260 | 한화갤러리아 |
5882128 rows × 8 columns
%%time
stock_price_pivot = stock_price.pivot(index='날짜', columns='종목명', values='종가')
stock_price_pivot
CPU times: user 756 ms, sys: 123 ms, total: 879 ms
Wall time: 879 ms
종목명 | 3S | AJ네트웍스 | AK홀딩스 | APS홀딩스 | AP시스템 | AP위성 | BGF | BGF리테일 | BGF에코머티리얼즈 | BNK금융지주 | ... | 휴온스 | 휴온스글로벌 | 휴젤 | 흥구석유 | 흥국 | 흥국에프엔비 | 흥국화재 | 흥아해운 | 희림 | 힘스 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
날짜 | |||||||||||||||||||||
2010-01-04 | 1282.0 | NaN | 10331.0 | 3282.0 | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | 5108.0 | NaN | 2655.0 | 2330.0 | NaN | 6850.0 | 6883.0 | 10900.0 | NaN |
2010-01-05 | 1277.0 | NaN | 10206.0 | 3278.0 | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | 5139.0 | NaN | 2670.0 | 2195.0 | NaN | 6630.0 | 6839.0 | 10550.0 | NaN |
2010-01-06 | 1307.0 | NaN | 10206.0 | 3275.0 | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | 5139.0 | NaN | 2700.0 | 2150.0 | NaN | 6690.0 | 6795.0 | 10200.0 | NaN |
2010-01-07 | 1431.0 | NaN | 10300.0 | 3323.0 | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | 5121.0 | NaN | 2680.0 | 2150.0 | NaN | 6500.0 | 7344.0 | 10100.0 | NaN |
2010-01-08 | 1391.0 | NaN | 10362.0 | 3442.0 | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | 5121.0 | NaN | 2680.0 | 2150.0 | NaN | 6440.0 | 7289.0 | 10100.0 | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2023-04-03 | 2305.0 | 4650.0 | 18180.0 | 14920.0 | 22450.0 | 21650.0 | 4520.0 | 180700.0 | 7890.0 | 6490.0 | ... | 31900.0 | 19740.0 | 126900.0 | 5720.0 | 6200.0 | 2760.0 | 3180.0 | 1379.0 | 9300.0 | 7110.0 |
2023-04-04 | 2355.0 | 4735.0 | 18330.0 | 14650.0 | 21900.0 | 21800.0 | 4480.0 | 187600.0 | 8040.0 | 6490.0 | ... | 33200.0 | 19740.0 | 130800.0 | 6150.0 | 6300.0 | 2755.0 | 3200.0 | 1385.0 | 9450.0 | 7280.0 |
2023-04-05 | 2345.0 | 4670.0 | 18450.0 | 14860.0 | 22050.0 | 22150.0 | 4480.0 | 189300.0 | 8250.0 | 6510.0 | ... | 32900.0 | 19920.0 | 127400.0 | 5890.0 | 6260.0 | 2695.0 | 3210.0 | 1388.0 | 9430.0 | 7220.0 |
2023-04-06 | 2290.0 | 4660.0 | 18480.0 | 14180.0 | 21350.0 | 20850.0 | 4410.0 | 189400.0 | 7970.0 | 6440.0 | ... | 32800.0 | 19790.0 | 129200.0 | 5740.0 | 6250.0 | 2615.0 | 3205.0 | 1368.0 | 9310.0 | 7050.0 |
2023-04-07 | 2400.0 | 4660.0 | 18900.0 | 14400.0 | 21750.0 | 21300.0 | 4465.0 | 183800.0 | 8080.0 | 6680.0 | ... | 32850.0 | 20300.0 | 135400.0 | 5750.0 | 6150.0 | 2620.0 | 3190.0 | 1352.0 | 9480.0 | 7260.0 |
3275 rows × 2334 columns
# stock_price.to_pickle('./data/stock_price.pickle')
# stock_price_pivot.to_pickle('./data/stock_price_pivot.pickle')
3. ETF 가격 크롤링 (etf_price)
from pykrx import stock
import requests
import json
from pandas.io.json import json_normalize
import pandas as pd
import datetime
date = datetime.datetime.now().strftime("%Y%m%d")
tickers = stock.get_etf_ticker_list(date)
tickers=pd.DataFrame(tickers,columns=['종목코드'])
url = 'https://finance.naver.com/api/sise/etfItemList.nhn'
json_data = json.loads(requests.get(url).text)
df = json_normalize(json_data['result']['etfItemList'])
df=df[['itemcode','itemname']]
df=df.rename(columns={"itemcode": "종목코드", "itemname": "종목명"})
etf=pd.merge(left=tickers,right=df,how='left',on='종목코드' )
etf
/var/folders/vr/g5rxclqn2qb11v47jh4p2lrw0000gn/T/ipykernel_1979/1657116227.py:14: FutureWarning: pandas.io.json.json_normalize is deprecated, use pandas.json_normalize instead.
df = json_normalize(json_data['result']['etfItemList'])
종목코드 | 종목명 | |
---|---|---|
0 | 445690 | BNK 주주가치액티브 |
1 | 292340 | 마이티 200커버드콜ATM레버리지 |
2 | 442260 | 마이티 다이나믹퀀트액티브 |
3 | 159800 | 마이티 코스피100 |
4 | 361580 | KBSTAR 200TR |
... | ... | ... |
688 | 195980 | ARIRANG 신흥국MSCI(합성 H) |
689 | 433250 | UNICORN R&D 액티브 |
690 | 215620 | HK S&P코리아로우볼 |
691 | 391670 | HK 베스트일레븐액티브 |
692 | 391680 | HK 하이볼액티브 |
693 rows × 2 columns
etf[['종목명', '종목코드']].itertuples(index=False)
<map at 0x17ed36d10>
etf_price = pd.read_pickle("./data/etf_price.pickle")
etf_price
NAV | 시가 | 고가 | 저가 | 종가 | 거래량 | 거래대금 | 기초지수 | 종목명 | 종목코드 | 날짜 | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 22631.83 | 22390 | 22585 | 22390 | 22550 | 58738 | 1325723895 | 223.49 | ACE 200 | 105190 | 2010-01-04 |
1 | 22566.21 | 22550 | 22725 | 22475 | 22555 | 47769 | 1081075485 | 222.84 | ACE 200 | 105190 | 2010-01-05 |
2 | 22748.91 | 22625 | 22735 | 22620 | 22715 | 74937 | 1701637315 | 224.67 | ACE 200 | 105190 | 2010-01-06 |
3 | 22409.69 | 22740 | 22740 | 22415 | 22455 | 26044 | 587893595 | 221.31 | ACE 200 | 105190 | 2010-01-07 |
4 | 22544.98 | 22365 | 22515 | 22230 | 22515 | 11803 | 264454100 | 222.66 | ACE 200 | 105190 | 2010-01-08 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
812503 | 7211.78 | 7245 | 7245 | 7180 | 7205 | 144 | 1037685 | 928.25 | 히어로즈 리츠이지스액티브 | 429870 | 2023-04-03 |
812504 | 7243.46 | 7205 | 7220 | 7175 | 7190 | 467 | 3351265 | 930.73 | 히어로즈 리츠이지스액티브 | 429870 | 2023-04-04 |
812505 | 7231.48 | 7285 | 7285 | 7190 | 7190 | 53 | 381195 | 929.02 | 히어로즈 리츠이지스액티브 | 429870 | 2023-04-05 |
812506 | 7183.86 | 0 | 0 | 0 | 7190 | 0 | 0 | 922.52 | 히어로즈 리츠이지스액티브 | 429870 | 2023-04-06 |
812507 | 7166.16 | 7155 | 7175 | 7115 | 7175 | 133 | 948425 | 920.66 | 히어로즈 리츠이지스액티브 | 429870 | 2023-04-07 |
812508 rows × 11 columns
import time
from tqdm import tqdm
df = pd.DataFrame(columns=['NAV','시가','고가','저가','종가','거래량','거래대금','기초지수','종목명','종목코드'])
error_list = []
start_date = (pd.to_datetime(sorted(etf_price['날짜'].unique())[-1])+datetime.timedelta(days=1)).strftime("%Y%m%d")
end_date = datetime.datetime.now().strftime("%Y%m%d")
print(start_date, end_date)
for a, b in tqdm(etf[['종목명', '종목코드']].itertuples(index=False)):
try:
price = stock.get_etf_ohlcv_by_date(start_date, end_date, b)
price['종목명']=a
price['종목코드']=b
price = price.reset_index()
df = pd.concat([df,price])
except:
error_list.append(a)
time.sleep(0.01)
df = df.reset_index(drop=True)
df
error_list
# 신규 추가 종목
set(df['종목명']).symmetric_difference(set(etf_price['종목명']))
etf_price = pd.concat([etf_price, df])
etf_price = etf_price.drop_duplicates(['종목명','종목코드','날짜'], keep='last').sort_values(by=['종목명','날짜']).reset_index(drop=True)
etf_price
# 종목명에 null값이 있을 때,
temp = etf_price[['종목명','종목코드']].dropna().drop_duplicates(['종목코드'], keep='last').set_index('종목코드')['종목명']
etf_price.loc[etf_price['종목명'].isnull(),'종목명'] = etf_price.loc[etf_price['종목명'].isnull(),'종목코드'].map(temp)
etf_price = etf_price.sort_values(by=['종목명','날짜']).reset_index(drop=True)
etf_price
%%time
etf_price_pivot = etf_price.pivot_table(index='날짜', columns = '종목명', values='종가')
etf_price_pivot
CPU times: user 23.7 s, sys: 107 ms, total: 23.8 s
Wall time: 23.8 s
종목명 | ACE 200 | ACE 200TR | ACE 23-12 회사채(AA-이상)액티브 | ACE 24-12 회사채(AA-이상)액티브 | ACE ESG액티브 | ACE Fn5G플러스 | ACE Fn성장소비주도주 | ACE G2전기차&자율주행액티브 | ACE KRX금현물 | ACE 골드선물 레버리지(합성 H) | ... | 에셋플러스 코리아플랫폼액티브 | 파워 200 | 파워 고배당저변동성 | 파워 코스피100 | 히어로즈 TDF2030액티브 | 히어로즈 TDF2040액티브 | 히어로즈 TDF2050액티브 | 히어로즈 글로벌리츠이지스액티브 | 히어로즈 단기채권ESG액티브 | 히어로즈 리츠이지스액티브 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
날짜 | |||||||||||||||||||||
2010-01-04 | 22550.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2010-01-05 | 22555.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2010-01-06 | 22715.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2010-01-07 | 22455.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2010-01-08 | 22515.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2023-04-03 | 32940.0 | 20445.0 | 103155.0 | 105115.0 | 7290.0 | 7165.0 | 6285.0 | 9000.0 | 11800.0 | 17305.0 | ... | 6960.0 | 33010.0 | 28360.0 | 24730.0 | 10390.0 | 10535.0 | 10550.0 | 8905.0 | 101925.0 | 7205.0 |
2023-04-04 | 33075.0 | 20525.0 | 103140.0 | 105180.0 | 7320.0 | 7115.0 | 6365.0 | 8875.0 | 12000.0 | 17600.0 | ... | 7030.0 | 33130.0 | 28420.0 | 24700.0 | 10430.0 | 10520.0 | 10590.0 | 8855.0 | 101925.0 | 7190.0 |
2023-04-05 | 33255.0 | 20650.0 | 103175.0 | 105210.0 | 7345.0 | 7145.0 | 6350.0 | 8800.0 | 12210.0 | 18385.0 | ... | 7065.0 | 33315.0 | 28435.0 | 24870.0 | 10390.0 | 10485.0 | 10530.0 | 8795.0 | 102000.0 | 7190.0 |
2023-04-06 | 32680.0 | 20270.0 | 103210.0 | 105310.0 | 7215.0 | 7000.0 | 6320.0 | 8680.0 | 12275.0 | 18420.0 | ... | 7025.0 | 32765.0 | 28145.0 | 24545.0 | 10405.0 | 10515.0 | 10540.0 | 8825.0 | 102030.0 | 7190.0 |
2023-04-07 | 33260.0 | 20640.0 | 103245.0 | 105280.0 | 7330.0 | 7130.0 | 6365.0 | 8710.0 | 12460.0 | 18290.0 | ... | 7070.0 | 33300.0 | 28070.0 | 24850.0 | 10390.0 | 10525.0 | 10550.0 | 9000.0 | 102060.0 | 7175.0 |
3275 rows × 697 columns
# etf_price.to_pickle("./data/etf_price.pickle")
# data.to_pickle("./data/etf_price_pivot.pickle")
4. 재무제표 (data_fs)
- “data_fs.pickle” : FNGUIDE 에서 최근 4분기 재무제표 크롤링
# 네이버증권에서 재무제표를 다운받으려면 동적으로 selenium을 써야하나 너무 느리다.
# comp.fnguide.com
❍ 한 종목 재무제표 크롤링
# 최근 시점의 보통주 데이터 불러오기
import pandas as pd
kor_ticker = pd.read_pickle('./data/kor_ticker.pickle')
kor_sector = pd.read_pickle('./data/kor_sector.pickle')
kor_ticker_last_day = kor_ticker['기준일'].unique()[-1]
kor_sector_last_day = kor_sector['기준일'].unique()[-1]
cond = (kor_ticker['종목구분'] == '보통주') & \
(kor_ticker['기준일']==kor_ticker_last_day)
ticker_list = kor_ticker[cond].reset_index(drop=True)
한 종목의 재무제표 데이터 다운로드
i=0
ticker = ticker_list['종목코드'][0]
url = f'https://comp.fnguide.com/SVO2/ASP/SVD_Finance.asp?pGB=1&gicode=A{ticker}'
data = pd.read_html(url, displayed_only = False)
#displayed_only = False하면 +로 숨겨져 있는 것들도 모두 가져옴
print(len(data))
6
print(data[0].columns.tolist(),'\n', #연 - 손익계산서
data[1].columns.tolist(), '\n', #분기 - 손익계산서
data[2].columns.tolist(), '\n', #연 - 재무상태표
data[3].columns.tolist(), '\n', #분기 - 재무상태표
data[4].columns.tolist(),'\n', #연 - 현금흐름표
data[5].columns.tolist()) #분기 - 현금흐름표
['IFRS(연결)', '2019/12', '2020/12', '2021/12', '2022/12', '전년동기', '전년동기(%)']
['IFRS(연결)', '2022/03', '2022/06', '2022/09', '2022/12', '전년동기', '전년동기(%)']
['IFRS(연결)', '2019/12', '2020/12', '2021/12', '2022/12']
['IFRS(연결)', '2022/03', '2022/06', '2022/09', '2022/12']
['IFRS(연결)', '2019/12', '2020/12', '2021/12', '2022/12']
['IFRS(연결)', '2022/03', '2022/06', '2022/09', '2022/12']
data[0].iloc[:, ~data[0].columns.str.contains('전년동기')].head(3)
IFRS(연결) | 2019/12 | 2020/12 | 2021/12 | 2022/12 | |
---|---|---|---|---|---|
0 | 매출액 | 10003.0 | 8720.0 | 9819.0 | 12084.0 |
1 | 매출원가 | NaN | NaN | NaN | NaN |
2 | 매출총이익 | 10003.0 | 8720.0 | 9819.0 | 12084.0 |
data[1].iloc[:, ~data[0].columns.str.contains('전년동기')].head(3)
IFRS(연결) | 2022/03 | 2022/06 | 2022/09 | 2022/12 | |
---|---|---|---|---|---|
0 | 매출액 | 2958.0 | 2842.0 | 3451.0 | 2833.0 |
1 | 매출원가 | NaN | NaN | NaN | NaN |
2 | 매출총이익 | 2958.0 | 2842.0 | 3451.0 | 2833.0 |
data[2].head(3)
IFRS(연결) | 2019/12 | 2020/12 | 2021/12 | 2022/12 | |
---|---|---|---|---|---|
0 | 자산 | 18033.0 | 15882.0 | 13550.0 | 14814.0 |
1 | 유동자산계산에 참여한 계정 펼치기 | 5011.0 | 3547.0 | 2739.0 | 3295.0 |
2 | 재고자산 | 586.0 | 337.0 | 197.0 | 250.0 |
data[3].head(3)
IFRS(연결) | 2022/03 | 2022/06 | 2022/09 | 2022/12 | |
---|---|---|---|---|---|
0 | 자산 | 13672.0 | 13932.0 | 15020.0 | 14814.0 |
1 | 유동자산계산에 참여한 계정 펼치기 | 2619.0 | 2702.0 | 3120.0 | 3295.0 |
2 | 재고자산 | 289.0 | 357.0 | 428.0 | 250.0 |
data[4].head(3)
IFRS(연결) | 2019/12 | 2020/12 | 2021/12 | 2022/12 | |
---|---|---|---|---|---|
0 | 영업활동으로인한현금흐름 | -379.0 | 177.0 | -113.0 | 183.0 |
1 | 당기순손익 | 421.0 | -33.0 | 767.0 | 88.0 |
2 | 법인세비용차감전계속사업이익 | NaN | NaN | NaN | NaN |
data[5].head(3)
IFRS(연결) | 2022/03 | 2022/06 | 2022/09 | 2022/12 | |
---|---|---|---|---|---|
0 | 영업활동으로인한현금흐름 | 30.0 | -231.0 | -312.0 | 696.0 |
1 | 당기순손익 | 109.0 | 97.0 | 103.0 | -221.0 |
2 | 법인세비용차감전계속사업이익 | NaN | NaN | NaN | NaN |
연기준 재무제표 만들기
# 결산월 확인하기 : 보통 12월 결산
import requests as rq
from bs4 import BeautifulSoup
import re
page_data = rq.get(url)
page_data_html = BeautifulSoup(page_data.content)
fiscal_data = page_data_html.select('div.corp_group1 > h2')
fiscal_data_text = fiscal_data[1].text
fiscal_data_text = re.findall('[0-9]+', fiscal_data_text)
print("결산월 : ", fiscal_data_text)
결산월 : ['12']
# 데이터 클린징
def clean_fs(df, ticker, frequency):
# 계정을 제외하고 모든 열의 na인 행을 제외, 적어도 하나의 값 있는 행만 생존
df = df[~df.loc[:, ~df.columns.isin(['계정'])].isna().all(axis=1)]
df = df.drop_duplicates(['계정'], keep='first') #중복되면 맨 앞 값만 생존
df = pd.melt(df, id_vars="계정", var_name='기준일', value_name="값")
df = df[~pd.isnull(df['값'])]
# + : 펼치기 버튼 삭제
df['계정'] = df['계정'].replace({"계산에 참여한 계정 펼치기": ""}, regex=True)
df['기준일'] = pd.to_datetime(df['기준일'], format="%Y-%m") + pd.tseries.offsets.MonthEnd() # 월말로 바꿔줌
df['종목코드'] = ticker
df['공시구분'] = frequency
return df
data_fs_y = pd.concat([data[0].iloc[:, ~data[0].columns.str.contains('전년동기')],
data[2],
data[4]
])
data_fs_y = data_fs_y.rename(columns = {data_fs_y.columns[0] : "계정"})
# 12월 결산이 아니라면... 분기 재무제표(최근 결산기준) 함께 표시됨. 분기재무제표 열 제거
data_fs_y = data_fs_y.loc[:, (data_fs_y.columns == "계정") |
(data_fs_y.columns.str[-2:].isin(fiscal_data_text))]
data_fs_y_clean = clean_fs(data_fs_y, ticker, 'y')
data_fs_y_clean
계정 | 기준일 | 값 | 종목코드 | 공시구분 | |
---|---|---|---|---|---|
0 | 매출액 | 2019-12-31 | 10003.0 | 095570 | y |
1 | 매출총이익 | 2019-12-31 | 10003.0 | 095570 | y |
2 | 판매비와관리비 | 2019-12-31 | 9846.0 | 095570 | y |
3 | 인건비 | 2019-12-31 | 852.0 | 095570 | y |
4 | 유무형자산상각비 | 2019-12-31 | 109.0 | 095570 | y |
... | ... | ... | ... | ... | ... |
586 | 배당금지급(-) | 2022-12-31 | -121.0 | 095570 | y |
588 | 환율변동효과 | 2022-12-31 | 12.0 | 095570 | y |
589 | 현금및현금성자산의증가 | 2022-12-31 | 466.0 | 095570 | y |
590 | 기초현금및현금성자산 | 2022-12-31 | 852.0 | 095570 | y |
591 | 기말현금및현금성자산 | 2022-12-31 | 1319.0 | 095570 | y |
544 rows × 5 columns
분기기준 재무제표 만들기
# 분기 재무제표
data_fs_q = pd.concat(
[data[1].iloc[:, ~data[1].columns.str.contains('전년동기')], data[3], data[5]])
data_fs_q = data_fs_q.rename(columns={data_fs_q.columns[0]: "계정"})
data_fs_q_clean = clean_fs(data_fs_q, ticker, 'q')
data_fs_q_clean
계정 | 기준일 | 값 | 종목코드 | 공시구분 | |
---|---|---|---|---|---|
0 | 매출액 | 2022-03-31 | 2958.0 | 095570 | q |
1 | 매출총이익 | 2022-03-31 | 2958.0 | 095570 | q |
2 | 판매비와관리비 | 2022-03-31 | 2766.0 | 095570 | q |
3 | 인건비 | 2022-03-31 | 259.0 | 095570 | q |
4 | 유무형자산상각비 | 2022-03-31 | 399.0 | 095570 | q |
... | ... | ... | ... | ... | ... |
547 | 배당금지급(-) | 2022-12-31 | 0.0 | 095570 | q |
548 | 환율변동효과 | 2022-12-31 | -17.0 | 095570 | q |
549 | 현금및현금성자산의증가 | 2022-12-31 | 559.0 | 095570 | q |
550 | 기초현금및현금성자산 | 2022-12-31 | 760.0 | 095570 | q |
551 | 기말현금및현금성자산 | 2022-12-31 | 1319.0 | 095570 | q |
467 rows × 5 columns
연간 재무제표 + 분기 재무제표
# 연간 재무제표 분기 재무제표 합치기
data_fs_bind = pd.concat([data_fs_y_clean, data_fs_q_clean]).reset_index(drop=True)
data_fs_bind
계정 | 기준일 | 값 | 종목코드 | 공시구분 | |
---|---|---|---|---|---|
0 | 매출액 | 2019-12-31 | 10003.0 | 095570 | y |
1 | 매출총이익 | 2019-12-31 | 10003.0 | 095570 | y |
2 | 판매비와관리비 | 2019-12-31 | 9846.0 | 095570 | y |
3 | 인건비 | 2019-12-31 | 852.0 | 095570 | y |
4 | 유무형자산상각비 | 2019-12-31 | 109.0 | 095570 | y |
... | ... | ... | ... | ... | ... |
1006 | 배당금지급(-) | 2022-12-31 | 0.0 | 095570 | q |
1007 | 환율변동효과 | 2022-12-31 | -17.0 | 095570 | q |
1008 | 현금및현금성자산의증가 | 2022-12-31 | 559.0 | 095570 | q |
1009 | 기초현금및현금성자산 | 2022-12-31 | 760.0 | 095570 | q |
1010 | 기말현금및현금성자산 | 2022-12-31 | 1319.0 | 095570 | q |
1011 rows × 5 columns
❍ 여러 종목 재무제표 크롤링
from tqdm import tqdm
import time
import pandas as pd
df = pd.DataFrame(columns = ['계정','기준일','값','종목코드','공시구분'])
error_list = []
for i in tqdm(range(0, len(ticker_list))):
#for ticker in tqdm(diff):
# ticker 선택
ticker = ticker_list['종목코드'][i]
try:
url = f'https://comp.fnguide.com/SVO2/ASP/SVD_Finance.asp?pGB=1&gicode=A{ticker}'
# 데이터 받아오기
data = pd.read_html(url, displayed_only=False)
# 연간 재무제표
data_fs_y = pd.concat([data[0].iloc[:, ~data[0].columns.str.contains('전년동기')],
data[2],
data[4]
])
data_fs_y = data_fs_y.rename(columns = {data_fs_y.columns[0] : "계정"})
# 결산년 찾기
page_data = rq.get(url)
page_data_html = BeautifulSoup(page_data.content, 'html.parser')
fiscal_data = page_data_html.select('div.corp_group1 > h2')
fiscal_data_text = fiscal_data[1].text
fiscal_data_text = re.findall('[0-9]+', fiscal_data_text)
# 결산년에 해당하는 계정만 남기기
data_fs_y = data_fs_y.loc[:, (data_fs_y.columns == '계정') | (data_fs_y.columns.str[-2:].isin(fiscal_data_text))]
# 클렌징
data_fs_y_clean = clean_fs(data_fs_y, ticker, 'y')
# 분기 재무제표
data_fs_q = pd.concat(
[data[1].iloc[:, ~data[1].columns.str.contains('전년동기')], data[3], data[5]])
data_fs_q = data_fs_q.rename(columns={data_fs_q.columns[0]: "계정"})
# 클렌징
data_fs_q_clean = clean_fs(data_fs_q, ticker, 'q')
# 연간/분기 합치기
data_fs_bind = pd.concat([data_fs_y_clean, data_fs_q_clean])
# db 합치기
df = pd.concat([df, data_fs_bind])
except:
print(ticker)
error_list.append(ticker)
break
time.sleep(0.01)
diff = list(set(ticker_list['종목코드']).symmetric_difference(set(df['종목코드'].unique())))
diff
# 한화갤러리아, 코오롱모빌리티그룹 >>> 신규상장한 기업들
['452260', '450140']
#df.to_pickle('./data/data_fs.pickle')
data_fs_history = pd.read_pickle('./data/data_fs.pickle')
data_fs_history = pd.concat([data_fs_history, df])
data_fs_history = data_fs_history.drop_duplicates(['계정','기준일','종목코드','공시구분'], keep='last')
data_fs_history = data_fs_history.sort_values(by=['종목코드','기준일','공시구분','계정'])
data_fs_history = data_fs_history.reset_index(drop=True)
data_fs_history
#data_fs_history.to_pickle('./data/data_fs.pickle')
계정 | 기준일 | 값 | 종목코드 | 공시구분 | |
---|---|---|---|---|---|
0 | (재무활동으로인한현금유출액) | 2019-12-31 | 16.0 | 000020 | y |
1 | (투자활동으로인한현금유출액) | 2019-12-31 | 1080.0 | 000020 | y |
2 | (현금유입이없는수익등차감) | 2019-12-31 | 99.0 | 000020 | y |
3 | *영업에서창출된현금흐름 | 2019-12-31 | 164.0 | 000020 | y |
4 | 감가상각비 | 2019-12-31 | 104.0 | 000020 | y |
... | ... | ... | ... | ... | ... |
2000595 | 판매비와관리비 | 2022-12-31 | 32.0 | 446070 | y |
2000596 | 현금및현금성자산 | 2022-12-31 | 548.0 | 446070 | y |
2000597 | 현금및현금성자산의증가 | 2022-12-31 | -270.0 | 446070 | y |
2000598 | 현금유출이없는비용등가산 | 2022-12-31 | 23.0 | 446070 | y |
2000599 | 환율변동효과 | 2022-12-31 | 0.0 | 446070 | y |
2000600 rows × 5 columns
5. 가치지표 계산 (data_value)
- “data_value.pickle” : data_fs로부터 TTM 데이터를 구한 후 계산
- PER(주가수익비율), PBR(주가순자산비율), PSR(주가매출비율), PCR(주가현금흐름비율)
- DY(배당률)
- 시가총액 기준일(ticker_list)을 기준으로 계산한 가치지표를 확인가능 (최신 재무제표 활용)
PER, PBR, PSR, PCR, DY
# 분기 재무제표 불러오기
import pandas as pd
import numpy as np
kor_fs = pd.read_pickle("./data/data_fs.pickle")
kor_fs.drop_duplicates(inplace=True)
cond = (kor_fs.공시구분 == 'q') & (kor_fs['계정'].isin(['당기순이익','자본','영업활동으로인한현금흐름','매출액']))
kor_fs = kor_fs[cond]
# TTM 구하기
kor_fs = kor_fs.sort_values(['종목코드','계정','기준일']).reset_index(drop=True)
kor_fs['ttm'] = kor_fs.groupby(['종목코드','계정'], as_index=False)['값'].rolling(window=4, min_periods=4).sum()['값']
#kor_fs_merge = kor_fs_merge.dropna()
# 자본은 평균으로
kor_fs['ttm'] = np.where(kor_fs['계정']=='자본',
kor_fs['ttm']/4, kor_fs['ttm'])
# tail(1) : 최근데이터만 남긴다
kor_fs = kor_fs.groupby(['계정','종목코드']).tail(1).reset_index(drop=True)
# 합치기
kor_fs_merge = kor_fs[['계정','종목코드','ttm']].merge(ticker_list[['종목코드','시가총액','기준일']], on='종목코드')
# 시가총액 : 원단위를 억원으로 변경
kor_fs_merge['시가총액'] = kor_fs_merge['시가총액']/10**8
# 지표 계산
cond = kor_fs_merge['ttm']==0
kor_fs_merge.loc[cond, 'ttm'] = 0.1 #0인 값 처리
kor_fs_merge['value'] = kor_fs_merge['시가총액'] / kor_fs_merge['ttm']
kor_fs_merge['value'] = kor_fs_merge['value'].astype(float).round(4)
kor_fs_merge['지표'] = np.where(
kor_fs_merge['계정'] == '매출액','PSR',
np.where(
kor_fs_merge['계정'] == '영업활동으로인한현금흐름','PCR',
np.where(
kor_fs_merge['계정'] == '자본', 'PBR',
np.where(
kor_fs_merge['계정'] == '당기순이익', 'PER', None))))
# value 중에 nan 이나 inf 를 None으로 변경하기, SQL에 nan, inf 저장 안됨
kor_fs_merge.rename(columns = {"value":"값"}, inplace=True)
kor_fs_merge = kor_fs_merge[['종목코드','기준일','지표','값']]
kor_fs_merge = kor_fs_merge.replace([np.inf, -np.inf, np.nan], None)
### 배당수익률
ticker_list['값'] = ticker_list['주당배당금'] / ticker_list['종가']
ticker_list['값'] = ticker_list['값'].astype(np.float64).round(4)
ticker_list['지표'] = "DY"
dy_list = ticker_list[['종목코드','기준일','지표','값']]
dy_list = dy_list.replace([np.inf, -np.inf, np.nan], None)
dy_list = dy_list[dy_list['값'] != 0] #배당을 0으로 주는 경우는 제외
# PER, PBR, PSR, PCR, DY 합치기
value_df = pd.concat([kor_fs_merge, dy_list]).reset_index(drop=True)
value_df
종목코드 | 기준일 | 지표 | 값 | |
---|---|---|---|---|
0 | 000020 | 20230331 | PER | 10.7459 |
1 | 000020 | 20230331 | PSR | 0.6817 |
2 | 000020 | 20230331 | PCR | 7.9219 |
3 | 000020 | 20230331 | PBR | 0.629 |
4 | 000040 | 20230331 | PER | -3.7272 |
... | ... | ... | ... | ... |
10299 | 024060 | 20230331 | DY | 0.0179 |
10300 | 010240 | 20230331 | DY | 0.0326 |
10301 | 189980 | 20230331 | DY | 0.011 |
10302 | 037440 | 20230331 | DY | 0.0164 |
10303 | 238490 | 20230331 | DY | 0.0141 |
10304 rows × 4 columns
여기서의 기준일은 시가총액 기준일
- 재무제표는 2022.12.31 결산기준이며, 시가총액은 2023.03.31 기준 > 기준일 시가총액 기준
# value_df.to_pickle("./data/data_value.pickle")
kor_fs.head(1)
계정 | 기준일 | 값 | 종목코드 | 공시구분 | ttm | |
---|---|---|---|---|---|---|
0 | 당기순이익 | 2022-12-31 | 25.0 | 000020 | q | 216.0 |
kor_fs_merge.head(1)
종목코드 | 기준일 | 지표 | 값 | |
---|---|---|---|---|
0 | 000020 | 20230331 | PER | 10.7459 |
value_df.head(1)
종목코드 | 기준일 | 지표 | 값 | |
---|---|---|---|---|
0 | 000020 | 20230331 | PER | 10.7459 |
temp = pd.read_pickle('./data/data_value.pickle')
temp = pd.concat([temp, value_df]).drop_duplicates(['종목코드','기준일','지표'], keep='last').reset_index(drop=True)
temp
#temp.to_pickle("./data/data_value.pickle")
종목코드 | 기준일 | 지표 | 값 | |
---|---|---|---|---|
0 | 000020 | 20230331 | PER | 10.7459 |
1 | 000020 | 20230331 | PSR | 0.6817 |
2 | 000020 | 20230331 | PCR | 7.9219 |
3 | 000020 | 20230331 | PBR | 0.629 |
4 | 000040 | 20230331 | PER | -3.7272 |
... | ... | ... | ... | ... |
10299 | 024060 | 20230331 | DY | 0.0179 |
10300 | 010240 | 20230331 | DY | 0.0326 |
10301 | 189980 | 20230331 | DY | 0.011 |
10302 | 037440 | 20230331 | DY | 0.0164 |
10303 | 238490 | 20230331 | DY | 0.0141 |
10304 rows × 4 columns
6. 상장폐지종목 (delistSymbol)
import FinanceDataReader as fdr
from pykrx import stock
import pandas_datareader.data as pdr
import yfinance as yf
# 가장 최근 영업일의 시장별 종목리스트를 가져옴
stocks = fdr.StockListing('KRX') # 코스피, 코스닥, 코넥스 전체
stocks
Code | ISU_CD | Name | Market | Dept | Close | ChangeCode | Changes | ChagesRatio | Open | High | Low | Volume | Amount | Marcap | Stocks | MarketId | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 005930 | KR7005930003 | 삼성전자 | KOSPI | 65000 | 1 | 2700 | 4.33 | 63800 | 65200 | 63800 | 27476120 | 1778208943700 | 388035865750000 | 5969782550 | STK | |
1 | 373220 | KR7373220003 | LG에너지솔루션 | KOSPI | 580000 | 3 | 0 | 0.00 | 581000 | 590000 | 579000 | 366225 | 213290884200 | 135720000000000 | 234000000 | STK | |
2 | 000660 | KR7000660001 | SK하이닉스 | KOSPI | 89100 | 1 | 5300 | 6.32 | 87900 | 89400 | 86000 | 10867359 | 959641473100 | 64865010721500 | 728002365 | STK | |
3 | 207940 | KR7207940008 | 삼성바이오로직스 | KOSPI | 796000 | 2 | -9000 | -1.12 | 804000 | 805000 | 792000 | 61342 | 48882068000 | 56654504000000 | 71174000 | STK | |
4 | 006400 | KR7006400006 | 삼성SDI | KOSPI | 738000 | 2 | -7000 | -0.94 | 742000 | 747000 | 731000 | 259085 | 191613326000 | 50748223140000 | 68764530 | STK | |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2709 | 308700 | KR7308700004 | 테크엔 | KONEX | 일반기업부 | 363 | 1 | 43 | 13.44 | 367 | 367 | 363 | 101 | 36667 | 1452000000 | 4000000 | KNX |
2710 | 322190 | KR7322190000 | 베른 | KONEX | 일반기업부 | 118 | 5 | -20 | -14.49 | 118 | 137 | 118 | 532 | 71838 | 1053173246 | 8925197 | KNX |
2711 | 058420 | KR7058420001 | 제이웨이 | KOSDAQ | 투자주의환기종목(소속부없음) | 215 | 2 | -83 | -27.85 | 260 | 293 | 210 | 3697643 | 845032403 | 776330385 | 3610839 | KSQ |
2712 | 271850 | KR7271850000 | 다이오진 | KONEX | 일반기업부 | 125 | 2 | -79 | -38.73 | 249 | 249 | 125 | 121711 | 19095800 | 400791625 | 3206333 | KNX |
2713 | 267060 | KR7267060002 | 명진홀딩스 | KONEX | 일반기업부 | 30 | 2 | -151 | -83.43 | 53 | 53 | 25 | 1283696 | 44440461 | 274254120 | 9141804 | KNX |
2714 rows × 17 columns
# KRX stock delisting symbol list 상장폐지 종목 전체 리스트
krx_delisting = fdr.StockListing('KRX-DELISTING')
krx_delisting
Symbol | Name | Market | SecuGroup | Kind | ListingDate | DelistingDate | Reason | ArrantEnforceDate | ArrantEndDate | Industry | ParValue | ListingShares | ToSymbol | ToName | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 06031012 | 3S R | KOSDAQ | 신주인수권증서 | 보통주 | 2012-05-14 | 2012-05-21 | NaT | NaT | 0.0 | 1194422.0 | ||||
1 | 00684014 | AK홀딩스8R | KOSPI | 신주인수권증서 | 보통주 | 2014-07-28 | 2014-08-04 | NaT | NaT | 0.0 | 1278299.0 | ||||
2 | 13893015 | BNK금융지주 8R | KOSPI | 신주인수권증서 | 보통주 | 2015-12-24 | 2016-01-05 | NaT | NaT | 0.0 | 55969410.0 | ||||
3 | 13893014 | BS금융지주5R | KOSPI | 신주인수권증서 | 보통주 | 2014-06-18 | 2014-06-25 | NaT | NaT | 0.0 | 32791220.0 | ||||
4 | 03204017 | C&S자산관리 34R | KOSDAQ | 신주인수권증서 | 보통주 | 2017-02-02 | 2017-02-09 | NaT | NaT | 0.0 | 3995063.0 | ||||
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
3487 | 034370 | 럭키소재 | KOSPI | 주권 | 1979-06-23 | 1991-11-11 | 해산 사유 발생 | NaT | NaT | NaN | NaN | ||||
3488 | 028460 | 태평양건설 | KOSPI | 주권 | 1977-06-09 | 1991-10-05 | 영업활동정지 6월 계속 | 1991-08-28 | 1991-10-04 | NaN | NaN | ||||
3489 | 028450 | 금성투자금융 | KOSPI | 주권 | 1986-08-29 | 1991-09-02 | 해산 사유 발생 | NaT | NaT | NaN | NaN | ||||
3490 | 028440 | 삼화 | KOSPI | 주권 | 1974-06-12 | 1991-07-12 | 감사의견 의견거절 | 1991-06-05 | 1991-07-11 | NaN | NaN | ||||
3491 | 029260 | 금성전기 | KOSPI | 주권 | 1976-06-29 | 1991-06-13 | 해산 사유 발생 | NaT | NaT | NaN | NaN |
3492 rows × 15 columns
import datetime
import pandas as pd
cond = (krx_delisting['DelistingDate'] > "2010-01-01")&(krx_delisting['Kind']=='보통주')
temp = krx_delisting[cond].sort_values(by='DelistingDate', ascending=False)
temp = [i for i in temp.Symbol.tolist() if len(i)==6]
# pd.DataFrame(temp, columns=['상장폐지종목코드']).to_pickle("./data/delistSymbol.pickle")
temp = pd.read_pickle("./data/delistSymbol.pickle")
temp
상장폐지종목코드 | |
---|---|
0 | 267810 |
1 | 056000 |
2 | 387310 |
3 | 353070 |
4 | 397500 |
... | ... |
696 | 037510 |
697 | 083120 |
698 | 000610 |
699 | 015940 |
700 | 045820 |
701 rows × 1 columns
7. 재무제표 - 다트 (API Key 필요)
- openapi를 활용해서 재무정보 다운로드
https://opendart.fss.or.kr/
# !pip install keyring --quiet
❍ 다트 티커리스트 다운받기
import keyring
import requests
from io import BytesIO
import zipfile
#keyring.set_password('dart_api_key', 'User Name', 'Password') # System , 본인이름, 발급받은 API키
keyring.set_password('dart_api_key', '###', '$$$$$$$$$$$$$$$$$$$$') # System , 본인이름, 발급받은 API키
api_key = keyring.get_password('dart_api_key', '###')
codezip_url = f'''https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key={api_key}'''
codezip_data = requests.get(codezip_url)
codezip_data.headers
{'Cache-Control': 'no-cache, no-store', 'Connection': 'keep-alive', 'Set-Cookie': 'WMONID=-Tq_CVAmWN2; Expires=Sun, 07-Apr-2024 20:6:17 GMT; Path=/', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Transfer-Encoding': 'binary', 'Content-Disposition': ': attachment; filename=CORPCODE.zip', 'Date': 'Sat, 08 Apr 2023 11:06:17 GMT', 'Content-Type': 'application/x-msdownload;charset=UTF-8', 'Content-Length': '1657392'}
codezip_data.headers['Content-Disposition']
': attachment; filename=CORPCODE.zip'
codezip_file = zipfile.ZipFile(BytesIO(codezip_data.content)) # 바이너리스트림 형태로 변경 > 집파일 풀기
codezip_file.namelist() # 어떤 파일이 있는지
['CORPCODE.xml']
code_data = codezip_file.read("CORPCODE.xml").decode("utf-8") # utf-8로 디코딩 #한글이 보이기 시작
# xml 형태를 dictionary 형태로 변경
import xmltodict
import json
import pandas as pd
data_odict = xmltodict.parse(code_data)
data_dict = json.loads(json.dumps(data_odict))
data = data_dict.get('result').get('list')
corp_list = pd.DataFrame(data)
corp_list
corp_code | corp_name | stock_code | modify_date | |
---|---|---|---|---|
0 | 00434003 | 다코 | None | 20170630 |
1 | 00434456 | 일산약품 | None | 20170630 |
2 | 00430964 | 굿앤엘에스 | None | 20170630 |
3 | 00432403 | 한라판지 | None | 20170630 |
4 | 00388953 | 크레디피아제이십오차유동화전문회사 | None | 20170630 |
... | ... | ... | ... | ... |
98028 | 00646510 | 미래에셋파트너스사호사모투자전문회사 | None | 20230228 |
98029 | 01184822 | 미래에셋파트너스9호사모투자 | None | 20230228 |
98030 | 00755252 | 시니안 | None | 20230228 |
98031 | 00227582 | 청호나이스 | None | 20230228 |
98032 | 01615845 | 메타버스월드 | None | 20230228 |
98033 rows × 4 columns
corp_list = corp_list[~corp_list.stock_code.isin([None])].reset_index(drop=True) # None은 비상장이므로 필터링
corp_list
corp_code | corp_name | stock_code | modify_date | |
---|---|---|---|---|
0 | 00260985 | 한빛네트 | 036720 | 20170630 |
1 | 00264529 | 엔플렉스 | 040130 | 20170630 |
2 | 00358545 | 동서정보기술 | 055000 | 20170630 |
3 | 00231567 | 애드모바일 | 032600 | 20170630 |
4 | 00247939 | 씨모스 | 037600 | 20170630 |
... | ... | ... | ... | ... |
3560 | 00413417 | 우리손에프앤지 | 073560 | 20230403 |
3561 | 00440712 | 어반리튬 | 073570 | 20230403 |
3562 | 00483735 | 해성옵틱스 | 076610 | 20230403 |
3563 | 00516246 | 알에프세미 | 096610 | 20230403 |
3564 | 00525679 | 차바이오텍 | 085660 | 20230403 |
3565 rows × 4 columns
# corp_list.to_pickle("./data/tickers_dart.pickle")
❍ 공시자료 다운로드
오픈다트 > 개발가이드 > 공시정보 > 공시검색
# 전체 종목의 최근 100개의 공시에 해당하는 내역을 받아오게 됨
from datetime import date
from dateutil.relativedelta import relativedelta
bgn_date = (date.today() + relativedelta(days=-30)).strftime("%Y%m%d")
end_date = (date.today()).strftime("%Y%m%d")
notice_url = f'''https://opendart.fss.or.kr/api/list.json?crtfc_key={api_key}&bgn_de={bgn_date}&end_de={end_date}&page_no=1&page_count=100''' # 최대 100까지 가능
notice_data = requests.get(notice_url)
notice_data_df = notice_data.json().get('list')
notice_data_df = pd.DataFrame(notice_data_df)
notice_data_df
corp_code | corp_name | stock_code | corp_cls | report_nm | rcept_no | flr_nm | rcept_dt | rm | |
---|---|---|---|---|---|---|---|---|---|
0 | 01101722 | 한송네오텍 | 226440 | K | [기재정정]기타시장안내(상장폐지 관련) | 20230407901312 | 코스닥시장본부 | 20230407 | 코 |
1 | 00610490 | 비디아이 | 148140 | K | [기재정정]주권매매거래정지기간변경(상장폐지 사유 해소, 사업보고서 미제출) | 20230407901311 | 코스닥시장본부 | 20230407 | 코 |
2 | 00610490 | 비디아이 | 148140 | K | 기타시장안내(상장폐지 관련) | 20230407901308 | 코스닥시장본부 | 20230407 | 코 |
3 | 00962393 | 포인트모바일 | 318020 | K | [기재정정]기타시장안내(상장폐지 관련) | 20230407901309 | 코스닥시장본부 | 20230407 | 코 |
4 | 00610490 | 비디아이 | 148140 | K | 주권매매거래정지기간변경(상장폐지 사유 해소) | 20230407901306 | 코스닥시장본부 | 20230407 | 코정 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
95 | 01653128 | 네오플랜 | E | 감사보고서 (2022.12) | 20230407003488 | 현대회계법인 | 20230407 | ||
96 | 01685774 | 두리랜드 | E | 감사보고서 (2022.12) | 20230407003487 | 인덕회계법인 | 20230407 | ||
97 | 00440712 | 어반리튬 | 073570 | K | 주식등의대량보유상황보고서(일반) | 20230407003486 | 리튬인사이트 | 20230407 | |
98 | 00990262 | 피노텍 | 150440 | N | 사업보고서 (2022.12) | 20230407003485 | 피노텍 | 20230407 | |
99 | 01476219 | 코스텍시스 | 355150 | K | 주식등의대량보유상황보고서(약식) | 20230407003484 | 유비쿼스인베스트먼트 | 20230407 |
100 rows × 9 columns
# 원하는 종목의 공시자료만 받아오기
corp_code = '00126380' # 종목티커(stock_code)와 별도로 종목코드(corp_code)가 있음
notice_url_ss = f'''https://opendart.fss.or.kr/api/list.json?crtfc_key={api_key}&corp_code={corp_code}&bgn_de={bgn_date}&end_de={end_date}&page_no=1&page_count=100''' # 최대 100까지 가능
notice_data_ss = requests.get(notice_url_ss)
notice_data_ss_df = notice_data_ss.json().get('list')
notice_data_ss_df = pd.DataFrame(notice_data_ss_df)
notice_data_ss_df.tail()
corp_code | corp_name | stock_code | corp_cls | report_nm | rcept_no | flr_nm | rcept_dt | rm | |
---|---|---|---|---|---|---|---|---|---|
0 | 00126380 | 삼성전자 | 005930 | Y | 연결재무제표기준영업(잠정)실적(공정공시) | 20230407800208 | 삼성전자 | 20230407 | 유 |
1 | 00126380 | 삼성전자 | 005930 | Y | 임원ㆍ주요주주특정증권등소유상황보고서 | 20230406002362 | 김이태 | 20230406 | |
2 | 00126380 | 삼성전자 | 005930 | Y | 임원ㆍ주요주주특정증권등소유상황보고서 | 20230324000884 | 경계현 | 20230324 | |
3 | 00126380 | 삼성전자 | 005930 | Y | 임원ㆍ주요주주특정증권등소유상황보고서 | 20230316000646 | 안재용 | 20230316 | |
4 | 00126380 | 삼성전자 | 005930 | Y | 정기주주총회결과 | 20230315802179 | 삼성전자 | 20230315 | 유 |
# 공시번호를 이용해서 rcept_no
notice_url_exam = notice_data_ss_df.loc[0, 'rcept_no']
notice_dart_url = f'https://dart.fss.or.kr/dsaf001/main.do?rcpNo={notice_url_exam}'
notice_dart_url
'https://dart.fss.or.kr/dsaf001/main.do?rcpNo=20230407800208'
# 개발가이드 > 사업보고서 주요정보 > 배당에 관한 사항 # https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS002&apiId=2019005
corp_code = '00126380'
bsns_year = '2022'
report_code = '11011'
url_div = f'''https://opendart.fss.or.kr/api/alotMatter.json?crtfc_key={api_key}&corp_code={corp_code}&bsns_year={bsns_year}&reprt_code={report_code}'''
div_data_ss = requests.get(url_div)
div_data_ss_df = div_data_ss.json().get('list')
div_data_ss_df = pd.DataFrame(div_data_ss_df)
div_data_ss_df
rcept_no | corp_cls | corp_code | corp_name | se | thstrm | frmtrm | lwfr | stock_knd | |
---|---|---|---|---|---|---|---|---|---|
0 | 20230307000542 | Y | 00126380 | 삼성전자 | 주당액면가액(원) | 100 | 100 | 100 | NaN |
1 | 20230307000542 | Y | 00126380 | 삼성전자 | (연결)당기순이익(백만원) | 54,730,018 | 39,243,791 | 26,090,846 | NaN |
2 | 20230307000542 | Y | 00126380 | 삼성전자 | (별도)당기순이익(백만원) | 25,418,778 | 30,970,954 | 15,615,018 | NaN |
3 | 20230307000542 | Y | 00126380 | 삼성전자 | (연결)주당순이익(원) | 8,057 | 5,777 | 3,841 | NaN |
4 | 20230307000542 | Y | 00126380 | 삼성전자 | 현금배당금총액(백만원) | 9,809,438 | 9,809,438 | 20,338,075 | NaN |
5 | 20230307000542 | Y | 00126380 | 삼성전자 | 주식배당금총액(백만원) | - | - | - | NaN |
6 | 20230307000542 | Y | 00126380 | 삼성전자 | (연결)현금배당성향(%) | 17.90 | 25.00 | 78.00 | NaN |
7 | 20230307000542 | Y | 00126380 | 삼성전자 | 현금배당수익률(%) | 2.50 | 1.80 | 4.00 | 보통주 |
8 | 20230307000542 | Y | 00126380 | 삼성전자 | 현금배당수익률(%) | 2.70 | 2.00 | 4.20 | 우선주 |
9 | 20230307000542 | Y | 00126380 | 삼성전자 | 주식배당수익률(%) | - | - | - | 보통주 |
10 | 20230307000542 | Y | 00126380 | 삼성전자 | 주식배당수익률(%) | - | - | - | 우선주 |
11 | 20230307000542 | Y | 00126380 | 삼성전자 | 주당 현금배당금(원) | 1,444 | 1,444 | 2,994 | 보통주 |
12 | 20230307000542 | Y | 00126380 | 삼성전자 | 주당 현금배당금(원) | 1,445 | 1,445 | 2,995 | 우선주 |
13 | 20230307000542 | Y | 00126380 | 삼성전자 | 주당 주식배당(주) | - | - | - | 보통주 |
14 | 20230307000542 | Y | 00126380 | 삼성전자 | 주당 주식배당(주) | - | - | - | 우선주 |
# API를 이용해서 전종목의 재무정보 다운받기
# 개발가이드 > 상장기업재무정보 > 단일회사 전체 재무제표 (금융회사 제외)
# https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS003&apiId=2019020
url_fs = f'''https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?crtfc_key={api_key}
&corp_code={corp_code}&bsns_year={bsns_year}&reprt_code={report_code}&fs_div=OFS''' #OFS : 연결
fs_data_ss = requests.get(url_fs)
fs_data_ss_df = fs_data_ss.json().get('list')
fs_data_ss_df = pd.DataFrame(fs_data_ss_df)
fs_data_ss_df
rcept_no | reprt_code | bsns_year | corp_code | sj_div | sj_nm | account_id | account_nm | account_detail | thstrm_nm | thstrm_amount | frmtrm_nm | frmtrm_amount | bfefrmtrm_nm | bfefrmtrm_amount | ord | currency | thstrm_add_amount | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20230307000542 | 11011 | 2022 | 00126380 | BS | 재무상태표 | ifrs-full_CurrentAssets | 유동자산 | - | 제 54 기 | 59062658000000 | 제 53 기 | 73553416000000 | 제 52 기 | 73798549000000 | 1 | KRW | NaN |
1 | 20230307000542 | 11011 | 2022 | 00126380 | BS | 재무상태표 | ifrs-full_CashAndCashEquivalents | 현금및현금성자산 | - | 제 54 기 | 3921593000000 | 제 53 기 | 3918872000000 | 제 52 기 | 989045000000 | 2 | KRW | NaN |
2 | 20230307000542 | 11011 | 2022 | 00126380 | BS | 재무상태표 | dart_ShortTermDepositsNotClassifiedAsCashEquiv... | 단기금융상품 | - | 제 54 기 | 137000000 | 제 53 기 | 15000576000000 | 제 52 기 | 29101284000000 | 3 | KRW | NaN |
3 | 20230307000542 | 11011 | 2022 | 00126380 | BS | 재무상태표 | dart_ShortTermTradeReceivable | 매출채권 | - | 제 54 기 | 20503223000000 | 제 53 기 | 33088247000000 | 제 52 기 | 24736740000000 | 4 | KRW | NaN |
4 | 20230307000542 | 11011 | 2022 | 00126380 | BS | 재무상태표 | dart_ShortTermOtherReceivables | 미수금 | - | 제 54 기 | 2925006000000 | 제 53 기 | 1832488000000 | 제 52 기 | 1898583000000 | 5 | KRW | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
109 | 20230307000542 | 11011 | 2022 | 00126380 | SCE | 자본변동표 | ifrs-full_Equity | 기말자본 | 별도재무제표 [member] | 제 54 기 | 209416191000000 | 제 53 기 | 193193732000000 | 제 52 기 | 183316724000000 | 6 | KRW | NaN |
110 | 20230307000542 | 11011 | 2022 | 00126380 | SCE | 자본변동표 | ifrs-full_Equity | 기말자본 | 자본 [member]|기타자본항목 | 제 54 기 | -273232000000 | 제 53 기 | -882010000000 | 제 52 기 | -268785000000 | 6 | KRW | NaN |
111 | 20230307000542 | 11011 | 2022 | 00126380 | SCE | 자본변동표 | ifrs-full_Equity | 기말자본 | 자본 [member]|이익잉여금 [member] | 제 54 기 | 204388016000000 | 제 53 기 | 188774335000000 | 제 52 기 | 178284102000000 | 6 | KRW | NaN |
112 | 20230307000542 | 11011 | 2022 | 00126380 | SCE | 자본변동표 | ifrs-full_Equity | 기말자본 | 자본 [member]|자본금 [member] | 제 54 기 | 897514000000 | 제 53 기 | 897514000000 | 제 52 기 | 897514000000 | 6 | KRW | NaN |
113 | 20230307000542 | 11011 | 2022 | 00126380 | SCE | 자본변동표 | ifrs-full_Equity | 기말자본 | 자본 [member]|주식발행초과금 | 제 54 기 | 4403893000000 | 제 53 기 | 4403893000000 | 제 52 기 | 4403893000000 | 6 | KRW | NaN |
114 rows × 18 columns
8. FRED 장단기 금리차, 공포탐욕지수
FRED : Federal Reserve Economic Data
대표적인 보조지표 : 장단기 금리차 > 장기로 돈을 빌리는 것이 단기로 빌리는 것보다 위험하므로, 장기 프리미엄이 붙게 됨. 주요 금융시장 및 경제지표 가운데 경기침체에 대한 예측력 좋다고 알려짐 » 경기 침체 이전에 장단기 금리차가 역전됨
https://fred.stlouisfed.org/series/T10Y2Y
import pandas_datareader as web
import pandas as pd
t10y2y = web.DataReader("T10Y2Y", 'fred', start='1990-01-01')
t10y3m = web.DataReader("T10Y3M", 'fred', start='1990-01-01')
rate_diff = pd.concat([t10y2y, t10y3m], axis=1)
rate_diff.columns = ['10Y-2Y', '10Y-3M']
rate_diff.tail()
10Y-2Y | 10Y-3M | |
---|---|---|
DATE | ||
2023-04-03 | -0.54 | -1.47 |
2023-04-04 | -0.49 | -1.53 |
2023-04-05 | -0.49 | -1.56 |
2023-04-06 | -0.52 | -1.61 |
2023-04-07 | -0.58 | -1.56 |
import matplotlib.pyplot as plt
import numpy as np
import yfinance as yf
sp = yf.download("^GSPC", start='1990-01-01')
[*********************100%***********************] 1 of 1 completed
plt.rc('font', family='AppleGothic') # window:Malgun Gothic
plt.rc('axes', unicode_minus=False)
fig, ax1 = plt.subplots(figsize=(8,5))
ax1.plot(t10y2y, color='black', lw=0.5, label='10Y-2Y')
ax1.plot(t10y3m, color='gray', lw=0.5, label='10Y-3M')
ax1.legend()
ax1.axhline(0, color='r', ls='dashed')
ax1.set_ylabel("장단기 금리차")
ax1.legend(loc='lower right')
ax2 = ax1.twinx()
ax2.plot(sp['Close'], label='S&P500')
ax2.set_ylabel("S&P500 지수(로그)")
ax2.legend(loc='upper right')
❍ Fear and Greed Index
https://edition.cnn.com/markets/fear-and-greed
7가지 지표를 활용해서 산정함
동적 페이지라서 셀레니움 필요
import selenium
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get(url='https://edition.cnn.com/markets/fear-and-greed')
time.sleep(2)
[WDM] - Downloading: 100%|█████████████████████████████████████████████████████████████████████████████████| 8.01M/8.01M [00:00<00:00, 11.6MB/s]
idx = driver.find_element(By.CLASS_NAME, 'market-fng-gauge__dial-number-value').text
driver.close()
idx
'57'
9. 해외 종목 다운로드
❍ 미국 Ticker
한경글로벌마켓 https://www.hankyung.com/globalmarket/equities/amex/top-market-capitalization
import json
import requests
import pandas as pd
import time
markets = ['nyse','nasdaq','amex']
stock = []
for market in markets:
url = f"https://www.hankyung.com/globalmarket/data/price?type={market}&sort=market_cap_top§or_nm=&industry_nm=&chg_net_text="
data = requests.get(url).json() # json 형태의 데이터를 불러옴
data_pd = pd.json_normalize(data['list'])
data_pd
data_pd['symbol'] = data_pd['symbol'].str.replace("-US","")
data_pd['symbol'] = data_pd['symbol'].str.replace(".","-")
stock.append(data_pd)
time.sleep(2)
/var/folders/vr/g5rxclqn2qb11v47jh4p2lrw0000gn/T/ipykernel_1979/770174442.py:16: FutureWarning: The default value of regex will change from True to False in a future version. In addition, single character regular expressions will *not* be treated as literal strings when regex=True.
data_pd['symbol'] = data_pd['symbol'].str.replace(".","-")
/var/folders/vr/g5rxclqn2qb11v47jh4p2lrw0000gn/T/ipykernel_1979/770174442.py:16: FutureWarning: The default value of regex will change from True to False in a future version. In addition, single character regular expressions will *not* be treated as literal strings when regex=True.
data_pd['symbol'] = data_pd['symbol'].str.replace(".","-")
/var/folders/vr/g5rxclqn2qb11v47jh4p2lrw0000gn/T/ipykernel_1979/770174442.py:16: FutureWarning: The default value of regex will change from True to False in a future version. In addition, single character regular expressions will *not* be treated as literal strings when regex=True.
data_pd['symbol'] = data_pd['symbol'].str.replace(".","-")
stock_bind = pd.concat(stock)
stock_bind
symbol | hname | primary_exchange_name | sector_nm | industry_nm | market_cap | close_price | chg_net | chg_rate | volume | avg_volume | turnover | dps | l52w_price | h52w_price | per | open_price | locdt | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | BRK-A | 버크셔 해서웨이 | NYSE | 금융 | 상해보험 | 700187.7787 | 478005.0000 | 6505.0000 | 1.3796 | 5095 | 4814.4286 | 2408407.7811 | 0 | 393012.2500 | 531880.0000 | 0 | 473479.4488 | 2023-04-06 |
1 | BRK-B | 버크셔 해서웨이 | NYSE | 금융 | 상해보험 | 686652.9097 | 312.5100 | 2.1200 | 0.6830 | 3132148 | 4765457.7619 | 975452.3787 | 0 | 259.8500 | 354.3300 | 0 | 309.8200 | 2023-04-06 |
2 | UNH | 유나이티드헬스그룹 | NYSE | 헬스케어 서비스 | 건강관리 | 478373.2701 | 512.8100 | 3.5800 | 0.7030 | 3472551 | 3381885.4762 | 1775900.0744 | 6.6 | 449.7009 | 558.1000 | 25.027 | 511.0000 | 2023-04-06 |
3 | XOM | 엑슨모빌 | NYSE | 에너지 | 종합석유산업 | 468367.3995 | 115.0500 | -1.9400 | -1.6583 | 15777957 | 19682549.4286 | 1824534.6842 | 3.64 | 79.2900 | 119.6300 | 8.3055 | 116.8600 | 2023-04-06 |
4 | TSM | TSMC | NYSE | 전기전자 | 반도체 | 468029.7600 | 90.2400 | 0.0400 | 0.0443 | 5874515 | 9969092.4286 | 530225.7386 | 1.4112 | 59.4300 | 104.5000 | 11.376 | 89.6600 | 2023-04-06 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
230 | NBY | NovaBay Pharmaceuticals, Inc. | AMEX | 헬스케어 기술 | 의약품 | 3.2567 | 1.6000 | -0.1000 | -5.8824 | 2843 | 14109.5238 | 4.2799 | 0 | 1.2400 | 12.6350 | 0 | 1.6100 | 2023-04-06 |
231 | EFSH | 1847 Holdings LLC | AMEX | 상업 서비스 | 기타 산업서비스 | 3.2225 | 0.7900 | -0.0599 | -7.0479 | 3806 | 14881.9524 | 2.9055 | 0.525 | 0.7007 | 18.0000 | 0 | 0.7699 | 2023-04-06 |
232 | CPHI | China Pharma Holdings, Inc. | AMEX | 헬스케어 기술 | 의약품 | 2.9519 | 0.3490 | 0.0080 | 2.3460 | 107812 | 163925.3333 | 35.4662 | 0 | 0.3200 | 4.1480 | 0 | 0.3388 | 2023-04-06 |
233 | DXF | Dunxin Financial Holdings Ltd | AMEX | 금융 | 금융리스 | 2.6183 | 0.1254 | -0.0271 | -17.7705 | 910720 | 864171.1429 | 116.0838 | 0 | 0.1213 | 0.8000 | 0 | 0.1440 | 2023-04-06 |
234 | UFAB | Unique Fabricating Inc | AMEX | 제조업 | 자동차 부품 | 1.8268 | 0.1557 | 0.0015 | 0.9728 | 172462 | 242471.7619 | 11.7589 | 0 | 0.1400 | 1.8600 | 0 | 0.1500 | 2023-04-06 |
5941 rows × 18 columns
temp = pd.read_pickle('./data/tickers_usa_hankyung.pickle')
temp
symbol | hname | primary_exchange_name | sector_nm | industry_nm | market_cap | close_price | chg_net | chg_rate | volume | avg_volume | turnover | dps | l52w_price | h52w_price | per | open_price | locdt | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | BRK-A | 버크셔 해서웨이 | NYSE | 금융 | 상해보험 | 690659.1723 | 471500.0000 | -8715.0000 | -1.8148 | 4412 | 4584.5556 | 2110422.5624 | 0 | 393012.2500 | 544389.2600 | 0 | 479733.2450 | 2023-03-07 |
1 | BRK-B | 버크셔 해서웨이 | NYSE | 금융 | 상해보험 | 683598.7753 | 311.1200 | -5.8500 | -1.8456 | 3610304 | 3430777.6111 | 1126597.7294 | 0 | 259.8500 | 362.1000 | 0 | 316.3900 | 2023-03-07 |
2 | TSM | TSMC | NYSE | 전기전자 | 반도체 | 460783.2080 | 88.8500 | -0.7300 | -0.8149 | 8761066 | 12403414.5000 | 778860.1537 | 1.4144 | 59.4300 | 109.7550 | 11.376 | 89.9500 | 2023-03-07 |
3 | XOM | 엑슨모빌 | NYSE | 에너지 | 종합석유산업 | 459642.3469 | 111.6100 | -2.2000 | -1.9330 | 11525289 | 14504950.0000 | 1286414.6486 | 3.64 | 76.2500 | 119.6300 | 8.3055 | 112.8100 | 2023-03-07 |
4 | V | 비자 | NYSE | 상업 서비스 | 기타 산업서비스 | 459603.1114 | 223.1700 | -3.5800 | -1.5788 | 4386798 | 5017618.5000 | 984718.8474 | 1.8 | 174.6000 | 234.3000 | 29.6487 | 226.7500 | 2023-03-07 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
226 | AMPE | Ampio Pharmaceuticals Inc | AMEX | 헬스케어 기술 | 의약품 | 4.2014 | 0.2785 | -0.0014 | -0.5002 | 45572 | 114996.2222 | 12.4635 | 0 | 0.1990 | 9.7050 | 0 | 0.2611 | 2023-03-07 |
227 | NBY | NovaBay Pharmaceuticals, Inc. | AMEX | 헬스케어 기술 | 의약품 | 4.1319 | 2.0300 | -0.1300 | -6.0185 | 9634 | 12672.6667 | 20.1074 | 0 | 1.2400 | 12.6350 | 0 | 2.1900 | 2023-03-07 |
228 | DXF | Dunxin Financial Holdings Ltd | AMEX | 금융 | 금융리스 | 3.4409 | 0.1648 | -0.0041 | -2.4275 | 55625 | 148780.6667 | 9.0143 | 0 | 0.1505 | 0.8000 | 0 | 0.1625 | 2023-03-07 |
229 | UFAB | Unique Fabricating Inc | AMEX | 제조업 | 자동차 부품 | 3.1679 | 0.2700 | 0.0000 | 0.0000 | 292401 | 254710.6111 | 76.9324 | 0 | 0.2480 | 2.2600 | 0 | 0.2882 | 2023-03-07 |
230 | CPHI | China Pharma Holdings, Inc. | AMEX | 헬스케어 기술 | 의약품 | 3.0815 | 0.5967 | -0.0333 | -5.2857 | 160661 | 1687045.2778 | 94.3365 | 0 | 0.5700 | 4.8400 | 0 | 0.6110 | 2023-03-07 |
5987 rows × 18 columns
# stock_bind.to_pickle('./data/tickers_usa_hankyung.pickle')
❍ 그외 해외
investing.com > Market > Stocks > Stock screener
Select Equity Type > ORD : 보통주 DRC : DR종목, Prefrred : 우선주
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
import math
import pandas as pd
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
import numpy as np
from tqdm import tqdm
import time
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
nationcode = '5' # 국가코드 5: 미국
url = f"https://www.investing.com/stock-screener/?sp=country::{nationcode}|sector::a|industry::a|equityType::ORD|exchange::a%3Ceq_market_cap;1"
driver.get(url)
# driver 페이지가 열리고 엘리먼트가 보일 때까지 기다린다 ! 최대 10초 동안
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.XPATH, '//*[@id="resultsTable"]/tbody')))
# 페이지 수 찾기
end_num = driver.find_element(By.CLASS_NAME, value = 'js-total-results').text
end_num = math.ceil(int(end_num) / 50) # 1페이지에 50개 종목
# 빈리스트 생성
all_data_df = []
for i in tqdm(range(1, end_num+1)):
url = f"https://www.investing.com/stock-screener/?sp=country::{nationcode}|sector::a|industry::a|equityType::ORD|exchange::a%3Ceq_market_cap;{i}"
driver.get(url)
try :
# driver 페이지가 열리고 엘리먼트가 보일 때까지 기다린다 ! 최대 10초 동안
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.XPATH, '//*[@id="resultsTable"]/tbody')))
except: # 오류가 있을 시
time.sleep(1) # 쉬고
driver.refresh() # 새로고침
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.XPATH, '//*[@id="resultsTable"]/tbody')))
html = BeautifulSoup(driver.page_source, 'lxml')
html_table = html.select('table.genTbl.openTbl.resultsStockScreenerTbl.elpTbl')
df_table = pd.read_html(html_table[0].prettify()) # beautifulsoup로 파싱한 것을 unicode로 바꿔줍니다.
df_table_select = df_table[0][["Name","Symbol","Exchange","Sector","Market Cap"]]
all_data_df.append(df_table_select)
time.sleep(1)
driver.quit()
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████| 171/171 [10:09<00:00, 3.56s/it]
all_data_df_bind = pd.concat(all_data_df, axis=0)
all_data_df_bind['country']='United States'
from datetime import datetime
all_data_df_bind['date'] = datetime.today().strftime("%Y-%m-%d")
all_data_df_bind = all_data_df_bind[~all_data_df_bind['Name'].isnull()] # Name이 빈칸인건 제외
all_data_df_bind = all_data_df_bind[all_data_df_bind['Exchange'].isin(['NASDAQ','NYSE','NYSE Amex'])] # 거래소 필터링
all_data_df_bind = all_data_df_bind.drop_duplicates(["Symbol"])
all_data_df_bind.reset_index(inplace=True, drop=True)
all_data_df_bind = all_data_df_bind.replace({np.nan : None})
all_data_df_bind
Name | Symbol | Exchange | Sector | Market Cap | country | date | |
---|---|---|---|---|---|---|---|
0 | Apple | AAPL | NASDAQ | Technology | 2.61T | United States | 2023-04-08 |
1 | Microsoft | MSFT | NASDAQ | Technology | 2.17T | United States | 2023-04-08 |
2 | Alphabet C | GOOG | NASDAQ | Technology | 1.39T | United States | 2023-04-08 |
3 | Alphabet A | GOOGL | NASDAQ | Technology | 1.39T | United States | 2023-04-08 |
4 | Amazon.com | AMZN | NASDAQ | Consumer Cyclicals | 1.05T | United States | 2023-04-08 |
... | ... | ... | ... | ... | ... | ... | ... |
5521 | 5E Advanced Materials | FEAM | NASDAQ | Basic Materials | - | United States | 2023-04-08 |
5522 | MGO Global | MGOL | NASDAQ | None | - | United States | 2023-04-08 |
5523 | CaliberCos | CWD | NASDAQ | None | - | United States | 2023-04-08 |
5524 | Chanson International Holding | CHSN | NASDAQ | None | - | United States | 2023-04-08 |
5525 | Hongli | HLP | NASDAQ | None | - | United States | 2023-04-08 |
5526 rows × 7 columns
# all_data_df_bind.to_pickle("./data/tickers_usa_investing.pickle")
10. 해외종목 가격, 재무제표
# 시간이 너무 오래걸려서 돌리지 않음
❍ 가격
# 미국 시장 데이터만 필요한 경우, 유료 데이터 벤더 이용하는 것도 좋음
# stock data api
# polygon, alpha vantage, finhub,
# tiingo > 10$/month >> api서비스 이용 , 안정적인 데이터를 더 빠르게
# 미국 외의 데이터
# finance.yahoo.com
#
import yfinance as yf
import pandas as pd
import numpy as np
from tqdm import tqdm
import time
#ticker_list = pd.read_pickle("./data/tickers_usa_hankyung.pickle")
ticker_list = pd.read_pickle("./data/tickers_usa_investing.pickle")
print(ticker_list.shape)
(5526, 7)
df = pd.DataFrame(columns = ['Date','Open','High','Low','Close','Adj Close','Volume','ticker'])
error_list = []
for i in tqdm(range(0, len(ticker_list))):
ticker = ticker_list['Symbol'][i]
# 오류 무시
try :
price = yf.download(ticker, progress=False) #progress=False : 진행상황 표기 없애줌
price = price.reset_index()
price['ticker'] = ticker
df = pd.concat([df, price])
except:
print(ticker)
error_list.append(ticker)
time.sleep(1)
❍ 재무제표
# !pip install yahooquery
from yahooquery import Ticker
import numpy as np
import pandas as pd
from tqdm import tqdm
import time
# 정보 다운로드
data = Ticker("AAPL")
# data.asset_profile
# data.summary_detail
# data.history()
# 연간 재무제표
data_y = data.all_financial_data(frequency='a') #'a': year, 'q':quarter
data_y.reset_index(inplace=True)
data_y = data_y.loc[:, ~data_y.columns.isin(['periodType','currencyCode'])] # 불필요열 삭제
data_y = data_y.melt(id_vars = ['symbol','asOfDate'])
data_y = data_y.replace([np.nan], None) # SQL에 np.nan은 저장안되므로 변경
data_y['freq'] = 'y'
data_y.columns = ['ticker','date','account','value','freq']
# 분기 재무제표
data_q = data.all_financial_data(frequency='q') #'a': year, 'q':quarter
data_q.reset_index(inplace=True)
data_q = data_q.loc[:, ~data_q.columns.isin(['periodType','currencyCode'])] # 불필요열 삭제
data_q = data_q.melt(id_vars = ['symbol','asOfDate'])
data_q = data_q.replace([np.nan], None) # SQL에 np.nan은 저장안되므로 변경
data_q['freq'] = 'q'
data_q.columns = ['ticker','date','account','value','freq']
# 데이터합치기
data_fs = pd.concat([data_y, data_q], axis=0)
data_fs
ticker | date | account | value | freq | |
---|---|---|---|---|---|
0 | AAPL | 2019-09-30 | AccountsPayable | 46236000000.0 | y |
1 | AAPL | 2020-09-30 | AccountsPayable | 42296000000.0 | y |
2 | AAPL | 2021-09-30 | AccountsPayable | 54763000000.0 | y |
3 | AAPL | 2022-09-30 | AccountsPayable | 64115000000.0 | y |
4 | AAPL | 2019-09-30 | AccountsReceivable | 22926000000.0 | y |
... | ... | ... | ... | ... | ... |
603 | AAPL | 2022-12-31 | TradeandOtherPayablesNonCurrent | None | q |
604 | AAPL | 2022-03-31 | WorkingCapital | -9328000000.0 | q |
605 | AAPL | 2022-06-30 | WorkingCapital | -17581000000.0 | q |
606 | AAPL | 2022-09-30 | WorkingCapital | -18577000000.0 | q |
607 | AAPL | 2022-12-31 | WorkingCapital | -8509000000.0 | q |
1216 rows × 5 columns
ticker_list = pd.read_pickle("./data/tickers_usa_investing.pickle")
df_fs= pd.DataFrame(columns = ['ticker','date','account','value','freq'])
error_list_fs = []
for i in tqdm(range(0, len(ticker_list))):
ticker = ticker_list['Symbol'][i]
# 오류 무시
try :
# 정보 다운로드
data = Ticker(ticker)
# 연간 재무제표
data_y = data.all_financial_data(frequency='a') #'a': year, 'q':quarter
data_y.reset_index(inplace=True)
data_y = data_y.loc[:, ~data_y.columns.isin(['periodType','currencyCode'])] # 불필요열 삭제
data_y = data_y.melt(id_vars = ['symbol','asOfDate'])
data_y = data_y.replace([np.nan], None) # SQL에 np.nan은 저장안되므로 변경
data_y['freq'] = 'y'
data_y.columns = ['ticker','date','account','value','freq']
# 분기 재무제표
data_q = data.all_financial_data(frequency='q') #'a': year, 'q':quarter
data_q.reset_index(inplace=True)
data_q = data_q.loc[:, ~data_q.columns.isin(['periodType','currencyCode'])] # 불필요열 삭제
data_q = data_q.melt(id_vars = ['symbol','asOfDate'])
data_q = data_q.replace([np.nan], None) # SQL에 np.nan은 저장안되므로 변경
data_q['freq'] = 'q'
data_q.columns = ['ticker','date','account','value','freq']
# 데이터합치기
data_fs = pd.concat([data_y, data_q], axis=0)
# 데이터프레임 합치기
df_fs = pd.concat([df_fs, data_fs])
except:
print(ticker)
error_list_fs.append(ticker)
time.sleep(1)
❒ (참고)
- 유투브 : 헨리의 퀀트대학 (https://www.youtube.com/channel/UCHfiWvw33aSBktAlWICfPKQ)
댓글남기기