make-quant-py/src/ui/pages/quality_page.py

118 lines
4.8 KiB
Python
Raw Normal View History

2025-03-30 21:19:41 +09:00
"""
Super Quality strategy page for the Streamlit Quant application.
"""
import streamlit as st
from datetime import datetime
from strategies.factors.f_score import get_f_score
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
date = get_last_year_end()
db = DBManager()
data = get_f_score(db, date)
# 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))
def get_last_year_end():
"""Get the last year's end date."""
today = datetime.now()
last_year = today.year - 1
last_year_end = datetime(last_year, 12, 31)
return last_year_end.date()