chore: streamlit 임시 commit

This commit is contained in:
ayuriel 2025-02-07 17:54:52 +09:00
parent 48e76a4106
commit b1a898c1e8
7 changed files with 276 additions and 0 deletions

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# 실행
streamlit run .\streamlit-quant\app.py --server.port 20000

13
streamlit-quant/app.py Normal file
View File

@ -0,0 +1,13 @@
import streamlit as st
crawling_page = st.Page("pages/crawling.py", title="크롤링")
super_quality_page = st.Page("pages/super_quality.py", title="슈퍼 퀄리티 전략")
super_value_momentum_page = st.Page("pages/super_value_momentum.py", title="슈퍼 밸류 모멘텀 전략")
pg = st.navigation({
'크롤링': [crawling_page],
'전략': [super_quality_page, super_value_momentum_page],
})
st.set_page_config(page_title="콴트 매니저", page_icon=":material/edit:")
pg.run()

View File

@ -0,0 +1,6 @@
import streamlit as st
st.button(label='동작1')
st.button(label='동작2')
st.button(label='동작3')
st.button(label='동작4')

View File

@ -0,0 +1,41 @@
import streamlit as st
st.write("""
'신F-스코어 3점 + 고GP/A 전략' '강환국 슈퍼 퀄리티 전략'이라 명명한다.
전략은 신F-스코어가 3점인 종목을 매수하되, GP/A로 순위를 매겨서 순위가 높은 종목만 매수하는 것이다.
경우 한국에서 수익이 어땠을지 분석해보자.
연도별로 신F-스코어 3점을 충족하는 종목은 600-700개였다.\n
신F-스코어 3 기업 내에서도 GP/A가 높은 종목이 3 종목 평균보다 CAGR 기준으로 3-4% 높았다.
반대로 GP/A가 낮은 종목의 수익률은 상대적으로 저조했다.\n\n
---
투자 전략: 강환국 슈퍼 퀄리티 전략 1.0\n
레벨: , 중급\n
스타일: 퀄리티\n
기대 CAGR: 20%\n
매수 전략:
- 신F-스코어 3 종목만 매수\n
- 여기에 GP/A 순위를 부여, 순위 높은 20-30종목을 매수\n
매도 전략: 1 리밸런싱\n\n\n
---
지금까지 소개한 거의 모든 전략에서 소형주 전략이 전체 주식 수익률보다 높았다.
시가총액 하위 20% 종목의 CAGR을 분석해보았다.\n\n
---
투자 전략: 강환국 슈퍼 퀄리티 전략 2.0\n
레벨: , 중급\n
스타일: 퀄리티\n
기대 CAGR: 20% 이상\n
매수 전략:
아래 조건을 만족하는 20-30종목 매수\n
- 신F-스코어 3 종목만 매수\n
- 여기에 GP/A 순위를 부여, 순위 높은 종목만 매수\n
- , 소형주(시가총액 최저 20%) 매수\n
매도 전략: 1 리밸런싱\n
---
소형주 신F-스코어가 3점인 종목을 찾아보니 2004-2016 구간에 80-100 종목이 남았다.
주식들을 통째로 매수해도 CAGR 34.55% 벌수 있었다!
정말 상당한 수익이다.
종목들을 샀으면 1,159 종목 14개가 파산했다.(1.2%)
1년간 마이너스 수익을 기록한 종목이 29.7%였다.\n
신F-스코어가 3점인 종목 GP/A가 높은 종목 위주로 매수했으면 (1) CAGR도 조금 개선되고 (2) 최상 30 종목을 매수했을 경우 선택받은 종목 360 파산한 기업은 1개였다.
F-스코어와 GP/A는 엄청난 잠재력을 지닌 콤비네이션임이 분명하다.
""")

View File

@ -0,0 +1,3 @@
import streamlit as st
st.write("슈퍼 밸류 모멘텀 전략 2.0")

View File

