"""
1. 참고책 : 퀀트투자포트폴리오만들기 / 이현열
2. 부분 : 챕터 10. 국내주식데이터수집
3. 지표계산방식
per : 시가총액 / 최근결산월 당기순이익 (← 이거때문에 네이버 per을 가져와야 할듯 네이는 최근 4개분기 합산값)
4. 아래 파일 3개 합친거임
; kor_ticker, kor_jaemu, kor_value
"""
import requests as rq
from bs4 import BeautifulSoup
import requests as rq
from io import BytesIO
import pandas as pd
import pymysql
import datetime as dt
import re
import numpy as np
import time
from tqdm import tqdm
# pd.set_option('display.max_columns',None)
# pd.set_option('display.max_row',None)
# 장이 열리는날이 아니면 프로그램 종료
import exchange_calendars as ecals
import datetime as dt
XKRX = ecals.get_calendar("XKRX") # 한국 코드
ismarketopen= XKRX.is_session(dt.date.today().strftime("%Y-%m-%d")) # 오늘은 개장일인지 확인
if ismarketopen == False :
exit(0)
#001 영업일 구하기
if __name__ == '__main__':
url = 'https://[Log in to view URL]'
data = rq.get(url)
data_html = BeautifulSoup(data.content)
# parse_day = data_html.select_one('div.subtop_sise_graph2 > ul.subtop_chart_note > li > span.tah').text
parse_day = data_html.select_one('#time').text
print(parse_day)
biz_day = re.findall('[0-9]+', parse_day)
biz_day = ''.join(biz_day)
print(biz_day)
# #효종추가 이건 db입력할때만 사용하자
# import datetime as dt
# biz_day=str(biz_day)
# biz_day=dt.datetime.strptime(biz_day,'%Y%m%d')
# biz_day= biz_day.strftime('%Y-%m-%d')
# print(biz_day)
#002 기본데이터 받기 (['종목코드', '종목명', '시장구분', '업종명', '종가', '대비', '등락률', '시가총액'], dtype='object')
if __name__ == '__main__':
gen_otp_url = 'http://[Log in to view URL]'
gen_otp_stk = {
'mktId': 'STK',
'trdDd': biz_day,
'money': '1',
'csvxls_isNo': 'false',
'name': 'fileDown',
'url': 'dbms/MDC/STAT/standard/MDCSTAT03901'
}
headers = {'Referer': 'http://[Log in to view URL]'}
otp_stk = rq.post(gen_otp_url, gen_otp_stk, headers=headers).text
down_url = 'http://[Log in to view URL]'
down_sector_stk = rq.post(down_url, {'code': otp_stk}, headers=headers)
sector_kospi = pd.read_csv(BytesIO(down_sector_stk.content), encoding='EUC-KR')
# print(sector_kospi)
# print(sector_kospi.columns)
gen_otp_ksq = {
'mktId': 'KSQ', # 코스닥 입력
'trdDd': biz_day,
'money': '1',
'csvxls_isNo': 'false',
'name': 'fileDown',
'url': 'dbms/MDC/STAT/standard/MDCSTAT03901'
}
otp_ksq = rq.post(gen_otp_url, gen_otp_ksq, headers=headers).text
down_sector_ksq = rq.post(down_url, {'code': otp_ksq}, headers=headers)
sector_kosdaq = pd.read_csv(BytesIO(down_sector_ksq.content), encoding='EUC-KR')
# print(sector_kosdaq)
#코스피 코스닥 합치기
krx_sector = pd.concat([sector_kospi, sector_kosdaq]).reset_index(drop=True)
krx_sector['종목명'] = krx_sector['종목명'].str.strip()
krx_sector['기준일'] = biz_day
# print(krx_sector)
#003 개별종목 data : (['종목코드', '종목명', '종가', '대비', '등락률', 'EPS', 'PER', '선행 EPS', '선행 PER', 'BPS', 'PBR', '주당배당금', '배당수익률', '기준일'],
if __name__ == '__main__':
gen_otp_url = 'http://[Log in to view URL]'
gen_otp_data = {
'searchType': '1',
'mktId': 'ALL',
'trdDd': biz_day,
'csvxls_isNo': 'false',
'name': 'fileDown',
'url': 'dbms/MDC/STAT/standard/MDCSTAT03501'
}
headers = {'Referer': 'http://[Log in to view URL]'}
otp = rq.post(gen_otp_url, gen_otp_data, headers=headers).text
down_url = 'http://[Log in to view URL]'
krx_ind = rq.post(down_url, {'code': otp}, headers=headers)
krx_ind = pd.read_csv(BytesIO(krx_ind.content), encoding='EUC-KR')
krx_ind['종목명'] = krx_ind['종목명'].str.strip()
krx_ind['기준일'] = biz_day
# print(krx_ind)
#004 001,002 번 중 하나에만 존재하는 항목 검색하기
if __name__ == '__main__':
# 001,002 데이터에서 안겹치는 항목 찾기
diff = list(set(krx_sector['종목명']).symmetric_difference(set(krx_ind['종목명'])))
# print(diff)
#005 002,003 데이터 합치기
if __name__ == '__main__':
kor_ticker = pd.merge(krx_sector, krx_ind, on=krx_sector.columns.intersection(krx_ind.columns).tolist(),how='outer')
print(kor_ticker)
#006 종목,스팩,우선주,리츠,기타주식 구분
if __name__ == '__main__':
#numpy 패키지의 where() 함수를 통해 각 조건에 맞는 종목구분을 입력한다. 종목명에 '스팩' 혹은 '제n호'가 포함된 종목은 스팩으로, 종목코드 끝이 0이 아닌 종목은 '우선주'로, 종목명이 '리츠'로 끝나는 종목은 '리츠'로, 선박펀드, 광물펀드, 해외종목 등은 '기타'로, 나머지 종목들은 '보통주'로 구분한다.
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), '기타',
'보통주'))))
#reset_index() 메서드를 통해 인덱스를 초기화 한다.
kor_ticker = kor_ticker.reset_index(drop=True)
#replace() 메서드를 통해 열 이름의 공백을 삭제한다.
kor_ticker.columns = kor_ticker.columns.str.replace(' ', '')
# 필요한 열만 선택한다. 위 001 002 컬럼명 참조
kor_ticker = kor_ticker[['종목코드', '종목명', '시장구분', '종가',
'시가총액', '기준일', 'PER', '선행PER', 'PBR', '주당배당금','배당수익률', '종목구분']]
# SQL에는 NaN이 입력되지 않으므로, None으로 변경한다.
kor_ticker = kor_ticker.replace({np.nan: "NULL"})
# 기준일을 to_datetime() 메서드를 이용해 yyyymmdd에서 yyyy-mm-dd 형태로 변경한다.
kor_ticker['기준일'] = pd.to_datetime(kor_ticker['기준일'])
print(kor_ticker['기준일'])
print(kor_ticker.columns)
##################################################################################################################################################################
# 마리아디비에 넣기 : 수정해야한다.
if __name__ == '__main__':
conn = pymysql.connect(host='localhost', user='root', password='njmr0623$$', db='stock_jaemu', charset='utf8')
# db없으면 신규생성
with conn.cursor() as curs:
sql = """
CREATE TABLE IF NOT EXISTS kor_ticker (
종목코드 VARCHAR(20) not null,
종목명 VARCHAR(40),
시장구분 VARCHAR(10),
종가 FLOAT(20),
시가총액 FLOAT(20),
기준일 DATE,
PER FLOAT(20),
선행PER FLOAT(20),
PBR FLOAT(20),
주당배당금 FLOAT(20),
배당수익률 FLOAT(20),
종목구분 VARCHAR(10),
PRIMARY KEY (종목코드))
"""
curs.execute(sql)
conn.commit()
with conn.cursor() as curs:
for r in kor_ticker.itertuples():
# print(r)
sql = f"REPLACE INTO kor_ticker VALUES ('{r.종목코드}','{r.종목명}','{r.시장구분}',{r.종가},{r.시가총액},'{r.기준일}',{r.PER},{r.선행PER},{r.PBR},{r.주당배당금},{r.배당수익률},'{r.종목구분}')"
curs.execute(sql)
conn.commit()
#디비 없음녀 생성하기
if __name__ == '__main__':
engine = pymysql.connect(host='localhost', user='root', password='njmr0623$$', db='stock_jaemu', charset='utf8')
#최종함수
if __name__ == '__main__':
# DB 연결
engine = pymysql.connect(host='localhost', user='root', password='njmr0623$$', db='stock_jaemu', charset='utf8')
#db없으면 신규생성
with engine.cursor() as curs:
sql = f"CREATE TABLE IF NOT EXISTS kor_fs (계정 varchar(30), 기준일 date, 값 float, 종목코드 varchar(6), 공시구분 varchar(1),primary key(계정, 기준일, 종목코드, 공시구분))"
curs.execute(sql)
engine.commit()
# 티커리스트 불러오기
ticker_list = pd.read_sql("select * from kor_ticker where 기준일 = (select max(기준일) from kor_ticker) and 종목구분 = '보통주'", con=engine)
# print(ticker_list)
# 오류 발생시 저장할 리스트 생성
error_list = []
# 재무제표 클렌징 함수
def clean_fs(df, ticker, frequency):
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
# for loop
for i in tqdm(range(0, len(ticker_list))):
# 티커 선택
ticker = ticker_list['종목코드'][i]
# 오류 발생 시 이를 무시하고 다음 루프로 진행
try:
# url 생성
url = f'http://[Log in to view URL]'
# 데이터 받아오기
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])
data_fs_bind_iter = data_fs_bind.itertuples()
for r in data_fs_bind_iter :
with engine.cursor() as curs:
query = f"replace INTO kor_fs VALUES ('{r.계정}','{r.기준일}','{r.값}','{r.종목코드}','{r.공시구분}')"
curs.execute(query)
engine.commit()
except Exception as ex:
print('오류발생')
print(ex)
# 오류 발생시 해당 종목명을 저장하고 다음 루프로 이동
print(ticker)
error_list.append(ticker)
# 타임슬립 적용
time.sleep(0.5)
# DB 연결
engine = pymysql.connect(host='localhost', user='root', password='njmr0623$$', db='stock_jaemu', charset='utf8')
with engine.cursor() as curs:
query = "create table if not exists kor_value(종목코드 varchar(6),기준일 date,지표 varchar(3),값 double,primary key (종목코드, 기준일, 지표))"
curs.execute(query)
engine.commit()
# DB 연결
mycursor = engine.cursor()
# 분기 재무제표 불러오기
kor_fs = pd.read_sql("""
select * from kor_fs
where 공시구분 = 'q'
and 계정 in ('당기순이익', '자본', '영업활동으로인한현금흐름', '매출액');
""", con=engine)
# 티커 리스트 불러오기
ticker_list = pd.read_sql("""
select * from kor_ticker
where 기준일 = (select max(기준일) from kor_ticker)
and 종목구분 = '보통주';
""", con=engine)
# TTM 구하기
kor_fs = kor_fs.sort_values(['종목코드', '계정', '기준일'])
kor_fs['ttm'] = kor_fs.groupby(['종목코드', '계정'], as_index=False)['값'].rolling(window=4, min_periods=4).sum()['값']
# 자본은 평균 구하기
kor_fs['ttm'] = np.where(kor_fs['계정'] == '자본', kor_fs['ttm'] / 4, kor_fs['ttm'])
kor_fs = kor_fs.groupby(['계정', '종목코드']).tail(1)
kor_fs_merge = kor_fs[['계정', '종목코드', 'ttm']].merge(ticker_list[['종목코드', '시가총액', '기준일']], on='종목코드')
kor_fs_merge['시가총액'] = kor_fs_merge['시가총액'] / 100000000
kor_fs_merge['value'] = kor_fs_merge['시가총액'] / kor_fs_merge['ttm']
kor_fs_merge['value'] = kor_fs_merge['value'].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))))
print(kor_fs_merge)
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], "NULL")
kor_fs_merge['기준일'] = pd.to_datetime(kor_fs_merge['기준일'],format='%Y-%m-%d')
print(kor_fs_merge.head(8))
print(kor_fs_merge.itertuples())
for i in kor_fs_merge.itertuples():
print(i)
for r in kor_fs_merge.itertuples():
with engine.cursor() as curs:
query = f"replace INTO kor_value VALUES ('{r.종목코드}','{r.기준일}','{r.지표}',{r.값})"
curs.execute(query)
engine.commit()
#배당율 추가
ticker_list['값'] = ticker_list['주당배당금'] / ticker_list['종가']
ticker_list['값'] = ticker_list['값'].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]
dy_list.head()
for r in dy_list.itertuples():
with engine.cursor() as curs:
query = f"replace INTO kor_value VALUES ('{r.종목코드}','{r.기준일}','{r.지표}',{r.값})"
curs.execute(query)
engine.commit()
To embed this project on your website, copy the following code and paste it into your website's HTML: