make-quant-py/src/ui/pages/quality_page.py
Ayuriel c01ea3208a
All checks were successful
CI Build / build (push) Successful in 4s
feat: 슈퍼 퀄리티 전략 구현
2025-03-31 10:36:56 +09:00

114 lines
4.7 KiB
Python

"""
Super Quality strategy page for the Streamlit Quant application.
"""
import streamlit as st
from datetime import datetime
from strategies.composite.super_quality import get_super_quality_top
from db.common import DBManager
def render_quality_page():
"""Render the Super Quality strategy page."""
st.title("슈퍼 퀄리티 전략")
with st.expander("전략 설명", expanded=False):
st.write("""
'신F-스코어 3점 + 고GP/A 전략''강환국 슈퍼 퀄리티 전략'이라 명명한다.
이 전략은 신F-스코어가 3점인 종목을 매수하되, GP/A로 순위를 매겨서 순위가 높은 종목만 매수하는 것이다.
이 경우 한국에서 수익이 어땠을지 분석해보자.
연도별로 신F-스코어 3점을 충족하는 종목은 600-700개였다.
신F-스코어 3점 기업 내에서도 GP/A가 높은 종목이 3점 종목 평균보다 CAGR 기준으로 3-4% 더 높았다.
반대로 GP/A가 낮은 종목의 수익률은 상대적으로 저조했다.
---
투자 전략: 강환국 슈퍼 퀄리티 전략 1.0
레벨: 초, 중급
스타일: 퀄리티
기대 CAGR: 약 20%
매수 전략:
- 신F-스코어 3점 종목만 매수
- 여기에 GP/A 순위를 부여, 순위 높은 20-30종목을 매수
매도 전략: 연 1회 리밸런싱
---
지금까지 소개한 거의 모든 전략에서 소형주 전략이 전체 주식 수익률보다 높았다.
시가총액 하위 20% 종목의 CAGR을 분석해보았다.
---
투자 전략: 강환국 슈퍼 퀄리티 전략 2.0
레벨: 초, 중급
스타일: 퀄리티
기대 CAGR: 20% 이상
매수 전략:
아래 조건을 만족하는 20-30종목 매수
- 신F-스코어 3점 종목만 매수
- 여기에 GP/A 순위를 부여, 순위 높은 종목만 매수
- 단, 소형주(시가총액 최저 20%)만 매수
매도 전략: 연 1회 리밸런싱
---
소형주 중 신F-스코어가 3점인 종목을 찾아보니 2004-2016년 구간에 80-100개 종목이 남았다.
그 주식들을 통째로 매수해도 CAGR 34.55%를 벌수 있었다!
정말 상당한 수익이다.
이 종목들을 다 샀으면 총 1,159개 종목 중 14개가 파산했다.(1.2%)
또 1년간 마이너스 수익을 기록한 종목이 29.7%였다.
신F-스코어가 3점인 종목 중 GP/A가 높은 종목 위주로 매수했으면 (1) CAGR도 조금 개선되고 (2) 최상 30개 종목을 매수했을 경우 선택받은 종목 360개 중 파산한 기업은 단 1개였다.
F-스코어와 GP/A는 엄청난 잠재력을 지닌 콤비네이션임이 분명하다.
""")
# Strategy implementation
st.write("## 슈퍼 퀄리티 전략 2.0 포트폴리오")
# Get data
db = DBManager()
data = get_super_quality_top(db)
st.write("### 포트폴리오")
st.write(data)
# # Display options
# col1, col2 = st.columns([1, 2])
#
# with col1:
# st.write("### 설정")
# min_f_score = st.slider("최소 F-스코어", min_value=0, max_value=3, value=3)
# include_small_caps = st.checkbox("소형주만 포함", value=True)
# num_stocks = st.slider("포트폴리오 종목수", min_value=5, max_value=50, value=20)
#
# # Filter data
# filtered_data = data[data['f-score'] >= min_f_score].copy()
#
# if include_small_caps:
# # Sort by market cap and keep only the bottom 20%
# filtered_data = filtered_data.sort_values('시가총액')
# filtered_data = filtered_data.head(int(len(filtered_data) * 0.2))
#
# # Sort by GP/A in descending order
# filtered_data = filtered_data.sort_values('GP/A', ascending=False)
#
# # Get top N stocks
# portfolio = filtered_data.head(num_stocks)
#
# # Display portfolio
# with col2:
# st.write(f"### 선택된 {len(portfolio)} 종목")
# st.dataframe(portfolio, use_container_width=True)
#
# # Display metrics
# st.write("### 포트폴리오 지표")
# col1, col2, col3, col4 = st.columns(4)
#
# with col1:
# st.metric(label="평균 F-스코어", value=f"{portfolio['f-score'].mean():.2f}")
#
# with col2:
# st.metric(label="평균 GP/A", value=f"{portfolio['GP/A'].mean():.2f}%")
#
# with col3:
# avg_market_cap = portfolio['시가총액'].mean() / 1_000_000_000
# st.metric(label="평균 시가총액", value=f"{avg_market_cap:.1f}십억원")
#
# with col4:
# st.metric(label="종목 수", value=len(portfolio))