@ -0,0 +1,192 @@
import re
import time
from io import BytesIO
import numpy as np
import pandas as pd
import requests as rq
from tqdm import tqdm
from bs4 import BeautifulSoup
from dotenv import load_dotenv
import quantcommon
load_dotenv()
GEN_OTP_URL = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
DOWN_URL = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
# 최근 영업일을 가져옴
def get_latest_biz_day():
url = 'https://finance.naver.com/sise/sise_deposit.nhn'
data = rq.post(url)
data_html = BeautifulSoup(data.content, 'lxml')
parse_day = data_html.select_one('div.subtop_sise_graph2 > ul.subtop_chart_note > li > span.tah').text
biz_day = re.findall('[0-9]+', parse_day)
biz_day = ''.join(biz_day)
return biz_day
# 업종 분류 현황 가져옴
def get_stock_data(biz_day, mkt_id):
# logging.basicConfig(level=logging.DEBUG)
gen_otp_data = {
'locale': 'ko_KR',
'mktId': mkt_id, # STK: 코스피, KSQ: 코스닥
'trdDd': biz_day,
'money': '1',
'csvxls_isNo': 'false',
'name': 'fileDown',
'url': 'dbms/MDC/STAT/standard/MDCSTAT03901'
}
headers = {
'Referer': 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader/index.cmd?menuId=MDC0201050201',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
}
otp = rq.post(url=GEN_OTP_URL, data=gen_otp_data, headers=headers, verify=False)
# # 요청 디버깅
# print("===== Request Details =====")
# print(f"Method: {otp.request.method}")
# print(f"URL: {otp.request.url}")
# print(f"Headers: {otp.request.headers}")
# print(f"Body: {otp.request.body}")
#
# # 응답 디버깅
# print("===== Response Details =====")
# print(f"Status Code: {otp.status_code}")
# print(f"Headers: {otp.headers}")
# print(f"Body: {otp.text}")
down_sector = rq.post(url=DOWN_URL, data={'code': otp.text}, headers=headers)
return pd.read_csv(BytesIO(down_sector.content), encoding='EUC-KR')
# 개별 지표 조회
def get_ind_stock_data(biz_day):
gen_otp_data = {
'locale': 'ko_KR',
'searchType': '1',
'mktId': 'ALL',
'trdDd': biz_day,
'csvxls_isNo': 'false',
'name': 'fileDown',
'url': 'dbms/MDC/STAT/standard/MDCSTAT03501'
}
headers = {
'Referer': 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader/index.cmd?menuId=MDC0201050201',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
}
otp = rq.post(url=GEN_OTP_URL, data=gen_otp_data, headers=headers, verify=False)
down_ind_sector = rq.post(url=DOWN_URL, data={'code': otp.text}, headers=headers)
return pd.read_csv(BytesIO(down_ind_sector.content), encoding='EUC-KR')
def process_for_total_stock(biz_day):
# 업종 분류 현황(코스피, 코스닥)
sector_stk = get_stock_data(biz_day, 'STK')
sector_ksq = get_stock_data(biz_day, 'KSQ')
# 각각 조회 후 합침
krx_sector = pd.concat([sector_stk, sector_ksq]).reset_index(drop=True)
krx_sector['종목명'] = krx_sector['종목명'].str.strip()
krx_sector['기준일'] = biz_day
# 개별 지표 조회
krx_ind = get_ind_stock_data(biz_day)
krx_ind['종목명'] = krx_ind['종목명'].str.strip()
krx_ind['기준일'] = biz_day
# 데이터 정리
# 종목, 개별 중 한군데만 있는 데이터 삭제(선박펀드, 광물펀드, 해외종목 등)
diff = list(set(krx_sector['종목명']).symmetric_difference(set(krx_ind['종목명'])))
kor_ticker = pd.merge(krx_sector,
krx_ind,
on=krx_sector.columns.intersection(
krx_ind.columns).tolist(),
how='outer')
# 일반적인 종목과 SPAC, 우선주, 리츠, 기타 주식을 구분
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})
kor_ticker['기준일'] = pd.to_datetime(kor_ticker['기준일'])
save_ticker(kor_ticker)
def save_ticker(ticker):
con = quantcommon.QuantCommon().connect()
mycursor = con.cursor()
query = f"""
insert into kor_ticker (종목코드,종목명,시장구분,종가,시가총액,기준일,EPS,선행EPS,BPS,주당배당금,종목구분)
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) as new
on duplicate key update
종목명=new.종목명,시장구분=new.시장구분,종가=new.종가,시가총액=new.시가총액,EPS=new.EPS,선행EPS=new.선행EPS,
BPS=new.BPS,주당배당금=new.주당배당금,종목구분 = new.종목구분;
"""
args = ticker.values.tolist()
mycursor.executemany(query, args)
con.commit()
con.close()
# WICS 기준 섹터정보 크롤링
def process_for_wics(biz_day):
sector_code = [
'G25', 'G35', 'G50', 'G40', 'G10', 'G20', 'G55', 'G30', 'G15', 'G45'
]
data_sector = []
# 모든 섹터에 대한 데이터 받아서 가공
for i in tqdm(sector_code):
url = f'''http://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['기준일'])
save_sector(kor_sector)
def save_sector(sector):
con = quantcommon.QuantCommon().connect()
mycursor = con.cursor()
query = f"""
insert into kor_sector (IDX_CD, CMP_CD, CMP_KOR, SEC_NM_KOR, 기준일)
values (%s,%s,%s,%s,%s) as new
on duplicate key update
IDX_CD = new.IDX_CD, CMP_KOR = new.CMP_KOR, SEC_NM_KOR = new.SEC_NM_KOR
"""
args = sector.values.tolist()
mycursor.executemany(query, args)
con.commit()
con.close()
if __name__ == '__main__':
latest_biz_day = get_latest_biz_day()
process_for_total_stock(latest_biz_day)
process_for_wics(latest_biz_day)

View File

@ -0,0 +1,19 @@
import pandas as pd
import numpy as np
import quantcommon
# DB 연결
engine = quantcommon.QuantCommon().create_engine()
ticker_list = pd.read_sql("""
select * from kor_ticker
where 기준일 = (select max(기준일) from kor_ticker)
and 종목구분 = '보통주';
""", con=engine)
value_list = pd.read_sql("""
select * from kor_value
where 기준일 = (select max(기준일) from kor_value);
""", con=engine)
engine.dispose()